mirror of
https://github.com/revanced/revanced-manager.git
synced 2025-05-05 08:14:25 +02:00
chore: merge dev
to main
(#844)
This commit is contained in:
commit
a290791410
2
.github/config.yaml
vendored
Normal file
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.
|
32
.github/workflows/analyze.yml
vendored
Normal file
32
.github/workflows/analyze.yml
vendored
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
name: Analyze Code
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [ "main", "dev" ]
|
||||||
|
paths:
|
||||||
|
- "**.dart"
|
||||||
|
- ".github/workflows/analyze.yml"
|
||||||
|
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
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
2
.github/workflows/crowdin.yml
vendored
@ -3,7 +3,7 @@ name: Sync Crowdin translations
|
|||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- "flutter"
|
- "dev"
|
||||||
paths:
|
paths:
|
||||||
- "assets/i18n/en_US.json"
|
- "assets/i18n/en_US.json"
|
||||||
- ".github/workflows/crowdin.yml"
|
- ".github/workflows/crowdin.yml"
|
||||||
|
78
.github/workflows/pull-request-build.yml
vendored
78
.github/workflows/pull-request-build.yml
vendored
@ -1,37 +1,41 @@
|
|||||||
name: "Android CI PR Build"
|
name: PR Build
|
||||||
|
|
||||||
on:
|
on:
|
||||||
pull_request:
|
workflow_dispatch:
|
||||||
branches:
|
|
||||||
- "**"
|
jobs:
|
||||||
|
build:
|
||||||
jobs:
|
name: Build
|
||||||
release:
|
runs-on: ubuntu-latest
|
||||||
runs-on: ubuntu-latest
|
steps:
|
||||||
steps:
|
- name: Checkout
|
||||||
- uses: actions/checkout@v3
|
uses: actions/checkout@v3
|
||||||
- name: Set env
|
with:
|
||||||
run: echo "RELEASE_VERSION=${GITHUB_REF#refs/*/}" >> $GITHUB_ENV
|
# Make sure the release step uses its own credentials:
|
||||||
- name: Set up JDK 12
|
# https://github.com/cycjimmy/semantic-release-action#private-packages
|
||||||
uses: actions/setup-java@v3
|
persist-credentials: false
|
||||||
with:
|
fetch-depth: 0
|
||||||
java-version: '12'
|
- name: Setup JDK
|
||||||
distribution: 'zulu'
|
uses: actions/setup-java@v3
|
||||||
- uses: subosito/flutter-action@v2
|
with:
|
||||||
with:
|
java-version: '17'
|
||||||
channel: 'stable'
|
distribution: 'zulu'
|
||||||
- name: Set up Flutter
|
cache: 'gradle'
|
||||||
run: flutter pub get
|
- name: Setup Flutter
|
||||||
- name: Generate files with Builder
|
uses: subosito/flutter-action@v2
|
||||||
run: flutter packages pub run build_runner build --delete-conflicting-outputs
|
with:
|
||||||
- name: Build with Flutter
|
channel: 'stable'
|
||||||
env:
|
cache: true
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
- name: Install Flutter dependencies
|
||||||
run: flutter build apk
|
run: flutter pub get
|
||||||
- name: Add version to APK
|
- name: Generate files with Builder
|
||||||
run: mv build/app/outputs/flutter-apk/app-release.apk revanced-manager-${{ env.RELEASE_VERSION }}.apk
|
run: flutter packages pub run build_runner build --delete-conflicting-outputs
|
||||||
- name: Upload APK
|
- name: Build with Flutter
|
||||||
uses: actions/upload-artifact@v3
|
env:
|
||||||
with:
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
name: revanced-manager-${{ env.RELEASE_VERSION }}
|
run: flutter build apk
|
||||||
path: revanced-manager-${{ env.RELEASE_VERSION }}.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
8
.github/workflows/release-build.yml
vendored
@ -15,11 +15,11 @@ jobs:
|
|||||||
- name: Set up JDK 12
|
- name: Set up JDK 12
|
||||||
uses: actions/setup-java@v3
|
uses: actions/setup-java@v3
|
||||||
with:
|
with:
|
||||||
java-version: '12'
|
java-version: "12"
|
||||||
distribution: 'zulu'
|
distribution: "zulu"
|
||||||
- uses: subosito/flutter-action@v2
|
- uses: subosito/flutter-action@v2
|
||||||
with:
|
with:
|
||||||
channel: 'stable'
|
channel: "stable"
|
||||||
- name: Set up Flutter
|
- name: Set up Flutter
|
||||||
run: flutter pub get
|
run: flutter pub get
|
||||||
- name: Generate files with Builder
|
- name: Generate files with Builder
|
||||||
@ -47,4 +47,4 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
repo_token: "${{ secrets.GITHUB_TOKEN }}"
|
repo_token: "${{ secrets.GITHUB_TOKEN }}"
|
||||||
prerelease: false
|
prerelease: false
|
||||||
files: revanced-manager-${{ env.RELEASE_VERSION }}.apk
|
files: revanced-manager-${{ env.RELEASE_VERSION }}.apk
|
68
.github/workflows/release.yml
vendored
Normal file
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
|
7
.gitignore
vendored
7
.gitignore
vendored
@ -134,5 +134,8 @@ app.*.map.json
|
|||||||
!/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages
|
!/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages
|
||||||
!/dev/ci/**/Gemfile.lock
|
!/dev/ci/**/Gemfile.lock
|
||||||
|
|
||||||
Firebase related
|
# Firebase related
|
||||||
.firebase
|
.firebase
|
||||||
|
|
||||||
|
# Dependency directories
|
||||||
|
node_modules/
|
75
.releaserc
Normal file
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
1
CHANGELOG.md
Normal file
@ -0,0 +1 @@
|
|||||||
|
|
@ -2,18 +2,12 @@
|
|||||||
|
|
||||||
The official ReVanced Manager based on Flutter.
|
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
|
## 🔽 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
|
## 📝 Prerequisites
|
||||||
1. Android 8 or higher
|
1. Android 8 or higher
|
||||||
2. Does not work on some armv7 devices
|
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
|
## 🔴 Issues
|
||||||
For suggestions and bug reports, open an issue [here](https://github.com/revanced/revanced-manager/issues/new/choose).
|
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:
|
analyzer:
|
||||||
exclude:
|
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:
|
linter:
|
||||||
rules:
|
rules:
|
||||||
|
@ -30,6 +30,7 @@ android {
|
|||||||
ndkVersion flutter.ndkVersion
|
ndkVersion flutter.ndkVersion
|
||||||
|
|
||||||
compileOptions {
|
compileOptions {
|
||||||
|
coreLibraryDesugaringEnabled true
|
||||||
sourceCompatibility JavaVersion.VERSION_11
|
sourceCompatibility JavaVersion.VERSION_11
|
||||||
targetCompatibility JavaVersion.VERSION_11
|
targetCompatibility JavaVersion.VERSION_11
|
||||||
}
|
}
|
||||||
@ -48,6 +49,7 @@ android {
|
|||||||
targetSdkVersion 33
|
targetSdkVersion 33
|
||||||
versionCode flutterVersionCode.toInteger()
|
versionCode flutterVersionCode.toInteger()
|
||||||
versionName flutterVersionName
|
versionName flutterVersionName
|
||||||
|
multiDexEnabled true
|
||||||
}
|
}
|
||||||
|
|
||||||
buildTypes {
|
buildTypes {
|
||||||
@ -81,4 +83,11 @@ dependencies {
|
|||||||
implementation("org.microg:cronet-common:$cronetVersion")
|
implementation("org.microg:cronet-common:$cronetVersion")
|
||||||
implementation("org.microg:cronet-native:$cronetVersion")
|
implementation("org.microg:cronet-native:$cronetVersion")
|
||||||
|
|
||||||
|
// Core libraries
|
||||||
|
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.5'
|
||||||
|
|
||||||
|
// Window
|
||||||
|
implementation 'androidx.window:window:1.0.0'
|
||||||
|
implementation 'androidx.window:window-java:1.0.0'
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -44,6 +44,8 @@ class MainActivity : FlutterActivity() {
|
|||||||
val selectedPatches = call.argument<List<String>>("selectedPatches")
|
val selectedPatches = call.argument<List<String>>("selectedPatches")
|
||||||
val cacheDirPath = call.argument<String>("cacheDirPath")
|
val cacheDirPath = call.argument<String>("cacheDirPath")
|
||||||
val keyStoreFilePath = call.argument<String>("keyStoreFilePath")
|
val keyStoreFilePath = call.argument<String>("keyStoreFilePath")
|
||||||
|
val keystorePassword = call.argument<String>("keystorePassword")
|
||||||
|
|
||||||
if (patchBundleFilePath != null &&
|
if (patchBundleFilePath != null &&
|
||||||
originalFilePath != null &&
|
originalFilePath != null &&
|
||||||
inputFilePath != null &&
|
inputFilePath != null &&
|
||||||
@ -52,7 +54,8 @@ class MainActivity : FlutterActivity() {
|
|||||||
integrationsPath != null &&
|
integrationsPath != null &&
|
||||||
selectedPatches != null &&
|
selectedPatches != null &&
|
||||||
cacheDirPath != null &&
|
cacheDirPath != null &&
|
||||||
keyStoreFilePath != null
|
keyStoreFilePath != null &&
|
||||||
|
keystorePassword != null
|
||||||
) {
|
) {
|
||||||
runPatcher(
|
runPatcher(
|
||||||
result,
|
result,
|
||||||
@ -64,7 +67,8 @@ class MainActivity : FlutterActivity() {
|
|||||||
integrationsPath,
|
integrationsPath,
|
||||||
selectedPatches,
|
selectedPatches,
|
||||||
cacheDirPath,
|
cacheDirPath,
|
||||||
keyStoreFilePath
|
keyStoreFilePath,
|
||||||
|
keystorePassword
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
result.notImplemented()
|
result.notImplemented()
|
||||||
@ -85,7 +89,8 @@ class MainActivity : FlutterActivity() {
|
|||||||
integrationsPath: String,
|
integrationsPath: String,
|
||||||
selectedPatches: List<String>,
|
selectedPatches: List<String>,
|
||||||
cacheDirPath: String,
|
cacheDirPath: String,
|
||||||
keyStoreFilePath: String
|
keyStoreFilePath: String,
|
||||||
|
keystorePassword: String
|
||||||
) {
|
) {
|
||||||
val originalFile = File(originalFilePath)
|
val originalFile = File(originalFilePath)
|
||||||
val inputFile = File(inputFilePath)
|
val inputFile = File(inputFilePath)
|
||||||
@ -242,7 +247,7 @@ class MainActivity : FlutterActivity() {
|
|||||||
// Signer("ReVanced", "s3cur3p@ssw0rd").signApk(patchedFile, outFile, keyStoreFile)
|
// Signer("ReVanced", "s3cur3p@ssw0rd").signApk(patchedFile, outFile, keyStoreFile)
|
||||||
|
|
||||||
try {
|
try {
|
||||||
Signer("ReVanced", "s3cur3p@ssw0rd").signApk(patchedFile, outFile, keyStoreFile)
|
Signer("ReVanced", keystorePassword).signApk(patchedFile, outFile, keyStoreFile)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
//log to console
|
//log to console
|
||||||
print("Error signing apk: ${e.message}")
|
print("Error signing apk: ${e.message}")
|
||||||
|
Binary file not shown.
Before Width: | Height: | Size: 9.2 KiB After Width: | Height: | Size: 5.1 KiB |
@ -1,6 +1,7 @@
|
|||||||
distributionBase=GRADLE_USER_HOME
|
distributionBase=GRADLE_USER_HOME
|
||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.6.1-bin.zip
|
distributionUrl=https\://services.gradle.org/distributions/gradle-7.6.1-bin.zip
|
||||||
|
distributionSha256Sum=6147605a23b4eff6c334927a86ff3508cb5d6722cd624c97ded4c2e8640f1f87
|
||||||
networkTimeout=10000
|
networkTimeout=10000
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
||||||
|
@ -13,6 +13,7 @@
|
|||||||
"settingsTab": "Settings"
|
"settingsTab": "Settings"
|
||||||
},
|
},
|
||||||
"homeView": {
|
"homeView": {
|
||||||
|
"refreshSuccess": "Refreshed successfully",
|
||||||
"widgetTitle": "Dashboard",
|
"widgetTitle": "Dashboard",
|
||||||
"updatesSubtitle": "Updates",
|
"updatesSubtitle": "Updates",
|
||||||
"patchedSubtitle": "Patched applications",
|
"patchedSubtitle": "Patched applications",
|
||||||
@ -47,7 +48,8 @@
|
|||||||
"patcherView": {
|
"patcherView": {
|
||||||
"widgetTitle": "Patcher",
|
"widgetTitle": "Patcher",
|
||||||
"patchButton": "Patch",
|
"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": {
|
"appSelectorCard": {
|
||||||
"widgetTitle": "Select an application",
|
"widgetTitle": "Select an application",
|
||||||
@ -55,7 +57,7 @@
|
|||||||
"widgetSubtitle": "No application selected",
|
"widgetSubtitle": "No application selected",
|
||||||
"noAppsLabel": "No applications found",
|
"noAppsLabel": "No applications found",
|
||||||
"currentVersion": "Current",
|
"currentVersion": "Current",
|
||||||
"recommendedVersion": "Recommended",
|
"suggestedVersion": "Suggested",
|
||||||
"anyVersion": "any"
|
"anyVersion": "any"
|
||||||
},
|
},
|
||||||
"patchSelectorCard": {
|
"patchSelectorCard": {
|
||||||
@ -71,23 +73,25 @@
|
|||||||
"appSelectorView": {
|
"appSelectorView": {
|
||||||
"viewTitle": "Select an application",
|
"viewTitle": "Select an application",
|
||||||
"searchBarHint": "Search applications",
|
"searchBarHint": "Search applications",
|
||||||
|
"selectFromStorageButton": "Select from storage",
|
||||||
"storageButton": "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 feature has not been added yet for non-root. You'll need to select APK files from storage for now."
|
||||||
},
|
},
|
||||||
"patchesSelectorView": {
|
"patchesSelectorView": {
|
||||||
"viewTitle": "Select patches",
|
"viewTitle": "Select patches",
|
||||||
"searchBarHint": "Search patches",
|
"searchBarHint": "Search patches",
|
||||||
"doneButton": "Done",
|
"doneButton": "Done",
|
||||||
"recommended": "Recommended",
|
"default": "Default",
|
||||||
"recommendedTooltip": "Select all recommended patches",
|
"defaultTooltip": "Select all default patches",
|
||||||
"all": "All",
|
|
||||||
"allTooltip": "Select all patches",
|
|
||||||
"none": "None",
|
"none": "None",
|
||||||
"noneTooltip": "Deselect all patches",
|
"noneTooltip": "Deselect all patches",
|
||||||
"loadPatchesSelection": "Load patches selection",
|
"loadPatchesSelection": "Load patches selection",
|
||||||
"noSavedPatches": "No saved patches for the selected app.\nPress Done to save current selection.",
|
"noSavedPatches": "No saved patches for the selected app.\nPress Done to save current selection.",
|
||||||
"noPatchesFound": "No patches found for the selected app",
|
"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": {
|
"patchItem": {
|
||||||
"unsupportedDialogText": "Selecting this patch may result in patching errors.\n\nApp version: {packageVersion}\nSupported versions:\n{supportedVersions}",
|
"unsupportedDialogText": "Selecting this patch may result in patching errors.\n\nApp version: {packageVersion}\nSupported versions:\n{supportedVersions}",
|
||||||
@ -151,6 +155,7 @@
|
|||||||
"restartAppForChanges": "Restart the app to apply changes",
|
"restartAppForChanges": "Restart the app to apply changes",
|
||||||
"deleteKeystoreLabel": "Delete keystore",
|
"deleteKeystoreLabel": "Delete keystore",
|
||||||
"deleteKeystoreHint": "Delete the keystore used to sign the app",
|
"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",
|
"deletedKeystore": "Keystore deleted",
|
||||||
"deleteTempDirLabel": "Delete temporary files",
|
"deleteTempDirLabel": "Delete temporary files",
|
||||||
"deleteTempDirHint": "Delete unused temporary files",
|
"deleteTempDirHint": "Delete unused temporary files",
|
||||||
@ -168,7 +173,17 @@
|
|||||||
"jsonSelectorErrorMessage": "Unable to use selected JSON file",
|
"jsonSelectorErrorMessage": "Unable to use selected JSON file",
|
||||||
"deleteLogsLabel": "Delete logs",
|
"deleteLogsLabel": "Delete logs",
|
||||||
"deleteLogsHint": "Delete collected manager 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": {
|
"appInfoView": {
|
||||||
"widgetTitle": "App info",
|
"widgetTitle": "App info",
|
||||||
|
@ -3,20 +3,23 @@ import 'dart:io';
|
|||||||
|
|
||||||
import 'package:collection/collection.dart';
|
import 'package:collection/collection.dart';
|
||||||
import 'package:dio/dio.dart';
|
import 'package:dio/dio.dart';
|
||||||
import 'package:dio_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/foundation.dart';
|
||||||
import 'package:flutter_cache_manager/flutter_cache_manager.dart';
|
import 'package:flutter_cache_manager/flutter_cache_manager.dart';
|
||||||
import 'package:injectable/injectable.dart';
|
import 'package:injectable/injectable.dart';
|
||||||
|
import 'package:native_dio_adapter/native_dio_adapter.dart';
|
||||||
import 'package:revanced_manager/models/patch.dart';
|
import 'package:revanced_manager/models/patch.dart';
|
||||||
|
|
||||||
@lazySingleton
|
@lazySingleton
|
||||||
class GithubAPI {
|
class GithubAPI {
|
||||||
late Dio _dio = Dio();
|
late Dio _dio = Dio();
|
||||||
final DioCacheManager _dioCacheManager = DioCacheManager(CacheConfig());
|
|
||||||
final Options _cacheOptions = buildCacheOptions(
|
final _cacheOptions = CacheOptions(
|
||||||
const Duration(hours: 6),
|
store: MemCacheStore(),
|
||||||
maxStale: const Duration(days: 1),
|
maxStale: const Duration(days: 1),
|
||||||
|
priority: CachePriority.high,
|
||||||
);
|
);
|
||||||
|
|
||||||
final Map<String, String> repoAppPath = {
|
final Map<String, String> repoAppPath = {
|
||||||
'com.google.android.youtube': 'youtube',
|
'com.google.android.youtube': 'youtube',
|
||||||
'com.google.android.apps.youtube.music': 'music',
|
'com.google.android.apps.youtube.music': 'music',
|
||||||
@ -30,13 +33,29 @@ class GithubAPI {
|
|||||||
|
|
||||||
Future<void> initialize(String repoUrl) async {
|
Future<void> initialize(String repoUrl) async {
|
||||||
try {
|
try {
|
||||||
|
if (Platform.isIOS || Platform.isMacOS || Platform.isAndroid) {
|
||||||
|
final CronetEngine androidCronetEngine = await CronetEngine.build(
|
||||||
|
userAgent: 'ReVanced Manager',
|
||||||
|
enableBrotli: true,
|
||||||
|
enableQuic: true,
|
||||||
|
);
|
||||||
|
_dio.httpClientAdapter =
|
||||||
|
NativeAdapter(androidCronetEngine: androidCronetEngine);
|
||||||
|
|
||||||
|
_dio = Dio(
|
||||||
|
BaseOptions(
|
||||||
|
baseUrl: repoUrl,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
_dio = Dio(
|
_dio = Dio(
|
||||||
BaseOptions(
|
BaseOptions(
|
||||||
baseUrl: repoUrl,
|
baseUrl: repoUrl,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
_dio.interceptors.add(_dioCacheManager.interceptor);
|
_dio.interceptors.add(DioCacheInterceptor(options: _cacheOptions));
|
||||||
} on Exception catch (e) {
|
} on Exception catch (e) {
|
||||||
if (kDebugMode) {
|
if (kDebugMode) {
|
||||||
print(e);
|
print(e);
|
||||||
@ -46,7 +65,7 @@ class GithubAPI {
|
|||||||
|
|
||||||
Future<void> clearAllCache() async {
|
Future<void> clearAllCache() async {
|
||||||
try {
|
try {
|
||||||
await _dioCacheManager.clearAll();
|
await _cacheOptions.store!.clean();
|
||||||
} on Exception catch (e) {
|
} on Exception catch (e) {
|
||||||
if (kDebugMode) {
|
if (kDebugMode) {
|
||||||
print(e);
|
print(e);
|
||||||
@ -54,11 +73,12 @@ class GithubAPI {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<Map<String, dynamic>?> getLatestRelease(String repoName) async {
|
Future<Map<String, dynamic>?> getLatestRelease(
|
||||||
|
String repoName,
|
||||||
|
) async {
|
||||||
try {
|
try {
|
||||||
final response = await _dio.get(
|
final response = await _dio.get(
|
||||||
'/repos/$repoName/releases',
|
'/repos/$repoName/releases',
|
||||||
options: _cacheOptions,
|
|
||||||
);
|
);
|
||||||
return response.data[0];
|
return response.data[0];
|
||||||
} on Exception catch (e) {
|
} on Exception catch (e) {
|
||||||
@ -83,7 +103,6 @@ class GithubAPI {
|
|||||||
'path': path,
|
'path': path,
|
||||||
'since': since.toIso8601String(),
|
'since': since.toIso8601String(),
|
||||||
},
|
},
|
||||||
options: _cacheOptions,
|
|
||||||
);
|
);
|
||||||
final List<dynamic> commits = response.data;
|
final List<dynamic> commits = response.data;
|
||||||
return commits
|
return commits
|
||||||
@ -102,9 +121,13 @@ class GithubAPI {
|
|||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<File?> getLatestReleaseFile(String extension, String repoName) async {
|
Future<File?> getLatestReleaseFile(
|
||||||
|
String extension,
|
||||||
|
String repoName,
|
||||||
|
) async {
|
||||||
try {
|
try {
|
||||||
final Map<String, dynamic>? release = await getLatestRelease(repoName);
|
final Map<String, dynamic>? release =
|
||||||
|
await getLatestRelease(repoName);
|
||||||
if (release != null) {
|
if (release != null) {
|
||||||
final Map<String, dynamic>? asset =
|
final Map<String, dynamic>? asset =
|
||||||
(release['assets'] as List<dynamic>).firstWhereOrNull(
|
(release['assets'] as List<dynamic>).firstWhereOrNull(
|
||||||
@ -143,7 +166,8 @@ class GithubAPI {
|
|||||||
|
|
||||||
Future<String> getLastestReleaseVersion(String repoName) async {
|
Future<String> getLastestReleaseVersion(String repoName) async {
|
||||||
try {
|
try {
|
||||||
final Map<String, dynamic>? release = await getLatestRelease(repoName);
|
final Map<String, dynamic>? release =
|
||||||
|
await getLatestRelease(repoName);
|
||||||
if (release != null) {
|
if (release != null) {
|
||||||
return release['tag_name'];
|
return release['tag_name'];
|
||||||
} else {
|
} else {
|
||||||
|
@ -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/github_api.dart';
|
||||||
import 'package:revanced_manager/services/revanced_api.dart';
|
import 'package:revanced_manager/services/revanced_api.dart';
|
||||||
import 'package:revanced_manager/services/root_api.dart';
|
import 'package:revanced_manager/services/root_api.dart';
|
||||||
|
import 'package:revanced_manager/utils/check_for_supported_patch.dart';
|
||||||
import 'package:shared_preferences/shared_preferences.dart';
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
|
|
||||||
@lazySingleton
|
@lazySingleton
|
||||||
@ -21,7 +22,11 @@ class ManagerAPI {
|
|||||||
final String patcherRepo = 'revanced-patcher';
|
final String patcherRepo = 'revanced-patcher';
|
||||||
final String cliRepo = 'revanced-cli';
|
final String cliRepo = 'revanced-cli';
|
||||||
late SharedPreferences _prefs;
|
late SharedPreferences _prefs;
|
||||||
|
bool isRooted = false;
|
||||||
String storedPatchesFile = '/selected-patches.json';
|
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 defaultApiUrl = 'https://releases.revanced.app/';
|
||||||
String defaultRepoUrl = 'https://api.github.com';
|
String defaultRepoUrl = 'https://api.github.com';
|
||||||
String defaultPatcherRepo = 'revanced/revanced-patcher';
|
String defaultPatcherRepo = 'revanced/revanced-patcher';
|
||||||
@ -29,9 +34,14 @@ class ManagerAPI {
|
|||||||
String defaultIntegrationsRepo = 'revanced/revanced-integrations';
|
String defaultIntegrationsRepo = 'revanced/revanced-integrations';
|
||||||
String defaultCliRepo = 'revanced/revanced-cli';
|
String defaultCliRepo = 'revanced/revanced-cli';
|
||||||
String defaultManagerRepo = 'revanced/revanced-manager';
|
String defaultManagerRepo = 'revanced/revanced-manager';
|
||||||
|
String? patchesVersion = '';
|
||||||
|
bool isDefaultPatchesRepo() {
|
||||||
|
return getPatchesRepo() == 'revanced/revanced-patches';
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> initialize() async {
|
Future<void> initialize() async {
|
||||||
_prefs = await SharedPreferences.getInstance();
|
_prefs = await SharedPreferences.getInstance();
|
||||||
|
isRooted = await _rootAPI.isRooted();
|
||||||
storedPatchesFile =
|
storedPatchesFile =
|
||||||
(await getApplicationDocumentsDirectory()).path + storedPatchesFile;
|
(await getApplicationDocumentsDirectory()).path + storedPatchesFile;
|
||||||
}
|
}
|
||||||
@ -114,6 +124,14 @@ class ManagerAPI {
|
|||||||
await _prefs.setBool('experimentalPatchesEnabled', value);
|
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 {
|
Future<void> deleteTempFolder() async {
|
||||||
final Directory dir = Directory('/data/local/tmp/revanced-manager');
|
final Directory dir = Directory('/data/local/tmp/revanced-manager');
|
||||||
if (await dir.exists()) {
|
if (await dir.exists()) {
|
||||||
@ -123,7 +141,7 @@ class ManagerAPI {
|
|||||||
|
|
||||||
Future<void> deleteKeystore() async {
|
Future<void> deleteKeystore() async {
|
||||||
final File keystore = File(
|
final File keystore = File(
|
||||||
'/sdcard/Android/data/app.revanced.manager.flutter/files/revanced-manager.keystore',
|
keystoreFile,
|
||||||
);
|
);
|
||||||
if (await keystore.exists()) {
|
if (await keystore.exists()) {
|
||||||
await keystore.delete();
|
await keystore.delete();
|
||||||
@ -135,7 +153,9 @@ class ManagerAPI {
|
|||||||
return apps.map((a) => PatchedApplication.fromJson(jsonDecode(a))).toList();
|
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) {
|
if (patchedApps.length > 1) {
|
||||||
patchedApps.sort((a, b) => a.name.compareTo(b.name));
|
patchedApps.sort((a, b) => a.name.compareTo(b.name));
|
||||||
}
|
}
|
||||||
@ -237,15 +257,24 @@ class ManagerAPI {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<File?> downloadManager() async {
|
Future<File?> downloadManager() async {
|
||||||
return await _revancedAPI.getLatestReleaseFile('.apk', defaultManagerRepo);
|
return await _revancedAPI.getLatestReleaseFile(
|
||||||
|
'.apk',
|
||||||
|
defaultManagerRepo,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<String?> getLatestPatcherReleaseTime() async {
|
Future<String?> getLatestPatcherReleaseTime() async {
|
||||||
return await _revancedAPI.getLatestReleaseTime('.gz', defaultPatcherRepo);
|
return await _revancedAPI.getLatestReleaseTime(
|
||||||
|
'.gz',
|
||||||
|
defaultPatcherRepo,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<String?> getLatestManagerReleaseTime() async {
|
Future<String?> getLatestManagerReleaseTime() async {
|
||||||
return await _revancedAPI.getLatestReleaseTime('.apk', defaultManagerRepo);
|
return await _revancedAPI.getLatestReleaseTime(
|
||||||
|
'.apk',
|
||||||
|
defaultManagerRepo,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<String?> getLatestManagerVersion() async {
|
Future<String?> getLatestManagerVersion() async {
|
||||||
@ -267,6 +296,19 @@ class ManagerAPI {
|
|||||||
return packageInfo.version;
|
return packageInfo.version;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<String?> getCurrentPatchesVersion() async {
|
||||||
|
if (isDefaultPatchesRepo()) {
|
||||||
|
patchesVersion = await getLatestPatchesVersion();
|
||||||
|
// print('Patches version: $patchesVersion');
|
||||||
|
return patchesVersion ?? '0.0.0';
|
||||||
|
} else {
|
||||||
|
// fetch from github
|
||||||
|
patchesVersion =
|
||||||
|
await _githubAPI.getLastestReleaseVersion(getPatchesRepo());
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
Future<List<PatchedApplication>> getAppsToRemove(
|
Future<List<PatchedApplication>> getAppsToRemove(
|
||||||
List<PatchedApplication> patchedApps,
|
List<PatchedApplication> patchedApps,
|
||||||
) async {
|
) async {
|
||||||
@ -359,8 +401,9 @@ class ManagerAPI {
|
|||||||
final int currentInstalledVersionInt = int.parse(
|
final int currentInstalledVersionInt = int.parse(
|
||||||
currentInstalledVersion.replaceAll(RegExp('[^0-9]'), ''),
|
currentInstalledVersion.replaceAll(RegExp('[^0-9]'), ''),
|
||||||
);
|
);
|
||||||
final int currentSavedVersionInt =
|
final int currentSavedVersionInt = int.parse(
|
||||||
int.parse(currentSavedVersion.replaceAll(RegExp('[^0-9]'), ''));
|
currentSavedVersion.replaceAll(RegExp('[^0-9]'), ''),
|
||||||
|
);
|
||||||
if (currentInstalledVersionInt > currentSavedVersionInt) {
|
if (currentInstalledVersionInt > currentSavedVersionInt) {
|
||||||
app.hasUpdates = true;
|
app.hasUpdates = true;
|
||||||
}
|
}
|
||||||
@ -383,7 +426,10 @@ class ManagerAPI {
|
|||||||
return !existsNonRoot;
|
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(
|
final List<String> commits = await _githubAPI.getCommits(
|
||||||
packageName,
|
packageName,
|
||||||
getPatchesRepo(),
|
getPatchesRepo(),
|
||||||
@ -421,7 +467,10 @@ class ManagerAPI {
|
|||||||
return app != null && app.isSplit;
|
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 File selectedPatchesFile = File(storedPatchesFile);
|
||||||
final Map<String, dynamic> patchesMap = await readSelectedPatchesFile();
|
final Map<String, dynamic> patchesMap = await readSelectedPatchesFile();
|
||||||
if (patches.isEmpty) {
|
if (patches.isEmpty) {
|
||||||
@ -432,9 +481,33 @@ class ManagerAPI {
|
|||||||
selectedPatchesFile.writeAsString(jsonEncode(patchesMap));
|
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 {
|
Future<List<String>> getSelectedPatches(String app) async {
|
||||||
final Map<String, dynamic> patchesMap = await readSelectedPatchesFile();
|
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 {
|
Future<Map<String, dynamic>> readSelectedPatchesFile() async {
|
||||||
|
@ -118,13 +118,17 @@ class PatcherAPI {
|
|||||||
return filteredPatches[packageName];
|
return filteredPatches[packageName];
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<List<Patch>> getAppliedPatches(List<String> appliedPatches) async {
|
Future<List<Patch>> getAppliedPatches(
|
||||||
|
List<String> appliedPatches,
|
||||||
|
) async {
|
||||||
return _patches
|
return _patches
|
||||||
.where((patch) => appliedPatches.contains(patch.name))
|
.where((patch) => appliedPatches.contains(patch.name))
|
||||||
.toList();
|
.toList();
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<bool> needsResourcePatching(List<Patch> selectedPatches) async {
|
Future<bool> needsResourcePatching(
|
||||||
|
List<Patch> selectedPatches,
|
||||||
|
) async {
|
||||||
return selectedPatches.any(
|
return selectedPatches.any(
|
||||||
(patch) => patch.dependencies.any(
|
(patch) => patch.dependencies.any(
|
||||||
(dep) => dep.contains('resource-'),
|
(dep) => dep.contains('resource-'),
|
||||||
@ -210,6 +214,7 @@ class PatcherAPI {
|
|||||||
'selectedPatches': selectedPatches.map((p) => p.name).toList(),
|
'selectedPatches': selectedPatches.map((p) => p.name).toList(),
|
||||||
'cacheDirPath': cacheDir.path,
|
'cacheDirPath': cacheDir.path,
|
||||||
'keyStoreFilePath': _keyStoreFile.path,
|
'keyStoreFilePath': _keyStoreFile.path,
|
||||||
|
'keystorePassword': _managerAPI.getKeystorePassword(),
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
} on Exception catch (e) {
|
} on Exception catch (e) {
|
||||||
@ -234,7 +239,9 @@ class PatcherAPI {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
await AppInstaller.installApk(_outFile!.path);
|
await AppInstaller.installApk(_outFile!.path);
|
||||||
return await DeviceApps.isAppInstalled(patchedApp.packageName);
|
return await DeviceApps.isAppInstalled(
|
||||||
|
patchedApp.packageName,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
} on Exception catch (e) {
|
} on Exception catch (e) {
|
||||||
if (kDebugMode) {
|
if (kDebugMode) {
|
||||||
@ -303,7 +310,7 @@ class PatcherAPI {
|
|||||||
ShareExtend.share(log.path, 'file');
|
ShareExtend.share(log.path, 'file');
|
||||||
}
|
}
|
||||||
|
|
||||||
String getRecommendedVersion(String packageName) {
|
String getSuggestedVersion(String packageName) {
|
||||||
final Map<String, int> versions = {};
|
final Map<String, int> versions = {};
|
||||||
for (final Patch patch in _patches) {
|
for (final Patch patch in _patches) {
|
||||||
final Package? package = patch.compatiblePackages.firstWhereOrNull(
|
final Package? package = patch.compatiblePackages.firstWhereOrNull(
|
||||||
|
@ -1,13 +1,14 @@
|
|||||||
|
import 'dart:async';
|
||||||
import 'dart:developer';
|
import 'dart:developer';
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:collection/collection.dart';
|
import 'package:collection/collection.dart';
|
||||||
import 'package:dio/dio.dart';
|
import 'package:dio/dio.dart';
|
||||||
import 'package:dio_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/foundation.dart';
|
||||||
import 'package:flutter_cache_manager/flutter_cache_manager.dart';
|
import 'package:flutter_cache_manager/flutter_cache_manager.dart';
|
||||||
import 'package:injectable/injectable.dart';
|
import 'package:injectable/injectable.dart';
|
||||||
import 'package:native_dio_client/native_dio_client.dart';
|
import 'package:native_dio_adapter/native_dio_adapter.dart';
|
||||||
import 'package:revanced_manager/models/patch.dart';
|
import 'package:revanced_manager/models/patch.dart';
|
||||||
import 'package:revanced_manager/utils/check_for_gms.dart';
|
import 'package:revanced_manager/utils/check_for_gms.dart';
|
||||||
import 'package:timeago/timeago.dart';
|
import 'package:timeago/timeago.dart';
|
||||||
@ -15,10 +16,11 @@ import 'package:timeago/timeago.dart';
|
|||||||
@lazySingleton
|
@lazySingleton
|
||||||
class RevancedAPI {
|
class RevancedAPI {
|
||||||
late Dio _dio = Dio();
|
late Dio _dio = Dio();
|
||||||
final DioCacheManager _dioCacheManager = DioCacheManager(CacheConfig());
|
|
||||||
final Options _cacheOptions = buildCacheOptions(
|
final _cacheOptions = CacheOptions(
|
||||||
const Duration(hours: 6),
|
store: MemCacheStore(),
|
||||||
maxStale: const Duration(days: 1),
|
maxStale: const Duration(days: 1),
|
||||||
|
priority: CachePriority.high,
|
||||||
);
|
);
|
||||||
|
|
||||||
Future<void> initialize(String apiUrl) async {
|
Future<void> initialize(String apiUrl) async {
|
||||||
@ -33,14 +35,25 @@ class RevancedAPI {
|
|||||||
);
|
);
|
||||||
log('ReVanced API: Using default engine + $isGMSInstalled');
|
log('ReVanced API: Using default engine + $isGMSInstalled');
|
||||||
} else {
|
} else {
|
||||||
_dio = Dio(
|
if (Platform.isIOS || Platform.isMacOS || Platform.isAndroid) {
|
||||||
BaseOptions(
|
final CronetEngine androidCronetEngine = await CronetEngine.build(
|
||||||
baseUrl: apiUrl,
|
userAgent: 'ReVanced Manager',
|
||||||
),
|
enableBrotli: true,
|
||||||
)..httpClientAdapter = NativeAdapter();
|
enableQuic: true,
|
||||||
|
);
|
||||||
|
_dio.httpClientAdapter =
|
||||||
|
NativeAdapter(androidCronetEngine: androidCronetEngine);
|
||||||
|
|
||||||
|
_dio = Dio(
|
||||||
|
BaseOptions(
|
||||||
|
baseUrl: apiUrl,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
log('ReVanced API: Using CronetEngine + $isGMSInstalled');
|
log('ReVanced API: Using CronetEngine + $isGMSInstalled');
|
||||||
}
|
}
|
||||||
_dio.interceptors.add(_dioCacheManager.interceptor);
|
_dio.interceptors.add(DioCacheInterceptor(options: _cacheOptions));
|
||||||
} on Exception catch (e) {
|
} on Exception catch (e) {
|
||||||
if (kDebugMode) {
|
if (kDebugMode) {
|
||||||
print(e);
|
print(e);
|
||||||
@ -50,7 +63,7 @@ class RevancedAPI {
|
|||||||
|
|
||||||
Future<void> clearAllCache() async {
|
Future<void> clearAllCache() async {
|
||||||
try {
|
try {
|
||||||
await _dioCacheManager.clearAll();
|
await _cacheOptions.store!.clean();
|
||||||
} on Exception catch (e) {
|
} on Exception catch (e) {
|
||||||
if (kDebugMode) {
|
if (kDebugMode) {
|
||||||
print(e);
|
print(e);
|
||||||
@ -61,7 +74,7 @@ class RevancedAPI {
|
|||||||
Future<Map<String, List<dynamic>>> getContributors() async {
|
Future<Map<String, List<dynamic>>> getContributors() async {
|
||||||
final Map<String, List<dynamic>> contributors = {};
|
final Map<String, List<dynamic>> contributors = {};
|
||||||
try {
|
try {
|
||||||
final response = await _dio.get('/contributors', options: _cacheOptions);
|
final response = await _dio.get('/contributors');
|
||||||
final List<dynamic> repositories = response.data['repositories'];
|
final List<dynamic> repositories = response.data['repositories'];
|
||||||
for (final Map<String, dynamic> repo in repositories) {
|
for (final Map<String, dynamic> repo in repositories) {
|
||||||
final String name = repo['name'];
|
final String name = repo['name'];
|
||||||
@ -78,7 +91,7 @@ class RevancedAPI {
|
|||||||
|
|
||||||
Future<List<Patch>> getPatches() async {
|
Future<List<Patch>> getPatches() async {
|
||||||
try {
|
try {
|
||||||
final response = await _dio.get('/patches', options: _cacheOptions);
|
final response = await _dio.get('/patches');
|
||||||
final List<dynamic> patches = response.data;
|
final List<dynamic> patches = response.data;
|
||||||
return patches.map((patch) => Patch.fromJson(patch)).toList();
|
return patches.map((patch) => Patch.fromJson(patch)).toList();
|
||||||
} on Exception catch (e) {
|
} on Exception catch (e) {
|
||||||
@ -94,7 +107,7 @@ class RevancedAPI {
|
|||||||
String repoName,
|
String repoName,
|
||||||
) async {
|
) async {
|
||||||
try {
|
try {
|
||||||
final response = await _dio.get('/tools', options: _cacheOptions);
|
final response = await _dio.get('/tools');
|
||||||
final List<dynamic> tools = response.data['tools'];
|
final List<dynamic> tools = response.data['tools'];
|
||||||
return tools.firstWhereOrNull(
|
return tools.firstWhereOrNull(
|
||||||
(t) =>
|
(t) =>
|
||||||
@ -130,7 +143,10 @@ class RevancedAPI {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<File?> getLatestReleaseFile(String extension, String repoName) async {
|
Future<File?> getLatestReleaseFile(
|
||||||
|
String extension,
|
||||||
|
String repoName,
|
||||||
|
) async {
|
||||||
try {
|
try {
|
||||||
final Map<String, dynamic>? release = await _getLatestRelease(
|
final Map<String, dynamic>? release = await _getLatestRelease(
|
||||||
extension,
|
extension,
|
||||||
@ -149,6 +165,43 @@ class RevancedAPI {
|
|||||||
return null;
|
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(
|
Future<String?> getLatestReleaseTime(
|
||||||
String extension,
|
String extension,
|
||||||
String repoName,
|
String repoName,
|
||||||
|
@ -3,6 +3,7 @@ import 'package:flutter_i18n/flutter_i18n.dart';
|
|||||||
import 'package:revanced_manager/ui/views/app_selector/app_selector_viewmodel.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/app_skeleton_loader.dart';
|
||||||
import 'package:revanced_manager/ui/widgets/appSelectorView/installed_app_item.dart';
|
import 'package:revanced_manager/ui/widgets/appSelectorView/installed_app_item.dart';
|
||||||
|
import 'package:revanced_manager/ui/widgets/appSelectorView/not_installed_app_item.dart';
|
||||||
import 'package:revanced_manager/ui/widgets/shared/search_bar.dart';
|
import 'package:revanced_manager/ui/widgets/shared/search_bar.dart';
|
||||||
import 'package:stacked/stacked.dart' hide SkeletonLoader;
|
import 'package:stacked/stacked.dart' hide SkeletonLoader;
|
||||||
|
|
||||||
@ -76,7 +77,16 @@ class _AppSelectorViewState extends State<AppSelectorView> {
|
|||||||
SliverToBoxAdapter(
|
SliverToBoxAdapter(
|
||||||
child: model.noApps
|
child: model.noApps
|
||||||
? Center(
|
? Center(
|
||||||
child: I18nText('appSelectorCard.noAppsLabel'),
|
child: I18nText(
|
||||||
|
'appSelectorView.noApps',
|
||||||
|
child: Text(
|
||||||
|
'',
|
||||||
|
style: TextStyle(
|
||||||
|
color:
|
||||||
|
Theme.of(context).textTheme.titleLarge!.color,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
)
|
)
|
||||||
: model.apps.isEmpty
|
: model.apps.isEmpty
|
||||||
? const AppSkeletonLoader()
|
? const AppSkeletonLoader()
|
||||||
@ -84,22 +94,48 @@ class _AppSelectorViewState extends State<AppSelectorView> {
|
|||||||
padding: const EdgeInsets.symmetric(horizontal: 12.0)
|
padding: const EdgeInsets.symmetric(horizontal: 12.0)
|
||||||
.copyWith(bottom: 80),
|
.copyWith(bottom: 80),
|
||||||
child: Column(
|
child: Column(
|
||||||
children: model
|
children: [
|
||||||
.getFilteredApps(_query)
|
...model
|
||||||
.map(
|
.getFilteredApps(_query)
|
||||||
(app) => InstalledAppItem(
|
.map(
|
||||||
name: app.appName,
|
(app) => InstalledAppItem(
|
||||||
pkgName: app.packageName,
|
name: app.appName,
|
||||||
icon: app.icon,
|
pkgName: app.packageName,
|
||||||
patchesCount:
|
icon: app.icon,
|
||||||
model.patchesCount(app.packageName),
|
patchesCount:
|
||||||
onTap: () {
|
model.patchesCount(app.packageName),
|
||||||
model.selectApp(app);
|
suggestedVersion:
|
||||||
Navigator.of(context).pop();
|
model.getSuggestedVersion(
|
||||||
},
|
app.packageName,
|
||||||
),
|
),
|
||||||
)
|
onTap: () {
|
||||||
.toList(),
|
model.isRooted
|
||||||
|
? model.selectApp(app).then(
|
||||||
|
(_) => Navigator.of(context)
|
||||||
|
.pop(),
|
||||||
|
)
|
||||||
|
: model.showSelectFromStorageDialog(
|
||||||
|
context,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.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:file_picker/file_picker.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_i18n/flutter_i18n.dart';
|
||||||
import 'package:revanced_manager/app/app.locator.dart';
|
import 'package:revanced_manager/app/app.locator.dart';
|
||||||
|
import 'package:revanced_manager/models/patch.dart';
|
||||||
import 'package:revanced_manager/models/patched_application.dart';
|
import 'package:revanced_manager/models/patched_application.dart';
|
||||||
import 'package:revanced_manager/services/manager_api.dart';
|
import 'package:revanced_manager/services/manager_api.dart';
|
||||||
import 'package:revanced_manager/services/patcher_api.dart';
|
import 'package:revanced_manager/services/patcher_api.dart';
|
||||||
|
import 'package:revanced_manager/services/revanced_api.dart';
|
||||||
import 'package:revanced_manager/services/toast.dart';
|
import 'package:revanced_manager/services/toast.dart';
|
||||||
import 'package:revanced_manager/ui/views/patcher/patcher_viewmodel.dart';
|
import 'package:revanced_manager/ui/views/patcher/patcher_viewmodel.dart';
|
||||||
|
import 'package:revanced_manager/ui/widgets/shared/custom_material_button.dart';
|
||||||
import 'package:stacked/stacked.dart';
|
import 'package:stacked/stacked.dart';
|
||||||
|
|
||||||
class AppSelectorViewModel extends BaseViewModel {
|
class AppSelectorViewModel extends BaseViewModel {
|
||||||
final PatcherAPI _patcherAPI = locator<PatcherAPI>();
|
final PatcherAPI _patcherAPI = locator<PatcherAPI>();
|
||||||
final ManagerAPI _managerAPI = locator<ManagerAPI>();
|
final ManagerAPI _managerAPI = locator<ManagerAPI>();
|
||||||
|
final RevancedAPI _revancedAPI = locator<RevancedAPI>();
|
||||||
final Toast _toast = locator<Toast>();
|
final Toast _toast = locator<Toast>();
|
||||||
final List<ApplicationWithIcon> apps = [];
|
final List<ApplicationWithIcon> apps = [];
|
||||||
|
List<String> allApps = [];
|
||||||
bool noApps = false;
|
bool noApps = false;
|
||||||
|
bool isRooted = false;
|
||||||
int patchesCount(String packageName) {
|
int patchesCount(String packageName) {
|
||||||
return _patcherAPI.getFilteredPatches(packageName).length;
|
return _patcherAPI.getFilteredPatches(packageName).length;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
List<Patch> patches = [];
|
||||||
|
|
||||||
Future<void> initialize() async {
|
Future<void> initialize() async {
|
||||||
|
patches = await _revancedAPI.getPatches();
|
||||||
|
isRooted = _managerAPI.isRooted;
|
||||||
|
|
||||||
apps.addAll(
|
apps.addAll(
|
||||||
await _patcherAPI
|
await _patcherAPI
|
||||||
.getFilteredInstalledApps(_managerAPI.areUniversalPatchesEnabled()),
|
.getFilteredInstalledApps(_managerAPI.areUniversalPatchesEnabled()),
|
||||||
@ -34,9 +46,25 @@ class AppSelectorViewModel extends BaseViewModel {
|
|||||||
.compareTo(_patcherAPI.getFilteredPatches(a.packageName).length),
|
.compareTo(_patcherAPI.getFilteredPatches(a.packageName).length),
|
||||||
);
|
);
|
||||||
noApps = apps.isEmpty;
|
noApps = apps.isEmpty;
|
||||||
|
getAllApps();
|
||||||
|
|
||||||
notifyListeners();
|
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<void> selectApp(ApplicationWithIcon application) async {
|
Future<void> selectApp(ApplicationWithIcon application) async {
|
||||||
locator<PatcherViewModel>().selectedApp = PatchedApplication(
|
locator<PatcherViewModel>().selectedApp = PatchedApplication(
|
||||||
name: application.appName,
|
name: application.appName,
|
||||||
@ -50,6 +78,80 @@ class AppSelectorViewModel extends BaseViewModel {
|
|||||||
locator<PatcherViewModel>().loadLastSelectedPatches();
|
locator<PatcherViewModel>().loadLastSelectedPatches();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
Navigator.pop(context);
|
||||||
|
},
|
||||||
|
label: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
const SizedBox(width: 10),
|
||||||
|
I18nText('cancelButton'),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> selectAppFromStorage(BuildContext context) async {
|
Future<void> selectAppFromStorage(BuildContext context) async {
|
||||||
try {
|
try {
|
||||||
final FilePickerResult? result = await FilePicker.platform.pickFiles(
|
final FilePickerResult? result = await FilePicker.platform.pickFiles(
|
||||||
@ -105,4 +207,18 @@ class AppSelectorViewModel extends BaseViewModel {
|
|||||||
)
|
)
|
||||||
.toList();
|
.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');
|
||||||
}
|
}
|
||||||
|
@ -17,6 +17,7 @@ class HomeView extends StatelessWidget {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return ViewModelBuilder<HomeViewModel>.reactive(
|
return ViewModelBuilder<HomeViewModel>.reactive(
|
||||||
disposeViewModel: false,
|
disposeViewModel: false,
|
||||||
|
fireOnViewModelReadyOnce: true,
|
||||||
onViewModelReady: (model) => model.initialize(context),
|
onViewModelReady: (model) => model.initialize(context),
|
||||||
viewModelBuilder: () => locator<HomeViewModel>(),
|
viewModelBuilder: () => locator<HomeViewModel>(),
|
||||||
builder: (context, model, child) => Scaffold(
|
builder: (context, model, child) => Scaffold(
|
||||||
@ -50,8 +51,9 @@ class HomeView extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
const SizedBox(height: 10),
|
const SizedBox(height: 10),
|
||||||
LatestCommitCard(
|
LatestCommitCard(
|
||||||
onPressed: () =>
|
onPressedManager: () =>
|
||||||
model.showUpdateConfirmationDialog(context),
|
model.showUpdateConfirmationDialog(context),
|
||||||
|
onPressedPatches: () => model.forceRefresh(context),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 23),
|
const SizedBox(height: 23),
|
||||||
I18nText(
|
I18nText(
|
||||||
|
@ -1,27 +1,30 @@
|
|||||||
// ignore_for_file: use_build_context_synchronously
|
// ignore_for_file: use_build_context_synchronously
|
||||||
|
import 'dart:async';
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:app_installer/app_installer.dart';
|
import 'package:app_installer/app_installer.dart';
|
||||||
import 'package:cross_connectivity/cross_connectivity.dart';
|
import 'package:cross_connectivity/cross_connectivity.dart';
|
||||||
import 'package:device_apps/device_apps.dart';
|
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_i18n/flutter_i18n.dart';
|
import 'package:flutter_i18n/flutter_i18n.dart';
|
||||||
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
|
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
|
||||||
import 'package:injectable/injectable.dart';
|
import 'package:injectable/injectable.dart';
|
||||||
|
import 'package:path_provider/path_provider.dart';
|
||||||
import 'package:revanced_manager/app/app.locator.dart';
|
import 'package:revanced_manager/app/app.locator.dart';
|
||||||
import 'package:revanced_manager/app/app.router.dart';
|
import 'package:revanced_manager/app/app.router.dart';
|
||||||
import 'package:revanced_manager/models/patched_application.dart';
|
import 'package:revanced_manager/models/patched_application.dart';
|
||||||
import 'package:revanced_manager/services/github_api.dart';
|
import 'package:revanced_manager/services/github_api.dart';
|
||||||
import 'package:revanced_manager/services/manager_api.dart';
|
import 'package:revanced_manager/services/manager_api.dart';
|
||||||
import 'package:revanced_manager/services/patcher_api.dart';
|
import 'package:revanced_manager/services/patcher_api.dart';
|
||||||
|
import 'package:revanced_manager/services/revanced_api.dart';
|
||||||
import 'package:revanced_manager/services/toast.dart';
|
import 'package:revanced_manager/services/toast.dart';
|
||||||
import 'package:revanced_manager/ui/views/navigation/navigation_viewmodel.dart';
|
import 'package:revanced_manager/ui/views/navigation/navigation_viewmodel.dart';
|
||||||
import 'package:revanced_manager/ui/views/patcher/patcher_viewmodel.dart';
|
import 'package:revanced_manager/ui/views/patcher/patcher_viewmodel.dart';
|
||||||
import 'package:revanced_manager/ui/widgets/homeView/update_confirmation_dialog.dart';
|
import 'package:revanced_manager/ui/widgets/homeView/update_confirmation_dialog.dart';
|
||||||
|
import 'package:revanced_manager/ui/widgets/shared/custom_material_button.dart';
|
||||||
|
import 'package:revanced_manager/utils/about_info.dart';
|
||||||
import 'package:stacked/stacked.dart';
|
import 'package:stacked/stacked.dart';
|
||||||
import 'package:stacked_services/stacked_services.dart';
|
import 'package:stacked_services/stacked_services.dart';
|
||||||
import 'package:timezone/timezone.dart' as tz;
|
|
||||||
|
|
||||||
@lazySingleton
|
@lazySingleton
|
||||||
class HomeViewModel extends BaseViewModel {
|
class HomeViewModel extends BaseViewModel {
|
||||||
@ -29,20 +32,35 @@ class HomeViewModel extends BaseViewModel {
|
|||||||
final ManagerAPI _managerAPI = locator<ManagerAPI>();
|
final ManagerAPI _managerAPI = locator<ManagerAPI>();
|
||||||
final PatcherAPI _patcherAPI = locator<PatcherAPI>();
|
final PatcherAPI _patcherAPI = locator<PatcherAPI>();
|
||||||
final GithubAPI _githubAPI = locator<GithubAPI>();
|
final GithubAPI _githubAPI = locator<GithubAPI>();
|
||||||
|
final RevancedAPI _revancedAPI = locator<RevancedAPI>();
|
||||||
final Toast _toast = locator<Toast>();
|
final Toast _toast = locator<Toast>();
|
||||||
final flutterLocalNotificationsPlugin = FlutterLocalNotificationsPlugin();
|
final flutterLocalNotificationsPlugin = FlutterLocalNotificationsPlugin();
|
||||||
DateTime? _lastUpdate;
|
DateTime? _lastUpdate;
|
||||||
bool showUpdatableApps = false;
|
bool showUpdatableApps = false;
|
||||||
List<PatchedApplication> patchedInstalledApps = [];
|
List<PatchedApplication> patchedInstalledApps = [];
|
||||||
List<PatchedApplication> patchedUpdatableApps = [];
|
List<PatchedApplication> patchedUpdatableApps = [];
|
||||||
|
String _managerVersion = '';
|
||||||
|
|
||||||
Future<void> initialize(BuildContext context) async {
|
Future<void> initialize(BuildContext context) async {
|
||||||
|
_managerVersion = await AboutInfo.getInfo().then(
|
||||||
|
(value) => value.keys.contains('version') ? value['version']! : '',
|
||||||
|
);
|
||||||
|
_managerVersion = await _managerAPI.getCurrentManagerVersion();
|
||||||
await flutterLocalNotificationsPlugin.initialize(
|
await flutterLocalNotificationsPlugin.initialize(
|
||||||
const InitializationSettings(
|
const InitializationSettings(
|
||||||
android: AndroidInitializationSettings('ic_notification'),
|
android: AndroidInitializationSettings('ic_notification'),
|
||||||
),
|
),
|
||||||
onSelectNotification: (p) =>
|
onDidReceiveNotificationResponse: (response) async {
|
||||||
DeviceApps.openApp('app.revanced.manager.flutter'),
|
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
|
flutterLocalNotificationsPlugin
|
||||||
.resolvePlatformSpecificImplementation<
|
.resolvePlatformSpecificImplementation<
|
||||||
@ -52,6 +70,17 @@ class HomeViewModel extends BaseViewModel {
|
|||||||
if (!isConnected) {
|
if (!isConnected) {
|
||||||
_toast.showBottom('homeView.noConnection');
|
_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();
|
_getPatchedApps();
|
||||||
_managerAPI.reAssessSavedApps().then((_) => _getPatchedApps());
|
_managerAPI.reAssessSavedApps().then((_) => _getPatchedApps());
|
||||||
}
|
}
|
||||||
@ -87,13 +116,28 @@ class HomeViewModel extends BaseViewModel {
|
|||||||
|
|
||||||
Future<bool> hasManagerUpdates() async {
|
Future<bool> hasManagerUpdates() async {
|
||||||
final String? latestVersion = await _managerAPI.getLatestManagerVersion();
|
final String? latestVersion = await _managerAPI.getLatestManagerVersion();
|
||||||
final String currentVersion = await _managerAPI.getCurrentManagerVersion();
|
String currentVersion = await _managerAPI.getCurrentManagerVersion();
|
||||||
|
|
||||||
|
// add v to current version
|
||||||
|
if (!currentVersion.startsWith('v')) {
|
||||||
|
currentVersion = 'v$currentVersion';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (latestVersion != currentVersion) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<bool> hasPatchesUpdates() async {
|
||||||
|
final String? latestVersion = await _managerAPI.getLatestPatchesVersion();
|
||||||
|
final String? currentVersion = await _managerAPI.getCurrentPatchesVersion();
|
||||||
if (latestVersion != null) {
|
if (latestVersion != null) {
|
||||||
try {
|
try {
|
||||||
final int latestVersionInt =
|
final int latestVersionInt =
|
||||||
int.parse(latestVersion.replaceAll(RegExp('[^0-9]'), ''));
|
int.parse(latestVersion.replaceAll(RegExp('[^0-9]'), ''));
|
||||||
final int currentVersionInt =
|
final int currentVersionInt =
|
||||||
int.parse(currentVersion.replaceAll(RegExp('[^0-9]'), ''));
|
int.parse(currentVersion!.replaceAll(RegExp('[^0-9]'), ''));
|
||||||
return latestVersionInt > currentVersionInt;
|
return latestVersionInt > currentVersionInt;
|
||||||
} on Exception catch (e) {
|
} on Exception catch (e) {
|
||||||
if (kDebugMode) {
|
if (kDebugMode) {
|
||||||
@ -105,35 +149,115 @@ class HomeViewModel extends BaseViewModel {
|
|||||||
return false;
|
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 {
|
Future<void> updateManager(BuildContext context) async {
|
||||||
try {
|
try {
|
||||||
_toast.showBottom('homeView.downloadingMessage');
|
_toast.showBottom('homeView.downloadingMessage');
|
||||||
final File? managerApk = await _managerAPI.downloadManager();
|
showDialog(
|
||||||
if (managerApk != null) {
|
context: context,
|
||||||
await flutterLocalNotificationsPlugin.zonedSchedule(
|
builder: (context) => SimpleDialog(
|
||||||
0,
|
contentPadding: const EdgeInsets.all(16.0),
|
||||||
FlutterI18n.translate(
|
title: I18nText(
|
||||||
context,
|
'homeView.downloadingMessage',
|
||||||
'homeView.notificationTitle',
|
child: Text(
|
||||||
),
|
'',
|
||||||
FlutterI18n.translate(
|
style: TextStyle(
|
||||||
context,
|
fontSize: 20,
|
||||||
'homeView.notificationText',
|
fontWeight: FontWeight.w500,
|
||||||
),
|
color: Theme.of(context).colorScheme.secondary,
|
||||||
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,
|
children: [
|
||||||
uiLocalNotificationDateInterpretation:
|
Column(
|
||||||
UILocalNotificationDateInterpretation.absoluteTime,
|
children: [
|
||||||
);
|
Row(
|
||||||
|
children: [
|
||||||
|
Icon(
|
||||||
|
Icons.new_releases_outlined,
|
||||||
|
color: Theme.of(context).colorScheme.secondary,
|
||||||
|
),
|
||||||
|
const SizedBox(width: 8.0),
|
||||||
|
Text(
|
||||||
|
'v$_managerVersion',
|
||||||
|
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');
|
_toast.showBottom('homeView.installingMessage');
|
||||||
await AppInstaller.installApk(managerApk.path);
|
await AppInstaller.installApk(managerApk.path);
|
||||||
} else {
|
} else {
|
||||||
@ -151,7 +275,9 @@ class HomeViewModel extends BaseViewModel {
|
|||||||
_toast.showBottom('homeView.updatesDisabled');
|
_toast.showBottom('homeView.updatesDisabled');
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> showUpdateConfirmationDialog(BuildContext parentContext) {
|
Future<void> showUpdateConfirmationDialog(
|
||||||
|
BuildContext parentContext,
|
||||||
|
) {
|
||||||
return showModalBottomSheet(
|
return showModalBottomSheet(
|
||||||
context: parentContext,
|
context: parentContext,
|
||||||
isScrollControlled: true,
|
isScrollControlled: true,
|
||||||
@ -180,6 +306,7 @@ class HomeViewModel extends BaseViewModel {
|
|||||||
_lastUpdate!.difference(DateTime.now()).inSeconds > 2) {
|
_lastUpdate!.difference(DateTime.now()).inSeconds > 2) {
|
||||||
_managerAPI.clearAllData();
|
_managerAPI.clearAllData();
|
||||||
}
|
}
|
||||||
|
_toast.showBottom('homeView.refreshSuccess');
|
||||||
initialize(context);
|
initialize(context);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -17,12 +17,14 @@ import 'package:stacked/stacked.dart';
|
|||||||
class NavigationViewModel extends IndexTrackingViewModel {
|
class NavigationViewModel extends IndexTrackingViewModel {
|
||||||
Future<void> initialize(BuildContext context) async {
|
Future<void> initialize(BuildContext context) async {
|
||||||
locator<Toast>().initialize(context);
|
locator<Toast>().initialize(context);
|
||||||
final SharedPreferences prefs = await SharedPreferences.getInstance();
|
final SharedPreferences prefs =
|
||||||
|
await SharedPreferences.getInstance();
|
||||||
if (prefs.getBool('permissionsRequested') == null) {
|
if (prefs.getBool('permissionsRequested') == null) {
|
||||||
await prefs.setBool('permissionsRequested', true);
|
await prefs.setBool('permissionsRequested', true);
|
||||||
RootAPI().hasRootPermissions().then(
|
RootAPI().hasRootPermissions().then(
|
||||||
(value) => Permission.requestInstallPackages.request().then(
|
(value) => Permission.requestInstallPackages.request().then(
|
||||||
(value) => Permission.ignoreBatteryOptimizations.request(),
|
(value) =>
|
||||||
|
Permission.ignoreBatteryOptimizations.request(),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -37,7 +39,8 @@ class NavigationViewModel extends IndexTrackingViewModel {
|
|||||||
SystemUiOverlayStyle(
|
SystemUiOverlayStyle(
|
||||||
systemNavigationBarColor: Colors.transparent,
|
systemNavigationBarColor: Colors.transparent,
|
||||||
systemNavigationBarIconBrightness:
|
systemNavigationBarIconBrightness:
|
||||||
DynamicTheme.of(context)!.theme.brightness == Brightness.light
|
DynamicTheme.of(context)!.theme.brightness ==
|
||||||
|
Brightness.light
|
||||||
? Brightness.dark
|
? Brightness.dark
|
||||||
: Brightness.light,
|
: Brightness.light,
|
||||||
),
|
),
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
// ignore_for_file: use_build_context_synchronously
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_i18n/flutter_i18n.dart';
|
import 'package:flutter_i18n/flutter_i18n.dart';
|
||||||
import 'package:injectable/injectable.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/manager_api.dart';
|
||||||
import 'package:revanced_manager/services/patcher_api.dart';
|
import 'package:revanced_manager/services/patcher_api.dart';
|
||||||
import 'package:revanced_manager/ui/widgets/shared/custom_material_button.dart';
|
import 'package:revanced_manager/ui/widgets/shared/custom_material_button.dart';
|
||||||
|
import 'package:revanced_manager/utils/about_info.dart';
|
||||||
import 'package:stacked/stacked.dart';
|
import 'package:stacked/stacked.dart';
|
||||||
import 'package:stacked_services/stacked_services.dart';
|
import 'package:stacked_services/stacked_services.dart';
|
||||||
|
|
||||||
@ -54,25 +57,25 @@ class PatcherViewModel extends BaseViewModel {
|
|||||||
final bool isValid = await isValidPatchConfig();
|
final bool isValid = await isValidPatchConfig();
|
||||||
if (context.mounted) {
|
if (context.mounted) {
|
||||||
if (isValid) {
|
if (isValid) {
|
||||||
navigateToInstaller();
|
showArmv7WarningDialog(context);
|
||||||
} else {
|
} else {
|
||||||
return showDialog(
|
return showDialog(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (context) => AlertDialog(
|
builder: (context) => AlertDialog(
|
||||||
title: I18nText('warning'),
|
title: I18nText('warning'),
|
||||||
backgroundColor: Theme.of(context).colorScheme.secondaryContainer,
|
backgroundColor: Theme.of(context).colorScheme.secondaryContainer,
|
||||||
content: I18nText('patcherView.patchDialogText'),
|
content: I18nText('patcherView.armv7WarningDialogText'),
|
||||||
actions: <Widget>[
|
actions: <Widget>[
|
||||||
CustomMaterialButton(
|
CustomMaterialButton(
|
||||||
isFilled: false,
|
|
||||||
label: I18nText('noButton'),
|
label: I18nText('noButton'),
|
||||||
onPressed: () => Navigator.of(context).pop(),
|
onPressed: () => Navigator.of(context).pop(),
|
||||||
),
|
),
|
||||||
CustomMaterialButton(
|
CustomMaterialButton(
|
||||||
label: I18nText('yesButton'),
|
label: I18nText('yesButton'),
|
||||||
|
isFilled: false,
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
Navigator.of(context).pop();
|
Navigator.of(context).pop();
|
||||||
navigateToInstaller();
|
showArmv7WarningDialog(context);
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
@ -82,6 +85,42 @@ class PatcherViewModel extends BaseViewModel {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> showArmv7WarningDialog(BuildContext context) async {
|
||||||
|
final bool armv7 = await AboutInfo.getInfo().then(
|
||||||
|
(info) =>
|
||||||
|
info['arch'] != null &&
|
||||||
|
info['arch']!.contains('armeabi-v7a') &&
|
||||||
|
!info['arch']!.contains('arm64-v8a'),
|
||||||
|
);
|
||||||
|
|
||||||
|
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 getAppSelectionString() {
|
||||||
String text = '${selectedApp!.name} (${selectedApp!.packageName})';
|
String text = '${selectedApp!.name} (${selectedApp!.packageName})';
|
||||||
if (text.length > 32) {
|
if (text.length > 32) {
|
||||||
@ -90,24 +129,24 @@ class PatcherViewModel extends BaseViewModel {
|
|||||||
return text;
|
return text;
|
||||||
}
|
}
|
||||||
|
|
||||||
String getRecommendedVersionString(BuildContext context) {
|
String getSuggestedVersionString(BuildContext context) {
|
||||||
String recommendedVersion =
|
String suggestedVersion =
|
||||||
_patcherAPI.getRecommendedVersion(selectedApp!.packageName);
|
_patcherAPI.getSuggestedVersion(selectedApp!.packageName);
|
||||||
if (recommendedVersion.isEmpty) {
|
if (suggestedVersion.isEmpty) {
|
||||||
recommendedVersion = FlutterI18n.translate(
|
suggestedVersion = FlutterI18n.translate(
|
||||||
context,
|
context,
|
||||||
'appSelectorCard.anyVersion',
|
'appSelectorCard.anyVersion',
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
recommendedVersion = 'v$recommendedVersion';
|
suggestedVersion = 'v$suggestedVersion';
|
||||||
}
|
}
|
||||||
return '${FlutterI18n.translate(
|
return '${FlutterI18n.translate(
|
||||||
context,
|
context,
|
||||||
'appSelectorCard.currentVersion',
|
'appSelectorCard.currentVersion',
|
||||||
)}: v${selectedApp!.version}\n${FlutterI18n.translate(
|
)}: v${selectedApp!.version}\n${FlutterI18n.translate(
|
||||||
context,
|
context,
|
||||||
'appSelectorCard.recommendedVersion',
|
'appSelectorCard.suggestedVersion',
|
||||||
)}: $recommendedVersion';
|
)}: $suggestedVersion';
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> loadLastSelectedPatches() async {
|
Future<void> loadLastSelectedPatches() async {
|
||||||
|
@ -4,6 +4,7 @@ import 'package:revanced_manager/ui/views/patches_selector/patches_selector_view
|
|||||||
import 'package:revanced_manager/ui/widgets/patchesSelectorView/patch_item.dart';
|
import 'package:revanced_manager/ui/widgets/patchesSelectorView/patch_item.dart';
|
||||||
import 'package:revanced_manager/ui/widgets/shared/custom_popup_menu.dart';
|
import 'package:revanced_manager/ui/widgets/shared/custom_popup_menu.dart';
|
||||||
import 'package:revanced_manager/ui/widgets/shared/search_bar.dart';
|
import 'package:revanced_manager/ui/widgets/shared/search_bar.dart';
|
||||||
|
import 'package:revanced_manager/utils/check_for_supported_patch.dart';
|
||||||
import 'package:stacked/stacked.dart';
|
import 'package:stacked/stacked.dart';
|
||||||
|
|
||||||
class PatchesSelectorView extends StatefulWidget {
|
class PatchesSelectorView extends StatefulWidget {
|
||||||
@ -135,25 +136,13 @@ class _PatchesSelectorViewState extends State<PatchesSelectorView> {
|
|||||||
Row(
|
Row(
|
||||||
children: [
|
children: [
|
||||||
ActionChip(
|
ActionChip(
|
||||||
label: I18nText('patchesSelectorView.recommended'),
|
label: I18nText('patchesSelectorView.default'),
|
||||||
tooltip: FlutterI18n.translate(
|
tooltip: FlutterI18n.translate(
|
||||||
context,
|
context,
|
||||||
'patchesSelectorView.recommendedTooltip',
|
'patchesSelectorView.defaultTooltip',
|
||||||
),
|
),
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
model.selectRecommendedPatches();
|
model.selectDefaultPatches();
|
||||||
},
|
|
||||||
),
|
|
||||||
const SizedBox(width: 8),
|
|
||||||
ActionChip(
|
|
||||||
label: I18nText('patchesSelectorView.all'),
|
|
||||||
tooltip: FlutterI18n.translate(
|
|
||||||
context,
|
|
||||||
'patchesSelectorView.allTooltip',
|
|
||||||
),
|
|
||||||
onPressed: () {
|
|
||||||
model.selectAllPatcherWarning(context);
|
|
||||||
model.selectAllPatches(true);
|
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
const SizedBox(width: 8),
|
const SizedBox(width: 8),
|
||||||
@ -180,7 +169,7 @@ class _PatchesSelectorViewState extends State<PatchesSelectorView> {
|
|||||||
packageVersion: model.getAppVersion(),
|
packageVersion: model.getAppVersion(),
|
||||||
supportedPackageVersions:
|
supportedPackageVersions:
|
||||||
model.getSupportedVersions(patch),
|
model.getSupportedVersions(patch),
|
||||||
isUnsupported: !model.isPatchSupported(patch),
|
isUnsupported: !isPatchSupported(patch),
|
||||||
isSelected: model.isSelected(patch),
|
isSelected: model.isSelected(patch),
|
||||||
onChanged: (value) =>
|
onChanged: (value) =>
|
||||||
model.selectPatch(patch, value),
|
model.selectPatch(patch, value),
|
||||||
|
@ -1,6 +1,4 @@
|
|||||||
import 'package:collection/collection.dart';
|
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/app/app.locator.dart';
|
||||||
import 'package:revanced_manager/models/patch.dart';
|
import 'package:revanced_manager/models/patch.dart';
|
||||||
import 'package:revanced_manager/models/patched_application.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/patcher_api.dart';
|
||||||
import 'package:revanced_manager/services/toast.dart';
|
import 'package:revanced_manager/services/toast.dart';
|
||||||
import 'package:revanced_manager/ui/views/patcher/patcher_viewmodel.dart';
|
import 'package:revanced_manager/ui/views/patcher/patcher_viewmodel.dart';
|
||||||
import 'package:revanced_manager/ui/widgets/shared/custom_material_button.dart';
|
import 'package:revanced_manager/utils/check_for_supported_patch.dart';
|
||||||
import 'package:stacked/stacked.dart';
|
import 'package:stacked/stacked.dart';
|
||||||
|
|
||||||
class PatchesSelectorViewModel extends BaseViewModel {
|
class PatchesSelectorViewModel extends BaseViewModel {
|
||||||
@ -50,39 +48,7 @@ class PatchesSelectorViewModel extends BaseViewModel {
|
|||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> selectAllPatcherWarning(BuildContext context) {
|
void selectDefaultPatches() {
|
||||||
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() {
|
|
||||||
selectedPatches.clear();
|
selectedPatches.clear();
|
||||||
|
|
||||||
if (_managerAPI.areExperimentalPatchesEnabled() == false) {
|
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) {
|
void onMenuSelection(value) {
|
||||||
switch (value) {
|
switch (value) {
|
||||||
case 0:
|
case 0:
|
||||||
|
@ -4,6 +4,7 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:flutter_i18n/flutter_i18n.dart';
|
import 'package:flutter_i18n/flutter_i18n.dart';
|
||||||
import 'package:revanced_manager/app/app.locator.dart';
|
import 'package:revanced_manager/app/app.locator.dart';
|
||||||
import 'package:revanced_manager/services/manager_api.dart';
|
import 'package:revanced_manager/services/manager_api.dart';
|
||||||
|
import 'package:revanced_manager/services/toast.dart';
|
||||||
import 'package:revanced_manager/ui/widgets/settingsView/custom_text_field.dart';
|
import 'package:revanced_manager/ui/widgets/settingsView/custom_text_field.dart';
|
||||||
import 'package:revanced_manager/ui/widgets/settingsView/settings_tile_dialog.dart';
|
import 'package:revanced_manager/ui/widgets/settingsView/settings_tile_dialog.dart';
|
||||||
import 'package:revanced_manager/ui/widgets/shared/custom_material_button.dart';
|
import 'package:revanced_manager/ui/widgets/shared/custom_material_button.dart';
|
||||||
@ -11,6 +12,7 @@ import 'package:stacked/stacked.dart';
|
|||||||
|
|
||||||
class SManageApiUrl extends BaseViewModel {
|
class SManageApiUrl extends BaseViewModel {
|
||||||
final ManagerAPI _managerAPI = locator<ManagerAPI>();
|
final ManagerAPI _managerAPI = locator<ManagerAPI>();
|
||||||
|
final Toast _toast = locator<Toast>();
|
||||||
|
|
||||||
final TextEditingController _apiUrlController = TextEditingController();
|
final TextEditingController _apiUrlController = TextEditingController();
|
||||||
|
|
||||||
@ -90,7 +92,7 @@ class SManageApiUrl extends BaseViewModel {
|
|||||||
label: I18nText('yesButton'),
|
label: I18nText('yesButton'),
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
_managerAPI.setApiUrl('');
|
_managerAPI.setApiUrl('');
|
||||||
Navigator.of(context).pop();
|
_toast.showBottom('settingsView.restartAppForChanges');
|
||||||
Navigator.of(context).pop();
|
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:flutter_i18n/flutter_i18n.dart';
|
||||||
import 'package:revanced_manager/app/app.locator.dart';
|
import 'package:revanced_manager/app/app.locator.dart';
|
||||||
import 'package:revanced_manager/services/manager_api.dart';
|
import 'package:revanced_manager/services/manager_api.dart';
|
||||||
|
import 'package:revanced_manager/services/toast.dart';
|
||||||
import 'package:revanced_manager/ui/widgets/settingsView/custom_text_field.dart';
|
import 'package:revanced_manager/ui/widgets/settingsView/custom_text_field.dart';
|
||||||
import 'package:revanced_manager/ui/widgets/settingsView/settings_tile_dialog.dart';
|
import 'package:revanced_manager/ui/widgets/settingsView/settings_tile_dialog.dart';
|
||||||
import 'package:revanced_manager/ui/widgets/shared/custom_material_button.dart';
|
import 'package:revanced_manager/ui/widgets/shared/custom_material_button.dart';
|
||||||
@ -11,6 +12,7 @@ import 'package:stacked/stacked.dart';
|
|||||||
|
|
||||||
class SManageSources extends BaseViewModel {
|
class SManageSources extends BaseViewModel {
|
||||||
final ManagerAPI _managerAPI = locator<ManagerAPI>();
|
final ManagerAPI _managerAPI = locator<ManagerAPI>();
|
||||||
|
final Toast _toast = locator<Toast>();
|
||||||
|
|
||||||
final TextEditingController _hostSourceController = TextEditingController();
|
final TextEditingController _hostSourceController = TextEditingController();
|
||||||
final TextEditingController _orgPatSourceController = TextEditingController();
|
final TextEditingController _orgPatSourceController = TextEditingController();
|
||||||
@ -117,13 +119,14 @@ class SManageSources extends BaseViewModel {
|
|||||||
CustomMaterialButton(
|
CustomMaterialButton(
|
||||||
label: I18nText('okButton'),
|
label: I18nText('okButton'),
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
_managerAPI.setRepoUrl(_hostSourceController.text);
|
_managerAPI.setRepoUrl(_hostSourceController.text.trim());
|
||||||
_managerAPI.setPatchesRepo(
|
_managerAPI.setPatchesRepo(
|
||||||
'${_orgPatSourceController.text}/${_patSourceController.text}',
|
'${_orgPatSourceController.text.trim()}/${_patSourceController.text.trim()}',
|
||||||
);
|
);
|
||||||
_managerAPI.setIntegrationsRepo(
|
_managerAPI.setIntegrationsRepo(
|
||||||
'${_orgIntSourceController.text}/${_intSourceController.text}',
|
'${_orgIntSourceController.text.trim()}/${_intSourceController.text.trim()}',
|
||||||
);
|
);
|
||||||
|
_toast.showBottom('settingsView.restartAppForChanges');
|
||||||
Navigator.of(context).pop();
|
Navigator.of(context).pop();
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
@ -151,7 +154,10 @@ class SManageSources extends BaseViewModel {
|
|||||||
_managerAPI.setRepoUrl('');
|
_managerAPI.setRepoUrl('');
|
||||||
_managerAPI.setPatchesRepo('');
|
_managerAPI.setPatchesRepo('');
|
||||||
_managerAPI.setIntegrationsRepo('');
|
_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';
|
import 'package:stacked_services/stacked_services.dart';
|
||||||
|
|
||||||
class SettingsViewModel extends BaseViewModel {
|
class SettingsViewModel extends BaseViewModel {
|
||||||
final NavigationService _navigationService = locator<NavigationService>();
|
final NavigationService _navigationService =
|
||||||
|
locator<NavigationService>();
|
||||||
final ManagerAPI _managerAPI = locator<ManagerAPI>();
|
final ManagerAPI _managerAPI = locator<ManagerAPI>();
|
||||||
final Toast _toast = locator<Toast>();
|
final Toast _toast = locator<Toast>();
|
||||||
|
|
||||||
@ -62,8 +63,11 @@ class SettingsViewModel extends BaseViewModel {
|
|||||||
try {
|
try {
|
||||||
final File outFile = File(_managerAPI.storedPatchesFile);
|
final File outFile = File(_managerAPI.storedPatchesFile);
|
||||||
if (outFile.existsSync()) {
|
if (outFile.existsSync()) {
|
||||||
final String dateTime =
|
final String dateTime = DateTime.now()
|
||||||
DateTime.now().toString().replaceAll(' ', '_').split('.').first;
|
.toString()
|
||||||
|
.replaceAll(' ', '_')
|
||||||
|
.split('.')
|
||||||
|
.first;
|
||||||
await CRFileSaver.saveFileWithDialog(
|
await CRFileSaver.saveFileWithDialog(
|
||||||
SaveFileDialogParams(
|
SaveFileDialogParams(
|
||||||
sourceFilePath: outFile.path,
|
sourceFilePath: outFile.path,
|
||||||
@ -83,7 +87,8 @@ class SettingsViewModel extends BaseViewModel {
|
|||||||
|
|
||||||
Future<void> importPatches() async {
|
Future<void> importPatches() async {
|
||||||
try {
|
try {
|
||||||
final FilePickerResult? result = await FilePicker.platform.pickFiles(
|
final FilePickerResult? result =
|
||||||
|
await FilePicker.platform.pickFiles(
|
||||||
type: FileType.custom,
|
type: FileType.custom,
|
||||||
allowedExtensions: ['json'],
|
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() {
|
void resetSelectedPatches() {
|
||||||
_managerAPI.resetLastSelectedPatches();
|
_managerAPI.resetLastSelectedPatches();
|
||||||
_toast.showBottom('settingsView.resetStoredPatches');
|
_toast.showBottom('settingsView.resetStoredPatches');
|
||||||
@ -111,7 +156,7 @@ class SettingsViewModel extends BaseViewModel {
|
|||||||
|
|
||||||
Future<int> getSdkVersion() async {
|
Future<int> getSdkVersion() async {
|
||||||
final AndroidDeviceInfo info = await DeviceInfoPlugin().androidInfo;
|
final AndroidDeviceInfo info = await DeviceInfoPlugin().androidInfo;
|
||||||
return info.version.sdkInt ?? -1;
|
return info.version.sdkInt;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> deleteLogs() async {
|
Future<void> deleteLogs() async {
|
||||||
|
@ -9,12 +9,14 @@ class InstalledAppItem extends StatefulWidget {
|
|||||||
required this.pkgName,
|
required this.pkgName,
|
||||||
required this.icon,
|
required this.icon,
|
||||||
required this.patchesCount,
|
required this.patchesCount,
|
||||||
|
required this.suggestedVersion,
|
||||||
this.onTap,
|
this.onTap,
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
final String name;
|
final String name;
|
||||||
final String pkgName;
|
final String pkgName;
|
||||||
final Uint8List icon;
|
final Uint8List icon;
|
||||||
final int patchesCount;
|
final int patchesCount;
|
||||||
|
final String suggestedVersion;
|
||||||
final Function()? onTap;
|
final Function()? onTap;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -46,31 +48,35 @@ class _InstalledAppItemState extends State<InstalledAppItem> {
|
|||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
|
Text(
|
||||||
|
widget.name,
|
||||||
|
maxLines: 2,
|
||||||
|
overflow: TextOverflow.visible,
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize: 16,
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 4),
|
||||||
|
Text(widget.pkgName),
|
||||||
Row(
|
Row(
|
||||||
children: <Widget>[
|
children: [
|
||||||
Text(
|
Text(
|
||||||
widget.name,
|
widget.suggestedVersion.isEmpty
|
||||||
maxLines: 2,
|
? 'All versions'
|
||||||
overflow: TextOverflow.visible,
|
: widget.suggestedVersion,
|
||||||
style: const TextStyle(
|
|
||||||
fontSize: 16,
|
|
||||||
fontWeight: FontWeight.w500,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
const SizedBox(width: 6),
|
const SizedBox(width: 4),
|
||||||
Text(
|
Text(
|
||||||
widget.patchesCount == 1
|
widget.patchesCount == 1
|
||||||
? '${widget.patchesCount} patch'
|
? '• ${widget.patchesCount} patch'
|
||||||
: '${widget.patchesCount} patches',
|
: '• ${widget.patchesCount} patches',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 8,
|
|
||||||
color: Theme.of(context).colorScheme.secondary,
|
color: Theme.of(context).colorScheme.secondary,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
const SizedBox(height: 4),
|
|
||||||
Text(widget.pkgName),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
85
lib/ui/widgets/appSelectorView/not_installed_app_item.dart
Normal file
85
lib/ui/widgets/appSelectorView/not_installed_app_item.dart
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
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(
|
||||||
|
widget.suggestedVersion.isEmpty
|
||||||
|
? 'All versions'
|
||||||
|
: 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(
|
Uri.parse(
|
||||||
widget.contributors[index]['html_url'],
|
widget.contributors[index]['html_url'],
|
||||||
),
|
),
|
||||||
|
mode: LaunchMode.externalApplication,
|
||||||
),
|
),
|
||||||
child: FutureBuilder<File?>(
|
child: FutureBuilder<File?>(
|
||||||
future: DefaultCacheManager().getSingleFile(
|
future: DefaultCacheManager().getSingleFile(
|
||||||
|
@ -8,9 +8,11 @@ import 'package:revanced_manager/ui/widgets/shared/custom_material_button.dart';
|
|||||||
class LatestCommitCard extends StatefulWidget {
|
class LatestCommitCard extends StatefulWidget {
|
||||||
const LatestCommitCard({
|
const LatestCommitCard({
|
||||||
Key? key,
|
Key? key,
|
||||||
required this.onPressed,
|
required this.onPressedManager,
|
||||||
|
required this.onPressedPatches,
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
final Function() onPressed;
|
final Function() onPressedManager;
|
||||||
|
final Function() onPressedPatches;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<LatestCommitCard> createState() => _LatestCommitCardState();
|
State<LatestCommitCard> createState() => _LatestCommitCardState();
|
||||||
@ -21,66 +23,109 @@ class _LatestCommitCardState extends State<LatestCommitCard> {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return CustomCard(
|
return Column(
|
||||||
child: Row(
|
children: [
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
// ReVanced Manager
|
||||||
children: <Widget>[
|
CustomCard(
|
||||||
Column(
|
child: Row(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
Row(
|
Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
I18nText('latestCommitCard.patcherLabel'),
|
Row(
|
||||||
FutureBuilder<String?>(
|
children: const <Widget>[
|
||||||
future: model.getLatestPatcherReleaseTime(),
|
Text('ReVanced Manager'),
|
||||||
builder: (context, snapshot) => Text(
|
],
|
||||||
snapshot.hasData && snapshot.data!.isNotEmpty
|
),
|
||||||
? FlutterI18n.translate(
|
const SizedBox(height: 4),
|
||||||
context,
|
Row(
|
||||||
'latestCommitCard.timeagoLabel',
|
children: <Widget>[
|
||||||
translationParams: {'time': snapshot.data!},
|
FutureBuilder<String?>(
|
||||||
)
|
future: model.getLatestManagerReleaseTime(),
|
||||||
: FlutterI18n.translate(
|
builder: (context, snapshot) =>
|
||||||
context,
|
snapshot.hasData && snapshot.data!.isNotEmpty
|
||||||
'latestCommitCard.loadingLabel',
|
? I18nText(
|
||||||
),
|
'latestCommitCard.timeagoLabel',
|
||||||
),
|
translationParams: {'time': snapshot.data!},
|
||||||
|
)
|
||||||
|
: I18nText('latestCommitCard.loadingLabel'),
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
const SizedBox(height: 4),
|
FutureBuilder<bool>(
|
||||||
Row(
|
future: model.hasManagerUpdates(),
|
||||||
children: <Widget>[
|
initialData: false,
|
||||||
I18nText('latestCommitCard.managerLabel'),
|
builder: (context, snapshot) => Opacity(
|
||||||
FutureBuilder<String?>(
|
opacity: snapshot.hasData && snapshot.data! ? 1.0 : 0.25,
|
||||||
future: model.getLatestManagerReleaseTime(),
|
child: CustomMaterialButton(
|
||||||
builder: (context, snapshot) =>
|
label: I18nText('updateButton'),
|
||||||
snapshot.hasData && snapshot.data!.isNotEmpty
|
onPressed: snapshot.hasData && snapshot.data!
|
||||||
? I18nText(
|
? widget.onPressedManager
|
||||||
'latestCommitCard.timeagoLabel',
|
: () => {},
|
||||||
translationParams: {'time': snapshot.data!},
|
|
||||||
)
|
|
||||||
: I18nText('latestCommitCard.loadingLabel'),
|
|
||||||
),
|
),
|
||||||
],
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
FutureBuilder<bool>(
|
),
|
||||||
future: locator<HomeViewModel>().hasManagerUpdates(),
|
|
||||||
initialData: false,
|
const SizedBox(height: 16),
|
||||||
builder: (context, snapshot) => Opacity(
|
|
||||||
opacity: snapshot.hasData && snapshot.data! ? 1.0 : 0.25,
|
// ReVanced Patches
|
||||||
child: CustomMaterialButton(
|
CustomCard(
|
||||||
label: I18nText('latestCommitCard.updateButton'),
|
child: Row(
|
||||||
onPressed: snapshot.hasData && snapshot.data!
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
? widget.onPressed
|
children: <Widget>[
|
||||||
: () => {},
|
Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: <Widget>[
|
||||||
|
Row(
|
||||||
|
children: const <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,
|
expand: false,
|
||||||
snap: true,
|
snap: true,
|
||||||
snapSizes: const [0.5],
|
snapSizes: const [0.5],
|
||||||
builder: (context, scrollController) => SingleChildScrollView(
|
builder: (_, scrollController) => SingleChildScrollView(
|
||||||
controller: scrollController,
|
controller: scrollController,
|
||||||
child: SafeArea(
|
child: SafeArea(
|
||||||
child: FutureBuilder<Map<String, dynamic>?>(
|
child: FutureBuilder<Map<String, dynamic>?>(
|
||||||
future: model.getLatestManagerRelease(),
|
future: model.getLatestManagerRelease(),
|
||||||
builder: (context, snapshot) {
|
builder: (_, snapshot) {
|
||||||
if (!snapshot.hasData) {
|
if (!snapshot.hasData) {
|
||||||
return const SizedBox(
|
return const SizedBox(
|
||||||
height: 300,
|
height: 300,
|
||||||
@ -45,7 +45,8 @@ class UpdateConfirmationDialog extends StatelessWidget {
|
|||||||
children: [
|
children: [
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment:
|
||||||
|
CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
I18nText(
|
I18nText(
|
||||||
'homeView.updateDialogTitle',
|
'homeView.updateDialogTitle',
|
||||||
@ -62,12 +63,14 @@ class UpdateConfirmationDialog extends StatelessWidget {
|
|||||||
children: [
|
children: [
|
||||||
Icon(
|
Icon(
|
||||||
Icons.new_releases_outlined,
|
Icons.new_releases_outlined,
|
||||||
color:
|
color: Theme.of(context)
|
||||||
Theme.of(context).colorScheme.secondary,
|
.colorScheme
|
||||||
|
.secondary,
|
||||||
),
|
),
|
||||||
const SizedBox(width: 8.0),
|
const SizedBox(width: 8.0),
|
||||||
Text(
|
Text(
|
||||||
snapshot.data!['tag_name'] ?? 'Unknown',
|
snapshot.data!['tag_name'] ??
|
||||||
|
'Unknown',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 20,
|
fontSize: 20,
|
||||||
fontWeight: FontWeight.w500,
|
fontWeight: FontWeight.w500,
|
||||||
@ -93,7 +96,8 @@ class UpdateConfirmationDialog extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.only(left: 24.0, bottom: 12.0),
|
padding:
|
||||||
|
const EdgeInsets.only(left: 24.0, bottom: 12.0),
|
||||||
child: I18nText(
|
child: I18nText(
|
||||||
'homeView.updateChangelogTitle',
|
'homeView.updateChangelogTitle',
|
||||||
child: Text(
|
child: Text(
|
||||||
@ -109,9 +113,12 @@ class UpdateConfirmationDialog extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
Container(
|
Container(
|
||||||
margin: const EdgeInsets.symmetric(horizontal: 24.0),
|
margin:
|
||||||
|
const EdgeInsets.symmetric(horizontal: 24.0),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: Theme.of(context).colorScheme.secondaryContainer,
|
color: Theme.of(context)
|
||||||
|
.colorScheme
|
||||||
|
.secondaryContainer,
|
||||||
borderRadius: BorderRadius.circular(12.0),
|
borderRadius: BorderRadius.circular(12.0),
|
||||||
),
|
),
|
||||||
child: Markdown(
|
child: Markdown(
|
||||||
|
@ -63,7 +63,7 @@ class AppSelectorCard extends StatelessWidget {
|
|||||||
const SizedBox(height: 4),
|
const SizedBox(height: 4),
|
||||||
Text(
|
Text(
|
||||||
locator<PatcherViewModel>()
|
locator<PatcherViewModel>()
|
||||||
.getRecommendedVersionString(context),
|
.getSuggestedVersionString(context),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
@ -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_patches.dart';
|
||||||
import 'package:revanced_manager/ui/widgets/settingsView/settings_experimental_universal_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/settingsView/settings_section.dart';
|
||||||
|
import 'package:revanced_manager/ui/widgets/shared/custom_material_button.dart';
|
||||||
|
|
||||||
final _settingsViewModel = SettingsViewModel();
|
final _settingsViewModel = SettingsViewModel();
|
||||||
|
|
||||||
@ -21,6 +22,7 @@ class SAdvancedSection extends StatelessWidget {
|
|||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
SManageApiUrlUI(),
|
SManageApiUrlUI(),
|
||||||
SManageSourcesUI(),
|
SManageSourcesUI(),
|
||||||
|
// SManageKeystorePasswordUI(),
|
||||||
SExperimentalUniversalPatches(),
|
SExperimentalUniversalPatches(),
|
||||||
SExperimentalPatches(),
|
SExperimentalPatches(),
|
||||||
ListTile(
|
ListTile(
|
||||||
@ -36,7 +38,7 @@ class SAdvancedSection extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
subtitle: I18nText('settingsView.deleteKeystoreHint'),
|
subtitle: I18nText('settingsView.deleteKeystoreHint'),
|
||||||
onTap: () => _settingsViewModel.deleteKeystore,
|
onTap: () => _showDeleteKeystoreDialog(context),
|
||||||
),
|
),
|
||||||
ListTile(
|
ListTile(
|
||||||
contentPadding: const EdgeInsets.symmetric(horizontal: 20.0),
|
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/material.dart';
|
||||||
import 'package:flutter_i18n/widgets/I18nText.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/views/settings/settings_viewmodel.dart';
|
||||||
import 'package:revanced_manager/ui/widgets/settingsView/settings_section.dart';
|
import 'package:revanced_manager/ui/widgets/settingsView/settings_section.dart';
|
||||||
|
|
||||||
@ -43,6 +44,40 @@ class SExportSection extends StatelessWidget {
|
|||||||
subtitle: I18nText('settingsView.importPatchesHint'),
|
subtitle: I18nText('settingsView.importPatchesHint'),
|
||||||
onTap: () => _settingsViewModel.importPatches(),
|
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(
|
ListTile(
|
||||||
contentPadding: const EdgeInsets.symmetric(horizontal: 20.0),
|
contentPadding: const EdgeInsets.symmetric(horizontal: 20.0),
|
||||||
title: I18nText(
|
title: I18nText(
|
||||||
|
@ -11,7 +11,7 @@ class AboutInfo {
|
|||||||
'flavor': kReleaseMode ? 'release' : 'debug',
|
'flavor': kReleaseMode ? 'release' : 'debug',
|
||||||
'model': info.model,
|
'model': info.model,
|
||||||
'androidVersion': info.version.release,
|
'androidVersion': info.version.release,
|
||||||
'arch': info.supportedAbis.first
|
'arch': info.supportedAbis
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
14
lib/utils/check_for_supported_patch.dart
Normal file
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
7179
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
10
package.json
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"
|
||||||
|
}
|
||||||
|
}
|
70
pubspec.yaml
70
pubspec.yaml
@ -4,86 +4,88 @@ homepage: https://github.com/revanced/revanced-manager
|
|||||||
|
|
||||||
publish_to: 'none'
|
publish_to: 'none'
|
||||||
|
|
||||||
version: 0.0.57+57
|
version: 1.0.0+100000000
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: ">=2.17.5 <3.0.0"
|
sdk: ">=2.17.5 <3.0.0"
|
||||||
|
|
||||||
dependencies:
|
dependencies:
|
||||||
animations: ^2.0.4
|
animations: ^2.0.7
|
||||||
app_installer: ^1.1.0
|
app_installer: ^1.1.0
|
||||||
collection: ^1.16.0
|
collection: ^1.17.0
|
||||||
cross_connectivity: ^3.0.5
|
cross_connectivity: ^3.0.5
|
||||||
cr_file_saver: ^0.0.2
|
cr_file_saver:
|
||||||
|
git:
|
||||||
|
url: https://github.com/dhruvanbhalara/cr_file_saver.git
|
||||||
|
ref: a08326ecb48f581b4b09e2e2665d31ed1704c7af
|
||||||
device_apps:
|
device_apps:
|
||||||
git:
|
git:
|
||||||
url: https://github.com/ponces/flutter_plugin_device_apps
|
url: https://github.com/ponces/flutter_plugin_device_apps
|
||||||
ref: revanced-manager
|
ref: revanced-manager
|
||||||
device_info_plus: ^4.1.2
|
device_info_plus: ^8.1.0
|
||||||
dio: ^4.0.6
|
dynamic_color: ^1.6.3
|
||||||
dio_brotli_transformer: ^1.0.1
|
dio: ^5.0.0
|
||||||
dio_http_cache_lts: ^0.4.1
|
|
||||||
dynamic_color: ^1.5.4
|
|
||||||
dynamic_themes: ^1.1.0
|
dynamic_themes: ^1.1.0
|
||||||
expandable: ^5.0.1
|
expandable: ^5.0.1
|
||||||
file_picker:
|
file_picker:
|
||||||
git:
|
git:
|
||||||
url: https://github.com/alexmercerind/flutter_file_picker
|
url: https://github.com/alexmercerind/flutter_file_picker
|
||||||
ref: master
|
ref: master
|
||||||
flex_color_scheme: ^6.0.0
|
flex_color_scheme: ^7.0.1
|
||||||
flutter:
|
flutter:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
flutter_background: ^1.1.0
|
flutter_background: ^1.2.0
|
||||||
flutter_cache_manager: ^3.3.0
|
flutter_cache_manager: ^3.3.0
|
||||||
flutter_i18n: ^0.32.4
|
flutter_i18n: ^0.32.4
|
||||||
flutter_local_notifications: ^9.8.0+1
|
flutter_local_notifications: ^13.0.0
|
||||||
flutter_localizations:
|
flutter_localizations:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
flutter_svg: ^1.1.1+1
|
flutter_svg: ^2.0.4
|
||||||
fluttertoast: ^8.0.9
|
fluttertoast: ^8.2.1
|
||||||
font_awesome_flutter: ^10.1.0
|
font_awesome_flutter: ^10.4.0
|
||||||
get_it: ^7.2.0
|
get_it: 7.2.0
|
||||||
google_fonts: ^3.0.1
|
google_fonts: ^4.0.3
|
||||||
http: ^0.13.4
|
http: ^0.13.5
|
||||||
injectable: ^1.5.3
|
injectable: ^2.1.1
|
||||||
intl: ^0.17.0
|
intl: ^0.17.0
|
||||||
json_annotation: ^4.6.0
|
json_annotation: ^4.8.0
|
||||||
logcat:
|
logcat:
|
||||||
git:
|
git:
|
||||||
url: https://github.com/SuaMusica/logcat
|
url: https://github.com/SuaMusica/logcat
|
||||||
ref: feature/nullSafe
|
ref: feature/nullSafe
|
||||||
native_dio_client: ^0.0.1-dev+1
|
package_info_plus: ^3.0.3
|
||||||
package_info_plus: ^1.4.3+1
|
path_provider: ^2.0.14
|
||||||
path_provider: ^2.0.11
|
permission_handler: ^10.2.0
|
||||||
permission_handler: ^10.0.0
|
native_dio_adapter: ^0.1.0
|
||||||
pull_to_refresh: ^2.0.0
|
pull_to_refresh: ^2.0.0
|
||||||
root:
|
root:
|
||||||
git:
|
git:
|
||||||
url: https://github.com/gokul1630/root
|
url: https://github.com/gokul1630/root
|
||||||
ref: main
|
ref: main
|
||||||
share_extend: ^2.0.0
|
share_extend: ^2.0.0
|
||||||
shared_preferences: ^2.0.15
|
shared_preferences: ^2.1.0
|
||||||
skeletons: ^0.0.3
|
skeletons: ^0.0.3
|
||||||
stacked: ^3.2.0
|
stacked: ^3.2.0
|
||||||
stacked_generator: ^1.0.0
|
stacked_generator: ^1.1.0
|
||||||
stacked_services: ^1.0.0
|
stacked_services: ^1.0.0
|
||||||
stacked_themes: ^0.3.9
|
stacked_themes: ^0.3.10
|
||||||
timeago: ^3.2.2
|
timeago: ^3.3.0
|
||||||
timezone: ^0.8.0
|
timezone: ^0.9.0
|
||||||
url_launcher: ^6.1.5
|
url_launcher: ^6.1.10
|
||||||
wakelock: ^0.6.2
|
wakelock: ^0.6.2
|
||||||
flutter_dotenv: ^5.0.2
|
flutter_dotenv: ^5.0.2
|
||||||
|
flutter_markdown: ^0.6.14
|
||||||
pub_release: ^8.0.3
|
pub_release: ^8.0.3
|
||||||
flutter_markdown: ^0.6.13
|
dio_cache_interceptor: ^3.4.0
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
json_serializable: ^6.3.1
|
json_serializable: ^6.6.1
|
||||||
build_runner: any
|
build_runner: any
|
||||||
flutter_launcher_icons: ^0.10.0
|
flutter_launcher_icons: ^0.13.0
|
||||||
flutter_lints: ^2.0.1
|
flutter_lints: ^2.0.1
|
||||||
flutter_test:
|
flutter_test:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
injectable_generator: ^1.5.4
|
injectable_generator: ^2.1.5
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user