Compare commits
99 Commits
Author | SHA1 | Date | |
---|---|---|---|
da0d88d86f | |||
b7347c312a | |||
c876f2f7e3 | |||
adad5fd8ff | |||
2aaa7ae8c9 | |||
1f64ea37bd | |||
cac1525da0 | |||
1ad906fedc | |||
20ffef39a3 | |||
185460c054 | |||
0079e74d77 | |||
a8e019482f | |||
f01b8e47aa | |||
e43dfb7599 | |||
6b8cfe2b49 | |||
7f7b14bae3 | |||
810b02d9fd | |||
62813145b2 | |||
4877058253 | |||
2a9fd3abb8 | |||
a244f7b598 | |||
5396457ad5 | |||
0186b6ea61 | |||
f6e99f7e88 | |||
c24a3828be | |||
2e38a4567a | |||
67c5d67a61 | |||
9592dde534 | |||
d030b0af70 | |||
4ccb9ac94d | |||
8af62b917c | |||
311f114132 | |||
d015bd03f7 | |||
a61b9de0fa | |||
ef1b283917 | |||
c677f00105 | |||
8d2f778dfe | |||
c549d102f6 | |||
39a9ee4e9d | |||
a27dc6ad1c | |||
8ccb75fc8d | |||
8fc86dbe02 | |||
359f052608 | |||
4150e2265c | |||
b803ce7435 | |||
289c6cd7a9 | |||
31fc7b74c2 | |||
3e565f25be | |||
e509be4e21 | |||
170fc537ac | |||
3fe5882145 | |||
a290791410 | |||
2ebd38ff68 | |||
cd987a5b19 | |||
7f1dab7ee1 | |||
bed3945aa5 | |||
15a32a18b7 | |||
d2e8e7dd5d | |||
ce12ec89c4 | |||
b286444ad9 | |||
941263102d | |||
e2ed296dc7 | |||
cfc866bef2 | |||
affba669ce | |||
7230152ab8 | |||
21fee7171f | |||
ad17995f28 | |||
ac830cbe7f | |||
65da6af3f9 | |||
3d90bf7588 | |||
62ef1c88fe | |||
d6918920b6 | |||
cdfb09fbfa | |||
bb681e31c9 | |||
c7483936ec | |||
0a1f2da33d | |||
f5aafdb7d6 | |||
c9adf1c492 | |||
4c9cb560e3 | |||
f0b028279c | |||
197770b68b | |||
37b583f560 | |||
dca2d4fe12 | |||
3b677f8ae3 | |||
0b952578d1 | |||
054afbbedd | |||
866a6e4a44 | |||
8ea7dd478b | |||
7839252934 | |||
fa4063220f | |||
d214a02abd | |||
d1c12edd1b | |||
ded1a44c37 | |||
790a6cd1e3 | |||
a79f883a0f | |||
d9c5a540a3 | |||
276f33b9ec | |||
ded59d2da0 | |||
62f7a820d8 |
2
.github/config.yaml
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
firstPRMergeComment: >
|
||||
Thank you for contributing to ReVanced. Join us on [Discord](https://revanced.app/discord) if you want to receive a contributor role.
|
27
.github/workflows/analyze.yml
vendored
Normal file
@ -0,0 +1,27 @@
|
||||
name: Analyze Code
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches: [ "main", "dev" ]
|
||||
paths:
|
||||
- "**.dart"
|
||||
- ".github/workflows/analyze.yml"
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Setup Flutter
|
||||
uses: subosito/flutter-action@v2
|
||||
with:
|
||||
channel: 'stable'
|
||||
cache: true
|
||||
- name: Install Flutter dependencies
|
||||
run: flutter pub get
|
||||
- name: Generate files with Builder
|
||||
run: flutter packages pub run build_runner build --delete-conflicting-outputs
|
||||
- name: Analyze code
|
||||
uses: ValentinVignal/action-dart-analyze@v0.15
|
||||
with:
|
||||
fail-on: warning
|
141
.github/workflows/commit-build.yml
vendored
@ -1,141 +0,0 @@
|
||||
name: Android Release
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- "**"
|
||||
tags-ignore:
|
||||
- "v*"
|
||||
|
||||
pull_request:
|
||||
branches:
|
||||
- "**"
|
||||
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
version:
|
||||
name: Create version number
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Fetch all history for all tags and branches
|
||||
run: |
|
||||
git config remote.origin.url https://x-access-token:${{ secrets.GITHUB_TOKEN }}@github.com/${{ github.repository }}
|
||||
git fetch --prune --depth=10000
|
||||
|
||||
- name: Install GitVersion
|
||||
uses: gittools/actions/gitversion/setup@v0.9.7
|
||||
with:
|
||||
versionSpec: "5.x"
|
||||
|
||||
- name: Use GitVersion
|
||||
id: gitversion
|
||||
uses: gittools/actions/gitversion/execute@v0.9.7
|
||||
|
||||
- name: Create version.txt with nuGetVersion
|
||||
run: echo ${{ steps.gitversion.outputs.nuGetVersion }} > version.txt
|
||||
|
||||
- name: Upload version.txt
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: gitversion
|
||||
path: version.txt
|
||||
|
||||
build:
|
||||
name: Create Android Build
|
||||
needs: version
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Get version.txt
|
||||
uses: actions/download-artifact@v2
|
||||
with:
|
||||
name: gitversion
|
||||
|
||||
- name: Create new file without newline char from version.txt
|
||||
run: tr -d '\n' < version.txt > version1.txt
|
||||
|
||||
- name: Read version
|
||||
id: version
|
||||
uses: juliangruber/read-file-action@v1
|
||||
with:
|
||||
path: version1.txt
|
||||
|
||||
- name: Update version in YAML
|
||||
run: sed -i 's/99.99.99+99/${{ steps.version.outputs.content }}+${{ github.run_number }}/g' pubspec.yaml
|
||||
|
||||
- name: Download Android keystore
|
||||
id: android_keystore
|
||||
uses: timheuer/base64-to-file@v1.0.3
|
||||
with:
|
||||
fileName: upload-keystore.jks
|
||||
encodedString: ${{ secrets.SIGNING_KEYSTORE }}
|
||||
|
||||
- name: Create key.properties
|
||||
run: |
|
||||
echo "storeFile=${{ steps.android_keystore.outputs.filePath }}" > android/key.properties
|
||||
echo "storePassword=${{ secrets.SIGNING_KEYSTORE_PASSWORD }}" >> android/key.properties
|
||||
echo "keyPassword=${{ secrets.SIGNING_KEY_PASSWORD }}" >> android/key.properties
|
||||
echo "keyAlias=${{ secrets.SIGNING_KEY_ALIAS }}" >> android/key.properties
|
||||
|
||||
- uses: actions/setup-java@v3
|
||||
with:
|
||||
distribution: 'zulu'
|
||||
java-version: "12.x"
|
||||
cache: gradle
|
||||
|
||||
- uses: subosito/flutter-action@v2
|
||||
with:
|
||||
channel: 'stable'
|
||||
cache: true
|
||||
|
||||
- name: Get dependencies
|
||||
run: flutter pub get
|
||||
|
||||
- name: Generate files with Builder
|
||||
run: flutter packages pub run build_runner build --delete-conflicting-outputs
|
||||
|
||||
- name: Build with Flutter
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
SIGNING_KEY_ALIAS: ${{ secrets.SIGNING_KEY_ALIAS }}
|
||||
SIGNING_KEY_PASSWORD: ${{ secrets.SIGNING_KEY_PASSWORD }}
|
||||
SIGNING_STORE_PASSWORD: ${{ secrets.SIGNING_KEYSTORE_PASSWORD }}
|
||||
run: flutter build apk
|
||||
|
||||
- name: Sign APK
|
||||
id: sign_apk
|
||||
uses: ilharp/sign-android-release@v1
|
||||
with:
|
||||
releaseDir: build/app/outputs/apk/release
|
||||
signingKey: ${{ secrets.SIGNING_KEYSTORE }}
|
||||
keyStorePassword: ${{ secrets.SIGNING_KEYSTORE_PASSWORD }}
|
||||
keyAlias: ${{ secrets.SIGNING_KEY_ALIAS }}
|
||||
keyPassword: ${{ secrets.SIGNING_KEY_PASSWORD }}
|
||||
|
||||
- name: Add version to APK
|
||||
run: mv ${{ steps.sign_apk.outputs.signedFile }} revanced-manager-${{ env.RELEASE_VERSION }}.apk
|
||||
|
||||
- name: Upload APK
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: revanced-manager-${{ env.RELEASE_VERSION }}
|
||||
path: revanced-manager-${{ env.RELEASE_VERSION }}.apk
|
||||
|
||||
# deploy:
|
||||
# name: Deploy Android Build
|
||||
# needs: build
|
||||
# runs-on: ubuntu-latest
|
||||
# steps:
|
||||
# - uses: actions/checkout@v1
|
||||
# - name: Get Android Build from artifacts
|
||||
# uses: actions/download-artifact@v2
|
||||
# with:
|
||||
# name: android-release
|
||||
|
||||
env:
|
||||
ACTIONS_ALLOW_UNSECURE_COMMANDS: true
|
2
.github/workflows/crowdin.yml
vendored
@ -3,7 +3,7 @@ name: Sync Crowdin translations
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- "flutter"
|
||||
- "dev"
|
||||
paths:
|
||||
- "assets/i18n/en_US.json"
|
||||
- ".github/workflows/crowdin.yml"
|
||||
|
78
.github/workflows/pull-request-build.yml
vendored
@ -1,37 +1,41 @@
|
||||
name: "Android CI PR Build"
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches:
|
||||
- "**"
|
||||
|
||||
jobs:
|
||||
release:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Set env
|
||||
run: echo "RELEASE_VERSION=${GITHUB_REF#refs/*/}" >> $GITHUB_ENV
|
||||
- name: Set up JDK 12
|
||||
uses: actions/setup-java@v3
|
||||
with:
|
||||
java-version: '12'
|
||||
distribution: 'zulu'
|
||||
- uses: subosito/flutter-action@v2
|
||||
with:
|
||||
channel: 'stable'
|
||||
- name: Set up Flutter
|
||||
run: flutter pub get
|
||||
- name: Generate files with Builder
|
||||
run: flutter packages pub run build_runner build --delete-conflicting-outputs
|
||||
- name: Build with Flutter
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: flutter build apk
|
||||
- name: Add version to APK
|
||||
run: mv build/app/outputs/flutter-apk/app-release.apk revanced-manager-${{ env.RELEASE_VERSION }}.apk
|
||||
- name: Upload APK
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: revanced-manager-${{ env.RELEASE_VERSION }}
|
||||
path: revanced-manager-${{ env.RELEASE_VERSION }}.apk
|
||||
name: PR Build
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Build
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
# Make sure the release step uses its own credentials:
|
||||
# https://github.com/cycjimmy/semantic-release-action#private-packages
|
||||
persist-credentials: false
|
||||
fetch-depth: 0
|
||||
- name: Setup JDK
|
||||
uses: actions/setup-java@v3
|
||||
with:
|
||||
java-version: '17'
|
||||
distribution: 'zulu'
|
||||
cache: 'gradle'
|
||||
- name: Setup Flutter
|
||||
uses: subosito/flutter-action@v2
|
||||
with:
|
||||
channel: 'stable'
|
||||
cache: true
|
||||
- name: Install Flutter dependencies
|
||||
run: flutter pub get
|
||||
- name: Generate files with Builder
|
||||
run: flutter packages pub run build_runner build --delete-conflicting-outputs
|
||||
- name: Build with Flutter
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: flutter build apk
|
||||
- name: Upload build
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: revanced-manager
|
||||
path: build/app/outputs/flutter-apk/app-release.apk
|
||||
|
8
.github/workflows/release-build.yml
vendored
@ -15,11 +15,11 @@ jobs:
|
||||
- name: Set up JDK 12
|
||||
uses: actions/setup-java@v3
|
||||
with:
|
||||
java-version: '12'
|
||||
distribution: 'zulu'
|
||||
java-version: "12"
|
||||
distribution: "zulu"
|
||||
- uses: subosito/flutter-action@v2
|
||||
with:
|
||||
channel: 'stable'
|
||||
channel: "stable"
|
||||
- name: Set up Flutter
|
||||
run: flutter pub get
|
||||
- name: Generate files with Builder
|
||||
@ -47,4 +47,4 @@ jobs:
|
||||
with:
|
||||
repo_token: "${{ secrets.GITHUB_TOKEN }}"
|
||||
prerelease: false
|
||||
files: revanced-manager-${{ env.RELEASE_VERSION }}.apk
|
||||
files: revanced-manager-${{ env.RELEASE_VERSION }}.apk
|
68
.github/workflows/release.yml
vendored
Normal file
@ -0,0 +1,68 @@
|
||||
name: Release
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- dev
|
||||
jobs:
|
||||
release:
|
||||
name: Release
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
# Make sure the release step uses its own credentials:
|
||||
# https://github.com/cycjimmy/semantic-release-action#private-packages
|
||||
persist-credentials: false
|
||||
fetch-depth: 0
|
||||
- name: Setup JDK
|
||||
uses: actions/setup-java@v3
|
||||
with:
|
||||
java-version: '17'
|
||||
distribution: 'zulu'
|
||||
cache: 'gradle'
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: '18'
|
||||
cache: 'npm'
|
||||
- name: Setup Flutter
|
||||
uses: subosito/flutter-action@v2
|
||||
with:
|
||||
channel: 'stable'
|
||||
cache: true
|
||||
- name: Install Flutter dependencies
|
||||
run: flutter pub get
|
||||
- name: Generate files with Builder
|
||||
run: flutter packages pub run build_runner build --delete-conflicting-outputs
|
||||
- name: Build with Flutter
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: flutter build apk
|
||||
- name: Sign APK
|
||||
id: sign_apk
|
||||
uses: ilharp/sign-android-release@v1
|
||||
with:
|
||||
releaseDir: build/app/outputs/apk/release
|
||||
signingKey: ${{ secrets.SIGNING_KEYSTORE }}
|
||||
keyStorePassword: ${{ secrets.SIGNING_KEYSTORE_PASSWORD }}
|
||||
keyAlias: ${{ secrets.SIGNING_KEY_ALIAS }}
|
||||
keyPassword: ${{ secrets.SIGNING_KEY_PASSWORD }}
|
||||
- name: Setup semantic-release
|
||||
run: npm ci
|
||||
- name: Get release version
|
||||
run: npm exec -- semantic-release --dry-run
|
||||
id: get-next-version
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Add version to APK
|
||||
run: mv $SIGNED_FILE_PATH $(dirname $SIGNED_FILE_PATH)/revanced-manager-${{ steps.get-next-version.outputs.new-release-version }}.apk
|
||||
env:
|
||||
SIGNED_FILE_PATH: ${{steps.sign_apk.outputs.signedFile}}
|
||||
- name: Release
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.REPOSITORY_PUSH_ACCESS }}
|
||||
run: npm exec semantic-release
|
19
.github/workflows/update-documentation.yml
vendored
Normal file
@ -0,0 +1,19 @@
|
||||
name: Update documentation
|
||||
|
||||
on:
|
||||
push:
|
||||
paths:
|
||||
- docs/**
|
||||
|
||||
jobs:
|
||||
trigger:
|
||||
runs-on: ubuntu-latest
|
||||
name: Dispatch event to documentation repository
|
||||
if: github.ref == 'refs/heads/main'
|
||||
steps:
|
||||
- uses: peter-evans/repository-dispatch@v2
|
||||
with:
|
||||
token: ${{ secrets.DOCUMENTATION_REPO_ACCESS_TOKEN }}
|
||||
repository: revanced/revanced-documentation
|
||||
event-type: update-documentation
|
||||
client-payload: '{"repo": "${{ github.event.repository.name }}", "ref": "${{ github.ref }}"}'
|
10
.gitignore
vendored
@ -134,5 +134,11 @@ app.*.map.json
|
||||
!/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages
|
||||
!/dev/ci/**/Gemfile.lock
|
||||
|
||||
Firebase related
|
||||
.firebase
|
||||
# Firebase related
|
||||
.firebase
|
||||
|
||||
# Dependency directories
|
||||
node_modules/
|
||||
|
||||
# FVM
|
||||
.fvm
|
75
.releaserc
Normal file
@ -0,0 +1,75 @@
|
||||
{
|
||||
"branches": [
|
||||
"main",
|
||||
{
|
||||
"name": "dev",
|
||||
"prerelease": true
|
||||
}
|
||||
],
|
||||
"plugins": [
|
||||
"semantic-release-export-data",
|
||||
"@semantic-release/commit-analyzer",
|
||||
[
|
||||
"@semantic-release/release-notes-generator",
|
||||
{
|
||||
"presetConfig": {
|
||||
"types": [
|
||||
{
|
||||
"type": "build",
|
||||
"section": "Dependency Updates"
|
||||
},
|
||||
{
|
||||
"type": "chore",
|
||||
"section": "Other Changes",
|
||||
"hidden": false
|
||||
},
|
||||
{
|
||||
"type": "perf",
|
||||
"section": "Performance Improvements",
|
||||
"hidden": false
|
||||
},
|
||||
{
|
||||
"type": "refactor",
|
||||
"section": "Code Improvements",
|
||||
"hidden": false
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
],
|
||||
"@semantic-release/changelog",
|
||||
"semantic-release-flutter-plugin",
|
||||
[
|
||||
"@semantic-release/git",
|
||||
{
|
||||
"assets": [
|
||||
"CHANGELOG.md",
|
||||
"pubspec.yaml"
|
||||
]
|
||||
}
|
||||
],
|
||||
[
|
||||
"@semantic-release/github",
|
||||
{
|
||||
"assets": [
|
||||
{
|
||||
"path": "build/app/outputs/apk/release/revanced-manager-*.apk"
|
||||
}
|
||||
],
|
||||
"successComment": false
|
||||
}
|
||||
],
|
||||
[
|
||||
"@saithodev/semantic-release-backmerge",
|
||||
{
|
||||
"backmergeBranches": [
|
||||
{
|
||||
"from": "main",
|
||||
"to": "dev"
|
||||
}
|
||||
],
|
||||
"clearWorkspace": true
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
1
CHANGELOG.md
Normal file
@ -0,0 +1 @@
|
||||
|
@ -2,18 +2,12 @@
|
||||
|
||||
The official ReVanced Manager based on Flutter.
|
||||
|
||||
> **Warning**: This repository currently has no active maintainer. For that reason, development is stale. Active development continues for [revanced-cli](https://github.com/revanced/revanced-cli). If you are interested in maintaining this repository, please let us know at manager@revanced.app.
|
||||
|
||||
## 🔽 Download
|
||||
To download the Alpha version of Manager, go [here](https://github.com/revanced/revanced-manager/releases/latest) and install the provided APK file.
|
||||
To download latest Manager, go [here](https://github.com/revanced/revanced-manager/releases/latest) and install the provided APK file.
|
||||
|
||||
## 📝 Prerequisites
|
||||
1. Android 8 or higher
|
||||
2. Does not work on some armv7 devices
|
||||
3. [Vanced MicroG](https://github.com/TeamVanced/VancedMicroG/releases) required for YouTube and YouTube Music (Only for non-root)
|
||||
|
||||
## ⚠️ Disclaimer
|
||||
*Please note that even though we're releasing the Manager, it is an ALPHA version. There's a big chance that the Manager might not work at all for you.*
|
||||
|
||||
## 🔴 Issues
|
||||
For suggestions and bug reports, open an issue [here](https://github.com/revanced/revanced-manager/issues/new/choose).
|
||||
|
@ -11,7 +11,10 @@ include: package:flutter_lints/flutter.yaml
|
||||
|
||||
analyzer:
|
||||
exclude:
|
||||
- lib/utils/env_class.g.dart
|
||||
- lib/app/app.locator.dart
|
||||
- lib/app/app.router.dart
|
||||
- lib/models/patch.g.dart
|
||||
- lib/models/patched_application.g.dart
|
||||
|
||||
linter:
|
||||
rules:
|
||||
@ -95,7 +98,6 @@ linter:
|
||||
- prefer_const_declarations
|
||||
- prefer_const_literals_to_create_immutables
|
||||
- prefer_contains
|
||||
- prefer_equal_for_default_values
|
||||
- prefer_final_fields
|
||||
- prefer_final_in_for_each
|
||||
- prefer_final_locals
|
||||
|
3
android/Gemfile
Normal file
@ -0,0 +1,3 @@
|
||||
source "https://rubygems.org"
|
||||
|
||||
gem "fastlane"
|
@ -71,14 +71,10 @@ dependencies {
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
|
||||
|
||||
// ReVanced
|
||||
implementation "app.revanced:revanced-patcher:7.0.0"
|
||||
implementation "app.revanced:revanced-patcher:11.0.1"
|
||||
|
||||
// Signing & aligning
|
||||
implementation("org.bouncycastle:bcpkix-jdk15on:1.70")
|
||||
implementation("com.android.tools.build:apksig:7.2.2")
|
||||
|
||||
// MicroG cronet
|
||||
implementation("org.microg:cronet-common:$cronetVersion")
|
||||
implementation("org.microg:cronet-native:$cronetVersion")
|
||||
|
||||
}
|
||||
|
@ -6,6 +6,8 @@
|
||||
<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_EXTERNAL_STORAGE" android:maxSdkVersion="32" />
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
||||
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
|
||||
<uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" />
|
||||
<uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS" />
|
||||
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
|
||||
|
@ -44,6 +44,8 @@ class MainActivity : FlutterActivity() {
|
||||
val selectedPatches = call.argument<List<String>>("selectedPatches")
|
||||
val cacheDirPath = call.argument<String>("cacheDirPath")
|
||||
val keyStoreFilePath = call.argument<String>("keyStoreFilePath")
|
||||
val keystorePassword = call.argument<String>("keystorePassword")
|
||||
|
||||
if (patchBundleFilePath != null &&
|
||||
originalFilePath != null &&
|
||||
inputFilePath != null &&
|
||||
@ -52,7 +54,8 @@ class MainActivity : FlutterActivity() {
|
||||
integrationsPath != null &&
|
||||
selectedPatches != null &&
|
||||
cacheDirPath != null &&
|
||||
keyStoreFilePath != null
|
||||
keyStoreFilePath != null &&
|
||||
keystorePassword != null
|
||||
) {
|
||||
runPatcher(
|
||||
result,
|
||||
@ -64,7 +67,8 @@ class MainActivity : FlutterActivity() {
|
||||
integrationsPath,
|
||||
selectedPatches,
|
||||
cacheDirPath,
|
||||
keyStoreFilePath
|
||||
keyStoreFilePath,
|
||||
keystorePassword
|
||||
)
|
||||
} else {
|
||||
result.notImplemented()
|
||||
@ -85,7 +89,8 @@ class MainActivity : FlutterActivity() {
|
||||
integrationsPath: String,
|
||||
selectedPatches: List<String>,
|
||||
cacheDirPath: String,
|
||||
keyStoreFilePath: String
|
||||
keyStoreFilePath: String,
|
||||
keystorePassword: String
|
||||
) {
|
||||
val originalFile = File(originalFilePath)
|
||||
val inputFile = File(inputFilePath)
|
||||
@ -242,7 +247,7 @@ class MainActivity : FlutterActivity() {
|
||||
// Signer("ReVanced", "s3cur3p@ssw0rd").signApk(patchedFile, outFile, keyStoreFile)
|
||||
|
||||
try {
|
||||
Signer("ReVanced", "s3cur3p@ssw0rd").signApk(patchedFile, outFile, keyStoreFile)
|
||||
Signer("ReVanced", keystorePassword).signApk(patchedFile, outFile, keyStoreFile)
|
||||
} catch (e: Exception) {
|
||||
//log to console
|
||||
print("Error signing apk: ${e.message}")
|
||||
@ -266,8 +271,8 @@ class MainActivity : FlutterActivity() {
|
||||
"update",
|
||||
mapOf(
|
||||
"progress" to -100.0,
|
||||
"header" to "Aborting...",
|
||||
"log" to "An error occurred! Aborting\nError:\n$stack"
|
||||
"header" to "Aborted...",
|
||||
"log" to "An error occurred! Aborted\nError:\n$stack"
|
||||
)
|
||||
)
|
||||
}
|
||||
|
Before Width: | Height: | Size: 2.1 KiB |
Before Width: | Height: | Size: 1.7 KiB |
Before Width: | Height: | Size: 1.5 KiB |
Before Width: | Height: | Size: 1.1 KiB |
Before Width: | Height: | Size: 2.7 KiB |
Before Width: | Height: | Size: 2.4 KiB |
Before Width: | Height: | Size: 4.1 KiB |
Before Width: | Height: | Size: 3.7 KiB |
Before Width: | Height: | Size: 5.5 KiB |
Before Width: | Height: | Size: 4.9 KiB |
Before Width: | Height: | Size: 9.2 KiB |
30
android/app/src/main/res/drawable/ic_launcher_foreground.xml
Normal file
@ -0,0 +1,30 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:aapt="http://schemas.android.com/aapt"
|
||||
android:width="108dp"
|
||||
android:height="108dp"
|
||||
android:viewportWidth="256"
|
||||
android:viewportHeight="256">
|
||||
<group android:scaleX="0.23"
|
||||
android:scaleY="0.23"
|
||||
android:translateX="98.56"
|
||||
android:translateY="98.56">
|
||||
<path
|
||||
android:pathData="M253.85,4.9C254.32,3.82 254.22,2.57 253.58,1.58C252.93,0.6 251.83,0 250.64,0C243.29,0 230.47,0 225.95,0C224.96,0 224.06,0.59 223.66,1.5C216.03,18.88 144.1,182.7 130.29,214.16C129.89,215.07 128.99,215.66 128,215.66C127.01,215.66 126.11,215.07 125.71,214.16C111.9,182.7 39.97,18.88 32.34,1.5C31.94,0.59 31.04,0 30.05,0C25.53,0 12.71,0 5.36,0C4.17,0 3.07,0.6 2.42,1.58C1.78,2.57 1.68,3.82 2.15,4.9C16.78,38.3 101.47,231.61 111.24,253.9C111.8,255.18 113.06,256 114.45,256C120.29,256 135.71,256 141.55,256C142.94,256 144.2,255.18 144.76,253.9C154.52,231.61 239.22,38.3 253.85,4.9Z"
|
||||
android:fillColor="#ffffff"/>
|
||||
<path
|
||||
android:pathData="M130.59,131.75C130.06,132.68 129.07,133.25 128,133.25C126.93,133.25 125.94,132.68 125.4,131.75C113.45,111.06 63.88,25.19 51.93,4.5C51.4,3.57 51.4,2.43 51.93,1.5C52.47,0.57 53.46,-0 54.53,-0L201.47,-0C202.54,-0 203.53,0.57 204.06,1.5C204.6,2.43 204.6,3.57 204.06,4.5C192.12,25.19 142.54,111.06 130.59,131.75Z">
|
||||
<aapt:attr name="android:fillColor">
|
||||
<gradient
|
||||
android:startX="128"
|
||||
android:startY="-0"
|
||||
android:endX="128"
|
||||
android:endY="254.6"
|
||||
android:type="linear">
|
||||
<item android:offset="0" android:color="#FFF04E98"/>
|
||||
<item android:offset="0.5" android:color="#FF5F65D4"/>
|
||||
<item android:offset="1" android:color="#FF4E98F0"/>
|
||||
</gradient>
|
||||
</aapt:attr>
|
||||
</path>
|
||||
</group>
|
||||
</vector>
|
Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 10 KiB |
Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 5.3 KiB |
@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@drawable/ic_launcher_background"/>
|
||||
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
|
||||
<monochrome android:drawable="@drawable/ic_launcher_monochrome"/>
|
||||
</adaptive-icon>
|
||||
<background android:drawable="@color/ic_launcher_background"/>
|
||||
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
|
||||
<monochrome android:drawable="@drawable/ic_launcher_foreground"/>
|
||||
</adaptive-icon>
|
Before Width: | Height: | Size: 3.1 KiB After Width: | Height: | Size: 2.9 KiB |
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 1.7 KiB |
Before Width: | Height: | Size: 4.5 KiB After Width: | Height: | Size: 4.1 KiB |
Before Width: | Height: | Size: 7.4 KiB After Width: | Height: | Size: 6.6 KiB |
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 9.4 KiB |
@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<color name="ic_launcher_background">#1B1B1B</color>
|
||||
</resources>
|
@ -1,5 +1,4 @@
|
||||
buildscript {
|
||||
ext.cronetVersion = '102.5005.125'
|
||||
ext.kotlin_version = '1.7.10'
|
||||
repositories {
|
||||
google()
|
||||
@ -32,6 +31,6 @@ subprojects {
|
||||
project.evaluationDependsOn(':app')
|
||||
}
|
||||
|
||||
task clean(type: Delete) {
|
||||
tasks.register("clean", Delete) {
|
||||
delete rootProject.buildDir
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.6.1-bin.zip
|
||||
distributionSha256Sum=6147605a23b4eff6c334927a86ff3508cb5d6722cd624c97ded4c2e8640f1f87
|
||||
networkTimeout=10000
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
|
@ -4,6 +4,8 @@
|
||||
"updateButton": "Update",
|
||||
"enabledLabel": "Enabled",
|
||||
"disabledLabel": "Disabled",
|
||||
"installed":"Installed: {version}",
|
||||
"suggested":"Suggested: {version}",
|
||||
"yesButton": "Yes",
|
||||
"noButton": "No",
|
||||
"warning": "Warning",
|
||||
@ -13,6 +15,7 @@
|
||||
"settingsTab": "Settings"
|
||||
},
|
||||
"homeView": {
|
||||
"refreshSuccess": "Refreshed successfully",
|
||||
"widgetTitle": "Dashboard",
|
||||
"updatesSubtitle": "Updates",
|
||||
"patchedSubtitle": "Patched applications",
|
||||
@ -47,7 +50,8 @@
|
||||
"patcherView": {
|
||||
"widgetTitle": "Patcher",
|
||||
"patchButton": "Patch",
|
||||
"patchDialogText": "You have selected a resource patch and a split APK installation has been detected, so patching errors may occur.\nAre you sure you want to proceed?"
|
||||
"patchDialogText": "You have selected a resource patch and a split APK installation has been detected, so patching errors may occur.\nAre you sure you want to proceed?",
|
||||
"armv7WarningDialogText": "Patching on ARMv7 devices is not yet supported and might fail. Proceed anyways?"
|
||||
},
|
||||
"appSelectorCard": {
|
||||
"widgetTitle": "Select an application",
|
||||
@ -55,7 +59,7 @@
|
||||
"widgetSubtitle": "No application selected",
|
||||
"noAppsLabel": "No applications found",
|
||||
"currentVersion": "Current",
|
||||
"recommendedVersion": "Recommended",
|
||||
"suggestedVersion": "Suggested",
|
||||
"anyVersion": "any"
|
||||
},
|
||||
"patchSelectorCard": {
|
||||
@ -71,23 +75,25 @@
|
||||
"appSelectorView": {
|
||||
"viewTitle": "Select an application",
|
||||
"searchBarHint": "Search applications",
|
||||
"selectFromStorageButton": "Select from storage",
|
||||
"storageButton": "Storage",
|
||||
"errorMessage": "Unable to use selected application"
|
||||
"errorMessage": "Unable to use selected application",
|
||||
"downloadToast": "Download function is not available yet",
|
||||
"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"
|
||||
},
|
||||
"patchesSelectorView": {
|
||||
"viewTitle": "Select patches",
|
||||
"searchBarHint": "Search patches",
|
||||
"doneButton": "Done",
|
||||
"recommended": "Recommended",
|
||||
"recommendedTooltip": "Select all recommended patches",
|
||||
"all": "All",
|
||||
"allTooltip": "Select all patches",
|
||||
"default": "Default",
|
||||
"defaultTooltip": "Select all default patches",
|
||||
"none": "None",
|
||||
"noneTooltip": "Deselect all patches",
|
||||
"loadPatchesSelection": "Load patches selection",
|
||||
"noSavedPatches": "No saved patches for the selected app.\nPress Done to save current selection.",
|
||||
"noPatchesFound": "No patches found for the selected app",
|
||||
"selectAllPatchesWarningContent": "You are about to select all patches, that includes unrecommended patches and can cause unwanted behavior."
|
||||
"selectAllPatchesWarningContent": "You are about to select all patches, that includes non-suggested patches and can cause unwanted behavior."
|
||||
},
|
||||
"patchItem": {
|
||||
"unsupportedDialogText": "Selecting this patch may result in patching errors.\n\nApp version: {packageVersion}\nSupported versions:\n{supportedVersions}",
|
||||
@ -151,6 +157,7 @@
|
||||
"restartAppForChanges": "Restart the app to apply changes",
|
||||
"deleteKeystoreLabel": "Delete keystore",
|
||||
"deleteKeystoreHint": "Delete the keystore used to sign the app",
|
||||
"deleteKeystoreDialogText": "Are you sure you want to delete the keystore used to sign patched applications?",
|
||||
"deletedKeystore": "Keystore deleted",
|
||||
"deleteTempDirLabel": "Delete temporary files",
|
||||
"deleteTempDirHint": "Delete unused temporary files",
|
||||
@ -168,7 +175,17 @@
|
||||
"jsonSelectorErrorMessage": "Unable to use selected JSON file",
|
||||
"deleteLogsLabel": "Delete logs",
|
||||
"deleteLogsHint": "Delete collected manager logs",
|
||||
"deletedLogs": "Logs deleted"
|
||||
"deletedLogs": "Logs deleted",
|
||||
"exportKeystoreLabel": "Export keystore",
|
||||
"exportKeystoreHint": "Export keystore used to sign apps",
|
||||
"exportedKeystore": "Keystore exported",
|
||||
"noKeystoreExportFileFound": "No keystore to export",
|
||||
"importKeystoreLabel": "Import keystore",
|
||||
"importKeystoreHint": "Import keystore used to sign apps",
|
||||
"importedKeystore": "Keystore imported",
|
||||
"keystoreSelectorErrorMessage": "Unable to use selected KEYSTORE file",
|
||||
"selectKeystorePassword": "Keystore Password",
|
||||
"selectKeystorePasswordHint": "Select keystore password used to sign the apk"
|
||||
},
|
||||
"appInfoView": {
|
||||
"widgetTitle": "App info",
|
||||
|
16
docs/0_prerequisites.md
Normal file
@ -0,0 +1,16 @@
|
||||
# 💼 Prerequisites
|
||||
|
||||
In order to use ReVanced Manager, certain requirements must be met.
|
||||
|
||||
## 🤝 Requirements
|
||||
|
||||
- An Android device running Android 8 or higher
|
||||
- Any device architecture except ARMv7[^1]
|
||||
|
||||
[^1]: This constraint only applies to patches, that require patching APK resources which is why some patches may or may not work on ARMv7 architecture. You can find out, which architectures your device supports here: [⚙️ Configuring ReVanced Manager](2_4_settings.md#%E2%84%B9%EF%B8%8F-about).
|
||||
|
||||
## ⏭️ What's next
|
||||
|
||||
The next page will guide you through patching an application.
|
||||
|
||||
Continue: [⬇️ Installation](1_installation.md)
|
14
docs/1_installation.md
Normal file
@ -0,0 +1,14 @@
|
||||
# ⬇️ Installation
|
||||
|
||||
In order to use ReVanced on your Android device, ReVanced Manager must be installed.
|
||||
|
||||
## 🪜 Installation steps
|
||||
|
||||
- Download the latest version of ReVanced Manager from [here](https://github.com/revanced/revanced-manager/releases/latest)
|
||||
- Install ReVanced Manager
|
||||
|
||||
## ⏭️ What's next
|
||||
|
||||
The next page will guide you through patching an application.
|
||||
|
||||
Continue: [🪛 Usage](2_usage.md)
|
29
docs/2_1_patching.md
Normal file
@ -0,0 +1,29 @@
|
||||
# 🧩 Patching applications
|
||||
|
||||
The following pages will guide you through using ReVanced Manager to patch applications.
|
||||
|
||||
## 🪜 Steps to patch applications
|
||||
|
||||
1. Navigate to the **Patcher** tab from the bottom navigation bar
|
||||
2. Tap on the **Select an application** card
|
||||
3. Choose an application to patch[^1]
|
||||
> **Note**: The application version suggested by ReVanced Manager is visible in each application's card. This is important in step 5. below, as most patches are compatible with this version.
|
||||
4. Tap on the **Select patches** card and select the patches you want to apply[^2]
|
||||
> **Warning**: If you see a warning icon next to a patch, it means that the patch is not compatible with the application version you selected in step 3. above. In this case, you should either go back and select a different application version or deselect the patch.
|
||||
5. Tap on the **Done** button to confirm your selection
|
||||
6. Tap on the **Patch** button to start patching
|
||||
> **Warning**: The patching process can take anywhere from 2 to 5 minutes depending on your device. Refrain from exiting ReVanced Manager while the process is still running, otherwise the patching time will increase significantly.
|
||||
7. After patching is complete, tap on the **Install** button to install the patched application
|
||||
> **Note**: If you have root access on your device, you may have the option to mount the patched application on top of the original application.[^4]
|
||||
> Optionally, you may tap on the three vertical dots in the top right to export the patched application to storage.
|
||||
|
||||
[^1]: You may be prompted to select an application from storage in case you have no root access on your device. In this case, select the APK file from your device storage.[^3]
|
||||
[^2]: It is suggested to use the default set of patches by tapping on the **Default** chip above the list of patches.
|
||||
[^3]: You can obtain `.apk` files from sites such as [APKMirror](https://www.apkmirror.com/).
|
||||
[^4]: Mounting the patched application on top of the original application will only work if the installed application version matches the version of the application selected in step 3. above.
|
||||
|
||||
## ⏭️ What's next
|
||||
|
||||
The next page will bring you back to the usage page.
|
||||
|
||||
Continue: [🛠️ Usage](2_usage.md)
|
16
docs/2_2_managing.md
Normal file
@ -0,0 +1,16 @@
|
||||
# 🧰 Managing patched applications
|
||||
|
||||
After patching an application, you may want to manage it. This page will guide you through managing patched applications.
|
||||
|
||||
## 🪜 Steps to manage patched applications
|
||||
|
||||
1. Tap on the **Dashboard** tab in the bottom navigation bar
|
||||
2. Select the **Installed** chip
|
||||
3. Tap on the **Info** button for the application you want to manage
|
||||
4. Choose one of the options from the menu
|
||||
|
||||
## ⏭️ What's next
|
||||
|
||||
The next page will bring you back to the usage page.
|
||||
|
||||
Continue: [🛠️ Usage](2_usage.md)
|
14
docs/2_3_updating.md
Normal file
@ -0,0 +1,14 @@
|
||||
# 🔄 Updating ReVanced Manager
|
||||
|
||||
In order to keep up with the latest features and bug fixes, it is recommended to keep ReVanced Manager up to date.
|
||||
|
||||
## 🪜 Updating steps
|
||||
|
||||
1. Navigate to the **Dashboard** tab from the bottom navigation bar
|
||||
2. Tap on the **Update** button in the **Updates** section
|
||||
|
||||
## ⏭️ What's next
|
||||
|
||||
The next page will bring you back to the usage page.
|
||||
|
||||
Continue: [🛠️ Usage](2_usage.md)
|
39
docs/2_4_settings.md
Normal file
@ -0,0 +1,39 @@
|
||||
# ⚙️ Configuring ReVanced Manager
|
||||
|
||||
ReVanced Manager has settings that can be configured to your liking.
|
||||
|
||||
## 🪛 Essential settings
|
||||
|
||||
- ### 🔗 API URL
|
||||
|
||||
Specify the URL of the API to use. This is used to fetch ReVanced Patches and update ReVanced Manager.
|
||||
|
||||
- ### 🧬 Sources
|
||||
|
||||
Override the API and change the source of ReVanced Patches.
|
||||
|
||||
- ### 🧪 Experimental ReVanced Patches support
|
||||
|
||||
Lift application version constraints from ReVanced Patches. This allows you to patch any version of an application, even if the patch is not explicitly compatible with it.
|
||||
|
||||
- ### 🧑🔬 Experimental universal support
|
||||
|
||||
This will show or hide ReVanced Patches, which are not meant for any application in particular but rather for all applications but may not work on all applications.
|
||||
|
||||
- ### 🔑 Export, import or delete keystore
|
||||
|
||||
Manage the keystore used to sign patched applications.
|
||||
|
||||
- ### 📄 Export, import or reset ReVanced Patches selection
|
||||
|
||||
Manage the ReVanced Patches selection. This is useful if you want to share your ReVanced Patches selection with others or reset it to the default selection.
|
||||
|
||||
- ### ℹ️ About
|
||||
|
||||
View information about your device and ReVanced Manager. This includes the version of ReVanced Manager and supported architectures of your device.
|
||||
|
||||
## ⏭️ What's next
|
||||
|
||||
The next page will bring you back to the usage page.
|
||||
|
||||
Continue: [🛠️ Usage](2_usage.md)
|
16
docs/2_usage.md
Normal file
@ -0,0 +1,16 @@
|
||||
# 🛠️ Usage
|
||||
|
||||
The following pages will guide you through using ReVanced Manager to patch applications, manage patched applications, and update ReVanced Manager.
|
||||
|
||||
## 📖 Table of contents
|
||||
|
||||
1. [🧩 Patching applications](2_1_patching.md)
|
||||
2. [🧰 Managing patched applications](2_2_managing.md)
|
||||
3. [🔄 Updating ReVanced Manager](2_3_updating.md)
|
||||
4. [⚙️ Configuring ReVanced Manager](2_4_settings.md)
|
||||
|
||||
## ⏭️ What's next
|
||||
|
||||
The next page will guide you through troubleshooting ReVanced Manager.
|
||||
|
||||
Continue: [🛟 Troubleshooting](3_troubleshooting.md)
|
27
docs/3_troubleshooting.md
Normal file
@ -0,0 +1,27 @@
|
||||
# 🛟 Troubleshooting
|
||||
|
||||
In case you encounter any issues while using ReVanced Manager, please refer to this page for possible solutions.
|
||||
|
||||
- ### 💥 App not installed as package conflicts with an existing package
|
||||
|
||||
An existing installation of the application you're trying to patch is conflicting with the patched application. Uninstall the existing application before installing the patched application.
|
||||
|
||||
- ### ❗️Error code `135`, `139` or `1` when patching the application
|
||||
|
||||
Your device is not supported. Refer to the [Prerequisites](0_prerequisites.md) page for supported devices.
|
||||
|
||||
Alternatively, you can use [ReVanced CLI](https://github.com/revanced/revanced-cli) to patch the application.
|
||||
|
||||
- ### 🚫 Non-root install is not possible with the current patches selection
|
||||
|
||||
Select the **Default** chip when choosing patches.
|
||||
|
||||
- ### 🚨 Patched application crashes on launch
|
||||
|
||||
Select the **Default** chip when choosing patches.
|
||||
|
||||
## ⏭️ What's next
|
||||
|
||||
The next page will teach you how to build ReVanced Manager from source.
|
||||
|
||||
Continue: [🛠️ Building from source](4_building.md)
|
40
docs/4_building.md
Normal file
@ -0,0 +1,40 @@
|
||||
# 🛠️ Building 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)
|
||||
|
||||
2. Clone the repository
|
||||
|
||||
```sh
|
||||
git clone https://github.com/revanced/revanced-manager.git && cd revanced-manager
|
||||
```
|
||||
|
||||
3. Create a GitHub personal access token with the `read:packages` scope [here](https://github.com/settings/tokens/new?scopes=read:packages&description=ReVanced)
|
||||
|
||||
4. Add your GitHub username and the token to `~/.gradle/gradle.properties`
|
||||
|
||||
```properties
|
||||
gpr.user = YourUsername
|
||||
gpr.key = ghp_longrandomkey
|
||||
```
|
||||
|
||||
5. Get dependencies
|
||||
|
||||
```sh
|
||||
flutter pub get
|
||||
```
|
||||
|
||||
6. Delete conflicting outputs
|
||||
|
||||
```sh
|
||||
flutter packages pub run build_runner build --delete-conflicting-outputs
|
||||
```
|
||||
|
||||
> **Note**: Must be run every time you sync your local repository with the remote repository.
|
||||
|
||||
7. Build the APK
|
||||
|
||||
```sh
|
||||
flutter build apk
|
||||
```
|
15
docs/README.md
Normal file
@ -0,0 +1,15 @@
|
||||
# 💊 ReVanced Manager
|
||||
|
||||
This documentation explains how to use [ReVanced Manager](https://github.com/revanced/revanced-manager).
|
||||
|
||||
## 📖 Table of contents
|
||||
|
||||
0. [💼 Prerequisites](0_prerequisites.md)
|
||||
1. [⬇️ Installation](1_installation.md)
|
||||
2. [🛠️ Usage](2_usage.md)
|
||||
1. [🧩 Patching applications](2_1_patching.md)
|
||||
2. [🧰 Managing patched applications](2_2_managing.md)
|
||||
3. [🔄 Updating ReVanced Manager](2_3_updating.md)
|
||||
4. [⚙️ Configuring ReVanced Manager](2_4_settings.md)
|
||||
3. [🛟 Troubleshooting](3_troubleshooting.md)
|
||||
4. [🛠 Building from source](4_building.md)
|
2
fastlane/Appfile
Normal file
@ -0,0 +1,2 @@
|
||||
json_key_file("") # Path to the json secret file - Follow https://docs.fastlane.tools/actions/supply/#setup to get one
|
||||
package_name("app.revanced.manager.flutter") # e.g. com.krausefx.app
|
38
fastlane/Fastfile
Normal file
@ -0,0 +1,38 @@
|
||||
# This file contains the fastlane.tools configuration
|
||||
# You can find the documentation at https://docs.fastlane.tools
|
||||
#
|
||||
# For a list of all available actions, check out
|
||||
#
|
||||
# https://docs.fastlane.tools/actions
|
||||
#
|
||||
# For a list of all available plugins, check out
|
||||
#
|
||||
# https://docs.fastlane.tools/plugins/available-plugins
|
||||
#
|
||||
|
||||
# Uncomment the line if you want fastlane to automatically update itself
|
||||
# update_fastlane
|
||||
|
||||
default_platform(:android)
|
||||
|
||||
platform :android do
|
||||
desc "Runs all the tests"
|
||||
lane :test do
|
||||
gradle(task: "test")
|
||||
end
|
||||
|
||||
desc "Submit a new Beta Build to Crashlytics Beta"
|
||||
lane :beta do
|
||||
gradle(task: "clean assembleRelease")
|
||||
crashlytics
|
||||
|
||||
# sh "your_script.sh"
|
||||
# You can also use other beta testing services here
|
||||
end
|
||||
|
||||
desc "Deploy a new version to the Google Play"
|
||||
lane :deploy do
|
||||
gradle(task: "clean assembleRelease")
|
||||
upload_to_play_store
|
||||
end
|
||||
end
|
48
fastlane/README.md
Normal file
@ -0,0 +1,48 @@
|
||||
fastlane documentation
|
||||
----
|
||||
|
||||
# Installation
|
||||
|
||||
Make sure you have the latest version of the Xcode command line tools installed:
|
||||
|
||||
```sh
|
||||
xcode-select --install
|
||||
```
|
||||
|
||||
For _fastlane_ installation instructions, see [Installing _fastlane_](https://docs.fastlane.tools/#installing-fastlane)
|
||||
|
||||
# Available Actions
|
||||
|
||||
## Android
|
||||
|
||||
### android test
|
||||
|
||||
```sh
|
||||
[bundle exec] fastlane android test
|
||||
```
|
||||
|
||||
Runs all the tests
|
||||
|
||||
### android beta
|
||||
|
||||
```sh
|
||||
[bundle exec] fastlane android beta
|
||||
```
|
||||
|
||||
Submit a new Beta Build to Crashlytics Beta
|
||||
|
||||
### android deploy
|
||||
|
||||
```sh
|
||||
[bundle exec] fastlane android deploy
|
||||
```
|
||||
|
||||
Deploy a new version to the Google Play
|
||||
|
||||
----
|
||||
|
||||
This README.md is auto-generated and will be re-generated every time [_fastlane_](https://fastlane.tools) is run.
|
||||
|
||||
More information about _fastlane_ can be found on [fastlane.tools](https://fastlane.tools).
|
||||
|
||||
The documentation of _fastlane_ can be found on [docs.fastlane.tools](https://docs.fastlane.tools).
|
1
fastlane/metadata/android/en-US/full_description.txt
Normal file
@ -0,0 +1 @@
|
||||
ReVanced Manager is an Android application that uses ReVanced Patcher to add, remove, and modify existing functionalities in Android applications
|
BIN
fastlane/metadata/android/en-US/images/featureGraphic.png
Normal file
After Width: | Height: | Size: 110 KiB |
BIN
fastlane/metadata/android/en-US/images/icon.png
Normal file
After Width: | Height: | Size: 10 KiB |
BIN
fastlane/metadata/android/en-US/images/phoneScreenshots/1.jpg
Normal file
After Width: | Height: | Size: 374 KiB |
BIN
fastlane/metadata/android/en-US/images/phoneScreenshots/2.jpg
Normal file
After Width: | Height: | Size: 694 KiB |
1
fastlane/metadata/android/en-US/short_description.txt
Normal file
@ -0,0 +1 @@
|
||||
Patch your favourite apps, right on your device.
|
1
fastlane/metadata/android/en-US/title.txt
Normal file
@ -0,0 +1 @@
|
||||
ReVanced Manager
|
20
fastlane/report.xml
Normal file
@ -3,7 +3,7 @@ import 'dart:io';
|
||||
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:dio_http_cache_lts/dio_http_cache_lts.dart';
|
||||
import 'package:dio_cache_interceptor/dio_cache_interceptor.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter_cache_manager/flutter_cache_manager.dart';
|
||||
import 'package:injectable/injectable.dart';
|
||||
@ -12,11 +12,13 @@ import 'package:revanced_manager/models/patch.dart';
|
||||
@lazySingleton
|
||||
class GithubAPI {
|
||||
late Dio _dio = Dio();
|
||||
final DioCacheManager _dioCacheManager = DioCacheManager(CacheConfig());
|
||||
final Options _cacheOptions = buildCacheOptions(
|
||||
const Duration(hours: 6),
|
||||
|
||||
final _cacheOptions = CacheOptions(
|
||||
store: MemCacheStore(),
|
||||
maxStale: const Duration(days: 1),
|
||||
priority: CachePriority.high,
|
||||
);
|
||||
|
||||
final Map<String, String> repoAppPath = {
|
||||
'com.google.android.youtube': 'youtube',
|
||||
'com.google.android.apps.youtube.music': 'music',
|
||||
@ -36,7 +38,7 @@ class GithubAPI {
|
||||
),
|
||||
);
|
||||
|
||||
_dio.interceptors.add(_dioCacheManager.interceptor);
|
||||
_dio.interceptors.add(DioCacheInterceptor(options: _cacheOptions));
|
||||
} on Exception catch (e) {
|
||||
if (kDebugMode) {
|
||||
print(e);
|
||||
@ -46,7 +48,7 @@ class GithubAPI {
|
||||
|
||||
Future<void> clearAllCache() async {
|
||||
try {
|
||||
await _dioCacheManager.clearAll();
|
||||
await _cacheOptions.store!.clean();
|
||||
} on Exception catch (e) {
|
||||
if (kDebugMode) {
|
||||
print(e);
|
||||
@ -54,11 +56,12 @@ class GithubAPI {
|
||||
}
|
||||
}
|
||||
|
||||
Future<Map<String, dynamic>?> getLatestRelease(String repoName) async {
|
||||
Future<Map<String, dynamic>?> getLatestRelease(
|
||||
String repoName,
|
||||
) async {
|
||||
try {
|
||||
final response = await _dio.get(
|
||||
'/repos/$repoName/releases',
|
||||
options: _cacheOptions,
|
||||
);
|
||||
return response.data[0];
|
||||
} on Exception catch (e) {
|
||||
@ -83,12 +86,11 @@ class GithubAPI {
|
||||
'path': path,
|
||||
'since': since.toIso8601String(),
|
||||
},
|
||||
options: _cacheOptions,
|
||||
);
|
||||
final List<dynamic> commits = response.data;
|
||||
return commits
|
||||
.map(
|
||||
(commit) => (commit['commit']['message']).split('\n')[0] +
|
||||
(commit) => commit['commit']['message'].split('\n')[0] +
|
||||
' - ' +
|
||||
commit['commit']['author']['name'] +
|
||||
'\n' as String,
|
||||
@ -102,7 +104,10 @@ class GithubAPI {
|
||||
return [];
|
||||
}
|
||||
|
||||
Future<File?> getLatestReleaseFile(String extension, String repoName) async {
|
||||
Future<File?> getLatestReleaseFile(
|
||||
String extension,
|
||||
String repoName,
|
||||
) async {
|
||||
try {
|
||||
final Map<String, dynamic>? release = await getLatestRelease(repoName);
|
||||
if (release != null) {
|
||||
|
@ -11,6 +11,7 @@ import 'package:revanced_manager/models/patched_application.dart';
|
||||
import 'package:revanced_manager/services/github_api.dart';
|
||||
import 'package:revanced_manager/services/revanced_api.dart';
|
||||
import 'package:revanced_manager/services/root_api.dart';
|
||||
import 'package:revanced_manager/utils/check_for_supported_patch.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
|
||||
@lazySingleton
|
||||
@ -21,7 +22,11 @@ class ManagerAPI {
|
||||
final String patcherRepo = 'revanced-patcher';
|
||||
final String cliRepo = 'revanced-cli';
|
||||
late SharedPreferences _prefs;
|
||||
bool isRooted = false;
|
||||
String storedPatchesFile = '/selected-patches.json';
|
||||
String keystoreFile =
|
||||
'/sdcard/Android/data/app.revanced.manager.flutter/files/revanced-manager.keystore';
|
||||
String defaultKeystorePassword = 's3cur3p@ssw0rd';
|
||||
String defaultApiUrl = 'https://releases.revanced.app/';
|
||||
String defaultRepoUrl = 'https://api.github.com';
|
||||
String defaultPatcherRepo = 'revanced/revanced-patcher';
|
||||
@ -29,9 +34,14 @@ class ManagerAPI {
|
||||
String defaultIntegrationsRepo = 'revanced/revanced-integrations';
|
||||
String defaultCliRepo = 'revanced/revanced-cli';
|
||||
String defaultManagerRepo = 'revanced/revanced-manager';
|
||||
String? patchesVersion = '';
|
||||
bool isDefaultPatchesRepo() {
|
||||
return getPatchesRepo() == 'revanced/revanced-patches';
|
||||
}
|
||||
|
||||
Future<void> initialize() async {
|
||||
_prefs = await SharedPreferences.getInstance();
|
||||
isRooted = await _rootAPI.isRooted();
|
||||
storedPatchesFile =
|
||||
(await getApplicationDocumentsDirectory()).path + storedPatchesFile;
|
||||
}
|
||||
@ -114,6 +124,14 @@ class ManagerAPI {
|
||||
await _prefs.setBool('experimentalPatchesEnabled', value);
|
||||
}
|
||||
|
||||
Future<void> setKeystorePassword(String password) async {
|
||||
await _prefs.setString('keystorePassword', password);
|
||||
}
|
||||
|
||||
String getKeystorePassword() {
|
||||
return _prefs.getString('keystorePassword') ?? defaultKeystorePassword;
|
||||
}
|
||||
|
||||
Future<void> deleteTempFolder() async {
|
||||
final Directory dir = Directory('/data/local/tmp/revanced-manager');
|
||||
if (await dir.exists()) {
|
||||
@ -123,7 +141,7 @@ class ManagerAPI {
|
||||
|
||||
Future<void> deleteKeystore() async {
|
||||
final File keystore = File(
|
||||
'/sdcard/Android/data/app.revanced.manager.flutter/files/revanced-manager.keystore',
|
||||
keystoreFile,
|
||||
);
|
||||
if (await keystore.exists()) {
|
||||
await keystore.delete();
|
||||
@ -135,7 +153,9 @@ class ManagerAPI {
|
||||
return apps.map((a) => PatchedApplication.fromJson(jsonDecode(a))).toList();
|
||||
}
|
||||
|
||||
Future<void> setPatchedApps(List<PatchedApplication> patchedApps) async {
|
||||
Future<void> setPatchedApps(
|
||||
List<PatchedApplication> patchedApps,
|
||||
) async {
|
||||
if (patchedApps.length > 1) {
|
||||
patchedApps.sort((a, b) => a.name.compareTo(b.name));
|
||||
}
|
||||
@ -237,15 +257,24 @@ class ManagerAPI {
|
||||
}
|
||||
|
||||
Future<File?> downloadManager() async {
|
||||
return await _revancedAPI.getLatestReleaseFile('.apk', defaultManagerRepo);
|
||||
return await _revancedAPI.getLatestReleaseFile(
|
||||
'.apk',
|
||||
defaultManagerRepo,
|
||||
);
|
||||
}
|
||||
|
||||
Future<String?> getLatestPatcherReleaseTime() async {
|
||||
return await _revancedAPI.getLatestReleaseTime('.gz', defaultPatcherRepo);
|
||||
return await _revancedAPI.getLatestReleaseTime(
|
||||
'.gz',
|
||||
defaultPatcherRepo,
|
||||
);
|
||||
}
|
||||
|
||||
Future<String?> getLatestManagerReleaseTime() async {
|
||||
return await _revancedAPI.getLatestReleaseTime('.apk', defaultManagerRepo);
|
||||
return await _revancedAPI.getLatestReleaseTime(
|
||||
'.apk',
|
||||
defaultManagerRepo,
|
||||
);
|
||||
}
|
||||
|
||||
Future<String?> getLatestManagerVersion() async {
|
||||
@ -267,6 +296,18 @@ class ManagerAPI {
|
||||
return packageInfo.version;
|
||||
}
|
||||
|
||||
Future<String?> getCurrentPatchesVersion() async {
|
||||
if (isDefaultPatchesRepo()) {
|
||||
patchesVersion = await getLatestPatchesVersion();
|
||||
// print('Patches version: $patchesVersion');
|
||||
} else {
|
||||
// fetch from github
|
||||
patchesVersion =
|
||||
await _githubAPI.getLastestReleaseVersion(getPatchesRepo());
|
||||
}
|
||||
return patchesVersion ?? '0.0.0';
|
||||
}
|
||||
|
||||
Future<List<PatchedApplication>> getAppsToRemove(
|
||||
List<PatchedApplication> patchedApps,
|
||||
) async {
|
||||
@ -359,8 +400,9 @@ class ManagerAPI {
|
||||
final int currentInstalledVersionInt = int.parse(
|
||||
currentInstalledVersion.replaceAll(RegExp('[^0-9]'), ''),
|
||||
);
|
||||
final int currentSavedVersionInt =
|
||||
int.parse(currentSavedVersion.replaceAll(RegExp('[^0-9]'), ''));
|
||||
final int currentSavedVersionInt = int.parse(
|
||||
currentSavedVersion.replaceAll(RegExp('[^0-9]'), ''),
|
||||
);
|
||||
if (currentInstalledVersionInt > currentSavedVersionInt) {
|
||||
app.hasUpdates = true;
|
||||
}
|
||||
@ -383,7 +425,10 @@ class ManagerAPI {
|
||||
return !existsNonRoot;
|
||||
}
|
||||
|
||||
Future<bool> hasAppUpdates(String packageName, DateTime patchDate) async {
|
||||
Future<bool> hasAppUpdates(
|
||||
String packageName,
|
||||
DateTime patchDate,
|
||||
) async {
|
||||
final List<String> commits = await _githubAPI.getCommits(
|
||||
packageName,
|
||||
getPatchesRepo(),
|
||||
@ -421,7 +466,10 @@ class ManagerAPI {
|
||||
return app != null && app.isSplit;
|
||||
}
|
||||
|
||||
Future<void> setSelectedPatches(String app, List<String> patches) async {
|
||||
Future<void> setSelectedPatches(
|
||||
String app,
|
||||
List<String> patches,
|
||||
) async {
|
||||
final File selectedPatchesFile = File(storedPatchesFile);
|
||||
final Map<String, dynamic> patchesMap = await readSelectedPatchesFile();
|
||||
if (patches.isEmpty) {
|
||||
@ -432,9 +480,33 @@ class ManagerAPI {
|
||||
selectedPatchesFile.writeAsString(jsonEncode(patchesMap));
|
||||
}
|
||||
|
||||
// get default patches for app
|
||||
Future<List<String>> getDefaultPatches() async {
|
||||
final List<Patch> patches = await getPatches();
|
||||
final List<String> defaultPatches = [];
|
||||
if (areExperimentalPatchesEnabled() == false) {
|
||||
defaultPatches.addAll(
|
||||
patches
|
||||
.where(
|
||||
(element) =>
|
||||
element.excluded == false && isPatchSupported(element),
|
||||
)
|
||||
.map((p) => p.name),
|
||||
);
|
||||
} else {
|
||||
defaultPatches.addAll(
|
||||
patches
|
||||
.where((element) => isPatchSupported(element))
|
||||
.map((p) => p.name),
|
||||
);
|
||||
}
|
||||
return defaultPatches;
|
||||
}
|
||||
|
||||
Future<List<String>> getSelectedPatches(String app) async {
|
||||
final Map<String, dynamic> patchesMap = await readSelectedPatchesFile();
|
||||
return List.from(patchesMap.putIfAbsent(app, () => List.empty()));
|
||||
final List<String> defaultPatches = await getDefaultPatches();
|
||||
return List.from(patchesMap.putIfAbsent(app, () => defaultPatches));
|
||||
}
|
||||
|
||||
Future<Map<String, dynamic>> readSelectedPatchesFile() async {
|
||||
|
@ -118,13 +118,17 @@ class PatcherAPI {
|
||||
return filteredPatches[packageName];
|
||||
}
|
||||
|
||||
Future<List<Patch>> getAppliedPatches(List<String> appliedPatches) async {
|
||||
Future<List<Patch>> getAppliedPatches(
|
||||
List<String> appliedPatches,
|
||||
) async {
|
||||
return _patches
|
||||
.where((patch) => appliedPatches.contains(patch.name))
|
||||
.toList();
|
||||
}
|
||||
|
||||
Future<bool> needsResourcePatching(List<Patch> selectedPatches) async {
|
||||
Future<bool> needsResourcePatching(
|
||||
List<Patch> selectedPatches,
|
||||
) async {
|
||||
return selectedPatches.any(
|
||||
(patch) => patch.dependencies.any(
|
||||
(dep) => dep.contains('resource-'),
|
||||
@ -210,6 +214,7 @@ class PatcherAPI {
|
||||
'selectedPatches': selectedPatches.map((p) => p.name).toList(),
|
||||
'cacheDirPath': cacheDir.path,
|
||||
'keyStoreFilePath': _keyStoreFile.path,
|
||||
'keystorePassword': _managerAPI.getKeystorePassword(),
|
||||
},
|
||||
);
|
||||
} on Exception catch (e) {
|
||||
@ -234,7 +239,9 @@ class PatcherAPI {
|
||||
}
|
||||
} else {
|
||||
await AppInstaller.installApk(_outFile!.path);
|
||||
return await DeviceApps.isAppInstalled(patchedApp.packageName);
|
||||
return await DeviceApps.isAppInstalled(
|
||||
patchedApp.packageName,
|
||||
);
|
||||
}
|
||||
} on Exception catch (e) {
|
||||
if (kDebugMode) {
|
||||
@ -303,7 +310,7 @@ class PatcherAPI {
|
||||
ShareExtend.share(log.path, 'file');
|
||||
}
|
||||
|
||||
String getRecommendedVersion(String packageName) {
|
||||
String getSuggestedVersion(String packageName) {
|
||||
final Map<String, int> versions = {};
|
||||
for (final Patch patch in _patches) {
|
||||
final Package? package = patch.compatiblePackages.firstWhereOrNull(
|
||||
|
@ -1,46 +1,34 @@
|
||||
import 'dart:developer';
|
||||
import 'dart:async';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:dio_http_cache_lts/dio_http_cache_lts.dart';
|
||||
import 'package:dio_cache_interceptor/dio_cache_interceptor.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter_cache_manager/flutter_cache_manager.dart';
|
||||
import 'package:injectable/injectable.dart';
|
||||
import 'package:native_dio_client/native_dio_client.dart';
|
||||
import 'package:revanced_manager/models/patch.dart';
|
||||
import 'package:revanced_manager/utils/check_for_gms.dart';
|
||||
import 'package:timeago/timeago.dart';
|
||||
|
||||
@lazySingleton
|
||||
class RevancedAPI {
|
||||
late Dio _dio = Dio();
|
||||
final DioCacheManager _dioCacheManager = DioCacheManager(CacheConfig());
|
||||
final Options _cacheOptions = buildCacheOptions(
|
||||
const Duration(hours: 6),
|
||||
|
||||
final _cacheOptions = CacheOptions(
|
||||
store: MemCacheStore(),
|
||||
maxStale: const Duration(days: 1),
|
||||
priority: CachePriority.high,
|
||||
);
|
||||
|
||||
Future<void> initialize(String apiUrl) async {
|
||||
try {
|
||||
final bool isGMSInstalled = await checkForGMS();
|
||||
_dio = Dio(
|
||||
BaseOptions(
|
||||
baseUrl: apiUrl,
|
||||
),
|
||||
);
|
||||
|
||||
if (!isGMSInstalled) {
|
||||
_dio = Dio(
|
||||
BaseOptions(
|
||||
baseUrl: apiUrl,
|
||||
),
|
||||
);
|
||||
log('ReVanced API: Using default engine + $isGMSInstalled');
|
||||
} else {
|
||||
_dio = Dio(
|
||||
BaseOptions(
|
||||
baseUrl: apiUrl,
|
||||
),
|
||||
)..httpClientAdapter = NativeAdapter();
|
||||
log('ReVanced API: Using CronetEngine + $isGMSInstalled');
|
||||
}
|
||||
_dio.interceptors.add(_dioCacheManager.interceptor);
|
||||
_dio.interceptors.add(DioCacheInterceptor(options: _cacheOptions));
|
||||
} on Exception catch (e) {
|
||||
if (kDebugMode) {
|
||||
print(e);
|
||||
@ -50,7 +38,7 @@ class RevancedAPI {
|
||||
|
||||
Future<void> clearAllCache() async {
|
||||
try {
|
||||
await _dioCacheManager.clearAll();
|
||||
await _cacheOptions.store!.clean();
|
||||
} on Exception catch (e) {
|
||||
if (kDebugMode) {
|
||||
print(e);
|
||||
@ -61,7 +49,7 @@ class RevancedAPI {
|
||||
Future<Map<String, List<dynamic>>> getContributors() async {
|
||||
final Map<String, List<dynamic>> contributors = {};
|
||||
try {
|
||||
final response = await _dio.get('/contributors', options: _cacheOptions);
|
||||
final response = await _dio.get('/contributors');
|
||||
final List<dynamic> repositories = response.data['repositories'];
|
||||
for (final Map<String, dynamic> repo in repositories) {
|
||||
final String name = repo['name'];
|
||||
@ -78,7 +66,7 @@ class RevancedAPI {
|
||||
|
||||
Future<List<Patch>> getPatches() async {
|
||||
try {
|
||||
final response = await _dio.get('/patches', options: _cacheOptions);
|
||||
final response = await _dio.get('/patches');
|
||||
final List<dynamic> patches = response.data;
|
||||
return patches.map((patch) => Patch.fromJson(patch)).toList();
|
||||
} on Exception catch (e) {
|
||||
@ -94,7 +82,7 @@ class RevancedAPI {
|
||||
String repoName,
|
||||
) async {
|
||||
try {
|
||||
final response = await _dio.get('/tools', options: _cacheOptions);
|
||||
final response = await _dio.get('/tools');
|
||||
final List<dynamic> tools = response.data['tools'];
|
||||
return tools.firstWhereOrNull(
|
||||
(t) =>
|
||||
@ -130,7 +118,10 @@ class RevancedAPI {
|
||||
return null;
|
||||
}
|
||||
|
||||
Future<File?> getLatestReleaseFile(String extension, String repoName) async {
|
||||
Future<File?> getLatestReleaseFile(
|
||||
String extension,
|
||||
String repoName,
|
||||
) async {
|
||||
try {
|
||||
final Map<String, dynamic>? release = await _getLatestRelease(
|
||||
extension,
|
||||
@ -149,6 +140,43 @@ class RevancedAPI {
|
||||
return null;
|
||||
}
|
||||
|
||||
StreamController<double> managerUpdateProgress = StreamController<double>();
|
||||
|
||||
void updateManagerDownloadProgress(int progress) {
|
||||
managerUpdateProgress.add(progress.toDouble());
|
||||
}
|
||||
|
||||
Stream<double> getManagerUpdateProgress() {
|
||||
return managerUpdateProgress.stream;
|
||||
}
|
||||
|
||||
void disposeManagerUpdateProgress() {
|
||||
managerUpdateProgress.close();
|
||||
}
|
||||
|
||||
Future<File?> downloadManager() async {
|
||||
final Map<String, dynamic>? release = await _getLatestRelease(
|
||||
'.apk',
|
||||
'revanced/revanced-manager',
|
||||
);
|
||||
File? outputFile;
|
||||
await for (final result in DefaultCacheManager().getFileStream(
|
||||
release!['browser_download_url'] as String,
|
||||
withProgress: true,
|
||||
)) {
|
||||
if (result is DownloadProgress) {
|
||||
final totalSize = result.totalSize ?? 10000000;
|
||||
final progress = (result.downloaded / totalSize * 100).round();
|
||||
|
||||
updateManagerDownloadProgress(progress);
|
||||
} else if (result is FileInfo) {
|
||||
// The download is complete; convert the FileInfo object to a File object
|
||||
outputFile = File(result.file.path);
|
||||
}
|
||||
}
|
||||
return outputFile;
|
||||
}
|
||||
|
||||
Future<String?> getLatestReleaseTime(
|
||||
String extension,
|
||||
String repoName,
|
||||
|
@ -1,8 +1,9 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/material.dart' hide SearchBar;
|
||||
import 'package:flutter_i18n/flutter_i18n.dart';
|
||||
import 'package:revanced_manager/ui/views/app_selector/app_selector_viewmodel.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/not_installed_app_item.dart';
|
||||
import 'package:revanced_manager/ui/widgets/shared/search_bar.dart';
|
||||
import 'package:stacked/stacked.dart' hide SkeletonLoader;
|
||||
|
||||
@ -76,30 +77,61 @@ class _AppSelectorViewState extends State<AppSelectorView> {
|
||||
SliverToBoxAdapter(
|
||||
child: model.noApps
|
||||
? Center(
|
||||
child: I18nText('appSelectorCard.noAppsLabel'),
|
||||
child: I18nText(
|
||||
'appSelectorCard.noAppsLabel',
|
||||
child: Text(
|
||||
'',
|
||||
style: TextStyle(
|
||||
color:
|
||||
Theme.of(context).textTheme.titleLarge!.color,
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
: model.apps.isEmpty
|
||||
? const AppSkeletonLoader()
|
||||
: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12.0)
|
||||
.copyWith(bottom: 80),
|
||||
.copyWith(
|
||||
bottom:
|
||||
MediaQuery.of(context).viewPadding.bottom + 8.0,
|
||||
),
|
||||
child: Column(
|
||||
children: model
|
||||
.getFilteredApps(_query)
|
||||
.map(
|
||||
(app) => InstalledAppItem(
|
||||
name: app.appName,
|
||||
pkgName: app.packageName,
|
||||
icon: app.icon,
|
||||
patchesCount:
|
||||
model.patchesCount(app.packageName),
|
||||
onTap: () {
|
||||
model.selectApp(app);
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
children: [
|
||||
...model
|
||||
.getFilteredApps(_query)
|
||||
.map(
|
||||
(app) => InstalledAppItem(
|
||||
name: app.appName,
|
||||
pkgName: app.packageName,
|
||||
icon: app.icon,
|
||||
patchesCount:
|
||||
model.patchesCount(app.packageName),
|
||||
suggestedVersion:
|
||||
model.getSuggestedVersion(
|
||||
app.packageName,
|
||||
),
|
||||
installedVersion: app.versionName!,
|
||||
onTap: () => model.canSelectInstalled(
|
||||
context, app.packageName),
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
...model
|
||||
.getFilteredAppsNames(_query)
|
||||
.map(
|
||||
(app) => NotInstalledAppItem(
|
||||
name: app,
|
||||
patchesCount: model.patchesCount(app),
|
||||
suggestedVersion:
|
||||
model.getSuggestedVersion(app),
|
||||
onTap: () {
|
||||
model.showDownloadToast();
|
||||
},
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
|
@ -4,25 +4,37 @@ import 'package:device_apps/device_apps.dart';
|
||||
import 'package:file_picker/file_picker.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_i18n/flutter_i18n.dart';
|
||||
import 'package:revanced_manager/app/app.locator.dart';
|
||||
import 'package:revanced_manager/models/patch.dart';
|
||||
import 'package:revanced_manager/models/patched_application.dart';
|
||||
import 'package:revanced_manager/services/manager_api.dart';
|
||||
import 'package:revanced_manager/services/patcher_api.dart';
|
||||
import 'package:revanced_manager/services/revanced_api.dart';
|
||||
import 'package:revanced_manager/services/toast.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';
|
||||
|
||||
class AppSelectorViewModel extends BaseViewModel {
|
||||
final PatcherAPI _patcherAPI = locator<PatcherAPI>();
|
||||
final ManagerAPI _managerAPI = locator<ManagerAPI>();
|
||||
final RevancedAPI _revancedAPI = locator<RevancedAPI>();
|
||||
final Toast _toast = locator<Toast>();
|
||||
final List<ApplicationWithIcon> apps = [];
|
||||
List<String> allApps = [];
|
||||
bool noApps = false;
|
||||
bool isRooted = false;
|
||||
int patchesCount(String packageName) {
|
||||
return _patcherAPI.getFilteredPatches(packageName).length;
|
||||
}
|
||||
|
||||
List<Patch> patches = [];
|
||||
|
||||
Future<void> initialize() async {
|
||||
patches = await _revancedAPI.getPatches();
|
||||
isRooted = _managerAPI.isRooted;
|
||||
|
||||
apps.addAll(
|
||||
await _patcherAPI
|
||||
.getFilteredInstalledApps(_managerAPI.areUniversalPatchesEnabled()),
|
||||
@ -34,9 +46,33 @@ class AppSelectorViewModel extends BaseViewModel {
|
||||
.compareTo(_patcherAPI.getFilteredPatches(a.packageName).length),
|
||||
);
|
||||
noApps = apps.isEmpty;
|
||||
getAllApps();
|
||||
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
List<String> getAllApps() {
|
||||
allApps = patches
|
||||
.expand((e) => e.compatiblePackages.map((p) => p.name))
|
||||
.toSet()
|
||||
.where((name) => !apps.any((app) => app.packageName == name))
|
||||
.toList();
|
||||
|
||||
return allApps;
|
||||
}
|
||||
|
||||
String getSuggestedVersion(String packageName) {
|
||||
return _patcherAPI.getSuggestedVersion(packageName);
|
||||
}
|
||||
|
||||
Future<bool> checkSplitApk(String packageName) async {
|
||||
final app = await DeviceApps.getApp(packageName);
|
||||
if (app != null) {
|
||||
return app.isSplit;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
Future<void> selectApp(ApplicationWithIcon application) async {
|
||||
locator<PatcherViewModel>().selectedApp = PatchedApplication(
|
||||
name: application.appName,
|
||||
@ -50,6 +86,95 @@ class AppSelectorViewModel extends BaseViewModel {
|
||||
locator<PatcherViewModel>().loadLastSelectedPatches();
|
||||
}
|
||||
|
||||
Future<void> canSelectInstalled(
|
||||
BuildContext context,
|
||||
String packageName,
|
||||
) async {
|
||||
final app =
|
||||
await DeviceApps.getApp(packageName, true) as ApplicationWithIcon?;
|
||||
if (app != null) {
|
||||
if (await checkSplitApk(packageName) && !isRooted) {
|
||||
return showSelectFromStorageDialog(context);
|
||||
} else if (!await checkSplitApk(packageName) || isRooted) {
|
||||
selectApp(app);
|
||||
Navigator.pop(context);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Future showSelectFromStorageDialog(BuildContext context) async {
|
||||
return showDialog(
|
||||
context: context,
|
||||
builder: (context) => SimpleDialog(
|
||||
alignment: Alignment.center,
|
||||
contentPadding:
|
||||
const EdgeInsets.symmetric(horizontal: 20, vertical: 20),
|
||||
children: [
|
||||
const SizedBox(height: 10),
|
||||
Icon(
|
||||
Icons.block,
|
||||
size: 28,
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
I18nText(
|
||||
'appSelectorView.featureNotAvailable',
|
||||
child: const Text(
|
||||
'',
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
fontSize: 20,
|
||||
fontWeight: FontWeight.w600,
|
||||
wordSpacing: 1.5,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
I18nText(
|
||||
'appSelectorView.featureNotAvailableText',
|
||||
child: const Text(
|
||||
'',
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 30),
|
||||
CustomMaterialButton(
|
||||
onPressed: () => selectAppFromStorage(context).then(
|
||||
(_) {
|
||||
Navigator.pop(context);
|
||||
Navigator.pop(context);
|
||||
},
|
||||
),
|
||||
label: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
const Icon(Icons.sd_card),
|
||||
const SizedBox(width: 10),
|
||||
I18nText('appSelectorView.selectFromStorageButton'),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
CustomMaterialButton(
|
||||
isFilled: false,
|
||||
onPressed: () {
|
||||
Navigator.pop(context);
|
||||
},
|
||||
label: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
const SizedBox(width: 10),
|
||||
I18nText('cancelButton'),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> selectAppFromStorage(BuildContext context) async {
|
||||
try {
|
||||
final FilePickerResult? result = await FilePicker.platform.pickFiles(
|
||||
@ -105,4 +230,18 @@ class AppSelectorViewModel extends BaseViewModel {
|
||||
)
|
||||
.toList();
|
||||
}
|
||||
|
||||
List<String> getFilteredAppsNames(String query) {
|
||||
return allApps
|
||||
.where(
|
||||
(app) =>
|
||||
query.isEmpty ||
|
||||
query.length < 2 ||
|
||||
app.toLowerCase().contains(query.toLowerCase()),
|
||||
)
|
||||
.toList();
|
||||
}
|
||||
|
||||
void showDownloadToast() =>
|
||||
_toast.showBottom('appSelectorView.downloadToast');
|
||||
}
|
||||
|
@ -57,6 +57,7 @@ class ContributorsView extends StatelessWidget {
|
||||
title: 'contributorsView.managerContributors',
|
||||
contributors: model.managerContributors,
|
||||
),
|
||||
SizedBox(height: MediaQuery.of(context).viewPadding.bottom)
|
||||
],
|
||||
),
|
||||
),
|
||||
|
@ -17,6 +17,7 @@ class HomeView extends StatelessWidget {
|
||||
Widget build(BuildContext context) {
|
||||
return ViewModelBuilder<HomeViewModel>.reactive(
|
||||
disposeViewModel: false,
|
||||
fireOnViewModelReadyOnce: true,
|
||||
onViewModelReady: (model) => model.initialize(context),
|
||||
viewModelBuilder: () => locator<HomeViewModel>(),
|
||||
builder: (context, model, child) => Scaffold(
|
||||
@ -50,8 +51,9 @@ class HomeView extends StatelessWidget {
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
LatestCommitCard(
|
||||
onPressed: () =>
|
||||
onPressedManager: () =>
|
||||
model.showUpdateConfirmationDialog(context),
|
||||
onPressedPatches: () => model.forceRefresh(context),
|
||||
),
|
||||
const SizedBox(height: 23),
|
||||
I18nText(
|
||||
|
@ -1,27 +1,29 @@
|
||||
// ignore_for_file: use_build_context_synchronously
|
||||
import 'dart:async';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:app_installer/app_installer.dart';
|
||||
import 'package:cross_connectivity/cross_connectivity.dart';
|
||||
import 'package:device_apps/device_apps.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_i18n/flutter_i18n.dart';
|
||||
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
|
||||
import 'package:injectable/injectable.dart';
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
import 'package:revanced_manager/app/app.locator.dart';
|
||||
import 'package:revanced_manager/app/app.router.dart';
|
||||
import 'package:revanced_manager/models/patched_application.dart';
|
||||
import 'package:revanced_manager/services/github_api.dart';
|
||||
import 'package:revanced_manager/services/manager_api.dart';
|
||||
import 'package:revanced_manager/services/patcher_api.dart';
|
||||
import 'package:revanced_manager/services/revanced_api.dart';
|
||||
import 'package:revanced_manager/services/toast.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/widgets/homeView/update_confirmation_dialog.dart';
|
||||
import 'package:revanced_manager/ui/widgets/shared/custom_material_button.dart';
|
||||
import 'package:stacked/stacked.dart';
|
||||
import 'package:stacked_services/stacked_services.dart';
|
||||
import 'package:timezone/timezone.dart' as tz;
|
||||
|
||||
@lazySingleton
|
||||
class HomeViewModel extends BaseViewModel {
|
||||
@ -29,20 +31,32 @@ class HomeViewModel extends BaseViewModel {
|
||||
final ManagerAPI _managerAPI = locator<ManagerAPI>();
|
||||
final PatcherAPI _patcherAPI = locator<PatcherAPI>();
|
||||
final GithubAPI _githubAPI = locator<GithubAPI>();
|
||||
final RevancedAPI _revancedAPI = locator<RevancedAPI>();
|
||||
final Toast _toast = locator<Toast>();
|
||||
final flutterLocalNotificationsPlugin = FlutterLocalNotificationsPlugin();
|
||||
DateTime? _lastUpdate;
|
||||
bool showUpdatableApps = false;
|
||||
List<PatchedApplication> patchedInstalledApps = [];
|
||||
List<PatchedApplication> patchedUpdatableApps = [];
|
||||
String? _latestManagerVersion = '';
|
||||
|
||||
Future<void> initialize(BuildContext context) async {
|
||||
_latestManagerVersion = await _managerAPI.getLatestManagerVersion();
|
||||
await flutterLocalNotificationsPlugin.initialize(
|
||||
const InitializationSettings(
|
||||
android: AndroidInitializationSettings('ic_notification'),
|
||||
),
|
||||
onSelectNotification: (p) =>
|
||||
DeviceApps.openApp('app.revanced.manager.flutter'),
|
||||
onDidReceiveNotificationResponse: (response) async {
|
||||
if (response.id == 0) {
|
||||
_toast.showBottom('homeView.installingMessage');
|
||||
final File? managerApk = await _managerAPI.downloadManager();
|
||||
if (managerApk != null) {
|
||||
await AppInstaller.installApk(managerApk.path);
|
||||
} else {
|
||||
_toast.showBottom('homeView.errorDownloadMessage');
|
||||
}
|
||||
}
|
||||
},
|
||||
);
|
||||
flutterLocalNotificationsPlugin
|
||||
.resolvePlatformSpecificImplementation<
|
||||
@ -52,6 +66,17 @@ class HomeViewModel extends BaseViewModel {
|
||||
if (!isConnected) {
|
||||
_toast.showBottom('homeView.noConnection');
|
||||
}
|
||||
final NotificationAppLaunchDetails? notificationAppLaunchDetails =
|
||||
await flutterLocalNotificationsPlugin.getNotificationAppLaunchDetails();
|
||||
if (notificationAppLaunchDetails?.didNotificationLaunchApp ?? false) {
|
||||
_toast.showBottom('homeView.installingMessage');
|
||||
final File? managerApk = await _managerAPI.downloadManager();
|
||||
if (managerApk != null) {
|
||||
await AppInstaller.installApk(managerApk.path);
|
||||
} else {
|
||||
_toast.showBottom('homeView.errorDownloadMessage');
|
||||
}
|
||||
}
|
||||
_getPatchedApps();
|
||||
_managerAPI.reAssessSavedApps().then((_) => _getPatchedApps());
|
||||
}
|
||||
@ -86,14 +111,29 @@ class HomeViewModel extends BaseViewModel {
|
||||
}
|
||||
|
||||
Future<bool> hasManagerUpdates() async {
|
||||
final String? latestVersion = await _managerAPI.getLatestManagerVersion();
|
||||
final String currentVersion = await _managerAPI.getCurrentManagerVersion();
|
||||
String currentVersion = await _managerAPI.getCurrentManagerVersion();
|
||||
_latestManagerVersion = await _managerAPI.getLatestManagerVersion();
|
||||
|
||||
// add v to current version
|
||||
if (!currentVersion.startsWith('v')) {
|
||||
currentVersion = 'v$currentVersion';
|
||||
}
|
||||
|
||||
if (_latestManagerVersion != currentVersion) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
Future<bool> hasPatchesUpdates() async {
|
||||
final String? latestVersion = await _managerAPI.getLatestPatchesVersion();
|
||||
final String? currentVersion = await _managerAPI.getCurrentPatchesVersion();
|
||||
if (latestVersion != null) {
|
||||
try {
|
||||
final int latestVersionInt =
|
||||
int.parse(latestVersion.replaceAll(RegExp('[^0-9]'), ''));
|
||||
final int currentVersionInt =
|
||||
int.parse(currentVersion.replaceAll(RegExp('[^0-9]'), ''));
|
||||
int.parse(currentVersion!.replaceAll(RegExp('[^0-9]'), ''));
|
||||
return latestVersionInt > currentVersionInt;
|
||||
} on Exception catch (e) {
|
||||
if (kDebugMode) {
|
||||
@ -105,35 +145,115 @@ class HomeViewModel extends BaseViewModel {
|
||||
return false;
|
||||
}
|
||||
|
||||
Future<File?> downloadManager() async {
|
||||
try {
|
||||
final response = await _revancedAPI.downloadManager();
|
||||
final bytes = await response!.readAsBytes();
|
||||
final tempDir = await getTemporaryDirectory();
|
||||
final tempFile = File('${tempDir.path}/revanced-manager.apk');
|
||||
await tempFile.writeAsBytes(bytes);
|
||||
return tempFile;
|
||||
} on Exception catch (e) {
|
||||
if (kDebugMode) {
|
||||
print(e);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> updateManager(BuildContext context) async {
|
||||
try {
|
||||
_toast.showBottom('homeView.downloadingMessage');
|
||||
final File? managerApk = await _managerAPI.downloadManager();
|
||||
if (managerApk != null) {
|
||||
await flutterLocalNotificationsPlugin.zonedSchedule(
|
||||
0,
|
||||
FlutterI18n.translate(
|
||||
context,
|
||||
'homeView.notificationTitle',
|
||||
),
|
||||
FlutterI18n.translate(
|
||||
context,
|
||||
'homeView.notificationText',
|
||||
),
|
||||
tz.TZDateTime.now(tz.local).add(const Duration(seconds: 5)),
|
||||
const NotificationDetails(
|
||||
android: AndroidNotificationDetails(
|
||||
'revanced_manager_channel',
|
||||
'ReVanced Manager Channel',
|
||||
importance: Importance.max,
|
||||
priority: Priority.high,
|
||||
ticker: 'ticker',
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) => SimpleDialog(
|
||||
contentPadding: const EdgeInsets.all(16.0),
|
||||
title: I18nText(
|
||||
'homeView.downloadingMessage',
|
||||
child: Text(
|
||||
'',
|
||||
style: TextStyle(
|
||||
fontSize: 20,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Theme.of(context).colorScheme.secondary,
|
||||
),
|
||||
),
|
||||
),
|
||||
androidAllowWhileIdle: true,
|
||||
uiLocalNotificationDateInterpretation:
|
||||
UILocalNotificationDateInterpretation.absoluteTime,
|
||||
);
|
||||
children: [
|
||||
Column(
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Icon(
|
||||
Icons.new_releases_outlined,
|
||||
color: Theme.of(context).colorScheme.secondary,
|
||||
),
|
||||
const SizedBox(width: 8.0),
|
||||
Text(
|
||||
'$_latestManagerVersion',
|
||||
style: TextStyle(
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Theme.of(context).colorScheme.secondary,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 16.0),
|
||||
StreamBuilder<double>(
|
||||
initialData: 0.0,
|
||||
stream: _revancedAPI.managerUpdateProgress.stream,
|
||||
builder: (context, snapshot) {
|
||||
return LinearProgressIndicator(
|
||||
value: snapshot.data! * 0.01,
|
||||
valueColor: AlwaysStoppedAnimation<Color>(
|
||||
Theme.of(context).colorScheme.secondary,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
const SizedBox(height: 16.0),
|
||||
Align(
|
||||
alignment: Alignment.centerRight,
|
||||
child: CustomMaterialButton(
|
||||
label: I18nText('cancelButton'),
|
||||
onPressed: () {
|
||||
_revancedAPI.disposeManagerUpdateProgress();
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
final File? managerApk = await downloadManager();
|
||||
if (managerApk != null) {
|
||||
// await flutterLocalNotificationsPlugin.zonedSchedule(
|
||||
// 0,
|
||||
// FlutterI18n.translate(
|
||||
// context,
|
||||
// 'homeView.notificationTitle',
|
||||
// ),
|
||||
// FlutterI18n.translate(
|
||||
// context,
|
||||
// 'homeView.notificationText',
|
||||
// ),
|
||||
// tz.TZDateTime.now(tz.local).add(const Duration(seconds: 5)),
|
||||
// const NotificationDetails(
|
||||
// android: AndroidNotificationDetails(
|
||||
// 'revanced_manager_channel',
|
||||
// 'ReVanced Manager Channel',
|
||||
// importance: Importance.max,
|
||||
// priority: Priority.high,
|
||||
// ticker: 'ticker',
|
||||
// ),
|
||||
// ),
|
||||
// androidAllowWhileIdle: true,
|
||||
// uiLocalNotificationDateInterpretation:
|
||||
// UILocalNotificationDateInterpretation.absoluteTime,
|
||||
// );
|
||||
_toast.showBottom('homeView.installingMessage');
|
||||
await AppInstaller.installApk(managerApk.path);
|
||||
} else {
|
||||
@ -151,7 +271,9 @@ class HomeViewModel extends BaseViewModel {
|
||||
_toast.showBottom('homeView.updatesDisabled');
|
||||
}
|
||||
|
||||
Future<void> showUpdateConfirmationDialog(BuildContext parentContext) {
|
||||
Future<void> showUpdateConfirmationDialog(
|
||||
BuildContext parentContext,
|
||||
) {
|
||||
return showModalBottomSheet(
|
||||
context: parentContext,
|
||||
isScrollControlled: true,
|
||||
@ -180,6 +302,7 @@ class HomeViewModel extends BaseViewModel {
|
||||
_lastUpdate!.difference(DateTime.now()).inSeconds > 2) {
|
||||
_managerAPI.clearAllData();
|
||||
}
|
||||
_toast.showBottom('homeView.refreshSuccess');
|
||||
initialize(context);
|
||||
}
|
||||
}
|
||||
|
@ -20,6 +20,7 @@ class InstallerView extends StatelessWidget {
|
||||
builder: (context, model, child) => WillPopScope(
|
||||
child: SafeArea(
|
||||
top: false,
|
||||
bottom: false,
|
||||
child: Scaffold(
|
||||
body: CustomScrollView(
|
||||
controller: model.scrollController,
|
||||
@ -153,6 +154,12 @@ class InstallerView extends StatelessWidget {
|
||||
),
|
||||
),
|
||||
),
|
||||
SliverFillRemaining(
|
||||
hasScrollBody: false,
|
||||
child: SizedBox(
|
||||
height: MediaQuery.of(context).viewPadding.bottom,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
@ -135,15 +135,15 @@ class InstallerViewModel extends BaseViewModel {
|
||||
} on Exception catch (e) {
|
||||
update(
|
||||
-100.0,
|
||||
'Aborting...',
|
||||
'An error occurred! Aborting\nError:\n$e',
|
||||
'Aborted...',
|
||||
'An error occurred! Aborted\nError:\n$e',
|
||||
);
|
||||
if (kDebugMode) {
|
||||
print(e);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
update(-100.0, 'Aborting...', 'No app or patches selected! Aborting');
|
||||
update(-100.0, 'Aborted...', 'No app or patches selected! Aborted');
|
||||
}
|
||||
if (FlutterBackground.isBackgroundExecutionEnabled) {
|
||||
try {
|
||||
|
@ -19,6 +19,8 @@ class NavigationViewModel extends IndexTrackingViewModel {
|
||||
locator<Toast>().initialize(context);
|
||||
final SharedPreferences prefs = await SharedPreferences.getInstance();
|
||||
if (prefs.getBool('permissionsRequested') == null) {
|
||||
await Permission.storage.request();
|
||||
await Permission.manageExternalStorage.request();
|
||||
await prefs.setBool('permissionsRequested', true);
|
||||
RootAPI().hasRootPermissions().then(
|
||||
(value) => Permission.requestInstallPackages.request().then(
|
||||
|
@ -1,3 +1,5 @@
|
||||
// ignore_for_file: use_build_context_synchronously
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_i18n/flutter_i18n.dart';
|
||||
import 'package:injectable/injectable.dart';
|
||||
@ -8,6 +10,7 @@ import 'package:revanced_manager/models/patched_application.dart';
|
||||
import 'package:revanced_manager/services/manager_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:stacked/stacked.dart';
|
||||
import 'package:stacked_services/stacked_services.dart';
|
||||
|
||||
@ -54,25 +57,25 @@ class PatcherViewModel extends BaseViewModel {
|
||||
final bool isValid = await isValidPatchConfig();
|
||||
if (context.mounted) {
|
||||
if (isValid) {
|
||||
navigateToInstaller();
|
||||
showArmv7WarningDialog(context);
|
||||
} else {
|
||||
return showDialog(
|
||||
context: context,
|
||||
builder: (context) => AlertDialog(
|
||||
title: I18nText('warning'),
|
||||
backgroundColor: Theme.of(context).colorScheme.secondaryContainer,
|
||||
content: I18nText('patcherView.patchDialogText'),
|
||||
content: I18nText('patcherView.armv7WarningDialogText'),
|
||||
actions: <Widget>[
|
||||
CustomMaterialButton(
|
||||
isFilled: false,
|
||||
label: I18nText('noButton'),
|
||||
onPressed: () => Navigator.of(context).pop(),
|
||||
),
|
||||
CustomMaterialButton(
|
||||
label: I18nText('yesButton'),
|
||||
isFilled: false,
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
navigateToInstaller();
|
||||
showArmv7WarningDialog(context);
|
||||
},
|
||||
)
|
||||
],
|
||||
@ -82,6 +85,40 @@ class PatcherViewModel extends BaseViewModel {
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> showArmv7WarningDialog(BuildContext context) async {
|
||||
final bool armv7 = await AboutInfo.getInfo().then((info) {
|
||||
final List<String> archs = info['supportedArch'];
|
||||
final supportedAbis = ['arm64-v8a', 'x86', 'x86_64'];
|
||||
return !archs.any((arch) => supportedAbis.contains(arch));
|
||||
});
|
||||
if (context.mounted && armv7) {
|
||||
return showDialog(
|
||||
context: context,
|
||||
builder: (context) => AlertDialog(
|
||||
title: I18nText('warning'),
|
||||
backgroundColor: Theme.of(context).colorScheme.secondaryContainer,
|
||||
content: I18nText('patcherView.armv7WarningDialogText'),
|
||||
actions: <Widget>[
|
||||
CustomMaterialButton(
|
||||
label: I18nText('noButton'),
|
||||
onPressed: () => Navigator.of(context).pop(),
|
||||
),
|
||||
CustomMaterialButton(
|
||||
label: I18nText('yesButton'),
|
||||
isFilled: false,
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
navigateToInstaller();
|
||||
},
|
||||
)
|
||||
],
|
||||
),
|
||||
);
|
||||
} else {
|
||||
navigateToInstaller();
|
||||
}
|
||||
}
|
||||
|
||||
String getAppSelectionString() {
|
||||
String text = '${selectedApp!.name} (${selectedApp!.packageName})';
|
||||
if (text.length > 32) {
|
||||
@ -90,24 +127,24 @@ class PatcherViewModel extends BaseViewModel {
|
||||
return text;
|
||||
}
|
||||
|
||||
String getRecommendedVersionString(BuildContext context) {
|
||||
String recommendedVersion =
|
||||
_patcherAPI.getRecommendedVersion(selectedApp!.packageName);
|
||||
if (recommendedVersion.isEmpty) {
|
||||
recommendedVersion = FlutterI18n.translate(
|
||||
String getSuggestedVersionString(BuildContext context) {
|
||||
String suggestedVersion =
|
||||
_patcherAPI.getSuggestedVersion(selectedApp!.packageName);
|
||||
if (suggestedVersion.isEmpty) {
|
||||
suggestedVersion = FlutterI18n.translate(
|
||||
context,
|
||||
'appSelectorCard.anyVersion',
|
||||
);
|
||||
} else {
|
||||
recommendedVersion = 'v$recommendedVersion';
|
||||
suggestedVersion = 'v$suggestedVersion';
|
||||
}
|
||||
return '${FlutterI18n.translate(
|
||||
context,
|
||||
'appSelectorCard.currentVersion',
|
||||
)}: v${selectedApp!.version}\n${FlutterI18n.translate(
|
||||
context,
|
||||
'appSelectorCard.recommendedVersion',
|
||||
)}: $recommendedVersion';
|
||||
'appSelectorCard.suggestedVersion',
|
||||
)}: $suggestedVersion';
|
||||
}
|
||||
|
||||
Future<void> loadLastSelectedPatches() async {
|
||||
|
@ -1,9 +1,10 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/material.dart' hide SearchBar;
|
||||
import 'package:flutter_i18n/flutter_i18n.dart';
|
||||
import 'package:revanced_manager/ui/views/patches_selector/patches_selector_viewmodel.dart';
|
||||
import 'package:revanced_manager/ui/widgets/patchesSelectorView/patch_item.dart';
|
||||
import 'package:revanced_manager/ui/widgets/shared/custom_popup_menu.dart';
|
||||
import 'package:revanced_manager/ui/widgets/shared/search_bar.dart';
|
||||
import 'package:revanced_manager/utils/check_for_supported_patch.dart';
|
||||
import 'package:stacked/stacked.dart';
|
||||
|
||||
class PatchesSelectorView extends StatefulWidget {
|
||||
@ -128,32 +129,22 @@ class _PatchesSelectorViewState extends State<PatchesSelectorView> {
|
||||
),
|
||||
)
|
||||
: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12.0)
|
||||
.copyWith(bottom: 80),
|
||||
padding:
|
||||
const EdgeInsets.symmetric(horizontal: 12.0).copyWith(
|
||||
bottom: MediaQuery.of(context).viewPadding.bottom + 8.0,
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
ActionChip(
|
||||
label: I18nText('patchesSelectorView.recommended'),
|
||||
label: I18nText('patchesSelectorView.default'),
|
||||
tooltip: FlutterI18n.translate(
|
||||
context,
|
||||
'patchesSelectorView.recommendedTooltip',
|
||||
'patchesSelectorView.defaultTooltip',
|
||||
),
|
||||
onPressed: () {
|
||||
model.selectRecommendedPatches();
|
||||
},
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
ActionChip(
|
||||
label: I18nText('patchesSelectorView.all'),
|
||||
tooltip: FlutterI18n.translate(
|
||||
context,
|
||||
'patchesSelectorView.allTooltip',
|
||||
),
|
||||
onPressed: () {
|
||||
model.selectAllPatcherWarning(context);
|
||||
model.selectAllPatches(true);
|
||||
model.selectDefaultPatches();
|
||||
},
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
@ -180,7 +171,7 @@ class _PatchesSelectorViewState extends State<PatchesSelectorView> {
|
||||
packageVersion: model.getAppVersion(),
|
||||
supportedPackageVersions:
|
||||
model.getSupportedVersions(patch),
|
||||
isUnsupported: !model.isPatchSupported(patch),
|
||||
isUnsupported: !isPatchSupported(patch),
|
||||
isSelected: model.isSelected(patch),
|
||||
onChanged: (value) =>
|
||||
model.selectPatch(patch, value),
|
||||
|
@ -1,6 +1,4 @@
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_i18n/widgets/I18nText.dart';
|
||||
import 'package:revanced_manager/app/app.locator.dart';
|
||||
import 'package:revanced_manager/models/patch.dart';
|
||||
import 'package:revanced_manager/models/patched_application.dart';
|
||||
@ -9,7 +7,7 @@ import 'package:revanced_manager/services/manager_api.dart';
|
||||
import 'package:revanced_manager/services/patcher_api.dart';
|
||||
import 'package:revanced_manager/services/toast.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:stacked/stacked.dart';
|
||||
|
||||
class PatchesSelectorViewModel extends BaseViewModel {
|
||||
@ -50,39 +48,7 @@ class PatchesSelectorViewModel extends BaseViewModel {
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
Future<void> selectAllPatcherWarning(BuildContext context) {
|
||||
return showDialog(
|
||||
context: context,
|
||||
builder: (context) => AlertDialog(
|
||||
title: I18nText('warning'),
|
||||
backgroundColor: Theme.of(context).colorScheme.secondaryContainer,
|
||||
content: I18nText('patchesSelectorView.selectAllPatchesWarningContent'),
|
||||
actions: <Widget>[
|
||||
CustomMaterialButton(
|
||||
label: I18nText('okButton'),
|
||||
onPressed: () => Navigator.of(context).pop(),
|
||||
)
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void selectAllPatches(bool isSelected) {
|
||||
selectedPatches.clear();
|
||||
|
||||
if (isSelected && _managerAPI.areExperimentalPatchesEnabled() == false) {
|
||||
selectedPatches
|
||||
.addAll(patches.where((element) => isPatchSupported(element)));
|
||||
}
|
||||
|
||||
if (isSelected && _managerAPI.areExperimentalPatchesEnabled()) {
|
||||
selectedPatches.addAll(patches);
|
||||
}
|
||||
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
void selectRecommendedPatches() {
|
||||
void selectDefaultPatches() {
|
||||
selectedPatches.clear();
|
||||
|
||||
if (_managerAPI.areExperimentalPatchesEnabled() == false) {
|
||||
@ -153,16 +119,6 @@ class PatchesSelectorViewModel extends BaseViewModel {
|
||||
}
|
||||
}
|
||||
|
||||
bool isPatchSupported(Patch patch) {
|
||||
final PatchedApplication app = locator<PatcherViewModel>().selectedApp!;
|
||||
return patch.compatiblePackages.isEmpty ||
|
||||
patch.compatiblePackages.any(
|
||||
(pack) =>
|
||||
pack.name == app.packageName &&
|
||||
(pack.versions.isEmpty || pack.versions.contains(app.version)),
|
||||
);
|
||||
}
|
||||
|
||||
void onMenuSelection(value) {
|
||||
switch (value) {
|
||||
case 0:
|
||||
|
@ -4,6 +4,7 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter_i18n/flutter_i18n.dart';
|
||||
import 'package:revanced_manager/app/app.locator.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/shared/custom_material_button.dart';
|
||||
@ -11,6 +12,7 @@ import 'package:stacked/stacked.dart';
|
||||
|
||||
class SManageApiUrl extends BaseViewModel {
|
||||
final ManagerAPI _managerAPI = locator<ManagerAPI>();
|
||||
final Toast _toast = locator<Toast>();
|
||||
|
||||
final TextEditingController _apiUrlController = TextEditingController();
|
||||
|
||||
@ -90,7 +92,7 @@ class SManageApiUrl extends BaseViewModel {
|
||||
label: I18nText('yesButton'),
|
||||
onPressed: () {
|
||||
_managerAPI.setApiUrl('');
|
||||
Navigator.of(context).pop();
|
||||
_toast.showBottom('settingsView.restartAppForChanges');
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
)
|
||||
|
@ -0,0 +1,86 @@
|
||||
// ignore_for_file: use_build_context_synchronously
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_i18n/flutter_i18n.dart';
|
||||
import 'package:revanced_manager/app/app.locator.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/shared/custom_material_button.dart';
|
||||
import 'package:stacked/stacked.dart';
|
||||
|
||||
class SManageKeystorePassword extends BaseViewModel {
|
||||
final ManagerAPI _managerAPI = locator<ManagerAPI>();
|
||||
|
||||
final TextEditingController _keystorePasswordController =
|
||||
TextEditingController();
|
||||
|
||||
Future<void> showKeystoreDialog(BuildContext context) async {
|
||||
final String keystorePasswordText = _managerAPI.getKeystorePassword();
|
||||
_keystorePasswordController.text = keystorePasswordText;
|
||||
return showDialog(
|
||||
context: context,
|
||||
builder: (context) => AlertDialog(
|
||||
title: Row(
|
||||
children: <Widget>[
|
||||
I18nText('settingsView.selectKeystorePassword'),
|
||||
const Spacer(),
|
||||
IconButton(
|
||||
icon: const Icon(Icons.manage_history_outlined),
|
||||
onPressed: () => _keystorePasswordController.text =
|
||||
_managerAPI.defaultKeystorePassword,
|
||||
color: Theme.of(context).colorScheme.secondary,
|
||||
)
|
||||
],
|
||||
),
|
||||
backgroundColor: Theme.of(context).colorScheme.secondaryContainer,
|
||||
content: SingleChildScrollView(
|
||||
child: Column(
|
||||
children: <Widget>[
|
||||
CustomTextField(
|
||||
inputController: _keystorePasswordController,
|
||||
label: I18nText('settingsView.selectKeystorePassword'),
|
||||
hint: '',
|
||||
onChanged: (value) => notifyListeners(),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
actions: <Widget>[
|
||||
CustomMaterialButton(
|
||||
isFilled: false,
|
||||
label: I18nText('cancelButton'),
|
||||
onPressed: () {
|
||||
_keystorePasswordController.clear();
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
),
|
||||
CustomMaterialButton(
|
||||
label: I18nText('okButton'),
|
||||
onPressed: () {
|
||||
final String passwd = _keystorePasswordController.text;
|
||||
_managerAPI.setKeystorePassword(passwd);
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
)
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
final sManageKeystorePassword = SManageKeystorePassword();
|
||||
|
||||
class SManageKeystorePasswordUI extends StatelessWidget {
|
||||
const SManageKeystorePasswordUI({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SettingsTileDialog(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 20.0),
|
||||
title: 'settingsView.selectKeystorePassword',
|
||||
subtitle: 'settingsView.selectKeystorePasswordHint',
|
||||
onTap: () => sManageKeystorePassword.showKeystoreDialog(context),
|
||||
);
|
||||
}
|
||||
}
|
@ -4,6 +4,7 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter_i18n/flutter_i18n.dart';
|
||||
import 'package:revanced_manager/app/app.locator.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/shared/custom_material_button.dart';
|
||||
@ -11,6 +12,7 @@ import 'package:stacked/stacked.dart';
|
||||
|
||||
class SManageSources extends BaseViewModel {
|
||||
final ManagerAPI _managerAPI = locator<ManagerAPI>();
|
||||
final Toast _toast = locator<Toast>();
|
||||
|
||||
final TextEditingController _hostSourceController = TextEditingController();
|
||||
final TextEditingController _orgPatSourceController = TextEditingController();
|
||||
@ -117,13 +119,14 @@ class SManageSources extends BaseViewModel {
|
||||
CustomMaterialButton(
|
||||
label: I18nText('okButton'),
|
||||
onPressed: () {
|
||||
_managerAPI.setRepoUrl(_hostSourceController.text);
|
||||
_managerAPI.setRepoUrl(_hostSourceController.text.trim());
|
||||
_managerAPI.setPatchesRepo(
|
||||
'${_orgPatSourceController.text}/${_patSourceController.text}',
|
||||
'${_orgPatSourceController.text.trim()}/${_patSourceController.text.trim()}',
|
||||
);
|
||||
_managerAPI.setIntegrationsRepo(
|
||||
'${_orgIntSourceController.text}/${_intSourceController.text}',
|
||||
'${_orgIntSourceController.text.trim()}/${_intSourceController.text.trim()}',
|
||||
);
|
||||
_toast.showBottom('settingsView.restartAppForChanges');
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
)
|
||||
@ -151,7 +154,10 @@ class SManageSources extends BaseViewModel {
|
||||
_managerAPI.setRepoUrl('');
|
||||
_managerAPI.setPatchesRepo('');
|
||||
_managerAPI.setIntegrationsRepo('');
|
||||
Navigator.of(context).pop();
|
||||
_toast.showBottom('settingsView.restartAppForChanges');
|
||||
Navigator.of(context)
|
||||
..pop()
|
||||
..pop();
|
||||
},
|
||||
)
|
||||
],
|
||||
|
@ -17,7 +17,8 @@ import 'package:stacked/stacked.dart';
|
||||
import 'package:stacked_services/stacked_services.dart';
|
||||
|
||||
class SettingsViewModel extends BaseViewModel {
|
||||
final NavigationService _navigationService = locator<NavigationService>();
|
||||
final NavigationService _navigationService =
|
||||
locator<NavigationService>();
|
||||
final ManagerAPI _managerAPI = locator<ManagerAPI>();
|
||||
final Toast _toast = locator<Toast>();
|
||||
|
||||
@ -62,8 +63,11 @@ class SettingsViewModel extends BaseViewModel {
|
||||
try {
|
||||
final File outFile = File(_managerAPI.storedPatchesFile);
|
||||
if (outFile.existsSync()) {
|
||||
final String dateTime =
|
||||
DateTime.now().toString().replaceAll(' ', '_').split('.').first;
|
||||
final String dateTime = DateTime.now()
|
||||
.toString()
|
||||
.replaceAll(' ', '_')
|
||||
.split('.')
|
||||
.first;
|
||||
await CRFileSaver.saveFileWithDialog(
|
||||
SaveFileDialogParams(
|
||||
sourceFilePath: outFile.path,
|
||||
@ -83,7 +87,8 @@ class SettingsViewModel extends BaseViewModel {
|
||||
|
||||
Future<void> importPatches() async {
|
||||
try {
|
||||
final FilePickerResult? result = await FilePicker.platform.pickFiles(
|
||||
final FilePickerResult? result =
|
||||
await FilePicker.platform.pickFiles(
|
||||
type: FileType.custom,
|
||||
allowedExtensions: ['json'],
|
||||
);
|
||||
@ -104,6 +109,46 @@ class SettingsViewModel extends BaseViewModel {
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> exportKeystore() async {
|
||||
try {
|
||||
final File outFile = File(_managerAPI.keystoreFile);
|
||||
if (outFile.existsSync()) {
|
||||
final String dateTime =
|
||||
DateTime.now().toString().replaceAll(' ', '_').split('.').first;
|
||||
await CRFileSaver.saveFileWithDialog(
|
||||
SaveFileDialogParams(
|
||||
sourceFilePath: outFile.path,
|
||||
destinationFileName: 'keystore_$dateTime.keystore',
|
||||
),
|
||||
);
|
||||
_toast.showBottom('settingsView.exportedKeystore');
|
||||
} else {
|
||||
_toast.showBottom('settingsView.noKeystoreExportFileFound');
|
||||
}
|
||||
} on Exception catch (e) {
|
||||
if (kDebugMode) {
|
||||
print(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> importKeystore() async {
|
||||
try {
|
||||
final FilePickerResult? result = await FilePicker.platform.pickFiles();
|
||||
if (result != null && result.files.single.path != null) {
|
||||
final File inFile = File(result.files.single.path!);
|
||||
inFile.copySync(_managerAPI.keystoreFile);
|
||||
|
||||
_toast.showBottom('settingsView.importedKeystore');
|
||||
}
|
||||
} on Exception catch (e) {
|
||||
if (kDebugMode) {
|
||||
print(e);
|
||||
}
|
||||
_toast.showBottom('settingsView.keystoreSelectorErrorMessage');
|
||||
}
|
||||
}
|
||||
|
||||
void resetSelectedPatches() {
|
||||
_managerAPI.resetLastSelectedPatches();
|
||||
_toast.showBottom('settingsView.resetStoredPatches');
|
||||
@ -111,7 +156,7 @@ class SettingsViewModel extends BaseViewModel {
|
||||
|
||||
Future<int> getSdkVersion() async {
|
||||
final AndroidDeviceInfo info = await DeviceInfoPlugin().androidInfo;
|
||||
return info.version.sdkInt ?? -1;
|
||||
return info.version.sdkInt;
|
||||
}
|
||||
|
||||
Future<void> deleteLogs() async {
|
||||
|
@ -1,5 +1,7 @@
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_i18n/flutter_i18n.dart';
|
||||
import 'package:revanced_manager/ui/widgets/shared/custom_card.dart';
|
||||
|
||||
class InstalledAppItem extends StatefulWidget {
|
||||
@ -9,12 +11,16 @@ class InstalledAppItem extends StatefulWidget {
|
||||
required this.pkgName,
|
||||
required this.icon,
|
||||
required this.patchesCount,
|
||||
required this.suggestedVersion,
|
||||
required this.installedVersion,
|
||||
this.onTap,
|
||||
}) : super(key: key);
|
||||
final String name;
|
||||
final String pkgName;
|
||||
final Uint8List icon;
|
||||
final int patchesCount;
|
||||
final String suggestedVersion;
|
||||
final String installedVersion;
|
||||
final Function()? onTap;
|
||||
|
||||
@override
|
||||
@ -47,7 +53,7 @@ class _InstalledAppItemState extends State<InstalledAppItem> {
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
Row(
|
||||
children: <Widget>[
|
||||
children: [
|
||||
Text(
|
||||
widget.name,
|
||||
maxLines: 2,
|
||||
@ -57,20 +63,39 @@ class _InstalledAppItemState extends State<InstalledAppItem> {
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 6),
|
||||
const SizedBox(width: 4),
|
||||
I18nText(
|
||||
widget.installedVersion.isEmpty
|
||||
? 'All versions'
|
||||
: 'v${widget.installedVersion}',
|
||||
),
|
||||
],
|
||||
),
|
||||
Text(widget.pkgName),
|
||||
Row(
|
||||
children: [
|
||||
I18nText(
|
||||
FlutterI18n.translate(
|
||||
context,
|
||||
'suggested',
|
||||
translationParams: {
|
||||
'version': widget.suggestedVersion.isEmpty
|
||||
? 'All versions'
|
||||
: 'v${widget.suggestedVersion}'
|
||||
},
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 4),
|
||||
Text(
|
||||
widget.patchesCount == 1
|
||||
? '${widget.patchesCount} patch'
|
||||
: '${widget.patchesCount} patches',
|
||||
? '• ${widget.patchesCount} patch'
|
||||
: '• ${widget.patchesCount} patches',
|
||||
style: TextStyle(
|
||||
fontSize: 8,
|
||||
color: Theme.of(context).colorScheme.secondary,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Text(widget.pkgName),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
83
lib/ui/widgets/appSelectorView/not_installed_app_item.dart
Normal file
@ -0,0 +1,83 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:revanced_manager/ui/widgets/shared/custom_card.dart';
|
||||
|
||||
class NotInstalledAppItem extends StatefulWidget {
|
||||
const NotInstalledAppItem({
|
||||
Key? key,
|
||||
required this.name,
|
||||
required this.patchesCount,
|
||||
required this.suggestedVersion,
|
||||
this.onTap,
|
||||
}) : super(key: key);
|
||||
final String name;
|
||||
final int patchesCount;
|
||||
final String suggestedVersion;
|
||||
final Function()? onTap;
|
||||
|
||||
@override
|
||||
State<NotInstalledAppItem> createState() => _NotInstalledAppItem();
|
||||
}
|
||||
|
||||
class _NotInstalledAppItem extends State<NotInstalledAppItem> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 4.0),
|
||||
child: CustomCard(
|
||||
onTap: widget.onTap,
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: <Widget>[
|
||||
Container(
|
||||
height: 48,
|
||||
padding: const EdgeInsets.symmetric(vertical: 4.0),
|
||||
alignment: Alignment.center,
|
||||
child: const CircleAvatar(
|
||||
backgroundColor: Colors.transparent,
|
||||
child: Icon(
|
||||
Icons.square_rounded,
|
||||
color: Colors.grey,
|
||||
size: 44,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
Text(
|
||||
widget.name,
|
||||
style: const TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
const Text('App not installed.'),
|
||||
const SizedBox(height: 4),
|
||||
Row(
|
||||
children: [
|
||||
Text(
|
||||
'Suggested: ${widget.suggestedVersion.isEmpty ? 'All versions' : 'v${widget.suggestedVersion}'}',
|
||||
),
|
||||
const SizedBox(width: 4),
|
||||
Text(
|
||||
widget.patchesCount == 1
|
||||
? '• ${widget.patchesCount} patch'
|
||||
: '• ${widget.patchesCount} patches',
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).colorScheme.secondary,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -55,6 +55,7 @@ class _ContributorsCardState extends State<ContributorsCard> {
|
||||
Uri.parse(
|
||||
widget.contributors[index]['html_url'],
|
||||
),
|
||||
mode: LaunchMode.externalApplication,
|
||||
),
|
||||
child: FutureBuilder<File?>(
|
||||
future: DefaultCacheManager().getSingleFile(
|
||||
|
@ -8,9 +8,11 @@ import 'package:revanced_manager/ui/widgets/shared/custom_material_button.dart';
|
||||
class LatestCommitCard extends StatefulWidget {
|
||||
const LatestCommitCard({
|
||||
Key? key,
|
||||
required this.onPressed,
|
||||
required this.onPressedManager,
|
||||
required this.onPressedPatches,
|
||||
}) : super(key: key);
|
||||
final Function() onPressed;
|
||||
final Function() onPressedManager;
|
||||
final Function() onPressedPatches;
|
||||
|
||||
@override
|
||||
State<LatestCommitCard> createState() => _LatestCommitCardState();
|
||||
@ -21,66 +23,109 @@ class _LatestCommitCardState extends State<LatestCommitCard> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return CustomCard(
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: <Widget>[
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
return Column(
|
||||
children: [
|
||||
// ReVanced Manager
|
||||
CustomCard(
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: <Widget>[
|
||||
Row(
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
I18nText('latestCommitCard.patcherLabel'),
|
||||
FutureBuilder<String?>(
|
||||
future: model.getLatestPatcherReleaseTime(),
|
||||
builder: (context, snapshot) => Text(
|
||||
snapshot.hasData && snapshot.data!.isNotEmpty
|
||||
? FlutterI18n.translate(
|
||||
context,
|
||||
'latestCommitCard.timeagoLabel',
|
||||
translationParams: {'time': snapshot.data!},
|
||||
)
|
||||
: FlutterI18n.translate(
|
||||
context,
|
||||
'latestCommitCard.loadingLabel',
|
||||
),
|
||||
),
|
||||
const Row(
|
||||
children: <Widget>[
|
||||
Text('ReVanced Manager'),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Row(
|
||||
children: <Widget>[
|
||||
FutureBuilder<String?>(
|
||||
future: model.getLatestManagerReleaseTime(),
|
||||
builder: (context, snapshot) =>
|
||||
snapshot.hasData && snapshot.data!.isNotEmpty
|
||||
? I18nText(
|
||||
'latestCommitCard.timeagoLabel',
|
||||
translationParams: {'time': snapshot.data!},
|
||||
)
|
||||
: I18nText('latestCommitCard.loadingLabel'),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Row(
|
||||
children: <Widget>[
|
||||
I18nText('latestCommitCard.managerLabel'),
|
||||
FutureBuilder<String?>(
|
||||
future: model.getLatestManagerReleaseTime(),
|
||||
builder: (context, snapshot) =>
|
||||
snapshot.hasData && snapshot.data!.isNotEmpty
|
||||
? I18nText(
|
||||
'latestCommitCard.timeagoLabel',
|
||||
translationParams: {'time': snapshot.data!},
|
||||
)
|
||||
: I18nText('latestCommitCard.loadingLabel'),
|
||||
FutureBuilder<bool>(
|
||||
future: model.hasManagerUpdates(),
|
||||
initialData: false,
|
||||
builder: (context, snapshot) => Opacity(
|
||||
opacity: snapshot.hasData && snapshot.data! ? 1.0 : 0.25,
|
||||
child: CustomMaterialButton(
|
||||
label: I18nText('updateButton'),
|
||||
onPressed: snapshot.hasData && snapshot.data!
|
||||
? widget.onPressedManager
|
||||
: () => {},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
FutureBuilder<bool>(
|
||||
future: locator<HomeViewModel>().hasManagerUpdates(),
|
||||
initialData: false,
|
||||
builder: (context, snapshot) => Opacity(
|
||||
opacity: snapshot.hasData && snapshot.data! ? 1.0 : 0.25,
|
||||
child: CustomMaterialButton(
|
||||
label: I18nText('latestCommitCard.updateButton'),
|
||||
onPressed: snapshot.hasData && snapshot.data!
|
||||
? widget.onPressed
|
||||
: () => {},
|
||||
),
|
||||
|
||||
const SizedBox(height: 16),
|
||||
|
||||
// ReVanced Patches
|
||||
CustomCard(
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: <Widget>[
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
const Row(
|
||||
children: <Widget>[
|
||||
Text('ReVanced Patches'),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Row(
|
||||
children: <Widget>[
|
||||
FutureBuilder<String?>(
|
||||
future: model.getLatestPatcherReleaseTime(),
|
||||
builder: (context, snapshot) => Text(
|
||||
snapshot.hasData && snapshot.data!.isNotEmpty
|
||||
? FlutterI18n.translate(
|
||||
context,
|
||||
'latestCommitCard.timeagoLabel',
|
||||
translationParams: {'time': snapshot.data!},
|
||||
)
|
||||
: FlutterI18n.translate(
|
||||
context,
|
||||
'latestCommitCard.loadingLabel',
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
FutureBuilder<bool>(
|
||||
future: locator<HomeViewModel>().hasPatchesUpdates(),
|
||||
initialData: false,
|
||||
builder: (context, snapshot) => Opacity(
|
||||
opacity: snapshot.hasData && snapshot.data! ? 1.0 : 0.25,
|
||||
child: CustomMaterialButton(
|
||||
label: I18nText('updateButton'),
|
||||
onPressed: snapshot.hasData && snapshot.data!
|
||||
? widget.onPressedPatches
|
||||
: () => {},
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -16,12 +16,12 @@ class UpdateConfirmationDialog extends StatelessWidget {
|
||||
expand: false,
|
||||
snap: true,
|
||||
snapSizes: const [0.5],
|
||||
builder: (context, scrollController) => SingleChildScrollView(
|
||||
builder: (_, scrollController) => SingleChildScrollView(
|
||||
controller: scrollController,
|
||||
child: SafeArea(
|
||||
child: FutureBuilder<Map<String, dynamic>?>(
|
||||
future: model.getLatestManagerRelease(),
|
||||
builder: (context, snapshot) {
|
||||
builder: (_, snapshot) {
|
||||
if (!snapshot.hasData) {
|
||||
return const SizedBox(
|
||||
height: 300,
|
||||
@ -45,7 +45,8 @@ class UpdateConfirmationDialog extends StatelessWidget {
|
||||
children: [
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
crossAxisAlignment:
|
||||
CrossAxisAlignment.start,
|
||||
children: [
|
||||
I18nText(
|
||||
'homeView.updateDialogTitle',
|
||||
@ -62,12 +63,14 @@ class UpdateConfirmationDialog extends StatelessWidget {
|
||||
children: [
|
||||
Icon(
|
||||
Icons.new_releases_outlined,
|
||||
color:
|
||||
Theme.of(context).colorScheme.secondary,
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
.secondary,
|
||||
),
|
||||
const SizedBox(width: 8.0),
|
||||
Text(
|
||||
snapshot.data!['tag_name'] ?? 'Unknown',
|
||||
snapshot.data!['tag_name'] ??
|
||||
'Unknown',
|
||||
style: TextStyle(
|
||||
fontSize: 20,
|
||||
fontWeight: FontWeight.w500,
|
||||
@ -93,7 +96,8 @@ class UpdateConfirmationDialog extends StatelessWidget {
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(left: 24.0, bottom: 12.0),
|
||||
padding:
|
||||
const EdgeInsets.only(left: 24.0, bottom: 12.0),
|
||||
child: I18nText(
|
||||
'homeView.updateChangelogTitle',
|
||||
child: Text(
|
||||
@ -109,9 +113,12 @@ class UpdateConfirmationDialog extends StatelessWidget {
|
||||
),
|
||||
),
|
||||
Container(
|
||||
margin: const EdgeInsets.symmetric(horizontal: 24.0),
|
||||
margin:
|
||||
const EdgeInsets.symmetric(horizontal: 24.0),
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).colorScheme.secondaryContainer,
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
.secondaryContainer,
|
||||
borderRadius: BorderRadius.circular(12.0),
|
||||
),
|
||||
child: Markdown(
|
||||
|
@ -63,7 +63,7 @@ class AppSelectorCard extends StatelessWidget {
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
locator<PatcherViewModel>()
|
||||
.getRecommendedVersionString(context),
|
||||
.getSuggestedVersionString(context),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
@ -29,7 +29,7 @@ class _AboutWidgetState extends State<AboutWidget> {
|
||||
text: 'Version: ${snapshot.data!['version']}\n'
|
||||
'Model: ${snapshot.data!['model']}\n'
|
||||
'Android Version: ${snapshot.data!['androidVersion']}\n'
|
||||
'Arch: ${snapshot.data!['arch']}\n',
|
||||
'${snapshot.data!['supportedArch'].length > 1 ? 'Supported Archs' : 'Supported Arch'}: ${snapshot.data!['supportedArch'].join(", ")}\n',
|
||||
),
|
||||
);
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
@ -84,7 +84,9 @@ class _AboutWidgetState extends State<AboutWidget> {
|
||||
),
|
||||
),
|
||||
Text(
|
||||
'Arch: ${snapshot.data!['arch']}',
|
||||
snapshot.data!['supportedArch'].length > 1
|
||||
? 'Supported Archs: ${snapshot.data!['supportedArch'].join(", ")}'
|
||||
: 'Supported Arch: ${snapshot.data!['supportedArch']}',
|
||||
style: const TextStyle(
|
||||
fontSize: 13,
|
||||
fontWeight: FontWeight.w300,
|
||||
|
@ -8,6 +8,7 @@ import 'package:revanced_manager/ui/views/settings/settings_viewmodel.dart';
|
||||
import 'package:revanced_manager/ui/widgets/settingsView/settings_experimental_patches.dart';
|
||||
import 'package:revanced_manager/ui/widgets/settingsView/settings_experimental_universal_patches.dart';
|
||||
import 'package:revanced_manager/ui/widgets/settingsView/settings_section.dart';
|
||||
import 'package:revanced_manager/ui/widgets/shared/custom_material_button.dart';
|
||||
|
||||
final _settingsViewModel = SettingsViewModel();
|
||||
|
||||
@ -21,6 +22,7 @@ class SAdvancedSection extends StatelessWidget {
|
||||
children: <Widget>[
|
||||
SManageApiUrlUI(),
|
||||
SManageSourcesUI(),
|
||||
// SManageKeystorePasswordUI(),
|
||||
SExperimentalUniversalPatches(),
|
||||
SExperimentalPatches(),
|
||||
ListTile(
|
||||
@ -36,7 +38,7 @@ class SAdvancedSection extends StatelessWidget {
|
||||
),
|
||||
),
|
||||
subtitle: I18nText('settingsView.deleteKeystoreHint'),
|
||||
onTap: () => _settingsViewModel.deleteKeystore,
|
||||
onTap: () => _showDeleteKeystoreDialog(context),
|
||||
),
|
||||
ListTile(
|
||||
contentPadding: const EdgeInsets.symmetric(horizontal: 20.0),
|
||||
@ -71,4 +73,31 @@ class SAdvancedSection extends StatelessWidget {
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _showDeleteKeystoreDialog(context) {
|
||||
return showDialog(
|
||||
context: context,
|
||||
builder: (context) => AlertDialog(
|
||||
title: I18nText('warning'),
|
||||
backgroundColor: Theme.of(context).colorScheme.secondaryContainer,
|
||||
content: I18nText(
|
||||
'settingsView.deleteKeystoreDialogText',
|
||||
),
|
||||
actions: <Widget>[
|
||||
CustomMaterialButton(
|
||||
isFilled: false,
|
||||
label: I18nText('noButton'),
|
||||
onPressed: () => Navigator.of(context).pop(),
|
||||
),
|
||||
CustomMaterialButton(
|
||||
label: I18nText('yesButton'),
|
||||
onPressed: () => {
|
||||
Navigator.of(context).pop(),
|
||||
_settingsViewModel.deleteKeystore()
|
||||
},
|
||||
)
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
import 'package:flutter/material.dart';
|
||||
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/settings_viewmodel.dart';
|
||||
import 'package:revanced_manager/ui/widgets/settingsView/settings_section.dart';
|
||||
|
||||
@ -43,6 +44,40 @@ class SExportSection extends StatelessWidget {
|
||||
subtitle: I18nText('settingsView.importPatchesHint'),
|
||||
onTap: () => _settingsViewModel.importPatches(),
|
||||
),
|
||||
ListTile(
|
||||
contentPadding: const EdgeInsets.symmetric(horizontal: 20.0),
|
||||
title: I18nText(
|
||||
'settingsView.exportKeystoreLabel',
|
||||
child: const Text(
|
||||
'',
|
||||
style: TextStyle(
|
||||
fontSize: 20,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
),
|
||||
subtitle: I18nText('settingsView.exportKeystoreHint'),
|
||||
onTap: () => _settingsViewModel.exportKeystore(),
|
||||
),
|
||||
ListTile(
|
||||
contentPadding: const EdgeInsets.symmetric(horizontal: 20.0),
|
||||
title: I18nText(
|
||||
'settingsView.importKeystoreLabel',
|
||||
child: const Text(
|
||||
'',
|
||||
style: TextStyle(
|
||||
fontSize: 20,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
),
|
||||
subtitle: I18nText('settingsView.importKeystoreHint'),
|
||||
onTap: () {
|
||||
_settingsViewModel.importKeystore();
|
||||
final sManageKeystorePassword = SManageKeystorePassword();
|
||||
sManageKeystorePassword.showKeystoreDialog(context);
|
||||
},
|
||||
),
|
||||
ListTile(
|
||||
contentPadding: const EdgeInsets.symmetric(horizontal: 20.0),
|
||||
title: I18nText(
|
||||
|
@ -39,9 +39,9 @@ class SocialMediaWidget extends StatelessWidget {
|
||||
),
|
||||
expanded: Padding(
|
||||
padding: padding ?? EdgeInsets.zero,
|
||||
child: CustomCard(
|
||||
child: const CustomCard(
|
||||
child: Column(
|
||||
children: const <Widget>[
|
||||
children: <Widget>[
|
||||
SocialMediaItem(
|
||||
icon: FaIcon(FontAwesomeIcons.github),
|
||||
title: Text('GitHub'),
|
||||
|
@ -11,7 +11,7 @@ class AboutInfo {
|
||||
'flavor': kReleaseMode ? 'release' : 'debug',
|
||||
'model': info.model,
|
||||
'androidVersion': info.version.release,
|
||||
'arch': info.supportedAbis.first
|
||||
'supportedArch': info.supportedAbis
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -1,10 +0,0 @@
|
||||
// Check for google mobile services on device
|
||||
|
||||
import 'package:device_apps/device_apps.dart';
|
||||
|
||||
Future<bool> checkForGMS() async {
|
||||
bool isGMSInstalled = true;
|
||||
isGMSInstalled = await DeviceApps.isAppInstalled('com.google.android.gms') ||
|
||||
await DeviceApps.isAppInstalled('com.android.vending');
|
||||
return isGMSInstalled;
|
||||
}
|
14
lib/utils/check_for_supported_patch.dart
Normal file
@ -0,0 +1,14 @@
|
||||
import 'package:revanced_manager/app/app.locator.dart';
|
||||
import 'package:revanced_manager/models/patch.dart';
|
||||
import 'package:revanced_manager/models/patched_application.dart';
|
||||
import 'package:revanced_manager/ui/views/patcher/patcher_viewmodel.dart';
|
||||
|
||||
bool isPatchSupported(Patch patch) {
|
||||
final PatchedApplication app = locator<PatcherViewModel>().selectedApp!;
|
||||
return patch.compatiblePackages.isEmpty ||
|
||||
patch.compatiblePackages.any(
|
||||
(pack) =>
|
||||
pack.name == app.packageName &&
|
||||
(pack.versions.isEmpty || pack.versions.contains(app.version)),
|
||||
);
|
||||
}
|
7179
package-lock.json
generated
Normal file
10
package.json
Normal file
@ -0,0 +1,10 @@
|
||||
{
|
||||
"devDependencies": {
|
||||
"@saithodev/semantic-release-backmerge": "^3.1.0",
|
||||
"@semantic-release/changelog": "^6.0.3",
|
||||
"@semantic-release/git": "^10.0.1",
|
||||
"semantic-release": "^21.0.1",
|
||||
"semantic-release-export-data": "^1.0.1",
|
||||
"semantic-release-flutter-plugin": "^1.1.2"
|
||||
}
|
||||
}
|
76
pubspec.yaml
@ -4,86 +4,86 @@ homepage: https://github.com/revanced/revanced-manager
|
||||
|
||||
publish_to: 'none'
|
||||
|
||||
version: 0.0.57+57
|
||||
version: 1.3.4+100300004
|
||||
|
||||
environment:
|
||||
sdk: ">=2.17.5 <3.0.0"
|
||||
sdk: '>=3.0.0 <4.0.0'
|
||||
|
||||
dependencies:
|
||||
animations: ^2.0.4
|
||||
animations: ^2.0.7
|
||||
app_installer: ^1.1.0
|
||||
collection: ^1.16.0
|
||||
collection: ^1.17.0
|
||||
cross_connectivity: ^3.0.5
|
||||
cr_file_saver: ^0.0.2
|
||||
cr_file_saver:
|
||||
git:
|
||||
url: https://github.com/dhruvanbhalara/cr_file_saver
|
||||
ref: "fix/incorrect_file_name"
|
||||
device_apps:
|
||||
git:
|
||||
url: https://github.com/ponces/flutter_plugin_device_apps
|
||||
ref: revanced-manager
|
||||
device_info_plus: ^4.1.2
|
||||
dio: ^4.0.6
|
||||
dio_brotli_transformer: ^1.0.1
|
||||
dio_http_cache_lts: ^0.4.1
|
||||
dynamic_color: ^1.5.4
|
||||
device_info_plus: ^8.1.0
|
||||
dynamic_color: ^1.6.3
|
||||
dio: ^5.0.0
|
||||
dynamic_themes: ^1.1.0
|
||||
expandable: ^5.0.1
|
||||
file_picker:
|
||||
git:
|
||||
url: https://github.com/alexmercerind/flutter_file_picker
|
||||
ref: master
|
||||
flex_color_scheme: ^6.0.0
|
||||
flex_color_scheme: ^7.0.1
|
||||
flutter:
|
||||
sdk: flutter
|
||||
flutter_background: ^1.1.0
|
||||
flutter_background: ^1.2.0
|
||||
flutter_cache_manager: ^3.3.0
|
||||
flutter_i18n: ^0.32.4
|
||||
flutter_local_notifications: ^9.8.0+1
|
||||
flutter_i18n: ^0.33.0
|
||||
flutter_local_notifications: ^13.0.0
|
||||
flutter_localizations:
|
||||
sdk: flutter
|
||||
flutter_svg: ^1.1.1+1
|
||||
fluttertoast: ^8.0.9
|
||||
font_awesome_flutter: ^10.1.0
|
||||
get_it: ^7.2.0
|
||||
google_fonts: ^3.0.1
|
||||
http: ^0.13.4
|
||||
injectable: ^1.5.3
|
||||
intl: ^0.17.0
|
||||
json_annotation: ^4.6.0
|
||||
flutter_svg: ^2.0.4
|
||||
fluttertoast: ^8.2.1
|
||||
font_awesome_flutter: ^10.4.0
|
||||
get_it: 7.2.0
|
||||
google_fonts: ^4.0.3
|
||||
http: ^0.13.5
|
||||
injectable: ^2.1.1
|
||||
intl: ^0.18.0
|
||||
json_annotation: ^4.8.0
|
||||
logcat:
|
||||
git:
|
||||
url: https://github.com/SuaMusica/logcat
|
||||
ref: feature/nullSafe
|
||||
native_dio_client: ^0.0.1-dev+1
|
||||
package_info_plus: ^1.4.3+1
|
||||
path_provider: ^2.0.11
|
||||
permission_handler: ^10.0.0
|
||||
package_info_plus: ^3.0.3
|
||||
path_provider: ^2.0.14
|
||||
permission_handler: ^10.2.0
|
||||
pull_to_refresh: ^2.0.0
|
||||
root:
|
||||
git:
|
||||
url: https://github.com/gokul1630/root
|
||||
ref: main
|
||||
share_extend: ^2.0.0
|
||||
shared_preferences: ^2.0.15
|
||||
shared_preferences: ^2.1.0
|
||||
skeletons: ^0.0.3
|
||||
stacked: ^3.2.0
|
||||
stacked_generator: ^1.0.0
|
||||
stacked_generator: ^1.1.0
|
||||
stacked_services: ^1.0.0
|
||||
stacked_themes: ^0.3.9
|
||||
timeago: ^3.2.2
|
||||
timezone: ^0.8.0
|
||||
url_launcher: ^6.1.5
|
||||
stacked_themes: ^0.3.10
|
||||
timeago: ^3.3.0
|
||||
timezone: ^0.9.0
|
||||
url_launcher: ^6.1.10
|
||||
wakelock: ^0.6.2
|
||||
flutter_dotenv: ^5.0.2
|
||||
pub_release: ^8.0.3
|
||||
flutter_markdown: ^0.6.13
|
||||
flutter_markdown: ^0.6.14
|
||||
dio_cache_interceptor: ^3.4.0
|
||||
|
||||
dev_dependencies:
|
||||
json_serializable: ^6.3.1
|
||||
json_serializable: ^6.6.1
|
||||
build_runner: any
|
||||
flutter_launcher_icons: ^0.10.0
|
||||
flutter_launcher_icons: ^0.13.0
|
||||
flutter_lints: ^2.0.1
|
||||
flutter_test:
|
||||
sdk: flutter
|
||||
injectable_generator: ^1.5.4
|
||||
injectable_generator: ^2.1.5
|
||||
|
||||
|
||||
|
||||
|