Compare commits
113 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8af62b917c | ||
|
|
311f114132 | ||
|
|
d015bd03f7 | ||
|
|
a61b9de0fa | ||
|
|
ef1b283917 | ||
|
|
c677f00105 | ||
|
|
8d2f778dfe | ||
|
|
c549d102f6 | ||
|
|
39a9ee4e9d | ||
|
|
a27dc6ad1c | ||
|
|
8ccb75fc8d | ||
|
|
8fc86dbe02 | ||
|
|
359f052608 | ||
|
|
4150e2265c | ||
|
|
b803ce7435 | ||
|
|
289c6cd7a9 | ||
|
|
31fc7b74c2 | ||
|
|
3e565f25be | ||
|
|
e509be4e21 | ||
|
|
170fc537ac | ||
|
|
3fe5882145 | ||
|
|
a290791410 | ||
|
|
2ebd38ff68 | ||
|
|
cd987a5b19 | ||
|
|
7f1dab7ee1 | ||
|
|
bed3945aa5 | ||
|
|
15a32a18b7 | ||
|
|
d2e8e7dd5d | ||
|
|
ce12ec89c4 | ||
|
|
b286444ad9 | ||
|
|
941263102d | ||
|
|
e2ed296dc7 | ||
|
|
cfc866bef2 | ||
|
|
affba669ce | ||
|
|
7230152ab8 | ||
|
|
21fee7171f | ||
|
|
ad17995f28 | ||
|
|
ac830cbe7f | ||
|
|
65da6af3f9 | ||
|
|
3d90bf7588 | ||
|
|
62ef1c88fe | ||
|
|
d6918920b6 | ||
|
|
cdfb09fbfa | ||
|
|
bb681e31c9 | ||
|
|
c7483936ec | ||
|
|
0a1f2da33d | ||
|
|
f5aafdb7d6 | ||
|
|
c9adf1c492 | ||
|
|
4c9cb560e3 | ||
|
|
f0b028279c | ||
|
|
197770b68b | ||
|
|
37b583f560 | ||
|
|
dca2d4fe12 | ||
|
|
3b677f8ae3 | ||
|
|
0b952578d1 | ||
|
|
054afbbedd | ||
|
|
866a6e4a44 | ||
|
|
8ea7dd478b | ||
|
|
7839252934 | ||
|
|
fa4063220f | ||
|
|
d214a02abd | ||
|
|
d1c12edd1b | ||
|
|
ded1a44c37 | ||
|
|
790a6cd1e3 | ||
|
|
a79f883a0f | ||
|
|
d9c5a540a3 | ||
|
|
276f33b9ec | ||
|
|
ded59d2da0 | ||
|
|
62f7a820d8 | ||
|
|
7063ffa013 | ||
|
|
bf4dc3c095 | ||
|
|
c10e5848bf | ||
|
|
92a3b0d6e0 | ||
|
|
b475bd25c8 | ||
|
|
d318224a6f | ||
|
|
0074fee865 | ||
|
|
5617535a63 | ||
|
|
68ccefc59f | ||
|
|
6d60541626 | ||
|
|
a635e5b8d0 | ||
|
|
48a10440fe | ||
|
|
8e3ba88318 | ||
|
|
ab8fccc544 | ||
|
|
8319dc9164 | ||
|
|
6829d3cdea | ||
|
|
3ae4d69110 | ||
|
|
dc665f227e | ||
|
|
a83496568f | ||
|
|
12d25570af | ||
|
|
378c947654 | ||
|
|
bd39a3140e | ||
|
|
7d3ca3dec1 | ||
|
|
1cb556c8f8 | ||
|
|
8c8f96de1c | ||
|
|
318cd87a9a | ||
|
|
5d63d5c2d3 | ||
|
|
7d347fccc6 | ||
|
|
a54ca799b9 | ||
|
|
f5bc1a996f | ||
|
|
8591bc4d01 | ||
|
|
40888c07f3 | ||
|
|
1c965c3788 | ||
|
|
4df690c2a2 | ||
|
|
d6abb61e2b | ||
|
|
3434c862e9 | ||
|
|
ea8af926fa | ||
|
|
c3df48174c | ||
|
|
f1e60f96c4 | ||
|
|
cdd852678b | ||
|
|
bf518b5467 | ||
|
|
ffd53fab26 | ||
|
|
5aad7dad35 | ||
|
|
b1c1a9f4e1 |
2
.github/ISSUE_TEMPLATE/bug-issue.yml
vendored
@@ -116,5 +116,5 @@ body:
|
|||||||
required: true
|
required: true
|
||||||
- label: I filled out all of the requested information in this issue properly.
|
- label: I filled out all of the requested information in this issue properly.
|
||||||
required: true
|
required: true
|
||||||
- label: The issue is related solely to the ReVanced Manager
|
- label: The issue is related solely to the ReVanced Manager and not related to patching errors or patches
|
||||||
required: true
|
required: true
|
||||||
|
|||||||
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
@@ -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
|
||||||
55
.github/workflows/commit-build.yml
vendored
@@ -1,55 +0,0 @@
|
|||||||
name: "Android CI Actions"
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches:
|
|
||||||
- "**"
|
|
||||||
tags-ignore:
|
|
||||||
- "v*" # Ignore tags that start with "v" (e.g. v1.0.0) because they are handled by release-build.yml
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
release:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v3
|
|
||||||
- name: Set env
|
|
||||||
run: echo "RELEASE_VERSION=${GITHUB_REF#refs/*/}" >> $GITHUB_ENV
|
|
||||||
- name: Set up JDK 12
|
|
||||||
uses: actions/setup-java@v3
|
|
||||||
with:
|
|
||||||
java-version: '12'
|
|
||||||
distribution: 'zulu'
|
|
||||||
- uses: subosito/flutter-action@v2
|
|
||||||
with:
|
|
||||||
channel: 'stable'
|
|
||||||
- name: Set environment variables
|
|
||||||
run: echo $SECRETS | base64 -d > lib/utils/environment.dart
|
|
||||||
env:
|
|
||||||
SECRETS: ${{ secrets.SECRETS }}
|
|
||||||
- name: Set up Flutter
|
|
||||||
run: flutter pub get
|
|
||||||
- name: Generate files with Builder
|
|
||||||
run: flutter packages pub run build_runner build --delete-conflicting-outputs
|
|
||||||
- name: Build with Flutter
|
|
||||||
env:
|
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
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@v2
|
|
||||||
with:
|
|
||||||
name: revanced-manager-${{ env.RELEASE_VERSION }}
|
|
||||||
path: revanced-manager-${{ env.RELEASE_VERSION }}.apk
|
|
||||||
4
.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"
|
||||||
@@ -19,7 +19,7 @@ jobs:
|
|||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v3
|
||||||
|
|
||||||
- name: Crowdin
|
- name: Crowdin
|
||||||
uses: crowdin/github-action@1.5.0
|
uses: crowdin/github-action@v1
|
||||||
with:
|
with:
|
||||||
config: crowdin.yml
|
config: crowdin.yml
|
||||||
upload_sources: true
|
upload_sources: true
|
||||||
|
|||||||
82
.github/workflows/pull-request-build.yml
vendored
@@ -1,41 +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 environment variables
|
cache: 'gradle'
|
||||||
run: echo $SECRETS | base64 -d > lib/utils/environment.dart
|
- name: Setup Flutter
|
||||||
env:
|
uses: subosito/flutter-action@v2
|
||||||
SECRETS: ${{ secrets.SECRETS }}
|
with:
|
||||||
- name: Set up Flutter
|
channel: 'stable'
|
||||||
run: flutter pub get
|
cache: true
|
||||||
- name: Generate files with Builder
|
- name: Install Flutter dependencies
|
||||||
run: flutter packages pub run build_runner build --delete-conflicting-outputs
|
run: flutter pub get
|
||||||
- name: Build with Flutter
|
- name: Generate files with Builder
|
||||||
env:
|
run: flutter packages pub run build_runner build --delete-conflicting-outputs
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
- name: Build with Flutter
|
||||||
run: flutter build apk
|
env:
|
||||||
- name: Add version to APK
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
run: mv build/app/outputs/flutter-apk/app-release.apk revanced-manager-${{ env.RELEASE_VERSION }}.apk
|
run: flutter build apk
|
||||||
- name: Upload APK
|
- name: Upload build
|
||||||
uses: actions/upload-artifact@v2
|
uses: actions/upload-artifact@v3
|
||||||
with:
|
with:
|
||||||
name: revanced-manager-${{ env.RELEASE_VERSION }}
|
name: revanced-manager
|
||||||
path: revanced-manager-${{ env.RELEASE_VERSION }}.apk
|
path: build/app/outputs/flutter-apk/app-release.apk
|
||||||
|
|||||||
12
.github/workflows/release-build.yml
vendored
@@ -15,15 +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 environment variables
|
|
||||||
run: echo $SECRETS | base64 -d > lib/utils/environment.dart
|
|
||||||
env:
|
|
||||||
SECRETS: ${{ secrets.SECRETS }}
|
|
||||||
- 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
|
||||||
@@ -51,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
@@ -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
|
||||||
10
.gitignore
vendored
@@ -134,5 +134,11 @@ 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/
|
||||||
|
|
||||||
|
# FVM
|
||||||
|
.fvm
|
||||||
75
.releaserc
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
{
|
||||||
|
"branches": [
|
||||||
|
"main",
|
||||||
|
{
|
||||||
|
"name": "dev",
|
||||||
|
"prerelease": true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"plugins": [
|
||||||
|
"semantic-release-export-data",
|
||||||
|
"@semantic-release/commit-analyzer",
|
||||||
|
[
|
||||||
|
"@semantic-release/release-notes-generator",
|
||||||
|
{
|
||||||
|
"presetConfig": {
|
||||||
|
"types": [
|
||||||
|
{
|
||||||
|
"type": "build",
|
||||||
|
"section": "Dependency Updates"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "chore",
|
||||||
|
"section": "Other Changes",
|
||||||
|
"hidden": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "perf",
|
||||||
|
"section": "Performance Improvements",
|
||||||
|
"hidden": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "refactor",
|
||||||
|
"section": "Code Improvements",
|
||||||
|
"hidden": false
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"@semantic-release/changelog",
|
||||||
|
"semantic-release-flutter-plugin",
|
||||||
|
[
|
||||||
|
"@semantic-release/git",
|
||||||
|
{
|
||||||
|
"assets": [
|
||||||
|
"CHANGELOG.md",
|
||||||
|
"pubspec.yaml"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"@semantic-release/github",
|
||||||
|
{
|
||||||
|
"assets": [
|
||||||
|
{
|
||||||
|
"path": "build/app/outputs/apk/release/revanced-manager-*.apk"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"successComment": false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"@saithodev/semantic-release-backmerge",
|
||||||
|
{
|
||||||
|
"backmergeBranches": [
|
||||||
|
{
|
||||||
|
"from": "main",
|
||||||
|
"to": "dev"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"clearWorkspace": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
]
|
||||||
|
}
|
||||||
1
CHANGELOG.md
Normal file
@@ -0,0 +1 @@
|
|||||||
|
|
||||||
@@ -3,15 +3,11 @@
|
|||||||
The official ReVanced Manager based on Flutter.
|
The official ReVanced Manager based on Flutter.
|
||||||
|
|
||||||
## 🔽 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).
|
||||||
@@ -28,7 +24,7 @@ If you wish to translate ReVanced Manager, we're accepting translations on [Crow
|
|||||||
## 🛠️ Building Manager from source
|
## 🛠️ Building Manager from source
|
||||||
1. Setup flutter environment for your [platform](https://docs.flutter.dev/get-started/install)
|
1. Setup flutter environment for your [platform](https://docs.flutter.dev/get-started/install)
|
||||||
2. Clone the repository locally
|
2. Clone the repository locally
|
||||||
3. Add your github token in gradle.properties like [this](https://github.com/revanced/revanced-documentation/blob/main/docs/revanced-development/2_building_from_source.md)
|
3. Add your github token in gradle.properties like [this](https://github.com/revanced/revanced-manager/blob/docs/docs/5_building-from-source.md)
|
||||||
4. Open the project in terminal
|
4. Open the project in terminal
|
||||||
5. Run `flutter pub get` in terminal
|
5. Run `flutter pub get` in terminal
|
||||||
6. Then `flutter packages pub run build_runner build --delete-conflicting-outputs` (Must be done on each git pull)
|
6. Then `flutter packages pub run build_runner build --delete-conflicting-outputs` (Must be done on each git pull)
|
||||||
|
|||||||
@@ -11,23 +11,154 @@ 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:
|
||||||
# The lint rules applied to this project can be customized in the
|
|
||||||
# section below to disable rules from the `package:flutter_lints/flutter.yaml`
|
|
||||||
# included above or to enable additional rules. A list of all available lints
|
|
||||||
# and their documentation is published at
|
|
||||||
# https://dart-lang.github.io/linter/lints/index.html.
|
|
||||||
#
|
|
||||||
# Instead of disabling a lint rule for the entire project in the
|
|
||||||
# section below, it can also be suppressed for a single line of code
|
|
||||||
# or a specific dart file by using the `// ignore: name_of_lint` and
|
|
||||||
# `// ignore_for_file: name_of_lint` syntax on the line or in the file
|
|
||||||
# producing the lint.
|
|
||||||
rules:
|
rules:
|
||||||
# avoid_print: false # Uncomment to disable the `avoid_print` rule
|
- always_declare_return_types
|
||||||
# prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule
|
- require_trailing_commas
|
||||||
|
- always_put_control_body_on_new_line
|
||||||
# Additional information about this file can be found at
|
- always_require_non_null_named_parameters
|
||||||
# https://dart.dev/guides/language/analysis-options
|
- always_use_package_imports # we do this commonly
|
||||||
|
- annotate_overrides
|
||||||
|
- avoid_bool_literals_in_conditional_expressions
|
||||||
|
- avoid_double_and_int_checks
|
||||||
|
- avoid_empty_else
|
||||||
|
- avoid_equals_and_hash_code_on_mutable_classes
|
||||||
|
- avoid_escaping_inner_quotes
|
||||||
|
- avoid_field_initializers_in_const_classes
|
||||||
|
- avoid_function_literals_in_foreach_calls
|
||||||
|
- avoid_implementing_value_types
|
||||||
|
- avoid_init_to_null
|
||||||
|
- avoid_js_rounded_ints
|
||||||
|
- avoid_null_checks_in_equality_operators
|
||||||
|
- avoid_print
|
||||||
|
- avoid_redundant_argument_values
|
||||||
|
- avoid_relative_lib_imports
|
||||||
|
- avoid_renaming_method_parameters
|
||||||
|
- avoid_return_types_on_setters
|
||||||
|
- avoid_returning_null
|
||||||
|
- avoid_returning_null_for_future
|
||||||
|
- avoid_returning_null_for_void
|
||||||
|
- avoid_setters_without_getters
|
||||||
|
- avoid_shadowing_type_parameters
|
||||||
|
- avoid_single_cascade_in_expression_statements
|
||||||
|
- avoid_type_to_string
|
||||||
|
- avoid_types_as_parameter_names
|
||||||
|
- avoid_unnecessary_containers
|
||||||
|
- avoid_void_async
|
||||||
|
- avoid_web_libraries_in_flutter # we use web libraries in web-specific code, and our tests prevent us from using them elsewhere
|
||||||
|
- await_only_futures
|
||||||
|
- camel_case_extensions
|
||||||
|
- camel_case_types
|
||||||
|
- cancel_subscriptions
|
||||||
|
- cast_nullable_to_non_nullable
|
||||||
|
- close_sinks # not reliable enough
|
||||||
|
- control_flow_in_finally
|
||||||
|
- curly_braces_in_flow_control_structures
|
||||||
|
- depend_on_referenced_packages
|
||||||
|
- deprecated_consistency
|
||||||
|
- directives_ordering
|
||||||
|
- empty_catches
|
||||||
|
- empty_constructor_bodies
|
||||||
|
- empty_statements
|
||||||
|
- eol_at_end_of_file
|
||||||
|
- exhaustive_cases
|
||||||
|
- file_names
|
||||||
|
- flutter_style_todos
|
||||||
|
- hash_and_equals
|
||||||
|
- implementation_imports
|
||||||
|
- iterable_contains_unrelated_type
|
||||||
|
- leading_newlines_in_multiline_strings
|
||||||
|
- library_names
|
||||||
|
- library_prefixes
|
||||||
|
- library_private_types_in_public_api
|
||||||
|
- list_remove_unrelated_type
|
||||||
|
- missing_whitespace_between_adjacent_strings
|
||||||
|
- no_adjacent_strings_in_list
|
||||||
|
- no_duplicate_case_values
|
||||||
|
- no_logic_in_create_state
|
||||||
|
- non_constant_identifier_names
|
||||||
|
- noop_primitive_operations
|
||||||
|
- null_check_on_nullable_type_parameter
|
||||||
|
- null_closures
|
||||||
|
- overridden_fields
|
||||||
|
- package_api_docs
|
||||||
|
- package_names
|
||||||
|
- package_prefixed_library_names
|
||||||
|
- prefer_adjacent_string_concatenation
|
||||||
|
- prefer_asserts_in_initializer_lists
|
||||||
|
- prefer_collection_literals
|
||||||
|
- prefer_conditional_assignment
|
||||||
|
- prefer_const_constructors
|
||||||
|
- prefer_const_constructors_in_immutables
|
||||||
|
- prefer_const_declarations
|
||||||
|
- prefer_const_literals_to_create_immutables
|
||||||
|
- prefer_contains
|
||||||
|
- prefer_final_fields
|
||||||
|
- prefer_final_in_for_each
|
||||||
|
- prefer_final_locals
|
||||||
|
- prefer_for_elements_to_map_fromIterable
|
||||||
|
- prefer_foreach
|
||||||
|
- prefer_function_declarations_over_variables
|
||||||
|
- prefer_generic_function_type_aliases
|
||||||
|
- prefer_if_elements_to_conditional_expressions
|
||||||
|
- prefer_if_null_operators
|
||||||
|
- prefer_initializing_formals
|
||||||
|
- prefer_inlined_adds
|
||||||
|
- prefer_interpolation_to_compose_strings
|
||||||
|
- prefer_is_empty
|
||||||
|
- prefer_is_not_empty
|
||||||
|
- prefer_is_not_operator
|
||||||
|
- prefer_iterable_whereType
|
||||||
|
- prefer_mixin # Has false positives, see https://github.com/dart-lang/linter/issues/3018
|
||||||
|
- prefer_null_aware_method_calls # "call()" is confusing to people new to the language since it's not documented anywhere
|
||||||
|
- prefer_null_aware_operators
|
||||||
|
- prefer_single_quotes
|
||||||
|
- prefer_spread_collections
|
||||||
|
- prefer_typing_uninitialized_variables
|
||||||
|
- prefer_void_to_null
|
||||||
|
- provide_deprecation_message
|
||||||
|
- recursive_getters
|
||||||
|
- sized_box_for_whitespace
|
||||||
|
- slash_for_doc_comments
|
||||||
|
- sort_child_properties_last
|
||||||
|
- sort_constructors_first
|
||||||
|
- sort_unnamed_constructors_first
|
||||||
|
- test_types_in_equals
|
||||||
|
- throw_in_finally
|
||||||
|
- tighten_type_of_initializing_formals
|
||||||
|
- type_init_formals
|
||||||
|
- unnecessary_brace_in_string_interps
|
||||||
|
- unnecessary_const
|
||||||
|
- unnecessary_getters_setters
|
||||||
|
- unnecessary_new
|
||||||
|
- unnecessary_null_aware_assignments
|
||||||
|
- unnecessary_null_checks
|
||||||
|
- unnecessary_null_in_if_null_operators
|
||||||
|
- unnecessary_nullable_for_final_variable_declarations
|
||||||
|
- unnecessary_overrides
|
||||||
|
- unnecessary_parenthesis
|
||||||
|
- unnecessary_statements
|
||||||
|
- unnecessary_string_escapes
|
||||||
|
- unnecessary_string_interpolations
|
||||||
|
- unnecessary_this
|
||||||
|
- unrelated_type_equality_checks
|
||||||
|
- unsafe_html
|
||||||
|
- use_build_context_synchronously
|
||||||
|
- use_full_hex_values_for_flutter_colors
|
||||||
|
- use_function_type_syntax_for_parameters
|
||||||
|
- use_if_null_to_convert_nulls_to_bools
|
||||||
|
- use_is_even_rather_than_modulo
|
||||||
|
- use_key_in_widget_constructors
|
||||||
|
- use_late_for_private_fields_and_variables
|
||||||
|
- use_named_constants
|
||||||
|
- use_raw_strings
|
||||||
|
- use_rethrow_when_possible
|
||||||
|
- use_setters_to_change_properties
|
||||||
|
- use_test_throws_matchers
|
||||||
|
- valid_regexps
|
||||||
|
- void_checks
|
||||||
|
|||||||
@@ -71,14 +71,10 @@ dependencies {
|
|||||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
|
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
|
||||||
|
|
||||||
// ReVanced
|
// ReVanced
|
||||||
implementation "app.revanced:revanced-patcher:6.3.2"
|
implementation "app.revanced:revanced-patcher:9.0.0"
|
||||||
|
|
||||||
// Signing & aligning
|
// Signing & aligning
|
||||||
implementation("org.bouncycastle:bcpkix-jdk15on:1.70")
|
implementation("org.bouncycastle:bcpkix-jdk15on:1.70")
|
||||||
implementation("com.android.tools.build:apksig:7.2.2")
|
implementation("com.android.tools.build:apksig:7.2.2")
|
||||||
|
|
||||||
// MicroG cronet
|
|
||||||
implementation("org.microg:cronet-common:$cronetVersion")
|
|
||||||
implementation("org.microg:cronet-native:$cronetVersion")
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,7 +17,8 @@
|
|||||||
android:name="${applicationName}"
|
android:name="${applicationName}"
|
||||||
android:icon="@mipmap/ic_launcher"
|
android:icon="@mipmap/ic_launcher"
|
||||||
android:largeHeap="true"
|
android:largeHeap="true"
|
||||||
android:extractNativeLibs="true">
|
android:extractNativeLibs="true"
|
||||||
|
android:enableOnBackInvokedCallback="true">
|
||||||
<activity
|
<activity
|
||||||
android:name=".MainActivity"
|
android:name=".MainActivity"
|
||||||
android:exported="true"
|
android:exported="true"
|
||||||
@@ -28,8 +29,7 @@
|
|||||||
android:windowSoftInputMode="adjustResize">
|
android:windowSoftInputMode="adjustResize">
|
||||||
<meta-data
|
<meta-data
|
||||||
android:name="io.flutter.embedding.android.NormalTheme"
|
android:name="io.flutter.embedding.android.NormalTheme"
|
||||||
android:resource="@style/NormalTheme"
|
android:resource="@style/NormalTheme"/>
|
||||||
/>
|
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.MAIN"/>
|
<action android:name="android.intent.action.MAIN"/>
|
||||||
<category android:name="android.intent.category.LAUNCHER"/>
|
<category android:name="android.intent.category.LAUNCHER"/>
|
||||||
|
|||||||
@@ -43,8 +43,9 @@ class MainActivity : FlutterActivity() {
|
|||||||
val integrationsPath = call.argument<String>("integrationsPath")
|
val integrationsPath = call.argument<String>("integrationsPath")
|
||||||
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 mergeIntegrations = call.argument<Boolean>("mergeIntegrations")
|
|
||||||
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 &&
|
||||||
@@ -53,8 +54,8 @@ class MainActivity : FlutterActivity() {
|
|||||||
integrationsPath != null &&
|
integrationsPath != null &&
|
||||||
selectedPatches != null &&
|
selectedPatches != null &&
|
||||||
cacheDirPath != null &&
|
cacheDirPath != null &&
|
||||||
mergeIntegrations != null &&
|
keyStoreFilePath != null &&
|
||||||
keyStoreFilePath != null
|
keystorePassword != null
|
||||||
) {
|
) {
|
||||||
runPatcher(
|
runPatcher(
|
||||||
result,
|
result,
|
||||||
@@ -66,8 +67,8 @@ class MainActivity : FlutterActivity() {
|
|||||||
integrationsPath,
|
integrationsPath,
|
||||||
selectedPatches,
|
selectedPatches,
|
||||||
cacheDirPath,
|
cacheDirPath,
|
||||||
mergeIntegrations,
|
keyStoreFilePath,
|
||||||
keyStoreFilePath
|
keystorePassword
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
result.notImplemented()
|
result.notImplemented()
|
||||||
@@ -88,8 +89,8 @@ class MainActivity : FlutterActivity() {
|
|||||||
integrationsPath: String,
|
integrationsPath: String,
|
||||||
selectedPatches: List<String>,
|
selectedPatches: List<String>,
|
||||||
cacheDirPath: String,
|
cacheDirPath: String,
|
||||||
mergeIntegrations: Boolean,
|
keyStoreFilePath: String,
|
||||||
keyStoreFilePath: String
|
keystorePassword: String
|
||||||
) {
|
) {
|
||||||
val originalFile = File(originalFilePath)
|
val originalFile = File(originalFilePath)
|
||||||
val inputFile = File(inputFilePath)
|
val inputFile = File(inputFilePath)
|
||||||
@@ -139,19 +140,17 @@ class MainActivity : FlutterActivity() {
|
|||||||
mapOf("progress" to 0.3, "header" to "", "log" to "")
|
mapOf("progress" to 0.3, "header" to "", "log" to "")
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
if (mergeIntegrations) {
|
handler.post {
|
||||||
handler.post {
|
installerChannel.invokeMethod(
|
||||||
installerChannel.invokeMethod(
|
"update",
|
||||||
"update",
|
mapOf(
|
||||||
mapOf(
|
"progress" to 0.4,
|
||||||
"progress" to 0.4,
|
"header" to "Merging integrations...",
|
||||||
"header" to "Merging integrations...",
|
"log" to "Merging integrations"
|
||||||
"log" to "Merging integrations"
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
}
|
)
|
||||||
patcher.addFiles(listOf(integrations)) {}
|
|
||||||
}
|
}
|
||||||
|
patcher.addIntegrations(listOf(integrations)) {}
|
||||||
|
|
||||||
handler.post {
|
handler.post {
|
||||||
installerChannel.invokeMethod(
|
installerChannel.invokeMethod(
|
||||||
@@ -248,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}")
|
||||||
|
|||||||
|
After Width: | Height: | Size: 2.1 KiB |
|
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.7 KiB |
|
After Width: | Height: | Size: 1.5 KiB |
|
Before Width: | Height: | Size: 814 B After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 2.7 KiB |
|
Before Width: | Height: | Size: 2.0 KiB After Width: | Height: | Size: 2.4 KiB |
|
After Width: | Height: | Size: 4.1 KiB |
|
Before Width: | Height: | Size: 3.4 KiB After Width: | Height: | Size: 3.7 KiB |
|
After Width: | Height: | Size: 5.5 KiB |
|
Before Width: | Height: | Size: 5.4 KiB After Width: | Height: | Size: 4.9 KiB |
|
After Width: | Height: | Size: 5.1 KiB |
@@ -1,5 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
<background android:drawable="@color/ic_launcher_background"/>
|
<background android:drawable="@drawable/ic_launcher_background"/>
|
||||||
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
|
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
|
||||||
|
<monochrome android:drawable="@drawable/ic_launcher_monochrome"/>
|
||||||
</adaptive-icon>
|
</adaptive-icon>
|
||||||
|
|||||||
@@ -1,4 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<resources>
|
|
||||||
<color name="ic_launcher_background">#1B1B1B</color>
|
|
||||||
</resources>
|
|
||||||
@@ -1,6 +1,5 @@
|
|||||||
buildscript {
|
buildscript {
|
||||||
ext.cronetVersion = '102.5005.125'
|
ext.kotlin_version = '1.7.10'
|
||||||
ext.kotlin_version = '1.7.20'
|
|
||||||
repositories {
|
repositories {
|
||||||
google()
|
google()
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
@@ -32,6 +31,6 @@ subprojects {
|
|||||||
project.evaluationDependsOn(':app')
|
project.evaluationDependsOn(':app')
|
||||||
}
|
}
|
||||||
|
|
||||||
task clean(type: Delete) {
|
tasks.register("clean", Delete) {
|
||||||
delete rootProject.buildDir
|
delete rootProject.buildDir
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
#Mon May 09 12:07:41 MSK 2022
|
|
||||||
distributionBase=GRADLE_USER_HOME
|
distributionBase=GRADLE_USER_HOME
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-rc-1-bin.zip
|
|
||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
zipStorePath=wrapper/dists
|
distributionUrl=https\://services.gradle.org/distributions/gradle-7.6.1-bin.zip
|
||||||
|
distributionSha256Sum=6147605a23b4eff6c334927a86ff3508cb5d6722cd624c97ded4c2e8640f1f87
|
||||||
|
networkTimeout=10000
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
|
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,20 +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 application is a split APK and cannot be selected. Unfortunately, this feature is only available for rooted users at the moment. However, you can still install the application by selecting its APK files from your device's storage instead"
|
||||||
},
|
},
|
||||||
"patchesSelectorView": {
|
"patchesSelectorView": {
|
||||||
"viewTitle": "Select patches",
|
"viewTitle": "Select patches",
|
||||||
"searchBarHint": "Search patches",
|
"searchBarHint": "Search patches",
|
||||||
"doneButton": "Done",
|
"doneButton": "Done",
|
||||||
"recommended": "Recommended",
|
"default": "Default",
|
||||||
"all": "All",
|
"defaultTooltip": "Select all default patches",
|
||||||
"none": "None",
|
"none": "None",
|
||||||
|
"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}",
|
||||||
@@ -145,11 +152,10 @@
|
|||||||
"exportSectionTitle": "Import & export",
|
"exportSectionTitle": "Import & export",
|
||||||
"aboutLabel": "About",
|
"aboutLabel": "About",
|
||||||
"snackbarMessage": "Copied to clipboard",
|
"snackbarMessage": "Copied to clipboard",
|
||||||
"sentryLabel": "Sentry logging",
|
|
||||||
"sentryHint": "Send anonymous logs to help us improve ReVanced Manager",
|
|
||||||
"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",
|
||||||
@@ -167,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",
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import 'package:revanced_manager/services/crowdin_api.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';
|
||||||
@@ -38,7 +37,6 @@ import 'package:stacked_services/stacked_services.dart';
|
|||||||
LazySingleton(classType: PatcherAPI),
|
LazySingleton(classType: PatcherAPI),
|
||||||
LazySingleton(classType: RevancedAPI),
|
LazySingleton(classType: RevancedAPI),
|
||||||
LazySingleton(classType: GithubAPI),
|
LazySingleton(classType: GithubAPI),
|
||||||
LazySingleton(classType: CrowdinAPI),
|
|
||||||
LazySingleton(classType: Toast),
|
LazySingleton(classType: Toast),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,18 +1,17 @@
|
|||||||
|
import 'dart:developer';
|
||||||
|
|
||||||
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_localizations/flutter_localizations.dart';
|
import 'package:flutter_localizations/flutter_localizations.dart';
|
||||||
import 'package:revanced_manager/app/app.locator.dart';
|
import 'package:revanced_manager/app/app.locator.dart';
|
||||||
import 'package:revanced_manager/services/crowdin_api.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/revanced_api.dart';
|
||||||
import 'package:revanced_manager/ui/theme/dynamic_theme_builder.dart';
|
import 'package:revanced_manager/ui/theme/dynamic_theme_builder.dart';
|
||||||
import 'package:revanced_manager/ui/views/navigation/navigation_view.dart';
|
import 'package:revanced_manager/ui/views/navigation/navigation_view.dart';
|
||||||
import 'package:revanced_manager/utils/environment.dart';
|
|
||||||
import 'package:shared_preferences/shared_preferences.dart';
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
import 'package:stacked_themes/stacked_themes.dart';
|
import 'package:stacked_themes/stacked_themes.dart';
|
||||||
import 'package:sentry_flutter/sentry_flutter.dart';
|
|
||||||
import 'package:timezone/data/latest.dart' as tz;
|
import 'package:timezone/data/latest.dart' as tz;
|
||||||
|
|
||||||
late SharedPreferences prefs;
|
late SharedPreferences prefs;
|
||||||
@@ -21,38 +20,14 @@ Future main() async {
|
|||||||
await setupLocator();
|
await setupLocator();
|
||||||
WidgetsFlutterBinding.ensureInitialized();
|
WidgetsFlutterBinding.ensureInitialized();
|
||||||
await locator<ManagerAPI>().initialize();
|
await locator<ManagerAPI>().initialize();
|
||||||
String apiUrl = locator<ManagerAPI>().getApiUrl();
|
final String apiUrl = locator<ManagerAPI>().getApiUrl();
|
||||||
await locator<RevancedAPI>().initialize(apiUrl);
|
await locator<RevancedAPI>().initialize(apiUrl);
|
||||||
await locator<CrowdinAPI>().initialize();
|
final String repoUrl = locator<ManagerAPI>().getRepoUrl();
|
||||||
bool isSentryEnabled = locator<ManagerAPI>().isSentryEnabled();
|
|
||||||
String repoUrl = locator<ManagerAPI>().getRepoUrl();
|
|
||||||
locator<GithubAPI>().initialize(repoUrl);
|
locator<GithubAPI>().initialize(repoUrl);
|
||||||
await locator<PatcherAPI>().initialize();
|
await locator<PatcherAPI>().initialize();
|
||||||
tz.initializeTimeZones();
|
tz.initializeTimeZones();
|
||||||
prefs = await SharedPreferences.getInstance();
|
prefs = await SharedPreferences.getInstance();
|
||||||
|
|
||||||
await SentryFlutter.init(
|
|
||||||
(options) {
|
|
||||||
options
|
|
||||||
..dsn = isSentryEnabled ? Environment.sentryDSN : ''
|
|
||||||
..environment = 'alpha'
|
|
||||||
..release = '0.1'
|
|
||||||
..tracesSampleRate = 1.0
|
|
||||||
..anrEnabled = true
|
|
||||||
..enableOutOfMemoryTracking = true
|
|
||||||
..sampleRate = isSentryEnabled ? 1.0 : 0.0
|
|
||||||
..beforeSend = (event, hint) {
|
|
||||||
if (isSentryEnabled) {
|
|
||||||
return event;
|
|
||||||
} else {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
} as BeforeSendCallback?;
|
|
||||||
},
|
|
||||||
appRunner: () {
|
|
||||||
runApp(const MyApp());
|
|
||||||
},
|
|
||||||
);
|
|
||||||
runApp(const MyApp());
|
runApp(const MyApp());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -65,7 +40,7 @@ class MyApp extends StatelessWidget {
|
|||||||
// String replaceLocale = rawLocale.replaceAll('_', '-');
|
// String replaceLocale = rawLocale.replaceAll('_', '-');
|
||||||
// List<String> localeList = replaceLocale.split('-');
|
// List<String> localeList = replaceLocale.split('-');
|
||||||
// Locale locale = Locale(localeList[0], localeList[1]);
|
// Locale locale = Locale(localeList[0], localeList[1]);
|
||||||
Locale locale = const Locale('en', 'US');
|
const Locale locale = Locale('en', 'US');
|
||||||
|
|
||||||
return DynamicThemeBuilder(
|
return DynamicThemeBuilder(
|
||||||
title: 'ReVanced Manager',
|
title: 'ReVanced Manager',
|
||||||
@@ -79,8 +54,9 @@ class MyApp extends StatelessWidget {
|
|||||||
useCountryCode: true,
|
useCountryCode: true,
|
||||||
),
|
),
|
||||||
missingTranslationHandler: (key, locale) {
|
missingTranslationHandler: (key, locale) {
|
||||||
print(
|
log(
|
||||||
'--> Missing translation: key: $key, languageCode: ${locale?.languageCode}');
|
'--> Missing translation: key: $key, languageCode: ${locale?.languageCode}',
|
||||||
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
GlobalMaterialLocalizations.delegate,
|
GlobalMaterialLocalizations.delegate,
|
||||||
|
|||||||
@@ -5,13 +5,6 @@ part 'patch.g.dart';
|
|||||||
|
|
||||||
@JsonSerializable()
|
@JsonSerializable()
|
||||||
class Patch {
|
class Patch {
|
||||||
final String name;
|
|
||||||
final String description;
|
|
||||||
final String version;
|
|
||||||
final bool excluded;
|
|
||||||
final List<String> dependencies;
|
|
||||||
final List<Package> compatiblePackages;
|
|
||||||
|
|
||||||
Patch({
|
Patch({
|
||||||
required this.name,
|
required this.name,
|
||||||
required this.description,
|
required this.description,
|
||||||
@@ -22,6 +15,12 @@ class Patch {
|
|||||||
});
|
});
|
||||||
|
|
||||||
factory Patch.fromJson(Map<String, dynamic> json) => _$PatchFromJson(json);
|
factory Patch.fromJson(Map<String, dynamic> json) => _$PatchFromJson(json);
|
||||||
|
final String name;
|
||||||
|
final String description;
|
||||||
|
final String version;
|
||||||
|
final bool excluded;
|
||||||
|
final List<String> dependencies;
|
||||||
|
final List<Package> compatiblePackages;
|
||||||
|
|
||||||
Map<String, dynamic> toJson() => _$PatchToJson(this);
|
Map<String, dynamic> toJson() => _$PatchToJson(this);
|
||||||
|
|
||||||
@@ -37,9 +36,6 @@ class Patch {
|
|||||||
|
|
||||||
@JsonSerializable()
|
@JsonSerializable()
|
||||||
class Package {
|
class Package {
|
||||||
final String name;
|
|
||||||
final List<String> versions;
|
|
||||||
|
|
||||||
Package({
|
Package({
|
||||||
required this.name,
|
required this.name,
|
||||||
required this.versions,
|
required this.versions,
|
||||||
@@ -47,6 +43,8 @@ class Package {
|
|||||||
|
|
||||||
factory Package.fromJson(Map<String, dynamic> json) =>
|
factory Package.fromJson(Map<String, dynamic> json) =>
|
||||||
_$PackageFromJson(json);
|
_$PackageFromJson(json);
|
||||||
|
final String name;
|
||||||
|
final List<String> versions;
|
||||||
|
|
||||||
Map toJson() => _$PackageToJson(this);
|
Map toJson() => _$PackageToJson(this);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,23 +6,6 @@ part 'patched_application.g.dart';
|
|||||||
|
|
||||||
@JsonSerializable()
|
@JsonSerializable()
|
||||||
class PatchedApplication {
|
class PatchedApplication {
|
||||||
String name;
|
|
||||||
String packageName;
|
|
||||||
String originalPackageName;
|
|
||||||
String version;
|
|
||||||
final String apkFilePath;
|
|
||||||
@JsonKey(
|
|
||||||
fromJson: decodeBase64,
|
|
||||||
toJson: encodeBase64,
|
|
||||||
)
|
|
||||||
Uint8List icon;
|
|
||||||
DateTime patchDate;
|
|
||||||
bool isRooted;
|
|
||||||
bool isFromStorage;
|
|
||||||
bool hasUpdates;
|
|
||||||
List<String> appliedPatches;
|
|
||||||
List<String> changelog;
|
|
||||||
|
|
||||||
PatchedApplication({
|
PatchedApplication({
|
||||||
required this.name,
|
required this.name,
|
||||||
required this.packageName,
|
required this.packageName,
|
||||||
@@ -40,6 +23,22 @@ class PatchedApplication {
|
|||||||
|
|
||||||
factory PatchedApplication.fromJson(Map<String, dynamic> json) =>
|
factory PatchedApplication.fromJson(Map<String, dynamic> json) =>
|
||||||
_$PatchedApplicationFromJson(json);
|
_$PatchedApplicationFromJson(json);
|
||||||
|
String name;
|
||||||
|
String packageName;
|
||||||
|
String originalPackageName;
|
||||||
|
String version;
|
||||||
|
final String apkFilePath;
|
||||||
|
@JsonKey(
|
||||||
|
fromJson: decodeBase64,
|
||||||
|
toJson: encodeBase64,
|
||||||
|
)
|
||||||
|
Uint8List icon;
|
||||||
|
DateTime patchDate;
|
||||||
|
bool isRooted;
|
||||||
|
bool isFromStorage;
|
||||||
|
bool hasUpdates;
|
||||||
|
List<String> appliedPatches;
|
||||||
|
List<String> changelog;
|
||||||
|
|
||||||
Map<String, dynamic> toJson() => _$PatchedApplicationToJson(this);
|
Map<String, dynamic> toJson() => _$PatchedApplicationToJson(this);
|
||||||
|
|
||||||
|
|||||||
@@ -1,61 +0,0 @@
|
|||||||
import 'package:dio/dio.dart';
|
|
||||||
import 'package:dio_http_cache_lts/dio_http_cache_lts.dart';
|
|
||||||
import 'package:injectable/injectable.dart' hide Environment;
|
|
||||||
import 'package:revanced_manager/utils/environment.dart';
|
|
||||||
import 'package:sentry_dio/sentry_dio.dart';
|
|
||||||
import 'package:sentry_flutter/sentry_flutter.dart';
|
|
||||||
|
|
||||||
@lazySingleton
|
|
||||||
class CrowdinAPI {
|
|
||||||
late Dio _dio = Dio();
|
|
||||||
final DioCacheManager _dioCacheManager = DioCacheManager(CacheConfig());
|
|
||||||
final apiKey = Environment.crowdinKEY;
|
|
||||||
|
|
||||||
Future<void> initialize() async {
|
|
||||||
try {
|
|
||||||
_dio = Dio(BaseOptions(
|
|
||||||
baseUrl: 'https://api.crowdin.com/api/v2',
|
|
||||||
));
|
|
||||||
|
|
||||||
_dio.interceptors.add(_dioCacheManager.interceptor);
|
|
||||||
_dio.addSentry(
|
|
||||||
captureFailedRequests: true,
|
|
||||||
);
|
|
||||||
} on Exception catch (e, s) {
|
|
||||||
await Sentry.captureException(e, stackTrace: s);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> clearAllCache() async {
|
|
||||||
try {
|
|
||||||
await _dioCacheManager.clearAll();
|
|
||||||
} on Exception catch (e, s) {
|
|
||||||
await Sentry.captureException(e, stackTrace: s);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<List> getLanguages() async {
|
|
||||||
try {
|
|
||||||
var response = await _dio.get(
|
|
||||||
'/projects',
|
|
||||||
options: buildCacheOptions(
|
|
||||||
const Duration(hours: 6),
|
|
||||||
maxStale: const Duration(days: 1),
|
|
||||||
options: Options(
|
|
||||||
headers: {
|
|
||||||
'Authorization': 'Bearer $apiKey',
|
|
||||||
},
|
|
||||||
contentType: 'application/json',
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
List targetLanguages =
|
|
||||||
await response.data['data'][0]['data']['targetLanguages'];
|
|
||||||
|
|
||||||
return targetLanguages;
|
|
||||||
} on Exception catch (e, s) {
|
|
||||||
await Sentry.captureException(e, stackTrace: s);
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,22 +1,24 @@
|
|||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
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_cache_manager/flutter_cache_manager.dart';
|
import 'package:flutter_cache_manager/flutter_cache_manager.dart';
|
||||||
import 'package:injectable/injectable.dart';
|
import 'package:injectable/injectable.dart';
|
||||||
import 'package:revanced_manager/models/patch.dart';
|
import 'package:revanced_manager/models/patch.dart';
|
||||||
import 'package:sentry_flutter/sentry_flutter.dart';
|
|
||||||
import 'package:sentry_dio/sentry_dio.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',
|
||||||
@@ -28,38 +30,44 @@ class GithubAPI {
|
|||||||
'com.spotify.music': 'spotify',
|
'com.spotify.music': 'spotify',
|
||||||
};
|
};
|
||||||
|
|
||||||
void initialize(String repoUrl) async {
|
Future<void> initialize(String repoUrl) async {
|
||||||
try {
|
try {
|
||||||
_dio = Dio(BaseOptions(
|
_dio = Dio(
|
||||||
baseUrl: repoUrl,
|
BaseOptions(
|
||||||
));
|
baseUrl: repoUrl,
|
||||||
|
),
|
||||||
_dio.interceptors.add(_dioCacheManager.interceptor);
|
|
||||||
_dio.addSentry(
|
|
||||||
captureFailedRequests: true,
|
|
||||||
);
|
);
|
||||||
} on Exception catch (e, s) {
|
|
||||||
await Sentry.captureException(e, stackTrace: s);
|
_dio.interceptors.add(DioCacheInterceptor(options: _cacheOptions));
|
||||||
|
} on Exception catch (e) {
|
||||||
|
if (kDebugMode) {
|
||||||
|
print(e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> clearAllCache() async {
|
Future<void> clearAllCache() async {
|
||||||
try {
|
try {
|
||||||
await _dioCacheManager.clearAll();
|
await _cacheOptions.store!.clean();
|
||||||
} on Exception catch (e, s) {
|
} on Exception catch (e) {
|
||||||
await Sentry.captureException(e, stackTrace: s);
|
if (kDebugMode) {
|
||||||
|
print(e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<Map<String, dynamic>?> getLatestRelease(String repoName) async {
|
Future<Map<String, dynamic>?> getLatestRelease(
|
||||||
|
String repoName,
|
||||||
|
) async {
|
||||||
try {
|
try {
|
||||||
var 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, s) {
|
} on Exception catch (e) {
|
||||||
await Sentry.captureException(e, stackTrace: s);
|
if (kDebugMode) {
|
||||||
|
print(e);
|
||||||
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -69,37 +77,41 @@ class GithubAPI {
|
|||||||
String repoName,
|
String repoName,
|
||||||
DateTime since,
|
DateTime since,
|
||||||
) async {
|
) async {
|
||||||
String path =
|
final String path =
|
||||||
'src/main/kotlin/app/revanced/patches/${repoAppPath[packageName]}';
|
'src/main/kotlin/app/revanced/patches/${repoAppPath[packageName]}';
|
||||||
try {
|
try {
|
||||||
var response = await _dio.get(
|
final response = await _dio.get(
|
||||||
'/repos/$repoName/commits',
|
'/repos/$repoName/commits',
|
||||||
queryParameters: {
|
queryParameters: {
|
||||||
'path': path,
|
'path': path,
|
||||||
'since': since.toIso8601String(),
|
'since': since.toIso8601String(),
|
||||||
},
|
},
|
||||||
options: _cacheOptions,
|
|
||||||
);
|
);
|
||||||
List<dynamic> commits = response.data;
|
final List<dynamic> commits = response.data;
|
||||||
return commits
|
return commits
|
||||||
.map(
|
.map(
|
||||||
(commit) => (commit['commit']['message']).split('\n')[0] +
|
(commit) => commit['commit']['message'].split('\n')[0] +
|
||||||
' - ' +
|
' - ' +
|
||||||
commit['commit']['author']['name'] +
|
commit['commit']['author']['name'] +
|
||||||
'\n' as String,
|
'\n' as String,
|
||||||
)
|
)
|
||||||
.toList();
|
.toList();
|
||||||
} on Exception catch (e, s) {
|
} on Exception catch (e) {
|
||||||
await Sentry.captureException(e, stackTrace: s);
|
if (kDebugMode) {
|
||||||
return List.empty();
|
print(e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<File?> getLatestReleaseFile(String extension, String repoName) async {
|
Future<File?> getLatestReleaseFile(
|
||||||
|
String extension,
|
||||||
|
String repoName,
|
||||||
|
) async {
|
||||||
try {
|
try {
|
||||||
Map<String, dynamic>? release = await getLatestRelease(repoName);
|
final Map<String, dynamic>? release = await getLatestRelease(repoName);
|
||||||
if (release != null) {
|
if (release != null) {
|
||||||
Map<String, dynamic>? asset =
|
final Map<String, dynamic>? asset =
|
||||||
(release['assets'] as List<dynamic>).firstWhereOrNull(
|
(release['assets'] as List<dynamic>).firstWhereOrNull(
|
||||||
(asset) => (asset['name'] as String).endsWith(extension),
|
(asset) => (asset['name'] as String).endsWith(extension),
|
||||||
);
|
);
|
||||||
@@ -109,9 +121,10 @@ class GithubAPI {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} on Exception catch (e, s) {
|
} on Exception catch (e) {
|
||||||
await Sentry.captureException(e, stackTrace: s);
|
if (kDebugMode) {
|
||||||
return null;
|
print(e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -119,29 +132,34 @@ class GithubAPI {
|
|||||||
Future<List<Patch>> getPatches(String repoName) async {
|
Future<List<Patch>> getPatches(String repoName) async {
|
||||||
List<Patch> patches = [];
|
List<Patch> patches = [];
|
||||||
try {
|
try {
|
||||||
File? f = await getLatestReleaseFile('.json', repoName);
|
final File? f = await getLatestReleaseFile('.json', repoName);
|
||||||
if (f != null) {
|
if (f != null) {
|
||||||
List<dynamic> list = jsonDecode(f.readAsStringSync());
|
final List<dynamic> list = jsonDecode(f.readAsStringSync());
|
||||||
patches = list.map((patch) => Patch.fromJson(patch)).toList();
|
patches = list.map((patch) => Patch.fromJson(patch)).toList();
|
||||||
}
|
}
|
||||||
} on Exception catch (e, s) {
|
} on Exception catch (e) {
|
||||||
await Sentry.captureException(e, stackTrace: s);
|
if (kDebugMode) {
|
||||||
return List.empty();
|
print(e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return patches;
|
return patches;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<String> getLastestReleaseVersion(String repoName) async {
|
Future<String> getLastestReleaseVersion(String repoName) async {
|
||||||
try {
|
try {
|
||||||
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 {
|
||||||
return 'Unknown';
|
return 'Unknown';
|
||||||
}
|
}
|
||||||
} on Exception catch (e, s) {
|
} on Exception catch (e) {
|
||||||
await Sentry.captureException(e, stackTrace: s);
|
if (kDebugMode) {
|
||||||
return '';
|
print(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
return 'Unknown';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
import 'package:device_apps/device_apps.dart';
|
import 'package:device_apps/device_apps.dart';
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:injectable/injectable.dart';
|
import 'package:injectable/injectable.dart';
|
||||||
import 'package:package_info_plus/package_info_plus.dart';
|
import 'package:package_info_plus/package_info_plus.dart';
|
||||||
import 'package:path_provider/path_provider.dart';
|
import 'package:path_provider/path_provider.dart';
|
||||||
@@ -10,7 +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:sentry_flutter/sentry_flutter.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
|
||||||
@@ -20,8 +21,12 @@ class ManagerAPI {
|
|||||||
final RootAPI _rootAPI = RootAPI();
|
final RootAPI _rootAPI = RootAPI();
|
||||||
final String patcherRepo = 'revanced-patcher';
|
final String patcherRepo = 'revanced-patcher';
|
||||||
final String cliRepo = 'revanced-cli';
|
final String cliRepo = 'revanced-cli';
|
||||||
late String storedPatchesFile = '/selected-patches.json';
|
|
||||||
late SharedPreferences _prefs;
|
late SharedPreferences _prefs;
|
||||||
|
bool isRooted = false;
|
||||||
|
String storedPatchesFile = '/selected-patches.json';
|
||||||
|
String keystoreFile =
|
||||||
|
'/sdcard/Android/data/app.revanced.manager.flutter/files/revanced-manager.keystore';
|
||||||
|
String defaultKeystorePassword = 's3cur3p@ssw0rd';
|
||||||
String defaultApiUrl = 'https://releases.revanced.app/';
|
String 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;
|
||||||
}
|
}
|
||||||
@@ -98,14 +108,6 @@ class ManagerAPI {
|
|||||||
await _prefs.setBool('useDarkTheme', value);
|
await _prefs.setBool('useDarkTheme', value);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool isSentryEnabled() {
|
|
||||||
return _prefs.getBool('sentryEnabled') ?? true;
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> setSentryStatus(bool value) async {
|
|
||||||
await _prefs.setBool('sentryEnabled', value);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool areUniversalPatchesEnabled() {
|
bool areUniversalPatchesEnabled() {
|
||||||
return _prefs.getBool('universalPatchesEnabled') ?? false;
|
return _prefs.getBool('universalPatchesEnabled') ?? false;
|
||||||
}
|
}
|
||||||
@@ -122,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()) {
|
||||||
@@ -131,29 +141,34 @@ 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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
List<PatchedApplication> getPatchedApps() {
|
List<PatchedApplication> getPatchedApps() {
|
||||||
List<String> apps = _prefs.getStringList('patchedApps') ?? [];
|
final List<String> apps = _prefs.getStringList('patchedApps') ?? [];
|
||||||
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));
|
||||||
}
|
}
|
||||||
await _prefs.setStringList('patchedApps',
|
await _prefs.setStringList(
|
||||||
patchedApps.map((a) => json.encode(a.toJson())).toList());
|
'patchedApps',
|
||||||
|
patchedApps.map((a) => json.encode(a.toJson())).toList(),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> savePatchedApp(PatchedApplication app) async {
|
Future<void> savePatchedApp(PatchedApplication app) async {
|
||||||
List<PatchedApplication> patchedApps = getPatchedApps();
|
final List<PatchedApplication> patchedApps = getPatchedApps();
|
||||||
patchedApps.removeWhere((a) => a.packageName == app.packageName);
|
patchedApps.removeWhere((a) => a.packageName == app.packageName);
|
||||||
ApplicationWithIcon? installed = await DeviceApps.getApp(
|
final ApplicationWithIcon? installed = await DeviceApps.getApp(
|
||||||
app.packageName,
|
app.packageName,
|
||||||
true,
|
true,
|
||||||
) as ApplicationWithIcon?;
|
) as ApplicationWithIcon?;
|
||||||
@@ -167,17 +182,19 @@ class ManagerAPI {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<void> deletePatchedApp(PatchedApplication app) async {
|
Future<void> deletePatchedApp(PatchedApplication app) async {
|
||||||
List<PatchedApplication> patchedApps = getPatchedApps();
|
final List<PatchedApplication> patchedApps = getPatchedApps();
|
||||||
patchedApps.removeWhere((a) => a.packageName == app.packageName);
|
patchedApps.removeWhere((a) => a.packageName == app.packageName);
|
||||||
await setPatchedApps(patchedApps);
|
await setPatchedApps(patchedApps);
|
||||||
}
|
}
|
||||||
|
|
||||||
void clearAllData() async {
|
Future<void> clearAllData() async {
|
||||||
try {
|
try {
|
||||||
_revancedAPI.clearAllCache();
|
_revancedAPI.clearAllCache();
|
||||||
_githubAPI.clearAllCache();
|
_githubAPI.clearAllCache();
|
||||||
} on Exception catch (e, s) {
|
} on Exception catch (e) {
|
||||||
await Sentry.captureException(e, stackTrace: s);
|
if (kDebugMode) {
|
||||||
|
print(e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -187,21 +204,23 @@ class ManagerAPI {
|
|||||||
|
|
||||||
Future<List<Patch>> getPatches() async {
|
Future<List<Patch>> getPatches() async {
|
||||||
try {
|
try {
|
||||||
String repoName = getPatchesRepo();
|
final String repoName = getPatchesRepo();
|
||||||
if (repoName == defaultPatchesRepo) {
|
if (repoName == defaultPatchesRepo) {
|
||||||
return await _revancedAPI.getPatches();
|
return await _revancedAPI.getPatches();
|
||||||
} else {
|
} else {
|
||||||
return await _githubAPI.getPatches(repoName);
|
return await _githubAPI.getPatches(repoName);
|
||||||
}
|
}
|
||||||
} on Exception catch (e, s) {
|
} on Exception catch (e) {
|
||||||
await Sentry.captureException(e, stackTrace: s);
|
if (kDebugMode) {
|
||||||
|
print(e);
|
||||||
|
}
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<File?> downloadPatches() async {
|
Future<File?> downloadPatches() async {
|
||||||
try {
|
try {
|
||||||
String repoName = getPatchesRepo();
|
final String repoName = getPatchesRepo();
|
||||||
if (repoName == defaultPatchesRepo) {
|
if (repoName == defaultPatchesRepo) {
|
||||||
return await _revancedAPI.getLatestReleaseFile(
|
return await _revancedAPI.getLatestReleaseFile(
|
||||||
'.jar',
|
'.jar',
|
||||||
@@ -210,15 +229,17 @@ class ManagerAPI {
|
|||||||
} else {
|
} else {
|
||||||
return await _githubAPI.getLatestReleaseFile('.jar', repoName);
|
return await _githubAPI.getLatestReleaseFile('.jar', repoName);
|
||||||
}
|
}
|
||||||
} on Exception catch (e, s) {
|
} on Exception catch (e) {
|
||||||
await Sentry.captureException(e, stackTrace: s);
|
if (kDebugMode) {
|
||||||
|
print(e);
|
||||||
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<File?> downloadIntegrations() async {
|
Future<File?> downloadIntegrations() async {
|
||||||
try {
|
try {
|
||||||
String repoName = getIntegrationsRepo();
|
final String repoName = getIntegrationsRepo();
|
||||||
if (repoName == defaultIntegrationsRepo) {
|
if (repoName == defaultIntegrationsRepo) {
|
||||||
return await _revancedAPI.getLatestReleaseFile(
|
return await _revancedAPI.getLatestReleaseFile(
|
||||||
'.apk',
|
'.apk',
|
||||||
@@ -227,22 +248,33 @@ class ManagerAPI {
|
|||||||
} else {
|
} else {
|
||||||
return await _githubAPI.getLatestReleaseFile('.apk', repoName);
|
return await _githubAPI.getLatestReleaseFile('.apk', repoName);
|
||||||
}
|
}
|
||||||
} on Exception catch (e, s) {
|
} on Exception catch (e) {
|
||||||
await Sentry.captureException(e, stackTrace: s);
|
if (kDebugMode) {
|
||||||
|
print(e);
|
||||||
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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 {
|
||||||
@@ -260,16 +292,29 @@ class ManagerAPI {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<String> getCurrentManagerVersion() async {
|
Future<String> getCurrentManagerVersion() async {
|
||||||
PackageInfo packageInfo = await PackageInfo.fromPlatform();
|
final PackageInfo packageInfo = await PackageInfo.fromPlatform();
|
||||||
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 {
|
||||||
List<PatchedApplication> toRemove = [];
|
final List<PatchedApplication> toRemove = [];
|
||||||
for (PatchedApplication app in patchedApps) {
|
for (final PatchedApplication app in patchedApps) {
|
||||||
bool isRemove = await isAppUninstalled(app);
|
final bool isRemove = await isAppUninstalled(app);
|
||||||
if (isRemove) {
|
if (isRemove) {
|
||||||
toRemove.add(app);
|
toRemove.add(app);
|
||||||
}
|
}
|
||||||
@@ -280,13 +325,13 @@ class ManagerAPI {
|
|||||||
Future<List<PatchedApplication>> getUnsavedApps(
|
Future<List<PatchedApplication>> getUnsavedApps(
|
||||||
List<PatchedApplication> patchedApps,
|
List<PatchedApplication> patchedApps,
|
||||||
) async {
|
) async {
|
||||||
List<PatchedApplication> unsavedApps = [];
|
final List<PatchedApplication> unsavedApps = [];
|
||||||
bool hasRootPermissions = await _rootAPI.hasRootPermissions();
|
final bool hasRootPermissions = await _rootAPI.hasRootPermissions();
|
||||||
if (hasRootPermissions) {
|
if (hasRootPermissions) {
|
||||||
List<String> installedApps = await _rootAPI.getInstalledApps();
|
final List<String> installedApps = await _rootAPI.getInstalledApps();
|
||||||
for (String packageName in installedApps) {
|
for (final String packageName in installedApps) {
|
||||||
if (!patchedApps.any((app) => app.packageName == packageName)) {
|
if (!patchedApps.any((app) => app.packageName == packageName)) {
|
||||||
ApplicationWithIcon? application = await DeviceApps.getApp(
|
final ApplicationWithIcon? application = await DeviceApps.getApp(
|
||||||
packageName,
|
packageName,
|
||||||
true,
|
true,
|
||||||
) as ApplicationWithIcon?;
|
) as ApplicationWithIcon?;
|
||||||
@@ -307,15 +352,13 @@ class ManagerAPI {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
List<Application> userApps = await DeviceApps.getInstalledApplications(
|
final List<Application> userApps =
|
||||||
includeSystemApps: false,
|
await DeviceApps.getInstalledApplications();
|
||||||
includeAppIcons: false,
|
for (final Application app in userApps) {
|
||||||
);
|
|
||||||
for (Application app in userApps) {
|
|
||||||
if (app.packageName.startsWith('app.revanced') &&
|
if (app.packageName.startsWith('app.revanced') &&
|
||||||
!app.packageName.startsWith('app.revanced.manager.') &&
|
!app.packageName.startsWith('app.revanced.manager.') &&
|
||||||
!patchedApps.any((uapp) => uapp.packageName == app.packageName)) {
|
!patchedApps.any((uapp) => uapp.packageName == app.packageName)) {
|
||||||
ApplicationWithIcon? application = await DeviceApps.getApp(
|
final ApplicationWithIcon? application = await DeviceApps.getApp(
|
||||||
app.packageName,
|
app.packageName,
|
||||||
true,
|
true,
|
||||||
) as ApplicationWithIcon?;
|
) as ApplicationWithIcon?;
|
||||||
@@ -329,7 +372,6 @@ class ManagerAPI {
|
|||||||
apkFilePath: application.apkFilePath,
|
apkFilePath: application.apkFilePath,
|
||||||
icon: application.icon,
|
icon: application.icon,
|
||||||
patchDate: DateTime.now(),
|
patchDate: DateTime.now(),
|
||||||
isRooted: false,
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -339,25 +381,29 @@ class ManagerAPI {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<void> reAssessSavedApps() async {
|
Future<void> reAssessSavedApps() async {
|
||||||
List<PatchedApplication> patchedApps = getPatchedApps();
|
final List<PatchedApplication> patchedApps = getPatchedApps();
|
||||||
List<PatchedApplication> unsavedApps = await getUnsavedApps(patchedApps);
|
final List<PatchedApplication> unsavedApps =
|
||||||
|
await getUnsavedApps(patchedApps);
|
||||||
patchedApps.addAll(unsavedApps);
|
patchedApps.addAll(unsavedApps);
|
||||||
List<PatchedApplication> toRemove = await getAppsToRemove(patchedApps);
|
final List<PatchedApplication> toRemove =
|
||||||
|
await getAppsToRemove(patchedApps);
|
||||||
patchedApps.removeWhere((a) => toRemove.contains(a));
|
patchedApps.removeWhere((a) => toRemove.contains(a));
|
||||||
for (PatchedApplication app in patchedApps) {
|
for (final PatchedApplication app in patchedApps) {
|
||||||
app.hasUpdates =
|
app.hasUpdates =
|
||||||
await hasAppUpdates(app.originalPackageName, app.patchDate);
|
await hasAppUpdates(app.originalPackageName, app.patchDate);
|
||||||
app.changelog =
|
app.changelog =
|
||||||
await getAppChangelog(app.originalPackageName, app.patchDate);
|
await getAppChangelog(app.originalPackageName, app.patchDate);
|
||||||
if (!app.hasUpdates) {
|
if (!app.hasUpdates) {
|
||||||
String? currentInstalledVersion =
|
final String? currentInstalledVersion =
|
||||||
(await DeviceApps.getApp(app.packageName))?.versionName;
|
(await DeviceApps.getApp(app.packageName))?.versionName;
|
||||||
if (currentInstalledVersion != null) {
|
if (currentInstalledVersion != null) {
|
||||||
String currentSavedVersion = app.version;
|
final String currentSavedVersion = app.version;
|
||||||
int currentInstalledVersionInt = int.parse(
|
final int currentInstalledVersionInt = int.parse(
|
||||||
currentInstalledVersion.replaceAll(RegExp('[^0-9]'), ''));
|
currentInstalledVersion.replaceAll(RegExp('[^0-9]'), ''),
|
||||||
int currentSavedVersionInt =
|
);
|
||||||
int.parse(currentSavedVersion.replaceAll(RegExp('[^0-9]'), ''));
|
final int currentSavedVersionInt = int.parse(
|
||||||
|
currentSavedVersion.replaceAll(RegExp('[^0-9]'), ''),
|
||||||
|
);
|
||||||
if (currentInstalledVersionInt > currentSavedVersionInt) {
|
if (currentInstalledVersionInt > currentSavedVersionInt) {
|
||||||
app.hasUpdates = true;
|
app.hasUpdates = true;
|
||||||
}
|
}
|
||||||
@@ -369,9 +415,9 @@ class ManagerAPI {
|
|||||||
|
|
||||||
Future<bool> isAppUninstalled(PatchedApplication app) async {
|
Future<bool> isAppUninstalled(PatchedApplication app) async {
|
||||||
bool existsRoot = false;
|
bool existsRoot = false;
|
||||||
bool existsNonRoot = await DeviceApps.isAppInstalled(app.packageName);
|
final bool existsNonRoot = await DeviceApps.isAppInstalled(app.packageName);
|
||||||
if (app.isRooted) {
|
if (app.isRooted) {
|
||||||
bool hasRootPermissions = await _rootAPI.hasRootPermissions();
|
final bool hasRootPermissions = await _rootAPI.hasRootPermissions();
|
||||||
if (hasRootPermissions) {
|
if (hasRootPermissions) {
|
||||||
existsRoot = await _rootAPI.isAppInstalled(app.packageName);
|
existsRoot = await _rootAPI.isAppInstalled(app.packageName);
|
||||||
}
|
}
|
||||||
@@ -380,8 +426,11 @@ class ManagerAPI {
|
|||||||
return !existsNonRoot;
|
return !existsNonRoot;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<bool> hasAppUpdates(String packageName, DateTime patchDate) async {
|
Future<bool> hasAppUpdates(
|
||||||
List<String> commits = await _githubAPI.getCommits(
|
String packageName,
|
||||||
|
DateTime patchDate,
|
||||||
|
) async {
|
||||||
|
final List<String> commits = await _githubAPI.getCommits(
|
||||||
packageName,
|
packageName,
|
||||||
getPatchesRepo(),
|
getPatchesRepo(),
|
||||||
patchDate,
|
patchDate,
|
||||||
@@ -390,7 +439,9 @@ class ManagerAPI {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<List<String>> getAppChangelog(
|
Future<List<String>> getAppChangelog(
|
||||||
String packageName, DateTime patchDate) async {
|
String packageName,
|
||||||
|
DateTime patchDate,
|
||||||
|
) async {
|
||||||
List<String> newCommits = await _githubAPI.getCommits(
|
List<String> newCommits = await _githubAPI.getCommits(
|
||||||
packageName,
|
packageName,
|
||||||
getPatchesRepo(),
|
getPatchesRepo(),
|
||||||
@@ -416,38 +467,59 @@ 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);
|
||||||
Map<String, dynamic> patchesMap = await readSelectedPatchesFile();
|
final Map<String, dynamic> patchesMap = await readSelectedPatchesFile();
|
||||||
if (patches.isEmpty) {
|
if (patches.isEmpty) {
|
||||||
patchesMap.remove(app);
|
patchesMap.remove(app);
|
||||||
} else {
|
} else {
|
||||||
patchesMap[app] = patches;
|
patchesMap[app] = patches;
|
||||||
}
|
}
|
||||||
if (selectedPatchesFile.existsSync()) {
|
|
||||||
selectedPatchesFile.createSync(recursive: true);
|
|
||||||
}
|
|
||||||
selectedPatchesFile.writeAsString(jsonEncode(patchesMap));
|
selectedPatchesFile.writeAsString(jsonEncode(patchesMap));
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<List<String>> getSelectedPatches(String app) async {
|
// get default patches for app
|
||||||
Map<String, dynamic> patchesMap = await readSelectedPatchesFile();
|
Future<List<String>> getDefaultPatches() async {
|
||||||
if (patchesMap.isNotEmpty) {
|
final List<Patch> patches = await getPatches();
|
||||||
final List<String> patches =
|
final List<String> defaultPatches = [];
|
||||||
List.from(patchesMap.putIfAbsent(app, () => List.empty()));
|
if (areExperimentalPatchesEnabled() == false) {
|
||||||
return patches;
|
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 List.empty();
|
return defaultPatches;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<List<String>> getSelectedPatches(String app) async {
|
||||||
|
final Map<String, dynamic> patchesMap = await readSelectedPatchesFile();
|
||||||
|
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 {
|
||||||
final File selectedPatchesFile = File(storedPatchesFile);
|
final File selectedPatchesFile = File(storedPatchesFile);
|
||||||
if (selectedPatchesFile.existsSync()) {
|
if (!selectedPatchesFile.existsSync()) {
|
||||||
String string = selectedPatchesFile.readAsStringSync();
|
return {};
|
||||||
if (string.trim().isEmpty) return {};
|
|
||||||
return json.decode(string);
|
|
||||||
}
|
}
|
||||||
return {};
|
final String string = selectedPatchesFile.readAsStringSync();
|
||||||
|
if (string.trim().isEmpty) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
return jsonDecode(string);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> resetLastSelectedPatches() async {
|
Future<void> resetLastSelectedPatches() async {
|
||||||
|
|||||||
@@ -1,7 +1,10 @@
|
|||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:app_installer/app_installer.dart';
|
import 'package:app_installer/app_installer.dart';
|
||||||
import 'package:collection/collection.dart';
|
import 'package:collection/collection.dart';
|
||||||
|
import 'package:cr_file_saver/file_saver.dart';
|
||||||
import 'package:device_apps/device_apps.dart';
|
import 'package:device_apps/device_apps.dart';
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:injectable/injectable.dart';
|
import 'package:injectable/injectable.dart';
|
||||||
import 'package:path_provider/path_provider.dart';
|
import 'package:path_provider/path_provider.dart';
|
||||||
@@ -10,9 +13,7 @@ import 'package:revanced_manager/models/patch.dart';
|
|||||||
import 'package:revanced_manager/models/patched_application.dart';
|
import 'package:revanced_manager/models/patched_application.dart';
|
||||||
import 'package:revanced_manager/services/manager_api.dart';
|
import 'package:revanced_manager/services/manager_api.dart';
|
||||||
import 'package:revanced_manager/services/root_api.dart';
|
import 'package:revanced_manager/services/root_api.dart';
|
||||||
import 'package:sentry_flutter/sentry_flutter.dart';
|
|
||||||
import 'package:share_extend/share_extend.dart';
|
import 'package:share_extend/share_extend.dart';
|
||||||
import 'package:cr_file_saver/file_saver.dart';
|
|
||||||
|
|
||||||
@lazySingleton
|
@lazySingleton
|
||||||
class PatcherAPI {
|
class PatcherAPI {
|
||||||
@@ -29,7 +30,7 @@ class PatcherAPI {
|
|||||||
|
|
||||||
Future<void> initialize() async {
|
Future<void> initialize() async {
|
||||||
await _loadPatches();
|
await _loadPatches();
|
||||||
Directory appCache = await getTemporaryDirectory();
|
final Directory appCache = await getTemporaryDirectory();
|
||||||
_dataDir = await getExternalStorageDirectory() ?? appCache;
|
_dataDir = await getExternalStorageDirectory() ?? appCache;
|
||||||
_tmpDir = Directory('${appCache.path}/patcher');
|
_tmpDir = Directory('${appCache.path}/patcher');
|
||||||
_keyStoreFile = File('${_dataDir.path}/revanced-manager.keystore');
|
_keyStoreFile = File('${_dataDir.path}/revanced-manager.keystore');
|
||||||
@@ -47,24 +48,29 @@ class PatcherAPI {
|
|||||||
if (_patches.isEmpty) {
|
if (_patches.isEmpty) {
|
||||||
_patches = await _managerAPI.getPatches();
|
_patches = await _managerAPI.getPatches();
|
||||||
}
|
}
|
||||||
} on Exception catch (e, s) {
|
} on Exception catch (e) {
|
||||||
await Sentry.captureException(e, stackTrace: s);
|
if (kDebugMode) {
|
||||||
|
print(e);
|
||||||
|
}
|
||||||
_patches = List.empty();
|
_patches = List.empty();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<List<ApplicationWithIcon>> getFilteredInstalledApps(bool showUniversalPatches) async {
|
Future<List<ApplicationWithIcon>> getFilteredInstalledApps(
|
||||||
List<ApplicationWithIcon> filteredApps = [];
|
bool showUniversalPatches,
|
||||||
bool? allAppsIncluded =
|
) async {
|
||||||
_patches.any((patch) => patch.compatiblePackages.isEmpty) && showUniversalPatches;
|
final List<ApplicationWithIcon> filteredApps = [];
|
||||||
|
final bool allAppsIncluded =
|
||||||
|
_patches.any((patch) => patch.compatiblePackages.isEmpty) &&
|
||||||
|
showUniversalPatches;
|
||||||
if (allAppsIncluded) {
|
if (allAppsIncluded) {
|
||||||
var allPackages = await DeviceApps.getInstalledApplications(
|
final allPackages = await DeviceApps.getInstalledApplications(
|
||||||
includeAppIcons: true,
|
includeAppIcons: true,
|
||||||
onlyAppsWithLaunchIntent: true,
|
onlyAppsWithLaunchIntent: true,
|
||||||
);
|
);
|
||||||
allPackages.forEach((pkg) async {
|
for (final pkg in allPackages) {
|
||||||
if (!filteredApps.any((app) => app.packageName == pkg.packageName)) {
|
if (!filteredApps.any((app) => app.packageName == pkg.packageName)) {
|
||||||
var appInfo = await DeviceApps.getApp(
|
final appInfo = await DeviceApps.getApp(
|
||||||
pkg.packageName,
|
pkg.packageName,
|
||||||
true,
|
true,
|
||||||
) as ApplicationWithIcon?;
|
) as ApplicationWithIcon?;
|
||||||
@@ -72,13 +78,13 @@ class PatcherAPI {
|
|||||||
filteredApps.add(appInfo);
|
filteredApps.add(appInfo);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
for (Patch patch in _patches) {
|
for (final Patch patch in _patches) {
|
||||||
for (Package package in patch.compatiblePackages) {
|
for (final Package package in patch.compatiblePackages) {
|
||||||
try {
|
try {
|
||||||
if (!filteredApps.any((app) => app.packageName == package.name)) {
|
if (!filteredApps.any((app) => app.packageName == package.name)) {
|
||||||
ApplicationWithIcon? app = await DeviceApps.getApp(
|
final ApplicationWithIcon? app = await DeviceApps.getApp(
|
||||||
package.name,
|
package.name,
|
||||||
true,
|
true,
|
||||||
) as ApplicationWithIcon?;
|
) as ApplicationWithIcon?;
|
||||||
@@ -86,9 +92,10 @@ class PatcherAPI {
|
|||||||
filteredApps.add(app);
|
filteredApps.add(app);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} on Exception catch (e, s) {
|
} on Exception catch (e) {
|
||||||
await Sentry.captureException(e, stackTrace: s);
|
if (kDebugMode) {
|
||||||
continue;
|
print(e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -97,44 +104,31 @@ class PatcherAPI {
|
|||||||
|
|
||||||
List<Patch> getFilteredPatches(String packageName) {
|
List<Patch> getFilteredPatches(String packageName) {
|
||||||
if (!filteredPatches.keys.contains(packageName)) {
|
if (!filteredPatches.keys.contains(packageName)) {
|
||||||
List<Patch> patches = _patches
|
final List<Patch> patches = _patches
|
||||||
.where((patch) =>
|
.where(
|
||||||
patch.compatiblePackages.isEmpty ||
|
(patch) =>
|
||||||
!patch.name.contains('settings') &&
|
patch.compatiblePackages.isEmpty ||
|
||||||
patch.compatiblePackages
|
!patch.name.contains('settings') &&
|
||||||
.any((pack) => pack.name == packageName))
|
patch.compatiblePackages
|
||||||
|
.any((pack) => pack.name == packageName),
|
||||||
|
)
|
||||||
.toList();
|
.toList();
|
||||||
filteredPatches[packageName] = patches;
|
filteredPatches[packageName] = patches;
|
||||||
}
|
}
|
||||||
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();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool dependencyNeedsIntegrations(String name) {
|
Future<bool> needsResourcePatching(
|
||||||
return name.contains('integrations') ||
|
List<Patch> selectedPatches,
|
||||||
_patches.any(
|
) async {
|
||||||
(patch) =>
|
|
||||||
patch.name == name &&
|
|
||||||
(patch.dependencies.any(
|
|
||||||
(dep) => dependencyNeedsIntegrations(dep),
|
|
||||||
)),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<bool> needsIntegrations(List<Patch> selectedPatches) async {
|
|
||||||
return selectedPatches.any(
|
|
||||||
(patch) => patch.dependencies.any(
|
|
||||||
(dep) => dependencyNeedsIntegrations(dep),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
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-'),
|
||||||
@@ -155,7 +149,7 @@ class PatcherAPI {
|
|||||||
String originalFilePath,
|
String originalFilePath,
|
||||||
) async {
|
) async {
|
||||||
try {
|
try {
|
||||||
bool hasRootPermissions = await _rootAPI.hasRootPermissions();
|
final bool hasRootPermissions = await _rootAPI.hasRootPermissions();
|
||||||
if (hasRootPermissions) {
|
if (hasRootPermissions) {
|
||||||
originalFilePath = await _rootAPI.getOriginalFilePath(
|
originalFilePath = await _rootAPI.getOriginalFilePath(
|
||||||
packageName,
|
packageName,
|
||||||
@@ -163,8 +157,10 @@ class PatcherAPI {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
return originalFilePath;
|
return originalFilePath;
|
||||||
} on Exception catch (e, s) {
|
} on Exception catch (e) {
|
||||||
await Sentry.captureException(e, stackTrace: s);
|
if (kDebugMode) {
|
||||||
|
print(e);
|
||||||
|
}
|
||||||
return originalFilePath;
|
return originalFilePath;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -174,11 +170,10 @@ class PatcherAPI {
|
|||||||
String originalFilePath,
|
String originalFilePath,
|
||||||
List<Patch> selectedPatches,
|
List<Patch> selectedPatches,
|
||||||
) async {
|
) async {
|
||||||
bool mergeIntegrations = await needsIntegrations(selectedPatches);
|
final bool includeSettings = await needsSettingsPatch(selectedPatches);
|
||||||
bool includeSettings = await needsSettingsPatch(selectedPatches);
|
|
||||||
if (includeSettings) {
|
if (includeSettings) {
|
||||||
try {
|
try {
|
||||||
Patch? settingsPatch = _patches.firstWhereOrNull(
|
final Patch? settingsPatch = _patches.firstWhereOrNull(
|
||||||
(patch) =>
|
(patch) =>
|
||||||
patch.name.contains('settings') &&
|
patch.name.contains('settings') &&
|
||||||
patch.compatiblePackages.any((pack) => pack.name == packageName),
|
patch.compatiblePackages.any((pack) => pack.name == packageName),
|
||||||
@@ -186,24 +181,22 @@ class PatcherAPI {
|
|||||||
if (settingsPatch != null) {
|
if (settingsPatch != null) {
|
||||||
selectedPatches.add(settingsPatch);
|
selectedPatches.add(settingsPatch);
|
||||||
}
|
}
|
||||||
} on Exception catch (e, s) {
|
} on Exception catch (e) {
|
||||||
await Sentry.captureException(e, stackTrace: s);
|
if (kDebugMode) {
|
||||||
// ignore
|
print(e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
File? patchBundleFile = await _managerAPI.downloadPatches();
|
final File? patchBundleFile = await _managerAPI.downloadPatches();
|
||||||
File? integrationsFile;
|
final File? integrationsFile = await _managerAPI.downloadIntegrations();
|
||||||
if (mergeIntegrations) {
|
|
||||||
integrationsFile = await _managerAPI.downloadIntegrations();
|
|
||||||
}
|
|
||||||
if (patchBundleFile != null) {
|
if (patchBundleFile != null) {
|
||||||
_dataDir.createSync();
|
_dataDir.createSync();
|
||||||
_tmpDir.createSync();
|
_tmpDir.createSync();
|
||||||
Directory workDir = _tmpDir.createTempSync('tmp-');
|
final Directory workDir = _tmpDir.createTempSync('tmp-');
|
||||||
File inputFile = File('${workDir.path}/base.apk');
|
final File inputFile = File('${workDir.path}/base.apk');
|
||||||
File patchedFile = File('${workDir.path}/patched.apk');
|
final File patchedFile = File('${workDir.path}/patched.apk');
|
||||||
_outFile = File('${workDir.path}/out.apk');
|
_outFile = File('${workDir.path}/out.apk');
|
||||||
Directory cacheDir = Directory('${workDir.path}/cache');
|
final Directory cacheDir = Directory('${workDir.path}/cache');
|
||||||
cacheDir.createSync();
|
cacheDir.createSync();
|
||||||
try {
|
try {
|
||||||
await patcherChannel.invokeMethod(
|
await patcherChannel.invokeMethod(
|
||||||
@@ -217,16 +210,17 @@ class PatcherAPI {
|
|||||||
'inputFilePath': inputFile.path,
|
'inputFilePath': inputFile.path,
|
||||||
'patchedFilePath': patchedFile.path,
|
'patchedFilePath': patchedFile.path,
|
||||||
'outFilePath': _outFile!.path,
|
'outFilePath': _outFile!.path,
|
||||||
'integrationsPath': mergeIntegrations ? integrationsFile!.path : '',
|
'integrationsPath': integrationsFile!.path,
|
||||||
'selectedPatches': selectedPatches.map((p) => p.name).toList(),
|
'selectedPatches': selectedPatches.map((p) => p.name).toList(),
|
||||||
'cacheDirPath': cacheDir.path,
|
'cacheDirPath': cacheDir.path,
|
||||||
'mergeIntegrations': mergeIntegrations,
|
|
||||||
'keyStoreFilePath': _keyStoreFile.path,
|
'keyStoreFilePath': _keyStoreFile.path,
|
||||||
|
'keystorePassword': _managerAPI.getKeystorePassword(),
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
} on Exception catch (e, s) {
|
} on Exception catch (e) {
|
||||||
print(e);
|
if (kDebugMode) {
|
||||||
throw await Sentry.captureException(e, stackTrace: s);
|
print(e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -235,7 +229,7 @@ class PatcherAPI {
|
|||||||
if (_outFile != null) {
|
if (_outFile != null) {
|
||||||
try {
|
try {
|
||||||
if (patchedApp.isRooted) {
|
if (patchedApp.isRooted) {
|
||||||
bool hasRootPermissions = await _rootAPI.hasRootPermissions();
|
final bool hasRootPermissions = await _rootAPI.hasRootPermissions();
|
||||||
if (hasRootPermissions) {
|
if (hasRootPermissions) {
|
||||||
return _rootAPI.installApp(
|
return _rootAPI.installApp(
|
||||||
patchedApp.packageName,
|
patchedApp.packageName,
|
||||||
@@ -245,10 +239,14 @@ 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) {
|
||||||
|
if (kDebugMode) {
|
||||||
|
print(e);
|
||||||
}
|
}
|
||||||
} on Exception catch (e, s) {
|
|
||||||
await Sentry.captureException(e, stackTrace: s);
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -258,67 +256,68 @@ class PatcherAPI {
|
|||||||
void exportPatchedFile(String appName, String version) {
|
void exportPatchedFile(String appName, String version) {
|
||||||
try {
|
try {
|
||||||
if (_outFile != null) {
|
if (_outFile != null) {
|
||||||
String newName = _getFileName(appName, version);
|
final String newName = _getFileName(appName, version);
|
||||||
|
CRFileSaver.saveFileWithDialog(
|
||||||
// This is temporary workaround to populate initial file name
|
SaveFileDialogParams(
|
||||||
// ref: https://github.com/Cleveroad/cr_file_saver/issues/7
|
sourceFilePath: _outFile!.path,
|
||||||
int lastSeparator = _outFile!.path.lastIndexOf('/');
|
destinationFileName: newName,
|
||||||
String newSourcePath =
|
),
|
||||||
_outFile!.path.substring(0, lastSeparator + 1) + newName;
|
);
|
||||||
_outFile!.copySync(newSourcePath);
|
}
|
||||||
|
} on Exception catch (e) {
|
||||||
CRFileSaver.saveFileWithDialog(SaveFileDialogParams(
|
if (kDebugMode) {
|
||||||
sourceFilePath: newSourcePath, destinationFileName: newName));
|
print(e);
|
||||||
}
|
}
|
||||||
} on Exception catch (e, s) {
|
|
||||||
Sentry.captureException(e, stackTrace: s);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void sharePatchedFile(String appName, String version) {
|
void sharePatchedFile(String appName, String version) {
|
||||||
try {
|
try {
|
||||||
if (_outFile != null) {
|
if (_outFile != null) {
|
||||||
String newName = _getFileName(appName, version);
|
final String newName = _getFileName(appName, version);
|
||||||
int lastSeparator = _outFile!.path.lastIndexOf('/');
|
final int lastSeparator = _outFile!.path.lastIndexOf('/');
|
||||||
String newPath =
|
final String newPath =
|
||||||
_outFile!.path.substring(0, lastSeparator + 1) + newName;
|
_outFile!.path.substring(0, lastSeparator + 1) + newName;
|
||||||
File shareFile = _outFile!.copySync(newPath);
|
final File shareFile = _outFile!.copySync(newPath);
|
||||||
ShareExtend.share(shareFile.path, 'file');
|
ShareExtend.share(shareFile.path, 'file');
|
||||||
}
|
}
|
||||||
} on Exception catch (e, s) {
|
} on Exception catch (e) {
|
||||||
Sentry.captureException(e, stackTrace: s);
|
if (kDebugMode) {
|
||||||
|
print(e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
String _getFileName(String appName, String version) {
|
String _getFileName(String appName, String version) {
|
||||||
String prefix = appName.toLowerCase().replaceAll(' ', '-');
|
final String prefix = appName.toLowerCase().replaceAll(' ', '-');
|
||||||
String newName = '$prefix-revanced_v$version.apk';
|
final String newName = '$prefix-revanced_v$version.apk';
|
||||||
return newName;
|
return newName;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> sharePatcherLog(String logs) async {
|
Future<void> sharePatcherLog(String logs) async {
|
||||||
Directory appCache = await getTemporaryDirectory();
|
final Directory appCache = await getTemporaryDirectory();
|
||||||
Directory logDir = Directory('${appCache.path}/logs');
|
final Directory logDir = Directory('${appCache.path}/logs');
|
||||||
logDir.createSync();
|
logDir.createSync();
|
||||||
String dateTime = DateTime.now()
|
final String dateTime = DateTime.now()
|
||||||
.toIso8601String()
|
.toIso8601String()
|
||||||
.replaceAll('-', '')
|
.replaceAll('-', '')
|
||||||
.replaceAll(':', '')
|
.replaceAll(':', '')
|
||||||
.replaceAll('T', '')
|
.replaceAll('T', '')
|
||||||
.replaceAll('.', '');
|
.replaceAll('.', '');
|
||||||
File log = File('${logDir.path}/revanced-manager_patcher_$dateTime.log');
|
final File log =
|
||||||
|
File('${logDir.path}/revanced-manager_patcher_$dateTime.log');
|
||||||
log.writeAsStringSync(logs);
|
log.writeAsStringSync(logs);
|
||||||
ShareExtend.share(log.path, 'file');
|
ShareExtend.share(log.path, 'file');
|
||||||
}
|
}
|
||||||
|
|
||||||
String getRecommendedVersion(String packageName) {
|
String getSuggestedVersion(String packageName) {
|
||||||
Map<String, int> versions = {};
|
final Map<String, int> versions = {};
|
||||||
for (Patch patch in _patches) {
|
for (final Patch patch in _patches) {
|
||||||
Package? package = patch.compatiblePackages.firstWhereOrNull(
|
final Package? package = patch.compatiblePackages.firstWhereOrNull(
|
||||||
(pack) => pack.name == packageName,
|
(pack) => pack.name == packageName,
|
||||||
);
|
);
|
||||||
if (package != null) {
|
if (package != null) {
|
||||||
for (String version in package.versions) {
|
for (final String version in package.versions) {
|
||||||
versions.update(
|
versions.update(
|
||||||
version,
|
version,
|
||||||
(value) => versions[version]! + 1,
|
(value) => versions[version]! + 1,
|
||||||
@@ -328,7 +327,7 @@ class PatcherAPI {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (versions.isNotEmpty) {
|
if (versions.isNotEmpty) {
|
||||||
var entries = versions.entries.toList()
|
final entries = versions.entries.toList()
|
||||||
..sort((a, b) => a.value.compareTo(b.value));
|
..sort((a, b) => a.value.compareTo(b.value));
|
||||||
versions
|
versions
|
||||||
..clear()
|
..clear()
|
||||||
|
|||||||
@@ -1,69 +1,64 @@
|
|||||||
|
import 'dart:async';
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:collection/collection.dart';
|
import 'package:collection/collection.dart';
|
||||||
import 'package:native_dio_client/native_dio_client.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_cache_manager/flutter_cache_manager.dart';
|
import 'package:flutter_cache_manager/flutter_cache_manager.dart';
|
||||||
import 'package:injectable/injectable.dart';
|
import 'package:injectable/injectable.dart';
|
||||||
import 'package:revanced_manager/models/patch.dart';
|
import 'package:revanced_manager/models/patch.dart';
|
||||||
import 'package:revanced_manager/utils/check_for_gms.dart';
|
|
||||||
import 'package:timeago/timeago.dart';
|
import 'package:timeago/timeago.dart';
|
||||||
import 'package:sentry_flutter/sentry_flutter.dart';
|
|
||||||
import 'package:sentry_dio/sentry_dio.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 {
|
||||||
try {
|
try {
|
||||||
bool isGMSInstalled = await checkForGMS();
|
_dio = Dio(
|
||||||
|
BaseOptions(
|
||||||
if (!isGMSInstalled) {
|
|
||||||
_dio = Dio(BaseOptions(
|
|
||||||
baseUrl: apiUrl,
|
baseUrl: apiUrl,
|
||||||
));
|
),
|
||||||
print('ReVanced API: Using default engine + $isGMSInstalled');
|
|
||||||
} else {
|
|
||||||
_dio = Dio(BaseOptions(
|
|
||||||
baseUrl: apiUrl,
|
|
||||||
))
|
|
||||||
..httpClientAdapter = NativeAdapter();
|
|
||||||
print('ReVanced API: Using CronetEngine + $isGMSInstalled');
|
|
||||||
}
|
|
||||||
_dio.interceptors.add(_dioCacheManager.interceptor);
|
|
||||||
_dio.addSentry(
|
|
||||||
captureFailedRequests: true,
|
|
||||||
);
|
);
|
||||||
} on Exception catch (e, s) {
|
|
||||||
await Sentry.captureException(e, stackTrace: s);
|
_dio.interceptors.add(DioCacheInterceptor(options: _cacheOptions));
|
||||||
|
} on Exception catch (e) {
|
||||||
|
if (kDebugMode) {
|
||||||
|
print(e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> clearAllCache() async {
|
Future<void> clearAllCache() async {
|
||||||
try {
|
try {
|
||||||
await _dioCacheManager.clearAll();
|
await _cacheOptions.store!.clean();
|
||||||
} on Exception catch (e, s) {
|
} on Exception catch (e) {
|
||||||
await Sentry.captureException(e, stackTrace: s);
|
if (kDebugMode) {
|
||||||
|
print(e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<Map<String, List<dynamic>>> getContributors() async {
|
Future<Map<String, List<dynamic>>> getContributors() async {
|
||||||
Map<String, List<dynamic>> contributors = {};
|
final Map<String, List<dynamic>> contributors = {};
|
||||||
try {
|
try {
|
||||||
var response = await _dio.get('/contributors', options: _cacheOptions);
|
final response = await _dio.get('/contributors');
|
||||||
List<dynamic> repositories = response.data['repositories'];
|
final List<dynamic> repositories = response.data['repositories'];
|
||||||
for (Map<String, dynamic> repo in repositories) {
|
for (final Map<String, dynamic> repo in repositories) {
|
||||||
String name = repo['name'];
|
final String name = repo['name'];
|
||||||
contributors[name] = repo['contributors'];
|
contributors[name] = repo['contributors'];
|
||||||
}
|
}
|
||||||
} on Exception catch (e, s) {
|
} on Exception catch (e) {
|
||||||
await Sentry.captureException(e, stackTrace: s);
|
if (kDebugMode) {
|
||||||
|
print(e);
|
||||||
|
}
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
return contributors;
|
return contributors;
|
||||||
@@ -71,11 +66,13 @@ class RevancedAPI {
|
|||||||
|
|
||||||
Future<List<Patch>> getPatches() async {
|
Future<List<Patch>> getPatches() async {
|
||||||
try {
|
try {
|
||||||
var response = await _dio.get('/patches', options: _cacheOptions);
|
final response = await _dio.get('/patches');
|
||||||
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, s) {
|
} on Exception catch (e) {
|
||||||
await Sentry.captureException(e, stackTrace: s);
|
if (kDebugMode) {
|
||||||
|
print(e);
|
||||||
|
}
|
||||||
return List.empty();
|
return List.empty();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -85,15 +82,17 @@ class RevancedAPI {
|
|||||||
String repoName,
|
String repoName,
|
||||||
) async {
|
) async {
|
||||||
try {
|
try {
|
||||||
var response = await _dio.get('/tools', options: _cacheOptions);
|
final response = await _dio.get('/tools');
|
||||||
List<dynamic> tools = response.data['tools'];
|
final List<dynamic> tools = response.data['tools'];
|
||||||
return tools.firstWhereOrNull(
|
return tools.firstWhereOrNull(
|
||||||
(t) =>
|
(t) =>
|
||||||
t['repository'] == repoName &&
|
t['repository'] == repoName &&
|
||||||
(t['name'] as String).endsWith(extension),
|
(t['name'] as String).endsWith(extension),
|
||||||
);
|
);
|
||||||
} on Exception catch (e, s) {
|
} on Exception catch (e) {
|
||||||
await Sentry.captureException(e, stackTrace: s);
|
if (kDebugMode) {
|
||||||
|
print(e);
|
||||||
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -103,52 +102,99 @@ class RevancedAPI {
|
|||||||
String repoName,
|
String repoName,
|
||||||
) async {
|
) async {
|
||||||
try {
|
try {
|
||||||
Map<String, dynamic>? release = await _getLatestRelease(
|
final Map<String, dynamic>? release = await _getLatestRelease(
|
||||||
extension,
|
extension,
|
||||||
repoName,
|
repoName,
|
||||||
);
|
);
|
||||||
if (release != null) {
|
if (release != null) {
|
||||||
return release['version'];
|
return release['version'];
|
||||||
}
|
}
|
||||||
} on Exception catch (e, s) {
|
} on Exception catch (e) {
|
||||||
await Sentry.captureException(e, stackTrace: s);
|
if (kDebugMode) {
|
||||||
|
print(e);
|
||||||
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<File?> getLatestReleaseFile(String extension, String repoName) async {
|
Future<File?> getLatestReleaseFile(
|
||||||
|
String extension,
|
||||||
|
String repoName,
|
||||||
|
) async {
|
||||||
try {
|
try {
|
||||||
Map<String, dynamic>? release = await _getLatestRelease(
|
final Map<String, dynamic>? release = await _getLatestRelease(
|
||||||
extension,
|
extension,
|
||||||
repoName,
|
repoName,
|
||||||
);
|
);
|
||||||
if (release != null) {
|
if (release != null) {
|
||||||
String url = release['browser_download_url'];
|
final String url = release['browser_download_url'];
|
||||||
return await DefaultCacheManager().getSingleFile(url);
|
return await DefaultCacheManager().getSingleFile(url);
|
||||||
}
|
}
|
||||||
} on Exception catch (e, s) {
|
} on Exception catch (e) {
|
||||||
await Sentry.captureException(e, stackTrace: s);
|
if (kDebugMode) {
|
||||||
|
print(e);
|
||||||
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
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,
|
||||||
) async {
|
) async {
|
||||||
try {
|
try {
|
||||||
Map<String, dynamic>? release = await _getLatestRelease(
|
final Map<String, dynamic>? release = await _getLatestRelease(
|
||||||
extension,
|
extension,
|
||||||
repoName,
|
repoName,
|
||||||
);
|
);
|
||||||
if (release != null) {
|
if (release != null) {
|
||||||
DateTime timestamp = DateTime.parse(release['timestamp'] as String);
|
final DateTime timestamp =
|
||||||
|
DateTime.parse(release['timestamp'] as String);
|
||||||
return format(timestamp, locale: 'en_short');
|
return format(timestamp, locale: 'en_short');
|
||||||
}
|
}
|
||||||
} on Exception catch (e, s) {
|
} on Exception catch (e) {
|
||||||
await Sentry.captureException(e, stackTrace: s);
|
if (kDebugMode) {
|
||||||
|
print(e);
|
||||||
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:root/root.dart';
|
import 'package:root/root.dart';
|
||||||
import 'package:sentry_flutter/sentry_flutter.dart';
|
|
||||||
|
|
||||||
class RootAPI {
|
class RootAPI {
|
||||||
final String _managerDirPath = '/data/local/tmp/revanced-manager';
|
final String _managerDirPath = '/data/local/tmp/revanced-manager';
|
||||||
@@ -8,10 +8,12 @@ class RootAPI {
|
|||||||
|
|
||||||
Future<bool> isRooted() async {
|
Future<bool> isRooted() async {
|
||||||
try {
|
try {
|
||||||
bool? isRooted = await Root.isRootAvailable();
|
final bool? isRooted = await Root.isRootAvailable();
|
||||||
return isRooted != null && isRooted;
|
return isRooted != null && isRooted;
|
||||||
} on Exception catch (e, s) {
|
} on Exception catch (e) {
|
||||||
await Sentry.captureException(e, stackTrace: s);
|
if (kDebugMode) {
|
||||||
|
print(e);
|
||||||
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -24,8 +26,10 @@ class RootAPI {
|
|||||||
return isRooted != null && isRooted;
|
return isRooted != null && isRooted;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
} on Exception catch (e, s) {
|
} on Exception catch (e) {
|
||||||
await Sentry.captureException(e, stackTrace: s);
|
if (kDebugMode) {
|
||||||
|
print(e);
|
||||||
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -70,16 +74,18 @@ class RootAPI {
|
|||||||
|
|
||||||
Future<List<String>> getInstalledApps() async {
|
Future<List<String>> getInstalledApps() async {
|
||||||
try {
|
try {
|
||||||
String? res = await Root.exec(
|
final String? res = await Root.exec(
|
||||||
cmd: 'ls "$_managerDirPath"',
|
cmd: 'ls "$_managerDirPath"',
|
||||||
);
|
);
|
||||||
if (res != null) {
|
if (res != null) {
|
||||||
List<String> apps = res.split('\n');
|
final List<String> apps = res.split('\n');
|
||||||
apps.removeWhere((pack) => pack.isEmpty);
|
apps.removeWhere((pack) => pack.isEmpty);
|
||||||
return apps.map((pack) => pack.trim()).toList();
|
return apps.map((pack) => pack.trim()).toList();
|
||||||
}
|
}
|
||||||
} on Exception catch (e, s) {
|
} on Exception catch (e) {
|
||||||
await Sentry.captureException(e, stackTrace: s);
|
if (kDebugMode) {
|
||||||
|
print(e);
|
||||||
|
}
|
||||||
return List.empty();
|
return List.empty();
|
||||||
}
|
}
|
||||||
return List.empty();
|
return List.empty();
|
||||||
@@ -125,19 +131,21 @@ class RootAPI {
|
|||||||
await installApk(packageName, patchedFilePath);
|
await installApk(packageName, patchedFilePath);
|
||||||
await mountApk(packageName, originalFilePath);
|
await mountApk(packageName, originalFilePath);
|
||||||
return true;
|
return true;
|
||||||
} on Exception catch (e, s) {
|
} on Exception catch (e) {
|
||||||
await Sentry.captureException(e, stackTrace: s);
|
if (kDebugMode) {
|
||||||
|
print(e);
|
||||||
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> installServiceDScript(String packageName) async {
|
Future<void> installServiceDScript(String packageName) async {
|
||||||
String content = '#!/system/bin/sh\n'
|
final String content = '#!/system/bin/sh\n'
|
||||||
'while [ "\$(getprop sys.boot_completed | tr -d \'"\'"\'\\\\r\'"\'"\')" != "1" ]; do sleep 3; done\n'
|
'while [ "\$(getprop sys.boot_completed | tr -d \'"\'"\'\\\\r\'"\'"\')" != "1" ]; do sleep 3; done\n'
|
||||||
'base_path=$_managerDirPath/$packageName/base.apk\n'
|
'base_path=$_managerDirPath/$packageName/base.apk\n'
|
||||||
'stock_path=\$(pm path $packageName | grep base | sed \'"\'"\'s/package://g\'"\'"\')\n'
|
'stock_path=\$(pm path $packageName | grep base | sed \'"\'"\'s/package://g\'"\'"\')\n'
|
||||||
'[ ! -z \$stock_path ] && mount -o bind \$base_path \$stock_path';
|
r'[ ! -z $stock_path ] && mount -o bind $base_path $stock_path';
|
||||||
String scriptFilePath = '$_serviceDDirPath/$packageName.sh';
|
final String scriptFilePath = '$_serviceDDirPath/$packageName.sh';
|
||||||
await Root.exec(
|
await Root.exec(
|
||||||
cmd: 'echo \'$content\' > "$scriptFilePath"',
|
cmd: 'echo \'$content\' > "$scriptFilePath"',
|
||||||
);
|
);
|
||||||
@@ -145,10 +153,10 @@ class RootAPI {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<void> installPostFsDataScript(String packageName) async {
|
Future<void> installPostFsDataScript(String packageName) async {
|
||||||
String content = '#!/system/bin/sh\n'
|
final String content = '#!/system/bin/sh\n'
|
||||||
'stock_path=\$(pm path $packageName | grep base | sed \'"\'"\'s/package://g\'"\'"\')\n'
|
'stock_path=\$(pm path $packageName | grep base | sed \'"\'"\'s/package://g\'"\'"\')\n'
|
||||||
'[ ! -z \$stock_path ] && umount -l \$stock_path';
|
r'[ ! -z $stock_path ] && umount -l $stock_path';
|
||||||
String scriptFilePath = '$_postFsDataDirPath/$packageName.sh';
|
final String scriptFilePath = '$_postFsDataDirPath/$packageName.sh';
|
||||||
await Root.exec(
|
await Root.exec(
|
||||||
cmd: 'echo \'$content\' > "$scriptFilePath"',
|
cmd: 'echo \'$content\' > "$scriptFilePath"',
|
||||||
);
|
);
|
||||||
@@ -156,7 +164,7 @@ class RootAPI {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<void> installApk(String packageName, String patchedFilePath) async {
|
Future<void> installApk(String packageName, String patchedFilePath) async {
|
||||||
String newPatchedFilePath = '$_managerDirPath/$packageName/base.apk';
|
final String newPatchedFilePath = '$_managerDirPath/$packageName/base.apk';
|
||||||
await Root.exec(
|
await Root.exec(
|
||||||
cmd: 'cp "$patchedFilePath" "$newPatchedFilePath"',
|
cmd: 'cp "$patchedFilePath" "$newPatchedFilePath"',
|
||||||
);
|
);
|
||||||
@@ -169,7 +177,7 @@ class RootAPI {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<void> mountApk(String packageName, String originalFilePath) async {
|
Future<void> mountApk(String packageName, String originalFilePath) async {
|
||||||
String newPatchedFilePath = '$_managerDirPath/$packageName/base.apk';
|
final String newPatchedFilePath = '$_managerDirPath/$packageName/base.apk';
|
||||||
await Root.exec(
|
await Root.exec(
|
||||||
cmd: 'am force-stop "$packageName"',
|
cmd: 'am force-stop "$packageName"',
|
||||||
);
|
);
|
||||||
@@ -182,7 +190,7 @@ class RootAPI {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<bool> isMounted(String packageName) async {
|
Future<bool> isMounted(String packageName) async {
|
||||||
String? res = await Root.exec(
|
final String? res = await Root.exec(
|
||||||
cmd: 'cat /proc/mounts | grep $packageName',
|
cmd: 'cat /proc/mounts | grep $packageName',
|
||||||
);
|
);
|
||||||
return res != null && res.isNotEmpty;
|
return res != null && res.isNotEmpty;
|
||||||
@@ -192,7 +200,7 @@ class RootAPI {
|
|||||||
String packageName,
|
String packageName,
|
||||||
String originalFilePath,
|
String originalFilePath,
|
||||||
) async {
|
) async {
|
||||||
bool isInstalled = await isAppInstalled(packageName);
|
final bool isInstalled = await isAppInstalled(packageName);
|
||||||
if (isInstalled && await isMounted(packageName)) {
|
if (isInstalled && await isMounted(packageName)) {
|
||||||
originalFilePath = '$_managerDirPath/$packageName/original.apk';
|
originalFilePath = '$_managerDirPath/$packageName/original.apk';
|
||||||
await setPermissions(
|
await setPermissions(
|
||||||
@@ -209,7 +217,8 @@ class RootAPI {
|
|||||||
String packageName,
|
String packageName,
|
||||||
String originalFilePath,
|
String originalFilePath,
|
||||||
) async {
|
) async {
|
||||||
String originalRootPath = '$_managerDirPath/$packageName/original.apk';
|
final String originalRootPath =
|
||||||
|
'$_managerDirPath/$packageName/original.apk';
|
||||||
await Root.exec(
|
await Root.exec(
|
||||||
cmd: 'mkdir -p "$_managerDirPath/$packageName"',
|
cmd: 'mkdir -p "$_managerDirPath/$packageName"',
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ import 'package:google_fonts/google_fonts.dart';
|
|||||||
|
|
||||||
var lightCustomColorScheme = ColorScheme.fromSeed(
|
var lightCustomColorScheme = ColorScheme.fromSeed(
|
||||||
seedColor: Colors.blue,
|
seedColor: Colors.blue,
|
||||||
brightness: Brightness.light,
|
|
||||||
primary: const Color(0xff1B73E8),
|
primary: const Color(0xff1B73E8),
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -13,7 +12,7 @@ var lightCustomTheme = ThemeData(
|
|||||||
navigationBarTheme: NavigationBarThemeData(
|
navigationBarTheme: NavigationBarThemeData(
|
||||||
labelTextStyle: MaterialStateProperty.all(
|
labelTextStyle: MaterialStateProperty.all(
|
||||||
TextStyle(
|
TextStyle(
|
||||||
color: lightCustomColorScheme.secondary,
|
color: lightCustomColorScheme.onSurface,
|
||||||
fontWeight: FontWeight.w500,
|
fontWeight: FontWeight.w500,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -34,13 +33,12 @@ var darkCustomTheme = ThemeData(
|
|||||||
navigationBarTheme: NavigationBarThemeData(
|
navigationBarTheme: NavigationBarThemeData(
|
||||||
labelTextStyle: MaterialStateProperty.all(
|
labelTextStyle: MaterialStateProperty.all(
|
||||||
TextStyle(
|
TextStyle(
|
||||||
color: darkCustomColorScheme.secondary,
|
color: darkCustomColorScheme.onSurface,
|
||||||
fontWeight: FontWeight.w500,
|
fontWeight: FontWeight.w500,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
canvasColor: const Color(0xff1B1A1D),
|
canvasColor: const Color(0xff1B1A1D),
|
||||||
scaffoldBackgroundColor: const Color(0xff1B1A1D),
|
scaffoldBackgroundColor: const Color(0xff1B1A1D),
|
||||||
toggleableActiveColor: const Color(0xffA5CAFF),
|
|
||||||
textTheme: GoogleFonts.robotoTextTheme(ThemeData.dark().textTheme),
|
textTheme: GoogleFonts.robotoTextTheme(ThemeData.dark().textTheme),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -7,65 +7,44 @@ import 'package:revanced_manager/theme.dart';
|
|||||||
import 'package:stacked_services/stacked_services.dart';
|
import 'package:stacked_services/stacked_services.dart';
|
||||||
|
|
||||||
class DynamicThemeBuilder extends StatelessWidget {
|
class DynamicThemeBuilder extends StatelessWidget {
|
||||||
final String title;
|
|
||||||
final Widget home;
|
|
||||||
final Iterable<LocalizationsDelegate> localizationsDelegates;
|
|
||||||
|
|
||||||
const DynamicThemeBuilder({
|
const DynamicThemeBuilder({
|
||||||
Key? key,
|
Key? key,
|
||||||
required this.title,
|
required this.title,
|
||||||
required this.home,
|
required this.home,
|
||||||
required this.localizationsDelegates,
|
required this.localizationsDelegates,
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
final String title;
|
||||||
|
final Widget home;
|
||||||
|
final Iterable<LocalizationsDelegate> localizationsDelegates;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return DynamicColorBuilder(
|
return DynamicColorBuilder(
|
||||||
builder: (lightColorScheme, darkColorScheme) {
|
builder: (lightColorScheme, darkColorScheme) {
|
||||||
ThemeData lightDynamicTheme = ThemeData(
|
final ThemeData lightDynamicTheme = ThemeData(
|
||||||
useMaterial3: true,
|
useMaterial3: true,
|
||||||
canvasColor: lightColorScheme?.background,
|
|
||||||
navigationBarTheme: NavigationBarThemeData(
|
navigationBarTheme: NavigationBarThemeData(
|
||||||
backgroundColor: lightColorScheme?.background,
|
|
||||||
indicatorColor: lightColorScheme?.primary.withAlpha(150),
|
|
||||||
labelTextStyle: MaterialStateProperty.all(
|
labelTextStyle: MaterialStateProperty.all(
|
||||||
GoogleFonts.roboto(
|
GoogleFonts.roboto(
|
||||||
color: lightColorScheme?.secondary,
|
color: lightColorScheme?.onSurface,
|
||||||
fontWeight: FontWeight.w500,
|
fontWeight: FontWeight.w500,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
iconTheme: MaterialStateProperty.all(
|
|
||||||
IconThemeData(
|
|
||||||
color: lightColorScheme?.secondary,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
scaffoldBackgroundColor: lightColorScheme?.background,
|
|
||||||
colorScheme: lightColorScheme?.harmonized(),
|
colorScheme: lightColorScheme?.harmonized(),
|
||||||
toggleableActiveColor: lightColorScheme?.primary,
|
|
||||||
textTheme: GoogleFonts.robotoTextTheme(ThemeData.light().textTheme),
|
textTheme: GoogleFonts.robotoTextTheme(ThemeData.light().textTheme),
|
||||||
);
|
);
|
||||||
ThemeData darkDynamicTheme = ThemeData(
|
final ThemeData darkDynamicTheme = ThemeData(
|
||||||
useMaterial3: true,
|
useMaterial3: true,
|
||||||
canvasColor: darkColorScheme?.background,
|
|
||||||
navigationBarTheme: NavigationBarThemeData(
|
navigationBarTheme: NavigationBarThemeData(
|
||||||
backgroundColor: darkColorScheme?.background,
|
|
||||||
indicatorColor: darkColorScheme?.primary.withOpacity(0.4),
|
|
||||||
labelTextStyle: MaterialStateProperty.all(
|
labelTextStyle: MaterialStateProperty.all(
|
||||||
GoogleFonts.roboto(
|
GoogleFonts.roboto(
|
||||||
color: darkColorScheme?.secondary,
|
color: darkColorScheme?.onSurface,
|
||||||
fontWeight: FontWeight.w500,
|
fontWeight: FontWeight.w500,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
iconTheme: MaterialStateProperty.all(
|
|
||||||
IconThemeData(
|
|
||||||
color: darkColorScheme?.secondary,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
scaffoldBackgroundColor: darkColorScheme?.background,
|
|
||||||
colorScheme: darkColorScheme?.harmonized(),
|
colorScheme: darkColorScheme?.harmonized(),
|
||||||
toggleableActiveColor: darkColorScheme?.primary,
|
|
||||||
textTheme: GoogleFonts.robotoTextTheme(ThemeData.dark().textTheme),
|
textTheme: GoogleFonts.robotoTextTheme(ThemeData.dark().textTheme),
|
||||||
);
|
);
|
||||||
return DynamicTheme(
|
return DynamicTheme(
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart' hide SearchBar;
|
||||||
import 'package:flutter_i18n/flutter_i18n.dart';
|
import 'package:flutter_i18n/flutter_i18n.dart';
|
||||||
import 'package:revanced_manager/ui/widgets/appSelectorView/installed_app_item.dart';
|
|
||||||
import 'package:revanced_manager/ui/widgets/shared/search_bar.dart';
|
|
||||||
import 'package:revanced_manager/ui/widgets/appSelectorView/app_skeleton_loader.dart';
|
|
||||||
import 'package:stacked/stacked.dart' hide SkeletonLoader;
|
|
||||||
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/installed_app_item.dart';
|
||||||
|
import 'package:revanced_manager/ui/widgets/appSelectorView/not_installed_app_item.dart';
|
||||||
|
import 'package:revanced_manager/ui/widgets/shared/search_bar.dart';
|
||||||
|
import 'package:stacked/stacked.dart' hide SkeletonLoader;
|
||||||
|
|
||||||
class AppSelectorView extends StatefulWidget {
|
class AppSelectorView extends StatefulWidget {
|
||||||
const AppSelectorView({Key? key}) : super(key: key);
|
const AppSelectorView({Key? key}) : super(key: key);
|
||||||
@@ -19,7 +20,7 @@ class _AppSelectorViewState extends State<AppSelectorView> {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return ViewModelBuilder<AppSelectorViewModel>.reactive(
|
return ViewModelBuilder<AppSelectorViewModel>.reactive(
|
||||||
onModelReady: (model) => model.initialize(),
|
onViewModelReady: (model) => model.initialize(),
|
||||||
viewModelBuilder: () => AppSelectorViewModel(),
|
viewModelBuilder: () => AppSelectorViewModel(),
|
||||||
builder: (context, model, child) => Scaffold(
|
builder: (context, model, child) => Scaffold(
|
||||||
resizeToAvoidBottomInset: false,
|
resizeToAvoidBottomInset: false,
|
||||||
@@ -36,20 +37,19 @@ class _AppSelectorViewState extends State<AppSelectorView> {
|
|||||||
SliverAppBar(
|
SliverAppBar(
|
||||||
pinned: true,
|
pinned: true,
|
||||||
floating: true,
|
floating: true,
|
||||||
snap: false,
|
|
||||||
title: I18nText(
|
title: I18nText(
|
||||||
'appSelectorView.viewTitle',
|
'appSelectorView.viewTitle',
|
||||||
child: Text(
|
child: Text(
|
||||||
'',
|
'',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: Theme.of(context).textTheme.headline6!.color,
|
color: Theme.of(context).textTheme.titleLarge!.color,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
leading: IconButton(
|
leading: IconButton(
|
||||||
icon: Icon(
|
icon: Icon(
|
||||||
Icons.arrow_back,
|
Icons.arrow_back,
|
||||||
color: Theme.of(context).textTheme.headline6!.color,
|
color: Theme.of(context).textTheme.titleLarge!.color,
|
||||||
),
|
),
|
||||||
onPressed: () => Navigator.of(context).pop(),
|
onPressed: () => Navigator.of(context).pop(),
|
||||||
),
|
),
|
||||||
@@ -61,7 +61,6 @@ class _AppSelectorViewState extends State<AppSelectorView> {
|
|||||||
horizontal: 12.0,
|
horizontal: 12.0,
|
||||||
),
|
),
|
||||||
child: SearchBar(
|
child: SearchBar(
|
||||||
showSelectIcon: false,
|
|
||||||
hintText: FlutterI18n.translate(
|
hintText: FlutterI18n.translate(
|
||||||
context,
|
context,
|
||||||
'appSelectorView.searchBarHint',
|
'appSelectorView.searchBarHint',
|
||||||
@@ -78,28 +77,59 @@ class _AppSelectorViewState extends State<AppSelectorView> {
|
|||||||
SliverToBoxAdapter(
|
SliverToBoxAdapter(
|
||||||
child: model.noApps
|
child: model.noApps
|
||||||
? Center(
|
? Center(
|
||||||
child: I18nText('appSelectorCard.noAppsLabel'),
|
child: I18nText(
|
||||||
|
'appSelectorView.noAppsLabel',
|
||||||
|
child: Text(
|
||||||
|
'',
|
||||||
|
style: TextStyle(
|
||||||
|
color:
|
||||||
|
Theme.of(context).textTheme.titleLarge!.color,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
)
|
)
|
||||||
: model.apps.isEmpty
|
: model.apps.isEmpty
|
||||||
? const AppSkeletonLoader()
|
? const AppSkeletonLoader()
|
||||||
: Padding(
|
: Padding(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 12.0)
|
padding: const EdgeInsets.symmetric(horizontal: 12.0)
|
||||||
.copyWith(bottom: 80),
|
.copyWith(
|
||||||
|
bottom:
|
||||||
|
MediaQuery.of(context).viewPadding.bottom + 8.0,
|
||||||
|
),
|
||||||
child: Column(
|
child: Column(
|
||||||
children: model
|
children: [
|
||||||
.getFilteredApps(_query)
|
...model
|
||||||
.map((app) => InstalledAppItem(
|
.getFilteredApps(_query)
|
||||||
|
.map(
|
||||||
|
(app) => InstalledAppItem(
|
||||||
name: app.appName,
|
name: app.appName,
|
||||||
pkgName: app.packageName,
|
pkgName: app.packageName,
|
||||||
icon: app.icon,
|
icon: app.icon,
|
||||||
patchesCount:
|
patchesCount:
|
||||||
model.patchesCount(app.packageName),
|
model.patchesCount(app.packageName),
|
||||||
|
suggestedVersion:
|
||||||
|
model.getSuggestedVersion(
|
||||||
|
app.packageName,
|
||||||
|
),
|
||||||
|
onTap: () => model.canSelectInstalled(context, app.packageName),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.toList(),
|
||||||
|
...model
|
||||||
|
.getFilteredAppsNames(_query)
|
||||||
|
.map(
|
||||||
|
(app) => NotInstalledAppItem(
|
||||||
|
name: app,
|
||||||
|
patchesCount: model.patchesCount(app),
|
||||||
|
suggestedVersion:
|
||||||
|
model.getSuggestedVersion(app),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
model.selectApp(app);
|
model.showDownloadToast();
|
||||||
Navigator.of(context).pop();
|
|
||||||
},
|
},
|
||||||
))
|
),
|
||||||
.toList(),
|
)
|
||||||
|
.toList(),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -1,37 +1,79 @@
|
|||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:device_apps/device_apps.dart';
|
import 'package:device_apps/device_apps.dart';
|
||||||
import 'package:file_picker/file_picker.dart';
|
import 'package:file_picker/file_picker.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/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:sentry_flutter/sentry_flutter.dart';
|
import 'package:revanced_manager/ui/widgets/shared/custom_material_button.dart';
|
||||||
import 'package:stacked/stacked.dart';
|
import 'package:stacked/stacked.dart';
|
||||||
import '../../../services/manager_api.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 {
|
||||||
apps.addAll(await _patcherAPI.getFilteredInstalledApps(_managerAPI.areUniversalPatchesEnabled()));
|
patches = await _revancedAPI.getPatches();
|
||||||
apps.sort(((a, b) => _patcherAPI
|
isRooted = _managerAPI.isRooted;
|
||||||
.getFilteredPatches(b.packageName)
|
|
||||||
.length
|
apps.addAll(
|
||||||
.compareTo(_patcherAPI.getFilteredPatches(a.packageName).length)));
|
await _patcherAPI
|
||||||
|
.getFilteredInstalledApps(_managerAPI.areUniversalPatchesEnabled()),
|
||||||
|
);
|
||||||
|
apps.sort(
|
||||||
|
(a, b) => _patcherAPI
|
||||||
|
.getFilteredPatches(b.packageName)
|
||||||
|
.length
|
||||||
|
.compareTo(_patcherAPI.getFilteredPatches(a.packageName).length),
|
||||||
|
);
|
||||||
noApps = apps.isEmpty;
|
noApps = apps.isEmpty;
|
||||||
|
getAllApps();
|
||||||
|
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
void selectApp(ApplicationWithIcon application) async {
|
List<String> getAllApps() {
|
||||||
|
allApps = patches
|
||||||
|
.expand((e) => e.compatiblePackages.map((p) => p.name))
|
||||||
|
.toSet()
|
||||||
|
.where((name) => !apps.any((app) => app.packageName == name))
|
||||||
|
.toList();
|
||||||
|
|
||||||
|
return allApps;
|
||||||
|
}
|
||||||
|
|
||||||
|
String getSuggestedVersion(String packageName) {
|
||||||
|
return _patcherAPI.getSuggestedVersion(packageName);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<bool> checkSplitApk(String packageName) async {
|
||||||
|
final app = await DeviceApps.getApp(packageName);
|
||||||
|
if (app != null) {
|
||||||
|
return app.isSplit;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> selectApp(ApplicationWithIcon application) async {
|
||||||
locator<PatcherViewModel>().selectedApp = PatchedApplication(
|
locator<PatcherViewModel>().selectedApp = PatchedApplication(
|
||||||
name: application.appName,
|
name: application.appName,
|
||||||
packageName: application.packageName,
|
packageName: application.packageName,
|
||||||
@@ -42,27 +84,117 @@ class AppSelectorViewModel extends BaseViewModel {
|
|||||||
patchDate: DateTime.now(),
|
patchDate: DateTime.now(),
|
||||||
);
|
);
|
||||||
locator<PatcherViewModel>().loadLastSelectedPatches();
|
locator<PatcherViewModel>().loadLastSelectedPatches();
|
||||||
locator<PatcherViewModel>().notifyListeners();
|
}
|
||||||
|
|
||||||
|
Future<void> canSelectInstalled(
|
||||||
|
BuildContext context,
|
||||||
|
String packageName,
|
||||||
|
) async {
|
||||||
|
final app =
|
||||||
|
await DeviceApps.getApp(packageName, true) as ApplicationWithIcon?;
|
||||||
|
if (app != null) {
|
||||||
|
if (await checkSplitApk(packageName) && !isRooted) {
|
||||||
|
return showSelectFromStorageDialog(context);
|
||||||
|
} else if (!await checkSplitApk(packageName) || isRooted) {
|
||||||
|
selectApp(app);
|
||||||
|
Navigator.pop(context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future showSelectFromStorageDialog(BuildContext context) async {
|
||||||
|
return showDialog(
|
||||||
|
context: context,
|
||||||
|
builder: (context) => SimpleDialog(
|
||||||
|
alignment: Alignment.center,
|
||||||
|
contentPadding:
|
||||||
|
const EdgeInsets.symmetric(horizontal: 20, vertical: 20),
|
||||||
|
children: [
|
||||||
|
const SizedBox(height: 10),
|
||||||
|
Icon(
|
||||||
|
Icons.block,
|
||||||
|
size: 28,
|
||||||
|
color: Theme.of(context).colorScheme.primary,
|
||||||
|
),
|
||||||
|
const SizedBox(height: 20),
|
||||||
|
I18nText(
|
||||||
|
'appSelectorView.featureNotAvailable',
|
||||||
|
child: const Text(
|
||||||
|
'',
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 20,
|
||||||
|
fontWeight: FontWeight.w600,
|
||||||
|
wordSpacing: 1.5,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 20),
|
||||||
|
I18nText(
|
||||||
|
'appSelectorView.featureNotAvailableText',
|
||||||
|
child: const Text(
|
||||||
|
'',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 14,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 30),
|
||||||
|
CustomMaterialButton(
|
||||||
|
onPressed: () => selectAppFromStorage(context).then(
|
||||||
|
(_) {
|
||||||
|
Navigator.pop(context);
|
||||||
|
Navigator.pop(context);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
label: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
const Icon(Icons.sd_card),
|
||||||
|
const SizedBox(width: 10),
|
||||||
|
I18nText('appSelectorView.selectFromStorageButton'),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 10),
|
||||||
|
CustomMaterialButton(
|
||||||
|
isFilled: false,
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.pop(context);
|
||||||
|
},
|
||||||
|
label: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
const SizedBox(width: 10),
|
||||||
|
I18nText('cancelButton'),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> selectAppFromStorage(BuildContext context) async {
|
Future<void> selectAppFromStorage(BuildContext context) async {
|
||||||
try {
|
try {
|
||||||
FilePickerResult? result = await FilePicker.platform.pickFiles(
|
final FilePickerResult? result = await FilePicker.platform.pickFiles(
|
||||||
type: FileType.custom,
|
type: FileType.custom,
|
||||||
allowedExtensions: ['apk'],
|
allowedExtensions: ['apk'],
|
||||||
);
|
);
|
||||||
if (result != null && result.files.single.path != null) {
|
if (result != null && result.files.single.path != null) {
|
||||||
File apkFile = File(result.files.single.path!);
|
final File apkFile = File(result.files.single.path!);
|
||||||
List<String> pathSplit = result.files.single.path!.split("/");
|
final List<String> pathSplit = result.files.single.path!.split('/');
|
||||||
pathSplit.removeLast();
|
pathSplit.removeLast();
|
||||||
Directory filePickerCacheDir = Directory(pathSplit.join("/"));
|
final Directory filePickerCacheDir = Directory(pathSplit.join('/'));
|
||||||
Iterable<File> deletableFiles =
|
final Iterable<File> deletableFiles =
|
||||||
(await filePickerCacheDir.list().toList()).whereType<File>();
|
(await filePickerCacheDir.list().toList()).whereType<File>();
|
||||||
for (var file in deletableFiles) {
|
for (final file in deletableFiles) {
|
||||||
if (file.path != apkFile.path && file.path.endsWith(".apk"))
|
if (file.path != apkFile.path && file.path.endsWith('.apk')) {
|
||||||
file.delete();
|
file.delete();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
ApplicationWithIcon? application = await DeviceApps.getAppFromStorage(
|
final ApplicationWithIcon? application =
|
||||||
|
await DeviceApps.getAppFromStorage(
|
||||||
apkFile.path,
|
apkFile.path,
|
||||||
true,
|
true,
|
||||||
) as ApplicationWithIcon?;
|
) as ApplicationWithIcon?;
|
||||||
@@ -78,21 +210,38 @@ class AppSelectorViewModel extends BaseViewModel {
|
|||||||
isFromStorage: true,
|
isFromStorage: true,
|
||||||
);
|
);
|
||||||
locator<PatcherViewModel>().loadLastSelectedPatches();
|
locator<PatcherViewModel>().loadLastSelectedPatches();
|
||||||
locator<PatcherViewModel>().notifyListeners();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} on Exception catch (e, s) {
|
} on Exception catch (e) {
|
||||||
await Sentry.captureException(e, stackTrace: s);
|
if (kDebugMode) {
|
||||||
|
print(e);
|
||||||
|
}
|
||||||
_toast.showBottom('appSelectorView.errorMessage');
|
_toast.showBottom('appSelectorView.errorMessage');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
List<ApplicationWithIcon> getFilteredApps(String query) {
|
List<ApplicationWithIcon> getFilteredApps(String query) {
|
||||||
return apps
|
return apps
|
||||||
.where((app) =>
|
.where(
|
||||||
query.isEmpty ||
|
(app) =>
|
||||||
query.length < 2 ||
|
query.isEmpty ||
|
||||||
app.appName.toLowerCase().contains(query.toLowerCase()))
|
query.length < 2 ||
|
||||||
|
app.appName.toLowerCase().contains(query.toLowerCase()),
|
||||||
|
)
|
||||||
.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');
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ class ContributorsView extends StatelessWidget {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return ViewModelBuilder<ContributorsViewModel>.reactive(
|
return ViewModelBuilder<ContributorsViewModel>.reactive(
|
||||||
viewModelBuilder: () => ContributorsViewModel(),
|
viewModelBuilder: () => ContributorsViewModel(),
|
||||||
onModelReady: (model) => model.getContributors(),
|
onViewModelReady: (model) => model.getContributors(),
|
||||||
builder: (context, model, child) => Scaffold(
|
builder: (context, model, child) => Scaffold(
|
||||||
body: CustomScrollView(
|
body: CustomScrollView(
|
||||||
slivers: <Widget>[
|
slivers: <Widget>[
|
||||||
@@ -23,7 +23,7 @@ class ContributorsView extends StatelessWidget {
|
|||||||
child: Text(
|
child: Text(
|
||||||
'',
|
'',
|
||||||
style: GoogleFonts.inter(
|
style: GoogleFonts.inter(
|
||||||
color: Theme.of(context).textTheme.headline6!.color,
|
color: Theme.of(context).textTheme.titleLarge!.color,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -57,6 +57,7 @@ class ContributorsView extends StatelessWidget {
|
|||||||
title: 'contributorsView.managerContributors',
|
title: 'contributorsView.managerContributors',
|
||||||
contributors: model.managerContributors,
|
contributors: model.managerContributors,
|
||||||
),
|
),
|
||||||
|
SizedBox(height: MediaQuery.of(context).viewPadding.bottom)
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ class ContributorsViewModel extends BaseViewModel {
|
|||||||
List<dynamic> managerContributors = [];
|
List<dynamic> managerContributors = [];
|
||||||
|
|
||||||
Future<void> getContributors() async {
|
Future<void> getContributors() async {
|
||||||
Map<String, List<dynamic>> contributors =
|
final Map<String, List<dynamic>> contributors =
|
||||||
await _managerAPI.getContributors();
|
await _managerAPI.getContributors();
|
||||||
patcherContributors = contributors[_managerAPI.defaultPatcherRepo] ?? [];
|
patcherContributors = contributors[_managerAPI.defaultPatcherRepo] ?? [];
|
||||||
patchesContributors = contributors[_managerAPI.getPatchesRepo()] ?? [];
|
patchesContributors = contributors[_managerAPI.getPatchesRepo()] ?? [];
|
||||||
|
|||||||
@@ -1,13 +1,12 @@
|
|||||||
|
import 'package:animations/animations.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:google_fonts/google_fonts.dart';
|
import 'package:google_fonts/google_fonts.dart';
|
||||||
import 'package:animations/animations.dart';
|
|
||||||
import 'package:revanced_manager/app/app.locator.dart';
|
import 'package:revanced_manager/app/app.locator.dart';
|
||||||
import 'package:revanced_manager/ui/views/home/home_viewmodel.dart';
|
import 'package:revanced_manager/ui/views/home/home_viewmodel.dart';
|
||||||
import 'package:revanced_manager/ui/widgets/homeView/available_updates_card.dart';
|
import 'package:revanced_manager/ui/widgets/homeView/available_updates_card.dart';
|
||||||
import 'package:revanced_manager/ui/widgets/homeView/installed_apps_card.dart';
|
import 'package:revanced_manager/ui/widgets/homeView/installed_apps_card.dart';
|
||||||
import 'package:revanced_manager/ui/widgets/homeView/latest_commit_card.dart';
|
import 'package:revanced_manager/ui/widgets/homeView/latest_commit_card.dart';
|
||||||
import 'package:revanced_manager/ui/widgets/shared/custom_chip.dart';
|
|
||||||
import 'package:revanced_manager/ui/widgets/shared/custom_sliver_app_bar.dart';
|
import 'package:revanced_manager/ui/widgets/shared/custom_sliver_app_bar.dart';
|
||||||
import 'package:stacked/stacked.dart';
|
import 'package:stacked/stacked.dart';
|
||||||
|
|
||||||
@@ -18,12 +17,11 @@ class HomeView extends StatelessWidget {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return ViewModelBuilder<HomeViewModel>.reactive(
|
return ViewModelBuilder<HomeViewModel>.reactive(
|
||||||
disposeViewModel: false,
|
disposeViewModel: false,
|
||||||
onModelReady: (model) => model.initialize(context),
|
fireOnViewModelReadyOnce: true,
|
||||||
|
onViewModelReady: (model) => model.initialize(context),
|
||||||
viewModelBuilder: () => locator<HomeViewModel>(),
|
viewModelBuilder: () => locator<HomeViewModel>(),
|
||||||
builder: (context, model, child) => Scaffold(
|
builder: (context, model, child) => Scaffold(
|
||||||
body: RefreshIndicator(
|
body: RefreshIndicator(
|
||||||
color: Theme.of(context).colorScheme.secondary,
|
|
||||||
backgroundColor: Theme.of(context).colorScheme.secondaryContainer,
|
|
||||||
onRefresh: () => model.forceRefresh(context),
|
onRefresh: () => model.forceRefresh(context),
|
||||||
child: CustomScrollView(
|
child: CustomScrollView(
|
||||||
slivers: <Widget>[
|
slivers: <Widget>[
|
||||||
@@ -34,7 +32,7 @@ class HomeView extends StatelessWidget {
|
|||||||
child: Text(
|
child: Text(
|
||||||
'',
|
'',
|
||||||
style: GoogleFonts.inter(
|
style: GoogleFonts.inter(
|
||||||
color: Theme.of(context).textTheme.headline6!.color,
|
color: Theme.of(context).textTheme.titleLarge!.color,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -48,40 +46,67 @@ class HomeView extends StatelessWidget {
|
|||||||
'homeView.updatesSubtitle',
|
'homeView.updatesSubtitle',
|
||||||
child: Text(
|
child: Text(
|
||||||
'',
|
'',
|
||||||
style: Theme.of(context).textTheme.headline6!,
|
style: Theme.of(context).textTheme.titleLarge,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
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(
|
||||||
'homeView.patchedSubtitle',
|
'homeView.patchedSubtitle',
|
||||||
child: Text(
|
child: Text(
|
||||||
'',
|
'',
|
||||||
style: Theme.of(context).textTheme.headline6!,
|
style: Theme.of(context).textTheme.titleLarge,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
Row(
|
Row(
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
CustomChip(
|
ActionChip(
|
||||||
|
avatar: const Icon(Icons.grid_view),
|
||||||
label: I18nText('homeView.installed'),
|
label: I18nText('homeView.installed'),
|
||||||
isSelected: !model.showUpdatableApps,
|
side: BorderSide(
|
||||||
onSelected: (value) {
|
color: model.showUpdatableApps
|
||||||
|
? Theme.of(context).colorScheme.outline
|
||||||
|
: Theme.of(context)
|
||||||
|
.colorScheme
|
||||||
|
.secondaryContainer,
|
||||||
|
width: model.showUpdatableApps ? 1 : 1,
|
||||||
|
),
|
||||||
|
backgroundColor: model.showUpdatableApps
|
||||||
|
? Theme.of(context).colorScheme.background
|
||||||
|
: Theme.of(context)
|
||||||
|
.colorScheme
|
||||||
|
.secondaryContainer,
|
||||||
|
onPressed: () {
|
||||||
model.toggleUpdatableApps(false);
|
model.toggleUpdatableApps(false);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
const SizedBox(width: 10),
|
const SizedBox(width: 10),
|
||||||
CustomChip(
|
ActionChip(
|
||||||
|
avatar: const Icon(Icons.update),
|
||||||
label: I18nText('homeView.updatesAvailable'),
|
label: I18nText('homeView.updatesAvailable'),
|
||||||
isSelected: model.showUpdatableApps,
|
side: BorderSide(
|
||||||
onSelected: (value) {
|
color: !model.showUpdatableApps
|
||||||
|
? Theme.of(context).colorScheme.outline
|
||||||
|
: Theme.of(context)
|
||||||
|
.colorScheme
|
||||||
|
.secondaryContainer,
|
||||||
|
width: !model.showUpdatableApps ? 1 : 1,
|
||||||
|
),
|
||||||
|
backgroundColor: !model.showUpdatableApps
|
||||||
|
? Theme.of(context).colorScheme.background
|
||||||
|
: Theme.of(context)
|
||||||
|
.colorScheme
|
||||||
|
.secondaryContainer,
|
||||||
|
onPressed: () {
|
||||||
model.toggleUpdatableApps(true);
|
model.toggleUpdatableApps(true);
|
||||||
},
|
},
|
||||||
)
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
const SizedBox(height: 14),
|
const SizedBox(height: 14),
|
||||||
|
|||||||
@@ -1,26 +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/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/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/github_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:sentry_flutter/sentry_flutter.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 {
|
||||||
@@ -28,29 +32,52 @@ 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? _latestManagerVersion = '';
|
||||||
|
|
||||||
Future<void> initialize(BuildContext context) async {
|
Future<void> initialize(BuildContext context) async {
|
||||||
|
_latestManagerVersion = await _managerAPI.getLatestManagerVersion();
|
||||||
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<
|
||||||
AndroidFlutterLocalNotificationsPlugin>()
|
AndroidFlutterLocalNotificationsPlugin>()
|
||||||
?.requestPermission();
|
?.requestPermission();
|
||||||
bool isConnected = await Connectivity().checkConnection();
|
final bool isConnected = await Connectivity().checkConnection();
|
||||||
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());
|
||||||
}
|
}
|
||||||
@@ -67,7 +94,7 @@ class HomeViewModel extends BaseViewModel {
|
|||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
void navigateToPatcher(PatchedApplication app) async {
|
Future<void> navigateToPatcher(PatchedApplication app) async {
|
||||||
locator<PatcherViewModel>().selectedApp = app;
|
locator<PatcherViewModel>().selectedApp = app;
|
||||||
locator<PatcherViewModel>().selectedPatches =
|
locator<PatcherViewModel>().selectedPatches =
|
||||||
await _patcherAPI.getAppliedPatches(app.appliedPatches);
|
await _patcherAPI.getAppliedPatches(app.appliedPatches);
|
||||||
@@ -76,10 +103,7 @@ class HomeViewModel extends BaseViewModel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void _getPatchedApps() {
|
void _getPatchedApps() {
|
||||||
patchedInstalledApps = _managerAPI
|
patchedInstalledApps = _managerAPI.getPatchedApps().toList();
|
||||||
.getPatchedApps()
|
|
||||||
.where((app) => app.hasUpdates == false)
|
|
||||||
.toList();
|
|
||||||
patchedUpdatableApps = _managerAPI
|
patchedUpdatableApps = _managerAPI
|
||||||
.getPatchedApps()
|
.getPatchedApps()
|
||||||
.where((app) => app.hasUpdates == true)
|
.where((app) => app.hasUpdates == true)
|
||||||
@@ -88,59 +112,157 @@ class HomeViewModel extends BaseViewModel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<bool> hasManagerUpdates() async {
|
Future<bool> hasManagerUpdates() async {
|
||||||
String? latestVersion = await _managerAPI.getLatestManagerVersion();
|
|
||||||
String currentVersion = await _managerAPI.getCurrentManagerVersion();
|
String currentVersion = await _managerAPI.getCurrentManagerVersion();
|
||||||
|
|
||||||
|
// add v to current version
|
||||||
|
if (!currentVersion.startsWith('v')) {
|
||||||
|
currentVersion = 'v$currentVersion';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_latestManagerVersion != currentVersion) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<bool> hasPatchesUpdates() async {
|
||||||
|
final String? latestVersion = await _managerAPI.getLatestPatchesVersion();
|
||||||
|
final String? currentVersion = await _managerAPI.getCurrentPatchesVersion();
|
||||||
if (latestVersion != null) {
|
if (latestVersion != null) {
|
||||||
try {
|
try {
|
||||||
int latestVersionInt =
|
final int latestVersionInt =
|
||||||
int.parse(latestVersion.replaceAll(RegExp('[^0-9]'), ''));
|
int.parse(latestVersion.replaceAll(RegExp('[^0-9]'), ''));
|
||||||
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, s) {
|
} on Exception catch (e) {
|
||||||
await Sentry.captureException(e, stackTrace: s);
|
if (kDebugMode) {
|
||||||
|
print(e);
|
||||||
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
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');
|
||||||
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$_latestManagerVersion',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 18,
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
color: Theme.of(context).colorScheme.secondary,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16.0),
|
||||||
|
StreamBuilder<double>(
|
||||||
|
initialData: 0.0,
|
||||||
|
stream: _revancedAPI.managerUpdateProgress.stream,
|
||||||
|
builder: (context, snapshot) {
|
||||||
|
return LinearProgressIndicator(
|
||||||
|
value: snapshot.data! * 0.01,
|
||||||
|
valueColor: AlwaysStoppedAnimation<Color>(
|
||||||
|
Theme.of(context).colorScheme.secondary,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16.0),
|
||||||
|
Align(
|
||||||
|
alignment: Alignment.centerRight,
|
||||||
|
child: CustomMaterialButton(
|
||||||
|
label: I18nText('cancelButton'),
|
||||||
|
onPressed: () {
|
||||||
|
_revancedAPI.disposeManagerUpdateProgress();
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
final File? managerApk = await downloadManager();
|
||||||
|
if (managerApk != null) {
|
||||||
|
// await flutterLocalNotificationsPlugin.zonedSchedule(
|
||||||
|
// 0,
|
||||||
|
// FlutterI18n.translate(
|
||||||
|
// context,
|
||||||
|
// 'homeView.notificationTitle',
|
||||||
|
// ),
|
||||||
|
// FlutterI18n.translate(
|
||||||
|
// context,
|
||||||
|
// 'homeView.notificationText',
|
||||||
|
// ),
|
||||||
|
// tz.TZDateTime.now(tz.local).add(const Duration(seconds: 5)),
|
||||||
|
// const NotificationDetails(
|
||||||
|
// android: AndroidNotificationDetails(
|
||||||
|
// 'revanced_manager_channel',
|
||||||
|
// 'ReVanced Manager Channel',
|
||||||
|
// importance: Importance.max,
|
||||||
|
// priority: Priority.high,
|
||||||
|
// ticker: 'ticker',
|
||||||
|
// ),
|
||||||
|
// ),
|
||||||
|
// androidAllowWhileIdle: true,
|
||||||
|
// uiLocalNotificationDateInterpretation:
|
||||||
|
// UILocalNotificationDateInterpretation.absoluteTime,
|
||||||
|
// );
|
||||||
_toast.showBottom('homeView.installingMessage');
|
_toast.showBottom('homeView.installingMessage');
|
||||||
await AppInstaller.installApk(managerApk.path);
|
await AppInstaller.installApk(managerApk.path);
|
||||||
} else {
|
} else {
|
||||||
_toast.showBottom('homeView.errorDownloadMessage');
|
_toast.showBottom('homeView.errorDownloadMessage');
|
||||||
}
|
}
|
||||||
} on Exception catch (e, s) {
|
} on Exception catch (e) {
|
||||||
await Sentry.captureException(e, stackTrace: s);
|
if (kDebugMode) {
|
||||||
|
print(e);
|
||||||
|
}
|
||||||
_toast.showBottom('homeView.errorInstallMessage');
|
_toast.showBottom('homeView.errorInstallMessage');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -149,7 +271,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,
|
||||||
@@ -178,6 +302,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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,9 +2,9 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:flutter_i18n/flutter_i18n.dart';
|
import 'package:flutter_i18n/flutter_i18n.dart';
|
||||||
import 'package:google_fonts/google_fonts.dart';
|
import 'package:google_fonts/google_fonts.dart';
|
||||||
import 'package:revanced_manager/ui/views/installer/installer_viewmodel.dart';
|
import 'package:revanced_manager/ui/views/installer/installer_viewmodel.dart';
|
||||||
import 'package:revanced_manager/ui/widgets/shared/custom_material_button.dart';
|
|
||||||
import 'package:revanced_manager/ui/widgets/installerView/gradient_progress_indicator.dart';
|
import 'package:revanced_manager/ui/widgets/installerView/gradient_progress_indicator.dart';
|
||||||
import 'package:revanced_manager/ui/widgets/shared/custom_card.dart';
|
import 'package:revanced_manager/ui/widgets/shared/custom_card.dart';
|
||||||
|
import 'package:revanced_manager/ui/widgets/shared/custom_material_button.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/custom_sliver_app_bar.dart';
|
import 'package:revanced_manager/ui/widgets/shared/custom_sliver_app_bar.dart';
|
||||||
import 'package:stacked/stacked.dart';
|
import 'package:stacked/stacked.dart';
|
||||||
@@ -15,11 +15,12 @@ class InstallerView extends StatelessWidget {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return ViewModelBuilder<InstallerViewModel>.reactive(
|
return ViewModelBuilder<InstallerViewModel>.reactive(
|
||||||
onModelReady: (model) => model.initialize(context),
|
onViewModelReady: (model) => model.initialize(context),
|
||||||
viewModelBuilder: () => InstallerViewModel(),
|
viewModelBuilder: () => InstallerViewModel(),
|
||||||
builder: (context, model, child) => WillPopScope(
|
builder: (context, model, child) => WillPopScope(
|
||||||
child: SafeArea(
|
child: SafeArea(
|
||||||
top: false,
|
top: false,
|
||||||
|
bottom: false,
|
||||||
child: Scaffold(
|
child: Scaffold(
|
||||||
body: CustomScrollView(
|
body: CustomScrollView(
|
||||||
controller: model.scrollController,
|
controller: model.scrollController,
|
||||||
@@ -28,7 +29,7 @@ class InstallerView extends StatelessWidget {
|
|||||||
title: Text(
|
title: Text(
|
||||||
model.headerLogs,
|
model.headerLogs,
|
||||||
style: GoogleFonts.inter(
|
style: GoogleFonts.inter(
|
||||||
color: Theme.of(context).textTheme.headline6!.color,
|
color: Theme.of(context).textTheme.titleLarge!.color,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
onBackButtonPressed: () => model.onWillPop(context),
|
onBackButtonPressed: () => model.onWillPop(context),
|
||||||
@@ -48,15 +49,15 @@ class InstallerView extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
1: I18nText(
|
1: I18nText(
|
||||||
'installerView.exportApkMenuOption',
|
'installerView.exportApkMenuOption',
|
||||||
child: const Text(
|
child: const Text(
|
||||||
'',
|
'',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
),
|
||||||
2: I18nText(
|
2: I18nText(
|
||||||
'installerView.shareLogMenuOption',
|
'installerView.shareLogMenuOption',
|
||||||
child: const Text(
|
child: const Text(
|
||||||
@@ -71,9 +72,9 @@ class InstallerView extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
],
|
],
|
||||||
bottom: PreferredSize(
|
bottom: PreferredSize(
|
||||||
preferredSize: const Size(double.infinity, 1.0),
|
preferredSize: const Size(double.infinity, 1.0),
|
||||||
child:
|
child: GradientProgressIndicator(progress: model.progress),
|
||||||
GradientProgressIndicator(progress: model.progress!)),
|
),
|
||||||
),
|
),
|
||||||
SliverPadding(
|
SliverPadding(
|
||||||
padding: const EdgeInsets.all(20.0),
|
padding: const EdgeInsets.all(20.0),
|
||||||
@@ -153,6 +154,11 @@ class InstallerView extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
SliverFillRemaining(
|
||||||
|
hasScrollBody: false,
|
||||||
|
child: SizedBox(
|
||||||
|
height: MediaQuery.of(context).viewPadding.bottom),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
// ignore_for_file: use_build_context_synchronously
|
// ignore_for_file: use_build_context_synchronously
|
||||||
import 'package:device_apps/device_apps.dart';
|
import 'package:device_apps/device_apps.dart';
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:flutter_background/flutter_background.dart';
|
import 'package:flutter_background/flutter_background.dart';
|
||||||
@@ -14,7 +15,6 @@ import 'package:revanced_manager/services/root_api.dart';
|
|||||||
import 'package:revanced_manager/services/toast.dart';
|
import 'package:revanced_manager/services/toast.dart';
|
||||||
import 'package:revanced_manager/ui/views/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/ui/widgets/shared/custom_material_button.dart';
|
||||||
import 'package:sentry_flutter/sentry_flutter.dart';
|
|
||||||
import 'package:stacked/stacked.dart';
|
import 'package:stacked/stacked.dart';
|
||||||
import 'package:wakelock/wakelock.dart';
|
import 'package:wakelock/wakelock.dart';
|
||||||
|
|
||||||
@@ -51,16 +51,15 @@ class InstallerViewModel extends BaseViewModel {
|
|||||||
context,
|
context,
|
||||||
'installerView.notificationText',
|
'installerView.notificationText',
|
||||||
),
|
),
|
||||||
notificationImportance: AndroidNotificationImportance.Default,
|
|
||||||
notificationIcon: const AndroidResource(
|
notificationIcon: const AndroidResource(
|
||||||
name: 'ic_notification',
|
name: 'ic_notification',
|
||||||
defType: 'drawable',
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
).then((value) => FlutterBackground.enableBackgroundExecution());
|
).then((value) => FlutterBackground.enableBackgroundExecution());
|
||||||
} on Exception catch (e, s) {
|
} on Exception catch (e) {
|
||||||
await Sentry.captureException(e, stackTrace: s);
|
if (kDebugMode) {
|
||||||
// ignore
|
print(e);
|
||||||
|
} // ignore
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
await Wakelock.enable();
|
await Wakelock.enable();
|
||||||
@@ -73,10 +72,10 @@ class InstallerViewModel extends BaseViewModel {
|
|||||||
switch (call.method) {
|
switch (call.method) {
|
||||||
case 'update':
|
case 'update':
|
||||||
if (call.arguments != null) {
|
if (call.arguments != null) {
|
||||||
Map<dynamic, dynamic> arguments = call.arguments;
|
final Map<dynamic, dynamic> arguments = call.arguments;
|
||||||
double progress = arguments['progress'];
|
final double progress = arguments['progress'];
|
||||||
String header = arguments['header'];
|
final String header = arguments['header'];
|
||||||
String log = arguments['log'];
|
final String log = arguments['log'];
|
||||||
update(progress, header, log);
|
update(progress, header, log);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
@@ -133,14 +132,15 @@ class InstallerViewModel extends BaseViewModel {
|
|||||||
_app.apkFilePath,
|
_app.apkFilePath,
|
||||||
_patches,
|
_patches,
|
||||||
);
|
);
|
||||||
} on Exception catch (e, s) {
|
} on Exception catch (e) {
|
||||||
update(
|
update(
|
||||||
-100.0,
|
-100.0,
|
||||||
'Aborting...',
|
'Aborting...',
|
||||||
'An error occurred! Aborting\nError:\n$e',
|
'An error occurred! Aborting\nError:\n$e',
|
||||||
);
|
);
|
||||||
await Sentry.captureException(e, stackTrace: s);
|
if (kDebugMode) {
|
||||||
throw await Sentry.captureException(e, stackTrace: s);
|
print(e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
update(-100.0, 'Aborting...', 'No app or patches selected! Aborting');
|
update(-100.0, 'Aborting...', 'No app or patches selected! Aborting');
|
||||||
@@ -148,24 +148,28 @@ class InstallerViewModel extends BaseViewModel {
|
|||||||
if (FlutterBackground.isBackgroundExecutionEnabled) {
|
if (FlutterBackground.isBackgroundExecutionEnabled) {
|
||||||
try {
|
try {
|
||||||
FlutterBackground.disableBackgroundExecution();
|
FlutterBackground.disableBackgroundExecution();
|
||||||
} on Exception catch (e, s) {
|
} on Exception catch (e) {
|
||||||
await Sentry.captureException(e, stackTrace: s);
|
if (kDebugMode) {
|
||||||
// ignore
|
print(e);
|
||||||
|
} // ignore
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
await Wakelock.disable();
|
await Wakelock.disable();
|
||||||
} on Exception catch (e, s) {
|
} on Exception catch (e) {
|
||||||
await Sentry.captureException(e, stackTrace: s);
|
if (kDebugMode) {
|
||||||
|
print(e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void installResult(BuildContext context, bool installAsRoot) async {
|
Future<void> installResult(BuildContext context, bool installAsRoot) async {
|
||||||
try {
|
try {
|
||||||
_app.isRooted = installAsRoot;
|
_app.isRooted = installAsRoot;
|
||||||
bool hasMicroG = _patches.any((p) => p.name.endsWith('microg-support'));
|
final bool hasMicroG =
|
||||||
bool rootMicroG = installAsRoot && hasMicroG;
|
_patches.any((p) => p.name.endsWith('microg-support'));
|
||||||
bool rootFromStorage = installAsRoot && _app.isFromStorage;
|
final bool rootMicroG = installAsRoot && hasMicroG;
|
||||||
bool ytWithoutRootMicroG =
|
final bool rootFromStorage = installAsRoot && _app.isFromStorage;
|
||||||
|
final bool ytWithoutRootMicroG =
|
||||||
!installAsRoot && !hasMicroG && _app.packageName.contains('youtube');
|
!installAsRoot && !hasMicroG && _app.packageName.contains('youtube');
|
||||||
if (rootMicroG || rootFromStorage || ytWithoutRootMicroG) {
|
if (rootMicroG || rootFromStorage || ytWithoutRootMicroG) {
|
||||||
return showDialog(
|
return showDialog(
|
||||||
@@ -212,24 +216,30 @@ class InstallerViewModel extends BaseViewModel {
|
|||||||
await _managerAPI.savePatchedApp(_app);
|
await _managerAPI.savePatchedApp(_app);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} on Exception catch (e, s) {
|
} on Exception catch (e) {
|
||||||
await Sentry.captureException(e, stackTrace: s);
|
if (kDebugMode) {
|
||||||
|
print(e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void exportResult() {
|
void exportResult() {
|
||||||
try {
|
try {
|
||||||
_patcherAPI.exportPatchedFile(_app.name, _app.version);
|
_patcherAPI.exportPatchedFile(_app.name, _app.version);
|
||||||
} on Exception catch (e, s) {
|
} on Exception catch (e) {
|
||||||
Sentry.captureException(e, stackTrace: s);
|
if (kDebugMode) {
|
||||||
|
print(e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void shareResult() {
|
void shareResult() {
|
||||||
try {
|
try {
|
||||||
_patcherAPI.sharePatchedFile(_app.name, _app.version);
|
_patcherAPI.sharePatchedFile(_app.name, _app.version);
|
||||||
} on Exception catch (e, s) {
|
} on Exception catch (e) {
|
||||||
Sentry.captureException(e, stackTrace: s);
|
if (kDebugMode) {
|
||||||
|
print(e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -243,8 +253,10 @@ class InstallerViewModel extends BaseViewModel {
|
|||||||
locator<PatcherViewModel>().selectedApp = null;
|
locator<PatcherViewModel>().selectedApp = null;
|
||||||
locator<PatcherViewModel>().selectedPatches.clear();
|
locator<PatcherViewModel>().selectedPatches.clear();
|
||||||
locator<PatcherViewModel>().notifyListeners();
|
locator<PatcherViewModel>().notifyListeners();
|
||||||
} on Exception catch (e, s) {
|
} on Exception catch (e) {
|
||||||
await Sentry.captureException(e, stackTrace: s);
|
if (kDebugMode) {
|
||||||
|
print(e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ class NavigationView extends StatelessWidget {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return ViewModelBuilder<NavigationViewModel>.reactive(
|
return ViewModelBuilder<NavigationViewModel>.reactive(
|
||||||
onModelReady: (model) => model.initialize(context),
|
onViewModelReady: (model) => model.initialize(context),
|
||||||
viewModelBuilder: () => locator<NavigationViewModel>(),
|
viewModelBuilder: () => locator<NavigationViewModel>(),
|
||||||
builder: (context, model, child) => Scaffold(
|
builder: (context, model, child) => Scaffold(
|
||||||
body: PageTransitionSwitcher(
|
body: PageTransitionSwitcher(
|
||||||
@@ -42,7 +42,6 @@ class NavigationView extends StatelessWidget {
|
|||||||
context,
|
context,
|
||||||
'navigationView.dashboardTab',
|
'navigationView.dashboardTab',
|
||||||
),
|
),
|
||||||
tooltip: '',
|
|
||||||
),
|
),
|
||||||
NavigationDestination(
|
NavigationDestination(
|
||||||
icon: model.isIndexSelected(1)
|
icon: model.isIndexSelected(1)
|
||||||
@@ -52,7 +51,6 @@ class NavigationView extends StatelessWidget {
|
|||||||
context,
|
context,
|
||||||
'navigationView.patcherTab',
|
'navigationView.patcherTab',
|
||||||
),
|
),
|
||||||
tooltip: '',
|
|
||||||
),
|
),
|
||||||
NavigationDestination(
|
NavigationDestination(
|
||||||
icon: model.isIndexSelected(2)
|
icon: model.isIndexSelected(2)
|
||||||
@@ -62,7 +60,6 @@ class NavigationView extends StatelessWidget {
|
|||||||
context,
|
context,
|
||||||
'navigationView.settingsTab',
|
'navigationView.settingsTab',
|
||||||
),
|
),
|
||||||
tooltip: '',
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -15,19 +15,21 @@ import 'package:stacked/stacked.dart';
|
|||||||
|
|
||||||
@lazySingleton
|
@lazySingleton
|
||||||
class NavigationViewModel extends IndexTrackingViewModel {
|
class NavigationViewModel extends IndexTrackingViewModel {
|
||||||
void initialize(BuildContext context) async {
|
Future<void> initialize(BuildContext context) async {
|
||||||
locator<Toast>().initialize(context);
|
locator<Toast>().initialize(context);
|
||||||
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(),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (prefs.getBool('useDarkTheme') == null) {
|
if (prefs.getBool('useDarkTheme') == null) {
|
||||||
bool isDark =
|
final bool isDark =
|
||||||
MediaQuery.of(context).platformBrightness != Brightness.light;
|
MediaQuery.of(context).platformBrightness != Brightness.light;
|
||||||
await prefs.setBool('useDarkTheme', isDark);
|
await prefs.setBool('useDarkTheme', isDark);
|
||||||
await DynamicTheme.of(context)!.setTheme(isDark ? 1 : 0);
|
await DynamicTheme.of(context)!.setTheme(isDark ? 1 : 0);
|
||||||
@@ -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,
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ class PatcherView extends StatelessWidget {
|
|||||||
child: Text(
|
child: Text(
|
||||||
'',
|
'',
|
||||||
style: GoogleFonts.inter(
|
style: GoogleFonts.inter(
|
||||||
color: Theme.of(context).textTheme.headline6!.color,
|
color: Theme.of(context).textTheme.titleLarge!.color,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -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';
|
||||||
|
|
||||||
@@ -40,35 +43,69 @@ class PatcherViewModel extends BaseViewModel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<bool> isValidPatchConfig() async {
|
Future<bool> isValidPatchConfig() async {
|
||||||
bool needsResourcePatching = await _patcherAPI.needsResourcePatching(
|
final bool needsResourcePatching = await _patcherAPI.needsResourcePatching(
|
||||||
selectedPatches,
|
selectedPatches,
|
||||||
);
|
);
|
||||||
if (needsResourcePatching && selectedApp != null) {
|
if (needsResourcePatching && selectedApp != null) {
|
||||||
bool isSplit = await _managerAPI.isSplitApk(selectedApp!);
|
final bool isSplit = await _managerAPI.isSplitApk(selectedApp!);
|
||||||
return !isSplit;
|
return !isSplit;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> showPatchConfirmationDialog(BuildContext context) async {
|
Future<void> showPatchConfirmationDialog(BuildContext context) async {
|
||||||
bool isValid = await isValidPatchConfig();
|
final bool isValid = await isValidPatchConfig();
|
||||||
if (isValid) {
|
if (context.mounted) {
|
||||||
navigateToInstaller();
|
if (isValid) {
|
||||||
} else {
|
showArmv7WarningDialog(context);
|
||||||
|
} else {
|
||||||
|
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();
|
||||||
|
showArmv7WarningDialog(context);
|
||||||
|
},
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> showArmv7WarningDialog(BuildContext context) async {
|
||||||
|
final bool armv7 = await AboutInfo.getInfo().then((info) {
|
||||||
|
final List<String> archs = info['arch'];
|
||||||
|
final supportedAbis = ['arm64-v8a', 'x86', 'x86_64'];
|
||||||
|
return !archs.any((arch) => supportedAbis.contains(arch));
|
||||||
|
});
|
||||||
|
if (context.mounted && armv7) {
|
||||||
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();
|
navigateToInstaller();
|
||||||
@@ -77,6 +114,8 @@ class PatcherViewModel extends BaseViewModel {
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
} else {
|
||||||
|
navigateToInstaller();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -88,31 +127,31 @@ 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 {
|
||||||
this.selectedPatches.clear();
|
this.selectedPatches.clear();
|
||||||
List<String> selectedPatches =
|
final List<String> selectedPatches =
|
||||||
await _managerAPI.getSelectedPatches(selectedApp!.originalPackageName);
|
await _managerAPI.getSelectedPatches(selectedApp!.originalPackageName);
|
||||||
List<Patch> patches =
|
final List<Patch> patches =
|
||||||
_patcherAPI.getFilteredPatches(selectedApp!.originalPackageName);
|
_patcherAPI.getFilteredPatches(selectedApp!.originalPackageName);
|
||||||
this
|
this
|
||||||
.selectedPatches
|
.selectedPatches
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart' hide SearchBar;
|
||||||
import 'package:flutter_i18n/flutter_i18n.dart';
|
import 'package:flutter_i18n/flutter_i18n.dart';
|
||||||
import 'package:revanced_manager/ui/views/patches_selector/patches_selector_viewmodel.dart';
|
import 'package:revanced_manager/ui/views/patches_selector/patches_selector_viewmodel.dart';
|
||||||
import 'package:revanced_manager/ui/widgets/patchesSelectorView/patch_item.dart';
|
import 'package:revanced_manager/ui/widgets/patchesSelectorView/patch_item.dart';
|
||||||
import 'package:revanced_manager/ui/widgets/shared/custom_chip.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 {
|
||||||
@@ -20,7 +20,7 @@ class _PatchesSelectorViewState extends State<PatchesSelectorView> {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return ViewModelBuilder<PatchesSelectorViewModel>.reactive(
|
return ViewModelBuilder<PatchesSelectorViewModel>.reactive(
|
||||||
onModelReady: (model) => model.initialize(),
|
onViewModelReady: (model) => model.initialize(),
|
||||||
viewModelBuilder: () => PatchesSelectorViewModel(),
|
viewModelBuilder: () => PatchesSelectorViewModel(),
|
||||||
builder: (context, model, child) => Scaffold(
|
builder: (context, model, child) => Scaffold(
|
||||||
resizeToAvoidBottomInset: false,
|
resizeToAvoidBottomInset: false,
|
||||||
@@ -45,26 +45,24 @@ class _PatchesSelectorViewState extends State<PatchesSelectorView> {
|
|||||||
SliverAppBar(
|
SliverAppBar(
|
||||||
pinned: true,
|
pinned: true,
|
||||||
floating: true,
|
floating: true,
|
||||||
snap: false,
|
|
||||||
title: I18nText(
|
title: I18nText(
|
||||||
'patchesSelectorView.viewTitle',
|
'patchesSelectorView.viewTitle',
|
||||||
child: Text(
|
child: Text(
|
||||||
'',
|
'',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: Theme.of(context).textTheme.headline6!.color,
|
color: Theme.of(context).textTheme.titleLarge!.color,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
leading: IconButton(
|
leading: IconButton(
|
||||||
icon: Icon(
|
icon: Icon(
|
||||||
Icons.arrow_back,
|
Icons.arrow_back,
|
||||||
color: Theme.of(context).textTheme.headline6!.color,
|
color: Theme.of(context).textTheme.titleLarge!.color,
|
||||||
),
|
),
|
||||||
onPressed: () => Navigator.of(context).pop(),
|
onPressed: () => Navigator.of(context).pop(),
|
||||||
),
|
),
|
||||||
actions: [
|
actions: [
|
||||||
Container(
|
Container(
|
||||||
height: 2,
|
|
||||||
margin: const EdgeInsets.only(top: 12, bottom: 12),
|
margin: const EdgeInsets.only(top: 12, bottom: 12),
|
||||||
padding:
|
padding:
|
||||||
const EdgeInsets.symmetric(horizontal: 6, vertical: 6),
|
const EdgeInsets.symmetric(horizontal: 6, vertical: 6),
|
||||||
@@ -76,7 +74,7 @@ class _PatchesSelectorViewState extends State<PatchesSelectorView> {
|
|||||||
child: Text(
|
child: Text(
|
||||||
model.patchesVersion!,
|
model.patchesVersion!,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: Theme.of(context).textTheme.headline6!.color,
|
color: Theme.of(context).textTheme.titleLarge!.color,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -103,7 +101,6 @@ class _PatchesSelectorViewState extends State<PatchesSelectorView> {
|
|||||||
horizontal: 12.0,
|
horizontal: 12.0,
|
||||||
),
|
),
|
||||||
child: SearchBar(
|
child: SearchBar(
|
||||||
showSelectIcon: true,
|
|
||||||
hintText: FlutterI18n.translate(
|
hintText: FlutterI18n.translate(
|
||||||
context,
|
context,
|
||||||
'patchesSelectorView.searchBarHint',
|
'patchesSelectorView.searchBarHint',
|
||||||
@@ -113,12 +110,6 @@ class _PatchesSelectorViewState extends State<PatchesSelectorView> {
|
|||||||
_query = searchQuery;
|
_query = searchQuery;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
onSelectAll: (value) {
|
|
||||||
if (value) {
|
|
||||||
model.selectAllPatcherWarning(context);
|
|
||||||
}
|
|
||||||
model.selectAllPatches(value);
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -138,30 +129,32 @@ class _PatchesSelectorViewState extends State<PatchesSelectorView> {
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
: Padding(
|
: Padding(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 12.0)
|
padding:
|
||||||
.copyWith(bottom: 80),
|
const EdgeInsets.symmetric(horizontal: 12.0).copyWith(
|
||||||
|
bottom: MediaQuery.of(context).viewPadding.bottom + 8.0,
|
||||||
|
),
|
||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
Row(
|
Row(
|
||||||
children: [
|
children: [
|
||||||
CustomChip(
|
ActionChip(
|
||||||
label:
|
label: I18nText('patchesSelectorView.default'),
|
||||||
I18nText('patchesSelectorView.recommended'),
|
tooltip: FlutterI18n.translate(
|
||||||
onSelected: (value) {
|
context,
|
||||||
model.selectRecommendedPatches();
|
'patchesSelectorView.defaultTooltip',
|
||||||
|
),
|
||||||
|
onPressed: () {
|
||||||
|
model.selectDefaultPatches();
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
const SizedBox(width: 8),
|
const SizedBox(width: 8),
|
||||||
CustomChip(
|
ActionChip(
|
||||||
label: I18nText('patchesSelectorView.all'),
|
|
||||||
onSelected: (value) {
|
|
||||||
model.selectAllPatches(true);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
const SizedBox(width: 8),
|
|
||||||
CustomChip(
|
|
||||||
label: I18nText('patchesSelectorView.none'),
|
label: I18nText('patchesSelectorView.none'),
|
||||||
onSelected: (value) {
|
tooltip: FlutterI18n.translate(
|
||||||
|
context,
|
||||||
|
'patchesSelectorView.noneTooltip',
|
||||||
|
),
|
||||||
|
onPressed: () {
|
||||||
model.clearPatches();
|
model.clearPatches();
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
@@ -178,7 +171,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,5 +1,4 @@
|
|||||||
import 'package:collection/collection.dart';
|
import 'package:collection/collection.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';
|
||||||
@@ -8,9 +7,8 @@ import 'package:revanced_manager/services/manager_api.dart';
|
|||||||
import 'package:revanced_manager/services/patcher_api.dart';
|
import 'package:revanced_manager/services/patcher_api.dart';
|
||||||
import 'package:revanced_manager/services/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';
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
|
|
||||||
class PatchesSelectorViewModel extends BaseViewModel {
|
class PatchesSelectorViewModel extends BaseViewModel {
|
||||||
final PatcherAPI _patcherAPI = locator<PatcherAPI>();
|
final PatcherAPI _patcherAPI = locator<PatcherAPI>();
|
||||||
@@ -25,10 +23,12 @@ class PatchesSelectorViewModel extends BaseViewModel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<void> initialize() async {
|
Future<void> initialize() async {
|
||||||
getPatchesVersion();
|
getPatchesVersion().whenComplete(() => notifyListeners());
|
||||||
patches.addAll(_patcherAPI.getFilteredPatches(
|
patches.addAll(
|
||||||
locator<PatcherViewModel>().selectedApp!.originalPackageName,
|
_patcherAPI.getFilteredPatches(
|
||||||
));
|
locator<PatcherViewModel>().selectedApp!.originalPackageName,
|
||||||
|
),
|
||||||
|
);
|
||||||
patches.sort((a, b) => a.name.compareTo(b.name));
|
patches.sort((a, b) => a.name.compareTo(b.name));
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
@@ -48,44 +48,15 @@ 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) {
|
||||||
selectedPatches.addAll(patches.where(
|
selectedPatches.addAll(
|
||||||
(element) => element.excluded == false && isPatchSupported(element)));
|
patches.where(
|
||||||
|
(element) => element.excluded == false && isPatchSupported(element),
|
||||||
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_managerAPI.areExperimentalPatchesEnabled()) {
|
if (_managerAPI.areExperimentalPatchesEnabled()) {
|
||||||
@@ -117,15 +88,18 @@ class PatchesSelectorViewModel extends BaseViewModel {
|
|||||||
patchesVersion = await _githubAPI
|
patchesVersion = await _githubAPI
|
||||||
.getLastestReleaseVersion(_managerAPI.getPatchesRepo());
|
.getLastestReleaseVersion(_managerAPI.getPatchesRepo());
|
||||||
}
|
}
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
List<Patch> getQueriedPatches(String query) {
|
List<Patch> getQueriedPatches(String query) {
|
||||||
return patches
|
return patches
|
||||||
.where((patch) =>
|
.where(
|
||||||
query.isEmpty ||
|
(patch) =>
|
||||||
query.length < 2 ||
|
query.isEmpty ||
|
||||||
patch.name.toLowerCase().contains(query.toLowerCase()) ||
|
query.length < 2 ||
|
||||||
patch.getSimpleName().toLowerCase().contains(query.toLowerCase()))
|
patch.name.toLowerCase().contains(query.toLowerCase()) ||
|
||||||
|
patch.getSimpleName().toLowerCase().contains(query.toLowerCase()),
|
||||||
|
)
|
||||||
.toList();
|
.toList();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -134,8 +108,8 @@ class PatchesSelectorViewModel extends BaseViewModel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
List<String> getSupportedVersions(Patch patch) {
|
List<String> getSupportedVersions(Patch patch) {
|
||||||
PatchedApplication app = locator<PatcherViewModel>().selectedApp!;
|
final PatchedApplication app = locator<PatcherViewModel>().selectedApp!;
|
||||||
Package? package = patch.compatiblePackages.firstWhereOrNull(
|
final Package? package = patch.compatiblePackages.firstWhereOrNull(
|
||||||
(pack) => pack.name == app.packageName,
|
(pack) => pack.name == app.packageName,
|
||||||
);
|
);
|
||||||
if (package != null) {
|
if (package != null) {
|
||||||
@@ -145,13 +119,6 @@ class PatchesSelectorViewModel extends BaseViewModel {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool isPatchSupported(Patch patch) {
|
|
||||||
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:
|
||||||
@@ -161,20 +128,23 @@ class PatchesSelectorViewModel extends BaseViewModel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<void> saveSelectedPatches() async {
|
Future<void> saveSelectedPatches() async {
|
||||||
List<String> selectedPatches =
|
final List<String> selectedPatches =
|
||||||
this.selectedPatches.map((patch) => patch.name).toList();
|
this.selectedPatches.map((patch) => patch.name).toList();
|
||||||
await _managerAPI.setSelectedPatches(
|
await _managerAPI.setSelectedPatches(
|
||||||
locator<PatcherViewModel>().selectedApp!.originalPackageName,
|
locator<PatcherViewModel>().selectedApp!.originalPackageName,
|
||||||
selectedPatches);
|
selectedPatches,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> loadSelectedPatches() async {
|
Future<void> loadSelectedPatches() async {
|
||||||
List<String> selectedPatches = await _managerAPI.getSelectedPatches(
|
final List<String> selectedPatches = await _managerAPI.getSelectedPatches(
|
||||||
locator<PatcherViewModel>().selectedApp!.originalPackageName);
|
locator<PatcherViewModel>().selectedApp!.originalPackageName,
|
||||||
|
);
|
||||||
if (selectedPatches.isNotEmpty) {
|
if (selectedPatches.isNotEmpty) {
|
||||||
this.selectedPatches.clear();
|
this.selectedPatches.clear();
|
||||||
this.selectedPatches.addAll(
|
this.selectedPatches.addAll(
|
||||||
patches.where((patch) => selectedPatches.contains(patch.name)));
|
patches.where((patch) => selectedPatches.contains(patch.name)),
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
locator<Toast>().showBottom('patchesSelectorView.noSavedPatches');
|
locator<Toast>().showBottom('patchesSelectorView.noSavedPatches');
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,11 +12,12 @@ 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();
|
||||||
|
|
||||||
Future<void> showApiUrlDialog(BuildContext context) async {
|
Future<void> showApiUrlDialog(BuildContext context) async {
|
||||||
String apiUrl = _managerAPI.getApiUrl();
|
final String apiUrl = _managerAPI.getApiUrl();
|
||||||
_apiUrlController.text = apiUrl.replaceAll('https://', '');
|
_apiUrlController.text = apiUrl.replaceAll('https://', '');
|
||||||
return showDialog(
|
return showDialog(
|
||||||
context: context,
|
context: context,
|
||||||
@@ -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();
|
||||||
@@ -19,9 +21,9 @@ class SManageSources extends BaseViewModel {
|
|||||||
final TextEditingController _intSourceController = TextEditingController();
|
final TextEditingController _intSourceController = TextEditingController();
|
||||||
|
|
||||||
Future<void> showSourcesDialog(BuildContext context) async {
|
Future<void> showSourcesDialog(BuildContext context) async {
|
||||||
String hostRepository = _managerAPI.getRepoUrl();
|
final String hostRepository = _managerAPI.getRepoUrl();
|
||||||
String patchesRepo = _managerAPI.getPatchesRepo();
|
final String patchesRepo = _managerAPI.getPatchesRepo();
|
||||||
String integrationsRepo = _managerAPI.getIntegrationsRepo();
|
final String integrationsRepo = _managerAPI.getIntegrationsRepo();
|
||||||
_hostSourceController.text = hostRepository;
|
_hostSourceController.text = hostRepository;
|
||||||
_orgPatSourceController.text = patchesRepo.split('/')[0];
|
_orgPatSourceController.text = patchesRepo.split('/')[0];
|
||||||
_patSourceController.text = patchesRepo.split('/')[1];
|
_patSourceController.text = patchesRepo.split('/')[1];
|
||||||
@@ -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,9 +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();
|
Navigator.of(context)
|
||||||
Navigator.of(context).pop();
|
..pop()
|
||||||
|
..pop();
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
@@ -4,7 +4,6 @@ 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/main.dart';
|
import 'package:revanced_manager/main.dart';
|
||||||
import 'package:revanced_manager/services/crowdin_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/settings/settings_viewmodel.dart';
|
import 'package:revanced_manager/ui/views/settings/settings_viewmodel.dart';
|
||||||
@@ -16,7 +15,6 @@ import 'package:timeago/timeago.dart' as timeago;
|
|||||||
final _settingViewModel = SettingsViewModel();
|
final _settingViewModel = SettingsViewModel();
|
||||||
|
|
||||||
class SUpdateLanguage extends BaseViewModel {
|
class SUpdateLanguage extends BaseViewModel {
|
||||||
final CrowdinAPI _crowdinAPI = locator<CrowdinAPI>();
|
|
||||||
final Toast _toast = locator<Toast>();
|
final Toast _toast = locator<Toast>();
|
||||||
late SharedPreferences _prefs;
|
late SharedPreferences _prefs;
|
||||||
String selectedLanguage = 'English';
|
String selectedLanguage = 'English';
|
||||||
@@ -43,7 +41,6 @@ class SUpdateLanguage extends BaseViewModel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<void> initLang() async {
|
Future<void> initLang() async {
|
||||||
languages = await _crowdinAPI.getLanguages();
|
|
||||||
languages.sort((a, b) => a['name'].compareTo(b['name']));
|
languages.sort((a, b) => a['name'].compareTo(b['name']));
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
@@ -7,7 +7,6 @@ import 'package:flutter_i18n/widgets/I18nText.dart';
|
|||||||
import 'package:revanced_manager/app/app.locator.dart';
|
import 'package:revanced_manager/app/app.locator.dart';
|
||||||
import 'package:revanced_manager/services/manager_api.dart';
|
import 'package:revanced_manager/services/manager_api.dart';
|
||||||
import 'package:revanced_manager/ui/views/settings/settings_viewmodel.dart';
|
import 'package:revanced_manager/ui/views/settings/settings_viewmodel.dart';
|
||||||
import 'package:revanced_manager/ui/widgets/settingsView/custom_switch_tile.dart';
|
|
||||||
import 'package:revanced_manager/ui/widgets/settingsView/settings_section.dart';
|
import 'package:revanced_manager/ui/widgets/settingsView/settings_section.dart';
|
||||||
import 'package:stacked/stacked.dart';
|
import 'package:stacked/stacked.dart';
|
||||||
|
|
||||||
@@ -23,9 +22,9 @@ class SUpdateTheme extends BaseViewModel {
|
|||||||
return _managerAPI.getUseDynamicTheme();
|
return _managerAPI.getUseDynamicTheme();
|
||||||
}
|
}
|
||||||
|
|
||||||
void setUseDynamicTheme(BuildContext context, bool value) async {
|
Future<void> setUseDynamicTheme(BuildContext context, bool value) async {
|
||||||
await _managerAPI.setUseDynamicTheme(value);
|
await _managerAPI.setUseDynamicTheme(value);
|
||||||
int currentTheme = DynamicTheme.of(context)!.themeId;
|
final int currentTheme = DynamicTheme.of(context)!.themeId;
|
||||||
if (currentTheme.isEven) {
|
if (currentTheme.isEven) {
|
||||||
await DynamicTheme.of(context)!.setTheme(value ? 2 : 0);
|
await DynamicTheme.of(context)!.setTheme(value ? 2 : 0);
|
||||||
} else {
|
} else {
|
||||||
@@ -38,9 +37,9 @@ class SUpdateTheme extends BaseViewModel {
|
|||||||
return _managerAPI.getUseDarkTheme();
|
return _managerAPI.getUseDarkTheme();
|
||||||
}
|
}
|
||||||
|
|
||||||
void setUseDarkTheme(BuildContext context, bool value) async {
|
Future<void> setUseDarkTheme(BuildContext context, bool value) async {
|
||||||
await _managerAPI.setUseDarkTheme(value);
|
await _managerAPI.setUseDarkTheme(value);
|
||||||
int currentTheme = DynamicTheme.of(context)!.themeId;
|
final int currentTheme = DynamicTheme.of(context)!.themeId;
|
||||||
if (currentTheme < 2) {
|
if (currentTheme < 2) {
|
||||||
await DynamicTheme.of(context)!.setTheme(value ? 1 : 0);
|
await DynamicTheme.of(context)!.setTheme(value ? 1 : 0);
|
||||||
} else {
|
} else {
|
||||||
@@ -64,8 +63,8 @@ class SUpdateThemeUI extends StatelessWidget {
|
|||||||
return SettingsSection(
|
return SettingsSection(
|
||||||
title: 'settingsView.appearanceSectionTitle',
|
title: 'settingsView.appearanceSectionTitle',
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
CustomSwitchTile(
|
SwitchListTile(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 20.0),
|
contentPadding: const EdgeInsets.symmetric(horizontal: 20.0),
|
||||||
title: I18nText(
|
title: I18nText(
|
||||||
'settingsView.darkThemeLabel',
|
'settingsView.darkThemeLabel',
|
||||||
child: const Text(
|
child: const Text(
|
||||||
@@ -78,7 +77,7 @@ class SUpdateThemeUI extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
subtitle: I18nText('settingsView.darkThemeHint'),
|
subtitle: I18nText('settingsView.darkThemeHint'),
|
||||||
value: SUpdateTheme().getDarkThemeStatus(),
|
value: SUpdateTheme().getDarkThemeStatus(),
|
||||||
onTap: (value) => SUpdateTheme().setUseDarkTheme(
|
onChanged: (value) => SUpdateTheme().setUseDarkTheme(
|
||||||
context,
|
context,
|
||||||
value,
|
value,
|
||||||
),
|
),
|
||||||
@@ -88,8 +87,8 @@ class SUpdateThemeUI extends StatelessWidget {
|
|||||||
builder: (context, snapshot) => Visibility(
|
builder: (context, snapshot) => Visibility(
|
||||||
visible:
|
visible:
|
||||||
snapshot.hasData && snapshot.data! >= ANDROID_12_SDK_VERSION,
|
snapshot.hasData && snapshot.data! >= ANDROID_12_SDK_VERSION,
|
||||||
child: CustomSwitchTile(
|
child: SwitchListTile(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 20.0),
|
contentPadding: const EdgeInsets.symmetric(horizontal: 20.0),
|
||||||
title: I18nText(
|
title: I18nText(
|
||||||
'settingsView.dynamicThemeLabel',
|
'settingsView.dynamicThemeLabel',
|
||||||
child: const Text(
|
child: const Text(
|
||||||
@@ -102,11 +101,12 @@ class SUpdateThemeUI extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
subtitle: I18nText('settingsView.dynamicThemeHint'),
|
subtitle: I18nText('settingsView.dynamicThemeHint'),
|
||||||
value: _settingViewModel.sUpdateTheme.getDynamicThemeStatus(),
|
value: _settingViewModel.sUpdateTheme.getDynamicThemeStatus(),
|
||||||
onTap: (value) =>
|
onChanged: (value) => {
|
||||||
_settingViewModel.sUpdateTheme.setUseDynamicTheme(
|
_settingViewModel.sUpdateTheme.setUseDynamicTheme(
|
||||||
context,
|
context,
|
||||||
value,
|
value,
|
||||||
),
|
),
|
||||||
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -3,8 +3,7 @@
|
|||||||
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:google_fonts/google_fonts.dart';
|
import 'package:google_fonts/google_fonts.dart';
|
||||||
import 'package:revanced_manager/ui/views/settings/settingsFragement/settings_update_language.dart';
|
import 'package:revanced_manager/ui/views/settings/settingsFragment/settings_update_theme.dart';
|
||||||
import 'package:revanced_manager/ui/views/settings/settingsFragement/settings_update_theme.dart';
|
|
||||||
import 'package:revanced_manager/ui/views/settings/settings_viewmodel.dart';
|
import 'package:revanced_manager/ui/views/settings/settings_viewmodel.dart';
|
||||||
import 'package:revanced_manager/ui/widgets/settingsView/settings_advanced_section.dart';
|
import 'package:revanced_manager/ui/widgets/settingsView/settings_advanced_section.dart';
|
||||||
import 'package:revanced_manager/ui/widgets/settingsView/settings_export_section.dart';
|
import 'package:revanced_manager/ui/widgets/settingsView/settings_export_section.dart';
|
||||||
@@ -33,7 +32,7 @@ class SettingsView extends StatelessWidget {
|
|||||||
child: Text(
|
child: Text(
|
||||||
'',
|
'',
|
||||||
style: GoogleFonts.inter(
|
style: GoogleFonts.inter(
|
||||||
color: Theme.of(context).textTheme.headline6!.color,
|
color: Theme.of(context).textTheme.titleLarge!.color,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -50,8 +49,6 @@ class SettingsView extends StatelessWidget {
|
|||||||
_settingsDivider,
|
_settingsDivider,
|
||||||
SExportSection(),
|
SExportSection(),
|
||||||
_settingsDivider,
|
_settingsDivider,
|
||||||
// SLoggingSection(),
|
|
||||||
// _settingsDivider,
|
|
||||||
SInfoSection(),
|
SInfoSection(),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import 'dart:io';
|
|||||||
import 'package:cr_file_saver/file_saver.dart';
|
import 'package:cr_file_saver/file_saver.dart';
|
||||||
import 'package:device_info_plus/device_info_plus.dart';
|
import 'package:device_info_plus/device_info_plus.dart';
|
||||||
import 'package:file_picker/file_picker.dart';
|
import 'package:file_picker/file_picker.dart';
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:logcat/logcat.dart';
|
import 'package:logcat/logcat.dart';
|
||||||
import 'package:path_provider/path_provider.dart';
|
import 'package:path_provider/path_provider.dart';
|
||||||
import 'package:revanced_manager/app/app.locator.dart';
|
import 'package:revanced_manager/app/app.locator.dart';
|
||||||
@@ -9,15 +10,15 @@ import 'package:revanced_manager/app/app.router.dart';
|
|||||||
import 'package:revanced_manager/services/manager_api.dart';
|
import 'package:revanced_manager/services/manager_api.dart';
|
||||||
import 'package:revanced_manager/services/toast.dart';
|
import 'package:revanced_manager/services/toast.dart';
|
||||||
import 'package:revanced_manager/ui/views/patcher/patcher_viewmodel.dart';
|
import 'package:revanced_manager/ui/views/patcher/patcher_viewmodel.dart';
|
||||||
import 'package:revanced_manager/ui/views/settings/settingsFragement/settings_update_language.dart';
|
import 'package:revanced_manager/ui/views/settings/settingsFragment/settings_update_language.dart';
|
||||||
import 'package:revanced_manager/ui/views/settings/settingsFragement/settings_update_theme.dart';
|
import 'package:revanced_manager/ui/views/settings/settingsFragment/settings_update_theme.dart';
|
||||||
import 'package:sentry_flutter/sentry_flutter.dart';
|
|
||||||
import 'package:share_extend/share_extend.dart';
|
import 'package:share_extend/share_extend.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';
|
||||||
|
|
||||||
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>();
|
||||||
|
|
||||||
@@ -28,16 +29,6 @@ class SettingsViewModel extends BaseViewModel {
|
|||||||
_navigationService.navigateTo(Routes.contributorsView);
|
_navigationService.navigateTo(Routes.contributorsView);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool isSentryEnabled() {
|
|
||||||
return _managerAPI.isSentryEnabled();
|
|
||||||
}
|
|
||||||
|
|
||||||
void useSentry(bool value) {
|
|
||||||
_managerAPI.setSentryStatus(value);
|
|
||||||
_toast.showBottom('settingsView.restartAppForChanges');
|
|
||||||
notifyListeners();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool areUniversalPatchesEnabled() {
|
bool areUniversalPatchesEnabled() {
|
||||||
return _managerAPI.areUniversalPatchesEnabled();
|
return _managerAPI.areUniversalPatchesEnabled();
|
||||||
}
|
}
|
||||||
@@ -70,47 +61,91 @@ class SettingsViewModel extends BaseViewModel {
|
|||||||
|
|
||||||
Future<void> exportPatches() async {
|
Future<void> exportPatches() async {
|
||||||
try {
|
try {
|
||||||
File outFile = File(_managerAPI.storedPatchesFile);
|
final File outFile = File(_managerAPI.storedPatchesFile);
|
||||||
if (outFile.existsSync()) {
|
if (outFile.existsSync()) {
|
||||||
String dateTime =
|
final String dateTime = DateTime.now()
|
||||||
DateTime.now().toString().replaceAll(' ', '_').split('.').first;
|
.toString()
|
||||||
String tempFilePath =
|
.replaceAll(' ', '_')
|
||||||
'${outFile.path.substring(0, outFile.path.lastIndexOf('/') + 1)}selected_patches_$dateTime.json';
|
.split('.')
|
||||||
outFile.copySync(tempFilePath);
|
.first;
|
||||||
await CRFileSaver.saveFileWithDialog(SaveFileDialogParams(
|
await CRFileSaver.saveFileWithDialog(
|
||||||
sourceFilePath: tempFilePath, destinationFileName: ''));
|
SaveFileDialogParams(
|
||||||
File(tempFilePath).delete();
|
sourceFilePath: outFile.path,
|
||||||
locator<Toast>().showBottom('settingsView.exportedPatches');
|
destinationFileName: 'selected_patches_$dateTime.json',
|
||||||
|
),
|
||||||
|
);
|
||||||
|
_toast.showBottom('settingsView.exportedPatches');
|
||||||
} else {
|
} else {
|
||||||
locator<Toast>().showBottom('settingsView.noExportFileFound');
|
_toast.showBottom('settingsView.noExportFileFound');
|
||||||
|
}
|
||||||
|
} on Exception catch (e) {
|
||||||
|
if (kDebugMode) {
|
||||||
|
print(e);
|
||||||
}
|
}
|
||||||
} on Exception catch (e, s) {
|
|
||||||
Sentry.captureException(e, stackTrace: s);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> importPatches() async {
|
Future<void> importPatches() async {
|
||||||
try {
|
try {
|
||||||
FilePickerResult? result = await FilePicker.platform.pickFiles(
|
final FilePickerResult? result =
|
||||||
|
await FilePicker.platform.pickFiles(
|
||||||
type: FileType.custom,
|
type: FileType.custom,
|
||||||
allowedExtensions: ['json'],
|
allowedExtensions: ['json'],
|
||||||
);
|
);
|
||||||
if (result != null && result.files.single.path != null) {
|
if (result != null && result.files.single.path != null) {
|
||||||
File inFile = File(result.files.single.path!);
|
final File inFile = File(result.files.single.path!);
|
||||||
final File storedPatchesFile = File(_managerAPI.storedPatchesFile);
|
inFile.copySync(_managerAPI.storedPatchesFile);
|
||||||
if (!storedPatchesFile.existsSync()) {
|
|
||||||
storedPatchesFile.createSync(recursive: true);
|
|
||||||
}
|
|
||||||
inFile.copySync(storedPatchesFile.path);
|
|
||||||
inFile.delete();
|
inFile.delete();
|
||||||
if (locator<PatcherViewModel>().selectedApp != null) {
|
if (locator<PatcherViewModel>().selectedApp != null) {
|
||||||
locator<PatcherViewModel>().loadLastSelectedPatches();
|
locator<PatcherViewModel>().loadLastSelectedPatches();
|
||||||
}
|
}
|
||||||
locator<Toast>().showBottom('settingsView.importedPatches');
|
_toast.showBottom('settingsView.importedPatches');
|
||||||
}
|
}
|
||||||
} on Exception catch (e, s) {
|
} on Exception catch (e) {
|
||||||
await Sentry.captureException(e, stackTrace: s);
|
if (kDebugMode) {
|
||||||
locator<Toast>().showBottom('settingsView.jsonSelectorErrorMessage');
|
print(e);
|
||||||
|
}
|
||||||
|
_toast.showBottom('settingsView.jsonSelectorErrorMessage');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -120,13 +155,13 @@ class SettingsViewModel extends BaseViewModel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<int> getSdkVersion() async {
|
Future<int> getSdkVersion() async {
|
||||||
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 {
|
||||||
Directory appCacheDir = await getTemporaryDirectory();
|
final Directory appCacheDir = await getTemporaryDirectory();
|
||||||
Directory logsDir = Directory('${appCacheDir.path}/logs');
|
final Directory logsDir = Directory('${appCacheDir.path}/logs');
|
||||||
if (logsDir.existsSync()) {
|
if (logsDir.existsSync()) {
|
||||||
logsDir.deleteSync(recursive: true);
|
logsDir.deleteSync(recursive: true);
|
||||||
}
|
}
|
||||||
@@ -134,17 +169,18 @@ class SettingsViewModel extends BaseViewModel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<void> exportLogcatLogs() async {
|
Future<void> exportLogcatLogs() async {
|
||||||
Directory appCache = await getTemporaryDirectory();
|
final Directory appCache = await getTemporaryDirectory();
|
||||||
Directory logDir = Directory('${appCache.path}/logs');
|
final Directory logDir = Directory('${appCache.path}/logs');
|
||||||
logDir.createSync();
|
logDir.createSync();
|
||||||
String dateTime = DateTime.now()
|
final String dateTime = DateTime.now()
|
||||||
.toIso8601String()
|
.toIso8601String()
|
||||||
.replaceAll('-', '')
|
.replaceAll('-', '')
|
||||||
.replaceAll(':', '')
|
.replaceAll(':', '')
|
||||||
.replaceAll('T', '')
|
.replaceAll('T', '')
|
||||||
.replaceAll('.', '');
|
.replaceAll('.', '');
|
||||||
File logcat = File('${logDir.path}/revanced-manager_logcat_$dateTime.log');
|
final File logcat =
|
||||||
String logs = await Logcat.execute();
|
File('${logDir.path}/revanced-manager_logcat_$dateTime.log');
|
||||||
|
final String logs = await Logcat.execute();
|
||||||
logcat.writeAsStringSync(logs);
|
logcat.writeAsStringSync(logs);
|
||||||
ShareExtend.share(logcat.path, 'file');
|
ShareExtend.share(logcat.path, 'file');
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,12 +8,11 @@ import 'package:revanced_manager/ui/widgets/shared/custom_sliver_app_bar.dart';
|
|||||||
import 'package:stacked/stacked.dart';
|
import 'package:stacked/stacked.dart';
|
||||||
|
|
||||||
class AppInfoView extends StatelessWidget {
|
class AppInfoView extends StatelessWidget {
|
||||||
final PatchedApplication app;
|
|
||||||
|
|
||||||
const AppInfoView({
|
const AppInfoView({
|
||||||
Key? key,
|
Key? key,
|
||||||
required this.app,
|
required this.app,
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
final PatchedApplication app;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
@@ -28,7 +27,7 @@ class AppInfoView extends StatelessWidget {
|
|||||||
child: Text(
|
child: Text(
|
||||||
'',
|
'',
|
||||||
style: GoogleFonts.inter(
|
style: GoogleFonts.inter(
|
||||||
color: Theme.of(context).textTheme.headline6!.color,
|
color: Theme.of(context).textTheme.titleLarge!.color,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -52,13 +51,13 @@ class AppInfoView extends StatelessWidget {
|
|||||||
Text(
|
Text(
|
||||||
app.name,
|
app.name,
|
||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
style: Theme.of(context).textTheme.headline6,
|
style: Theme.of(context).textTheme.titleLarge,
|
||||||
),
|
),
|
||||||
const SizedBox(height: 4),
|
const SizedBox(height: 4),
|
||||||
Text(
|
Text(
|
||||||
app.version,
|
app.version,
|
||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
style: Theme.of(context).textTheme.subtitle1,
|
style: Theme.of(context).textTheme.titleLarge,
|
||||||
),
|
),
|
||||||
const SizedBox(height: 20),
|
const SizedBox(height: 20),
|
||||||
Padding(
|
Padding(
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ class AppInfoViewModel extends BaseViewModel {
|
|||||||
) async {
|
) async {
|
||||||
bool isUninstalled = true;
|
bool isUninstalled = true;
|
||||||
if (app.isRooted) {
|
if (app.isRooted) {
|
||||||
bool hasRootPermissions = await _rootAPI.hasRootPermissions();
|
final bool hasRootPermissions = await _rootAPI.hasRootPermissions();
|
||||||
if (hasRootPermissions) {
|
if (hasRootPermissions) {
|
||||||
await _rootAPI.deleteApp(app.packageName, app.apkFilePath);
|
await _rootAPI.deleteApp(app.packageName, app.apkFilePath);
|
||||||
if (!onlyUnpatch) {
|
if (!onlyUnpatch) {
|
||||||
@@ -45,7 +45,7 @@ class AppInfoViewModel extends BaseViewModel {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void navigateToPatcher(PatchedApplication app) async {
|
Future<void> navigateToPatcher(PatchedApplication app) async {
|
||||||
locator<PatcherViewModel>().selectedApp = app;
|
locator<PatcherViewModel>().selectedApp = app;
|
||||||
locator<PatcherViewModel>().selectedPatches =
|
locator<PatcherViewModel>().selectedPatches =
|
||||||
await _patcherAPI.getAppliedPatches(app.appliedPatches);
|
await _patcherAPI.getAppliedPatches(app.appliedPatches);
|
||||||
@@ -62,7 +62,7 @@ class AppInfoViewModel extends BaseViewModel {
|
|||||||
PatchedApplication app,
|
PatchedApplication app,
|
||||||
bool onlyUnpatch,
|
bool onlyUnpatch,
|
||||||
) async {
|
) async {
|
||||||
bool hasRootPermissions = await _rootAPI.hasRootPermissions();
|
final bool hasRootPermissions = await _rootAPI.hasRootPermissions();
|
||||||
if (app.isRooted && !hasRootPermissions) {
|
if (app.isRooted && !hasRootPermissions) {
|
||||||
return showDialog(
|
return showDialog(
|
||||||
context: context,
|
context: context,
|
||||||
@@ -134,7 +134,8 @@ class AppInfoViewModel extends BaseViewModel {
|
|||||||
title: I18nText('appInfoView.appliedPatchesLabel'),
|
title: I18nText('appInfoView.appliedPatchesLabel'),
|
||||||
backgroundColor: Theme.of(context).colorScheme.secondaryContainer,
|
backgroundColor: Theme.of(context).colorScheme.secondaryContainer,
|
||||||
content: SingleChildScrollView(
|
content: SingleChildScrollView(
|
||||||
child: Text(getAppliedPatchesString(app.appliedPatches))),
|
child: Text(getAppliedPatchesString(app.appliedPatches)),
|
||||||
|
),
|
||||||
actions: <Widget>[
|
actions: <Widget>[
|
||||||
CustomMaterialButton(
|
CustomMaterialButton(
|
||||||
label: I18nText('okButton'),
|
label: I18nText('okButton'),
|
||||||
@@ -146,13 +147,15 @@ class AppInfoViewModel extends BaseViewModel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
String getAppliedPatchesString(List<String> appliedPatches) {
|
String getAppliedPatchesString(List<String> appliedPatches) {
|
||||||
List<String> names = appliedPatches
|
final List<String> names = appliedPatches
|
||||||
.map((p) => p
|
.map(
|
||||||
.replaceAll('-', ' ')
|
(p) => p
|
||||||
.split('-')
|
.replaceAll('-', ' ')
|
||||||
.join(' ')
|
.split('-')
|
||||||
.toTitleCase()
|
.join(' ')
|
||||||
.replaceFirst('Microg', 'MicroG'))
|
.toTitleCase()
|
||||||
|
.replaceFirst('Microg', 'MicroG'),
|
||||||
|
)
|
||||||
.toList();
|
.toList();
|
||||||
return '\u2022 ${names.join('\n\u2022 ')}';
|
return '\u2022 ${names.join('\n\u2022 ')}';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,7 +21,6 @@ class AppSkeletonLoader extends StatelessWidget {
|
|||||||
style: SkeletonAvatarStyle(
|
style: SkeletonAvatarStyle(
|
||||||
width: screenWidth * 0.15,
|
width: screenWidth * 0.15,
|
||||||
height: screenWidth * 0.15,
|
height: screenWidth * 0.15,
|
||||||
shape: BoxShape.rectangle,
|
|
||||||
borderRadius: const BorderRadius.all(Radius.circular(12)),
|
borderRadius: const BorderRadius.all(Radius.circular(12)),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -3,20 +3,21 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:revanced_manager/ui/widgets/shared/custom_card.dart';
|
import 'package:revanced_manager/ui/widgets/shared/custom_card.dart';
|
||||||
|
|
||||||
class InstalledAppItem extends StatefulWidget {
|
class InstalledAppItem extends StatefulWidget {
|
||||||
final String name;
|
|
||||||
final String pkgName;
|
|
||||||
final Uint8List icon;
|
|
||||||
final int patchesCount;
|
|
||||||
final Function()? onTap;
|
|
||||||
|
|
||||||
const InstalledAppItem({
|
const InstalledAppItem({
|
||||||
Key? key,
|
Key? key,
|
||||||
required this.name,
|
required this.name,
|
||||||
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 pkgName;
|
||||||
|
final Uint8List icon;
|
||||||
|
final int patchesCount;
|
||||||
|
final String suggestedVersion;
|
||||||
|
final Function()? onTap;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<InstalledAppItem> createState() => _InstalledAppItemState();
|
State<InstalledAppItem> createState() => _InstalledAppItemState();
|
||||||
@@ -47,32 +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(
|
||||||
crossAxisAlignment: CrossAxisAlignment.center,
|
children: [
|
||||||
children: <Widget>[
|
|
||||||
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
@@ -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,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -6,14 +6,13 @@ import 'package:revanced_manager/ui/widgets/shared/custom_card.dart';
|
|||||||
import 'package:url_launcher/url_launcher.dart';
|
import 'package:url_launcher/url_launcher.dart';
|
||||||
|
|
||||||
class ContributorsCard extends StatefulWidget {
|
class ContributorsCard extends StatefulWidget {
|
||||||
final String title;
|
|
||||||
final List<dynamic> contributors;
|
|
||||||
|
|
||||||
const ContributorsCard({
|
const ContributorsCard({
|
||||||
Key? key,
|
Key? key,
|
||||||
required this.title,
|
required this.title,
|
||||||
required this.contributors,
|
required this.contributors,
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
final String title;
|
||||||
|
final List<dynamic> contributors;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<ContributorsCard> createState() => _ContributorsCardState();
|
State<ContributorsCard> createState() => _ContributorsCardState();
|
||||||
@@ -56,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(
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ import 'package:flutter_i18n/flutter_i18n.dart';
|
|||||||
import 'package:revanced_manager/app/app.locator.dart';
|
import 'package:revanced_manager/app/app.locator.dart';
|
||||||
import 'package:revanced_manager/models/patched_application.dart';
|
import 'package:revanced_manager/models/patched_application.dart';
|
||||||
import 'package:revanced_manager/ui/views/home/home_viewmodel.dart';
|
import 'package:revanced_manager/ui/views/home/home_viewmodel.dart';
|
||||||
import 'package:revanced_manager/ui/widgets/shared/application_item.dart';
|
|
||||||
import 'package:revanced_manager/ui/widgets/shared/custom_card.dart';
|
import 'package:revanced_manager/ui/widgets/shared/custom_card.dart';
|
||||||
|
|
||||||
class AvailableUpdatesCard extends StatelessWidget {
|
class AvailableUpdatesCard extends StatelessWidget {
|
||||||
@@ -29,7 +28,7 @@ class AvailableUpdatesCard extends StatelessWidget {
|
|||||||
child: Text(
|
child: Text(
|
||||||
'',
|
'',
|
||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
style: Theme.of(context).textTheme.subtitle1!.copyWith(
|
style: Theme.of(context).textTheme.titleMedium!.copyWith(
|
||||||
color: Theme.of(context).colorScheme.secondary,
|
color: Theme.of(context).colorScheme.secondary,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -54,7 +53,7 @@ class AvailableUpdatesCard extends StatelessWidget {
|
|||||||
// child: Text(
|
// child: Text(
|
||||||
// '',
|
// '',
|
||||||
// textAlign: TextAlign.center,
|
// textAlign: TextAlign.center,
|
||||||
// style: Theme.of(context).textTheme.subtitle1!.copyWith(
|
// style: Theme.of(context).textTheme.titleMedium!.copyWith(
|
||||||
// color: Theme.of(context).colorScheme.secondary,
|
// color: Theme.of(context).colorScheme.secondary,
|
||||||
// ),
|
// ),
|
||||||
// ),
|
// ),
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ class InstalledAppsCard extends StatelessWidget {
|
|||||||
child: Text(
|
child: Text(
|
||||||
'',
|
'',
|
||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
style: Theme.of(context).textTheme.subtitle1!.copyWith(
|
style: Theme.of(context).textTheme.titleMedium!.copyWith(
|
||||||
color: Theme.of(context).colorScheme.secondary,
|
color: Theme.of(context).colorScheme.secondary,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -2,16 +2,17 @@ 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/ui/views/home/home_viewmodel.dart';
|
import 'package:revanced_manager/ui/views/home/home_viewmodel.dart';
|
||||||
import 'package:revanced_manager/ui/widgets/shared/custom_material_button.dart';
|
|
||||||
import 'package:revanced_manager/ui/widgets/shared/custom_card.dart';
|
import 'package:revanced_manager/ui/widgets/shared/custom_card.dart';
|
||||||
|
import 'package:revanced_manager/ui/widgets/shared/custom_material_button.dart';
|
||||||
|
|
||||||
class LatestCommitCard extends StatefulWidget {
|
class LatestCommitCard extends StatefulWidget {
|
||||||
final Function() onPressed;
|
|
||||||
|
|
||||||
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() onPressedManager;
|
||||||
|
final Function() onPressedPatches;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<LatestCommitCard> createState() => _LatestCommitCardState();
|
State<LatestCommitCard> createState() => _LatestCommitCardState();
|
||||||
@@ -22,67 +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'),
|
const Row(
|
||||||
FutureBuilder<String?>(
|
children: <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(
|
||||||
isExpanded: false,
|
child: Row(
|
||||||
label: I18nText('latestCommitCard.updateButton'),
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
onPressed: snapshot.hasData && snapshot.data!
|
children: <Widget>[
|
||||||
? widget.onPressed
|
Column(
|
||||||
: () => {},
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: <Widget>[
|
||||||
|
const Row(
|
||||||
|
children: <Widget>[
|
||||||
|
Text('ReVanced Patches'),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const SizedBox(height: 4),
|
||||||
|
Row(
|
||||||
|
children: <Widget>[
|
||||||
|
FutureBuilder<String?>(
|
||||||
|
future: model.getLatestPatcherReleaseTime(),
|
||||||
|
builder: (context, snapshot) => Text(
|
||||||
|
snapshot.hasData && snapshot.data!.isNotEmpty
|
||||||
|
? FlutterI18n.translate(
|
||||||
|
context,
|
||||||
|
'latestCommitCard.timeagoLabel',
|
||||||
|
translationParams: {'time': snapshot.data!},
|
||||||
|
)
|
||||||
|
: FlutterI18n.translate(
|
||||||
|
context,
|
||||||
|
'latestCommitCard.loadingLabel',
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
),
|
FutureBuilder<bool>(
|
||||||
|
future: locator<HomeViewModel>().hasPatchesUpdates(),
|
||||||
|
initialData: false,
|
||||||
|
builder: (context, snapshot) => Opacity(
|
||||||
|
opacity: snapshot.hasData && snapshot.data! ? 1.0 : 0.25,
|
||||||
|
child: CustomMaterialButton(
|
||||||
|
label: I18nText('updateButton'),
|
||||||
|
onPressed: snapshot.hasData && snapshot.data!
|
||||||
|
? widget.onPressedPatches
|
||||||
|
: () => {},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
],
|
),
|
||||||
),
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,15 +14,14 @@ class UpdateConfirmationDialog extends StatelessWidget {
|
|||||||
|
|
||||||
return DraggableScrollableSheet(
|
return DraggableScrollableSheet(
|
||||||
expand: false,
|
expand: false,
|
||||||
initialChildSize: 0.5,
|
|
||||||
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,
|
||||||
@@ -37,20 +36,26 @@ class UpdateConfirmationDialog extends StatelessWidget {
|
|||||||
children: [
|
children: [
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.only(
|
padding: const EdgeInsets.only(
|
||||||
top: 40.0, left: 24.0, right: 24.0, bottom: 32.0),
|
top: 40.0,
|
||||||
|
left: 24.0,
|
||||||
|
right: 24.0,
|
||||||
|
bottom: 32.0,
|
||||||
|
),
|
||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment:
|
||||||
|
CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
I18nText(
|
I18nText(
|
||||||
'homeView.updateDialogTitle',
|
'homeView.updateDialogTitle',
|
||||||
child: const Text(
|
child: const Text(
|
||||||
"",
|
'',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 24,
|
fontSize: 24,
|
||||||
fontWeight: FontWeight.bold),
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 4.0),
|
const SizedBox(height: 4.0),
|
||||||
@@ -58,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,
|
||||||
@@ -89,31 +96,36 @@ 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(
|
||||||
"",
|
'',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 20,
|
fontSize: 20,
|
||||||
fontWeight: FontWeight.w500,
|
fontWeight: FontWeight.w500,
|
||||||
color: Theme.of(context)
|
color: Theme.of(context)
|
||||||
.colorScheme
|
.colorScheme
|
||||||
.onSecondaryContainer),
|
.onSecondaryContainer,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
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(
|
||||||
shrinkWrap: true,
|
shrinkWrap: true,
|
||||||
physics: const NeverScrollableScrollPhysics(),
|
physics: const NeverScrollableScrollPhysics(),
|
||||||
padding: const EdgeInsets.all(20.0),
|
padding: const EdgeInsets.all(20.0),
|
||||||
data: snapshot.data!["body"] ?? "",
|
data: snapshot.data!['body'] ?? '',
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
class GradientProgressIndicator extends StatefulWidget {
|
class GradientProgressIndicator extends StatefulWidget {
|
||||||
final double? progress;
|
|
||||||
const GradientProgressIndicator({required this.progress, super.key});
|
const GradientProgressIndicator({required this.progress, super.key});
|
||||||
|
final double? progress;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<GradientProgressIndicator> createState() =>
|
State<GradientProgressIndicator> createState() =>
|
||||||
|
|||||||
@@ -6,12 +6,11 @@ import 'package:revanced_manager/ui/views/patcher/patcher_viewmodel.dart';
|
|||||||
import 'package:revanced_manager/ui/widgets/shared/custom_card.dart';
|
import 'package:revanced_manager/ui/widgets/shared/custom_card.dart';
|
||||||
|
|
||||||
class AppSelectorCard extends StatelessWidget {
|
class AppSelectorCard extends StatelessWidget {
|
||||||
final Function() onPressed;
|
|
||||||
|
|
||||||
const AppSelectorCard({
|
const AppSelectorCard({
|
||||||
Key? key,
|
Key? key,
|
||||||
required this.onPressed,
|
required this.onPressed,
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
final Function() onPressed;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
@@ -33,40 +32,41 @@ class AppSelectorCard extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
locator<PatcherViewModel>().selectedApp == null
|
if (locator<PatcherViewModel>().selectedApp == null)
|
||||||
? I18nText('appSelectorCard.widgetSubtitle')
|
I18nText('appSelectorCard.widgetSubtitle')
|
||||||
: Row(
|
else
|
||||||
children: <Widget>[
|
Row(
|
||||||
SizedBox(
|
children: <Widget>[
|
||||||
height: 18.0,
|
SizedBox(
|
||||||
child: ClipOval(
|
height: 18.0,
|
||||||
child: Image.memory(
|
child: ClipOval(
|
||||||
locator<PatcherViewModel>().selectedApp == null
|
child: Image.memory(
|
||||||
? Uint8List(0)
|
locator<PatcherViewModel>().selectedApp == null
|
||||||
: locator<PatcherViewModel>().selectedApp!.icon,
|
? Uint8List(0)
|
||||||
fit: BoxFit.cover,
|
: locator<PatcherViewModel>().selectedApp!.icon,
|
||||||
),
|
fit: BoxFit.cover,
|
||||||
),
|
|
||||||
),
|
),
|
||||||
const SizedBox(width: 6),
|
),
|
||||||
Text(
|
|
||||||
locator<PatcherViewModel>()
|
|
||||||
.getAppSelectionString(),
|
|
||||||
style: const TextStyle(fontWeight: FontWeight.w600),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
locator<PatcherViewModel>().selectedApp == null
|
const SizedBox(width: 6),
|
||||||
? Container()
|
Text(
|
||||||
: Column(
|
locator<PatcherViewModel>().getAppSelectionString(),
|
||||||
children: [
|
style: const TextStyle(fontWeight: FontWeight.w600),
|
||||||
const SizedBox(height: 4),
|
|
||||||
Text(
|
|
||||||
locator<PatcherViewModel>()
|
|
||||||
.getRecommendedVersionString(context),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
if (locator<PatcherViewModel>().selectedApp == null)
|
||||||
|
Container()
|
||||||
|
else
|
||||||
|
Column(
|
||||||
|
children: [
|
||||||
|
const SizedBox(height: 4),
|
||||||
|
Text(
|
||||||
|
locator<PatcherViewModel>()
|
||||||
|
.getSuggestedVersionString(context),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -6,12 +6,11 @@ import 'package:revanced_manager/ui/views/patcher/patcher_viewmodel.dart';
|
|||||||
import 'package:revanced_manager/ui/widgets/shared/custom_card.dart';
|
import 'package:revanced_manager/ui/widgets/shared/custom_card.dart';
|
||||||
|
|
||||||
class PatchSelectorCard extends StatelessWidget {
|
class PatchSelectorCard extends StatelessWidget {
|
||||||
final Function() onPressed;
|
|
||||||
|
|
||||||
const PatchSelectorCard({
|
const PatchSelectorCard({
|
||||||
Key? key,
|
Key? key,
|
||||||
required this.onPressed,
|
required this.onPressed,
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
final Function() onPressed;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
@@ -46,11 +45,12 @@ class PatchSelectorCard extends StatelessWidget {
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
const SizedBox(height: 4),
|
const SizedBox(height: 4),
|
||||||
locator<PatcherViewModel>().selectedApp == null
|
if (locator<PatcherViewModel>().selectedApp == null)
|
||||||
? I18nText('patchSelectorCard.widgetSubtitle')
|
I18nText('patchSelectorCard.widgetSubtitle')
|
||||||
: locator<PatcherViewModel>().selectedPatches.isEmpty
|
else
|
||||||
? I18nText('patchSelectorCard.widgetEmptySubtitle')
|
locator<PatcherViewModel>().selectedPatches.isEmpty
|
||||||
: Text(_getPatchesSelection()),
|
? I18nText('patchSelectorCard.widgetEmptySubtitle')
|
||||||
|
: Text(_getPatchesSelection()),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@@ -58,7 +58,7 @@ class PatchSelectorCard extends StatelessWidget {
|
|||||||
|
|
||||||
String _getPatchesSelection() {
|
String _getPatchesSelection() {
|
||||||
String text = '';
|
String text = '';
|
||||||
for (Patch p in locator<PatcherViewModel>().selectedPatches) {
|
for (final Patch p in locator<PatcherViewModel>().selectedPatches) {
|
||||||
text += '\u2022 ${p.getSimpleName()} (v${p.version})\n';
|
text += '\u2022 ${p.getSimpleName()} (v${p.version})\n';
|
||||||
}
|
}
|
||||||
return text.substring(0, text.length - 1);
|
return text.substring(0, text.length - 1);
|
||||||
|
|||||||
@@ -3,11 +3,24 @@ import 'package:flutter_i18n/flutter_i18n.dart';
|
|||||||
import 'package:revanced_manager/app/app.locator.dart';
|
import 'package:revanced_manager/app/app.locator.dart';
|
||||||
import 'package:revanced_manager/services/manager_api.dart';
|
import 'package:revanced_manager/services/manager_api.dart';
|
||||||
import 'package:revanced_manager/services/toast.dart';
|
import 'package:revanced_manager/services/toast.dart';
|
||||||
import 'package:revanced_manager/ui/widgets/shared/custom_material_button.dart';
|
|
||||||
import 'package:revanced_manager/ui/widgets/shared/custom_card.dart';
|
import 'package:revanced_manager/ui/widgets/shared/custom_card.dart';
|
||||||
|
import 'package:revanced_manager/ui/widgets/shared/custom_material_button.dart';
|
||||||
|
|
||||||
// ignore: must_be_immutable
|
// ignore: must_be_immutable
|
||||||
class PatchItem extends StatefulWidget {
|
class PatchItem extends StatefulWidget {
|
||||||
|
PatchItem({
|
||||||
|
Key? key,
|
||||||
|
required this.name,
|
||||||
|
required this.simpleName,
|
||||||
|
required this.description,
|
||||||
|
required this.version,
|
||||||
|
required this.packageVersion,
|
||||||
|
required this.supportedPackageVersions,
|
||||||
|
required this.isUnsupported,
|
||||||
|
required this.isSelected,
|
||||||
|
required this.onChanged,
|
||||||
|
this.child,
|
||||||
|
}) : super(key: key);
|
||||||
final String name;
|
final String name;
|
||||||
final String simpleName;
|
final String simpleName;
|
||||||
final String description;
|
final String description;
|
||||||
@@ -21,20 +34,6 @@ class PatchItem extends StatefulWidget {
|
|||||||
final toast = locator<Toast>();
|
final toast = locator<Toast>();
|
||||||
final _managerAPI = locator<ManagerAPI>();
|
final _managerAPI = locator<ManagerAPI>();
|
||||||
|
|
||||||
PatchItem(
|
|
||||||
{Key? key,
|
|
||||||
required this.name,
|
|
||||||
required this.simpleName,
|
|
||||||
required this.description,
|
|
||||||
required this.version,
|
|
||||||
required this.packageVersion,
|
|
||||||
required this.supportedPackageVersions,
|
|
||||||
required this.isUnsupported,
|
|
||||||
required this.isSelected,
|
|
||||||
required this.onChanged,
|
|
||||||
this.child})
|
|
||||||
: super(key: key);
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<PatchItem> createState() => _PatchItemState();
|
State<PatchItem> createState() => _PatchItemState();
|
||||||
}
|
}
|
||||||
@@ -75,30 +74,31 @@ class _PatchItemState extends State<PatchItem> {
|
|||||||
Row(
|
Row(
|
||||||
crossAxisAlignment: CrossAxisAlignment.end,
|
crossAxisAlignment: CrossAxisAlignment.end,
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
Text(
|
Expanded(
|
||||||
widget.simpleName,
|
child: Text(
|
||||||
maxLines: 2,
|
widget.simpleName,
|
||||||
overflow: TextOverflow.visible,
|
maxLines: 2,
|
||||||
style: const TextStyle(
|
overflow: TextOverflow.visible,
|
||||||
fontSize: 16,
|
style: const TextStyle(
|
||||||
fontWeight: FontWeight.w600,
|
fontSize: 16,
|
||||||
),
|
fontWeight: FontWeight.w600,
|
||||||
),
|
),
|
||||||
const SizedBox(width: 6),
|
|
||||||
Text(
|
|
||||||
widget.version,
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 16,
|
|
||||||
color: Theme.of(context).colorScheme.secondary,
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
const SizedBox(height: 1),
|
||||||
|
Text(
|
||||||
|
widget.version,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 14,
|
||||||
|
color: Theme.of(context).colorScheme.secondary,
|
||||||
|
),
|
||||||
|
),
|
||||||
const SizedBox(height: 4),
|
const SizedBox(height: 4),
|
||||||
Text(
|
Text(
|
||||||
widget.description,
|
widget.description,
|
||||||
softWrap: true,
|
softWrap: true,
|
||||||
maxLines: 3,
|
|
||||||
overflow: TextOverflow.visible,
|
overflow: TextOverflow.visible,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
@@ -139,38 +139,37 @@ class _PatchItemState extends State<PatchItem> {
|
|||||||
)
|
)
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
widget.isUnsupported
|
if (widget.isUnsupported)
|
||||||
? Row(
|
Row(
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.only(top: 8),
|
padding: const EdgeInsets.only(top: 8),
|
||||||
child: TextButton.icon(
|
child: TextButton.icon(
|
||||||
label: I18nText('warning'),
|
label: I18nText('warning'),
|
||||||
icon: const Icon(Icons.warning, size: 20.0),
|
icon: const Icon(Icons.warning, size: 20.0),
|
||||||
onPressed: () => _showUnsupportedWarningDialog(),
|
onPressed: () => _showUnsupportedWarningDialog(),
|
||||||
style: ButtonStyle(
|
style: ButtonStyle(
|
||||||
shape: MaterialStateProperty.all(
|
shape: MaterialStateProperty.all(
|
||||||
RoundedRectangleBorder(
|
RoundedRectangleBorder(
|
||||||
borderRadius: BorderRadius.circular(8),
|
borderRadius: BorderRadius.circular(8),
|
||||||
side: BorderSide(
|
side: BorderSide(
|
||||||
width: 1,
|
color: Theme.of(context).colorScheme.secondary,
|
||||||
color:
|
|
||||||
Theme.of(context).colorScheme.secondary,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
backgroundColor: MaterialStateProperty.all(
|
|
||||||
Colors.transparent,
|
|
||||||
),
|
|
||||||
foregroundColor: MaterialStateProperty.all(
|
|
||||||
Theme.of(context).colorScheme.secondary,
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
backgroundColor: MaterialStateProperty.all(
|
||||||
|
Colors.transparent,
|
||||||
|
),
|
||||||
|
foregroundColor: MaterialStateProperty.all(
|
||||||
|
Theme.of(context).colorScheme.secondary,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
],
|
),
|
||||||
)
|
),
|
||||||
: Container(),
|
],
|
||||||
|
)
|
||||||
|
else
|
||||||
|
Container(),
|
||||||
widget.child ?? const SizedBox(),
|
widget.child ?? const SizedBox(),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -3,8 +3,8 @@ import 'package:flutter_i18n/flutter_i18n.dart';
|
|||||||
import 'package:google_fonts/google_fonts.dart';
|
import 'package:google_fonts/google_fonts.dart';
|
||||||
|
|
||||||
class OptionsTextField extends StatelessWidget {
|
class OptionsTextField extends StatelessWidget {
|
||||||
final String hint;
|
|
||||||
const OptionsTextField({Key? key, required this.hint}) : super(key: key);
|
const OptionsTextField({Key? key, required this.hint}) : super(key: key);
|
||||||
|
final String hint;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
@@ -12,7 +12,7 @@ class OptionsTextField extends StatelessWidget {
|
|||||||
final sWidth = MediaQuery.of(context).size.width;
|
final sWidth = MediaQuery.of(context).size.width;
|
||||||
return Container(
|
return Container(
|
||||||
margin: const EdgeInsets.only(top: 12, bottom: 6),
|
margin: const EdgeInsets.only(top: 12, bottom: 6),
|
||||||
padding: const EdgeInsets.all(0),
|
padding: EdgeInsets.zero,
|
||||||
child: TextField(
|
child: TextField(
|
||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
constraints: BoxConstraints(
|
constraints: BoxConstraints(
|
||||||
@@ -28,9 +28,9 @@ class OptionsTextField extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class OptionsFilePicker extends StatelessWidget {
|
class OptionsFilePicker extends StatelessWidget {
|
||||||
final String optionName;
|
|
||||||
const OptionsFilePicker({Key? key, required this.optionName})
|
const OptionsFilePicker({Key? key, required this.optionName})
|
||||||
: super(key: key);
|
: super(key: key);
|
||||||
|
final String optionName;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
@@ -61,7 +61,7 @@ class OptionsFilePicker extends StatelessWidget {
|
|||||||
child: Text(
|
child: Text(
|
||||||
'Select File',
|
'Select File',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: Theme.of(context).textTheme.bodyText1?.color,
|
color: Theme.of(context).textTheme.bodyLarge?.color,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
import 'package:flutter_i18n/flutter_i18n.dart';
|
import 'package:flutter_i18n/flutter_i18n.dart';
|
||||||
import 'package:revanced_manager/utils/about_info.dart';
|
import 'package:revanced_manager/utils/about_info.dart';
|
||||||
import 'package:flutter/services.dart';
|
|
||||||
|
|
||||||
class AboutWidget extends StatefulWidget {
|
class AboutWidget extends StatefulWidget {
|
||||||
const AboutWidget({Key? key, this.padding}) : super(key: key);
|
const AboutWidget({Key? key, this.padding}) : super(key: key);
|
||||||
|
|||||||
@@ -1,14 +1,13 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
class CustomSwitch extends StatelessWidget {
|
class CustomSwitch extends StatelessWidget {
|
||||||
final ValueChanged<bool> onChanged;
|
|
||||||
final bool value;
|
|
||||||
|
|
||||||
const CustomSwitch({
|
const CustomSwitch({
|
||||||
Key? key,
|
Key? key,
|
||||||
required this.onChanged,
|
required this.onChanged,
|
||||||
required this.value,
|
required this.value,
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
final ValueChanged<bool> onChanged;
|
||||||
|
final bool value;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
@@ -45,7 +44,7 @@ class CustomSwitch extends StatelessWidget {
|
|||||||
shape: BoxShape.circle,
|
shape: BoxShape.circle,
|
||||||
color: value
|
color: value
|
||||||
? Theme.of(context).colorScheme.primaryContainer
|
? Theme.of(context).colorScheme.primaryContainer
|
||||||
: Colors.white,
|
: Theme.of(context).colorScheme.surface,
|
||||||
boxShadow: [
|
boxShadow: [
|
||||||
BoxShadow(
|
BoxShadow(
|
||||||
color: Colors.black12.withOpacity(0.1),
|
color: Colors.black12.withOpacity(0.1),
|
||||||
|
|||||||
@@ -2,12 +2,6 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:revanced_manager/ui/widgets/settingsView/custom_switch.dart';
|
import 'package:revanced_manager/ui/widgets/settingsView/custom_switch.dart';
|
||||||
|
|
||||||
class CustomSwitchTile extends StatelessWidget {
|
class CustomSwitchTile extends StatelessWidget {
|
||||||
final Widget title;
|
|
||||||
final Widget subtitle;
|
|
||||||
final bool value;
|
|
||||||
final Function(bool) onTap;
|
|
||||||
final EdgeInsetsGeometry? padding;
|
|
||||||
|
|
||||||
const CustomSwitchTile({
|
const CustomSwitchTile({
|
||||||
Key? key,
|
Key? key,
|
||||||
required this.title,
|
required this.title,
|
||||||
@@ -16,6 +10,11 @@ class CustomSwitchTile extends StatelessWidget {
|
|||||||
required this.onTap,
|
required this.onTap,
|
||||||
this.padding,
|
this.padding,
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
final Widget title;
|
||||||
|
final Widget subtitle;
|
||||||
|
final bool value;
|
||||||
|
final Function(bool) onTap;
|
||||||
|
final EdgeInsetsGeometry? padding;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
|||||||
@@ -1,12 +1,6 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
class CustomTextField extends StatelessWidget {
|
class CustomTextField extends StatelessWidget {
|
||||||
final TextEditingController inputController;
|
|
||||||
final Widget label;
|
|
||||||
final String hint;
|
|
||||||
final Widget? leadingIcon;
|
|
||||||
final Function(String)? onChanged;
|
|
||||||
|
|
||||||
const CustomTextField({
|
const CustomTextField({
|
||||||
Key? key,
|
Key? key,
|
||||||
required this.inputController,
|
required this.inputController,
|
||||||
@@ -15,6 +9,11 @@ class CustomTextField extends StatelessWidget {
|
|||||||
this.leadingIcon,
|
this.leadingIcon,
|
||||||
required this.onChanged,
|
required this.onChanged,
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
final TextEditingController inputController;
|
||||||
|
final Widget label;
|
||||||
|
final String hint;
|
||||||
|
final Widget? leadingIcon;
|
||||||
|
final Function(String)? onChanged;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
@@ -45,10 +44,8 @@ class CustomTextField extends StatelessWidget {
|
|||||||
border: OutlineInputBorder(
|
border: OutlineInputBorder(
|
||||||
borderSide: BorderSide(
|
borderSide: BorderSide(
|
||||||
color: Theme.of(context).colorScheme.primary,
|
color: Theme.of(context).colorScheme.primary,
|
||||||
width: 1.0,
|
|
||||||
),
|
),
|
||||||
borderRadius: BorderRadius.circular(10),
|
borderRadius: BorderRadius.circular(10),
|
||||||
gapPadding: 4.0,
|
|
||||||
),
|
),
|
||||||
focusedBorder: OutlineInputBorder(
|
focusedBorder: OutlineInputBorder(
|
||||||
borderSide: BorderSide(
|
borderSide: BorderSide(
|
||||||
@@ -60,14 +57,12 @@ class CustomTextField extends StatelessWidget {
|
|||||||
errorBorder: OutlineInputBorder(
|
errorBorder: OutlineInputBorder(
|
||||||
borderSide: BorderSide(
|
borderSide: BorderSide(
|
||||||
color: Theme.of(context).colorScheme.error,
|
color: Theme.of(context).colorScheme.error,
|
||||||
width: 1.0,
|
|
||||||
),
|
),
|
||||||
borderRadius: BorderRadius.circular(10),
|
borderRadius: BorderRadius.circular(10),
|
||||||
),
|
),
|
||||||
enabledBorder: OutlineInputBorder(
|
enabledBorder: OutlineInputBorder(
|
||||||
borderSide: BorderSide(
|
borderSide: BorderSide(
|
||||||
color: Theme.of(context).colorScheme.primary,
|
color: Theme.of(context).colorScheme.primary,
|
||||||
width: 1.0,
|
|
||||||
),
|
),
|
||||||
borderRadius: BorderRadius.circular(10),
|
borderRadius: BorderRadius.circular(10),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -2,12 +2,13 @@
|
|||||||
|
|
||||||
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/settingsFragement/settings_manage_api_url.dart';
|
import 'package:revanced_manager/ui/views/settings/settingsFragment/settings_manage_api_url.dart';
|
||||||
import 'package:revanced_manager/ui/views/settings/settingsFragement/settings_manage_sources.dart';
|
import 'package:revanced_manager/ui/views/settings/settingsFragment/settings_manage_sources.dart';
|
||||||
import 'package:revanced_manager/ui/views/settings/settings_viewmodel.dart';
|
import 'package:revanced_manager/ui/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,7 +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/settings_viewmodel.dart';
|
import 'package:revanced_manager/ui/views/settings/settings_viewmodel.dart';
|
||||||
import 'package:revanced_manager/ui/widgets/settingsView/custom_switch_tile.dart';
|
|
||||||
|
|
||||||
class SExperimentalPatches extends StatefulWidget {
|
class SExperimentalPatches extends StatefulWidget {
|
||||||
const SExperimentalPatches({super.key});
|
const SExperimentalPatches({super.key});
|
||||||
@@ -15,8 +14,8 @@ final _settingsViewModel = SettingsViewModel();
|
|||||||
class _SExperimentalPatchesState extends State<SExperimentalPatches> {
|
class _SExperimentalPatchesState extends State<SExperimentalPatches> {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return CustomSwitchTile(
|
return SwitchListTile(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 20.0),
|
contentPadding: const EdgeInsets.symmetric(horizontal: 20.0),
|
||||||
title: I18nText(
|
title: I18nText(
|
||||||
'settingsView.experimentalPatchesLabel',
|
'settingsView.experimentalPatchesLabel',
|
||||||
child: const Text(
|
child: const Text(
|
||||||
@@ -29,7 +28,7 @@ class _SExperimentalPatchesState extends State<SExperimentalPatches> {
|
|||||||
),
|
),
|
||||||
subtitle: I18nText('settingsView.experimentalPatchesHint'),
|
subtitle: I18nText('settingsView.experimentalPatchesHint'),
|
||||||
value: _settingsViewModel.areExperimentalPatchesEnabled(),
|
value: _settingsViewModel.areExperimentalPatchesEnabled(),
|
||||||
onTap: (value) {
|
onChanged: (value) {
|
||||||
setState(() {
|
setState(() {
|
||||||
_settingsViewModel.useExperimentalPatches(value);
|
_settingsViewModel.useExperimentalPatches(value);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,22 +1,23 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_i18n/widgets/I18nText.dart';
|
import 'package:flutter_i18n/widgets/I18nText.dart';
|
||||||
import 'package:revanced_manager/ui/views/settings/settings_viewmodel.dart';
|
import 'package:revanced_manager/ui/views/settings/settings_viewmodel.dart';
|
||||||
import 'package:revanced_manager/ui/widgets/settingsView/custom_switch_tile.dart';
|
|
||||||
|
|
||||||
class SExperimentalUniversalPatches extends StatefulWidget {
|
class SExperimentalUniversalPatches extends StatefulWidget {
|
||||||
const SExperimentalUniversalPatches({super.key});
|
const SExperimentalUniversalPatches({super.key});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<SExperimentalUniversalPatches> createState() => _SExperimentalUniversalPatchesState();
|
State<SExperimentalUniversalPatches> createState() =>
|
||||||
|
_SExperimentalUniversalPatchesState();
|
||||||
}
|
}
|
||||||
|
|
||||||
final _settingsViewModel = SettingsViewModel();
|
final _settingsViewModel = SettingsViewModel();
|
||||||
|
|
||||||
class _SExperimentalUniversalPatchesState extends State<SExperimentalUniversalPatches> {
|
class _SExperimentalUniversalPatchesState
|
||||||
|
extends State<SExperimentalUniversalPatches> {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return CustomSwitchTile(
|
return SwitchListTile(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 20.0),
|
contentPadding: const EdgeInsets.symmetric(horizontal: 20.0),
|
||||||
title: I18nText(
|
title: I18nText(
|
||||||
'settingsView.experimentalUniversalPatchesLabel',
|
'settingsView.experimentalUniversalPatchesLabel',
|
||||||
child: const Text(
|
child: const Text(
|
||||||
@@ -29,7 +30,7 @@ class _SExperimentalUniversalPatchesState extends State<SExperimentalUniversalPa
|
|||||||
),
|
),
|
||||||
subtitle: I18nText('settingsView.experimentalUniversalPatchesHint'),
|
subtitle: I18nText('settingsView.experimentalUniversalPatchesHint'),
|
||||||
value: _settingsViewModel.areUniversalPatchesEnabled(),
|
value: _settingsViewModel.areUniversalPatchesEnabled(),
|
||||||
onTap: (value) {
|
onChanged: (value) {
|
||||||
setState(() {
|
setState(() {
|
||||||
_settingsViewModel.showUniversalPatches(value);
|
_settingsViewModel.showUniversalPatches(value);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -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(
|
||||||
|
|||||||
@@ -1,36 +0,0 @@
|
|||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:flutter_i18n/widgets/I18nText.dart';
|
|
||||||
import 'package:revanced_manager/ui/views/settings/settings_viewmodel.dart';
|
|
||||||
import 'package:revanced_manager/ui/widgets/settingsView/custom_switch_tile.dart';
|
|
||||||
import 'package:revanced_manager/ui/widgets/settingsView/settings_section.dart';
|
|
||||||
|
|
||||||
final _settingsViewModel = SettingsViewModel();
|
|
||||||
|
|
||||||
class SLoggingSection extends StatelessWidget {
|
|
||||||
const SLoggingSection({super.key});
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return SettingsSection(
|
|
||||||
title: 'settingsView.logsSectionTitle',
|
|
||||||
children: <Widget>[
|
|
||||||
CustomSwitchTile(
|
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 20.0),
|
|
||||||
title: I18nText(
|
|
||||||
'settingsView.sentryLabel',
|
|
||||||
child: const Text(
|
|
||||||
'',
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 20,
|
|
||||||
fontWeight: FontWeight.w500,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
subtitle: I18nText('settingsView.sentryHint'),
|
|
||||||
value: _settingsViewModel.isSentryEnabled(),
|
|
||||||
onTap: (value) => _settingsViewModel.useSentry(value),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -2,14 +2,13 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:flutter_i18n/flutter_i18n.dart';
|
import 'package:flutter_i18n/flutter_i18n.dart';
|
||||||
|
|
||||||
class SettingsSection extends StatelessWidget {
|
class SettingsSection extends StatelessWidget {
|
||||||
final String title;
|
|
||||||
final List<Widget> children;
|
|
||||||
|
|
||||||
const SettingsSection({
|
const SettingsSection({
|
||||||
Key? key,
|
Key? key,
|
||||||
required this.title,
|
required this.title,
|
||||||
required this.children,
|
required this.children,
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
final String title;
|
||||||
|
final List<Widget> children;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
|||||||
@@ -2,11 +2,6 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:flutter_i18n/flutter_i18n.dart';
|
import 'package:flutter_i18n/flutter_i18n.dart';
|
||||||
|
|
||||||
class SettingsTileDialog extends StatelessWidget {
|
class SettingsTileDialog extends StatelessWidget {
|
||||||
final String title;
|
|
||||||
final String subtitle;
|
|
||||||
final Function()? onTap;
|
|
||||||
final EdgeInsetsGeometry? padding;
|
|
||||||
|
|
||||||
const SettingsTileDialog({
|
const SettingsTileDialog({
|
||||||
Key? key,
|
Key? key,
|
||||||
required this.title,
|
required this.title,
|
||||||
@@ -14,6 +9,10 @@ class SettingsTileDialog extends StatelessWidget {
|
|||||||
required this.onTap,
|
required this.onTap,
|
||||||
this.padding,
|
this.padding,
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
final String title;
|
||||||
|
final String subtitle;
|
||||||
|
final Function()? onTap;
|
||||||
|
final EdgeInsetsGeometry? padding;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
|||||||
@@ -1,13 +1,7 @@
|
|||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:url_launcher/url_launcher.dart';
|
import 'package:url_launcher/url_launcher.dart';
|
||||||
|
|
||||||
class SocialMediaItem extends StatelessWidget {
|
class SocialMediaItem extends StatelessWidget {
|
||||||
final Widget? icon;
|
|
||||||
final Widget title;
|
|
||||||
final Widget? subtitle;
|
|
||||||
final String? url;
|
|
||||||
|
|
||||||
const SocialMediaItem({
|
const SocialMediaItem({
|
||||||
Key? key,
|
Key? key,
|
||||||
this.icon,
|
this.icon,
|
||||||
@@ -15,6 +9,10 @@ class SocialMediaItem extends StatelessWidget {
|
|||||||
this.subtitle,
|
this.subtitle,
|
||||||
this.url,
|
this.url,
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
final Widget? icon;
|
||||||
|
final Widget title;
|
||||||
|
final Widget? subtitle;
|
||||||
|
final String? url;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
|||||||
@@ -6,12 +6,11 @@ import 'package:revanced_manager/ui/widgets/settingsView/social_media_item.dart'
|
|||||||
import 'package:revanced_manager/ui/widgets/shared/custom_card.dart';
|
import 'package:revanced_manager/ui/widgets/shared/custom_card.dart';
|
||||||
|
|
||||||
class SocialMediaWidget extends StatelessWidget {
|
class SocialMediaWidget extends StatelessWidget {
|
||||||
final EdgeInsetsGeometry? padding;
|
|
||||||
|
|
||||||
const SocialMediaWidget({
|
const SocialMediaWidget({
|
||||||
Key? key,
|
Key? key,
|
||||||
this.padding,
|
this.padding,
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
final EdgeInsetsGeometry? padding;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
@@ -40,9 +39,9 @@ class SocialMediaWidget extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
expanded: Padding(
|
expanded: Padding(
|
||||||
padding: padding ?? EdgeInsets.zero,
|
padding: padding ?? EdgeInsets.zero,
|
||||||
child: CustomCard(
|
child: const CustomCard(
|
||||||
child: Column(
|
child: Column(
|
||||||
children: const <Widget>[
|
children: <Widget>[
|
||||||
SocialMediaItem(
|
SocialMediaItem(
|
||||||
icon: FaIcon(FontAwesomeIcons.github),
|
icon: FaIcon(FontAwesomeIcons.github),
|
||||||
title: Text('GitHub'),
|
title: Text('GitHub'),
|
||||||
|
|||||||
@@ -1,19 +1,13 @@
|
|||||||
import 'dart:typed_data';
|
import 'dart:typed_data';
|
||||||
|
|
||||||
|
import 'package:expandable/expandable.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:revanced_manager/ui/widgets/shared/custom_material_button.dart';
|
|
||||||
import 'package:revanced_manager/ui/widgets/shared/custom_card.dart';
|
import 'package:revanced_manager/ui/widgets/shared/custom_card.dart';
|
||||||
import 'package:expandable/expandable.dart';
|
import 'package:revanced_manager/ui/widgets/shared/custom_material_button.dart';
|
||||||
import 'package:timeago/timeago.dart';
|
import 'package:timeago/timeago.dart';
|
||||||
|
|
||||||
class ApplicationItem extends StatefulWidget {
|
class ApplicationItem extends StatefulWidget {
|
||||||
final Uint8List icon;
|
|
||||||
final String name;
|
|
||||||
final DateTime patchDate;
|
|
||||||
final List<String> changelog;
|
|
||||||
final bool isUpdatableApp;
|
|
||||||
final Function() onPressed;
|
|
||||||
|
|
||||||
const ApplicationItem({
|
const ApplicationItem({
|
||||||
Key? key,
|
Key? key,
|
||||||
required this.icon,
|
required this.icon,
|
||||||
@@ -23,6 +17,12 @@ class ApplicationItem extends StatefulWidget {
|
|||||||
required this.isUpdatableApp,
|
required this.isUpdatableApp,
|
||||||
required this.onPressed,
|
required this.onPressed,
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
final Uint8List icon;
|
||||||
|
final String name;
|
||||||
|
final DateTime patchDate;
|
||||||
|
final List<String> changelog;
|
||||||
|
final bool isUpdatableApp;
|
||||||
|
final Function() onPressed;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<ApplicationItem> createState() => _ApplicationItemState();
|
State<ApplicationItem> createState() => _ApplicationItemState();
|
||||||
@@ -33,7 +33,7 @@ class _ApplicationItemState extends State<ApplicationItem>
|
|||||||
late AnimationController _animationController;
|
late AnimationController _animationController;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
_animationController = AnimationController(
|
_animationController = AnimationController(
|
||||||
vsync: this,
|
vsync: this,
|
||||||
@@ -49,95 +49,100 @@ class _ApplicationItemState extends State<ApplicationItem>
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
ExpandableController expController = ExpandableController();
|
final ExpandableController expController = ExpandableController();
|
||||||
return Container(
|
return Container(
|
||||||
margin: const EdgeInsets.only(bottom: 16.0),
|
margin: const EdgeInsets.only(bottom: 16.0),
|
||||||
child: CustomCard(
|
child: CustomCard(
|
||||||
onTap: () {
|
onTap: () {
|
||||||
expController.toggle();
|
expController.toggle();
|
||||||
_animationController.isCompleted
|
_animationController.isCompleted
|
||||||
? _animationController.reverse()
|
? _animationController.reverse()
|
||||||
: _animationController.forward();
|
: _animationController.forward();
|
||||||
},
|
},
|
||||||
child: ExpandablePanel(
|
child: ExpandablePanel(
|
||||||
controller: expController,
|
controller: expController,
|
||||||
theme: const ExpandableThemeData(
|
theme: const ExpandableThemeData(
|
||||||
inkWellBorderRadius: BorderRadius.all(Radius.circular(16)),
|
inkWellBorderRadius: BorderRadius.all(Radius.circular(16)),
|
||||||
tapBodyToCollapse: false,
|
tapBodyToCollapse: false,
|
||||||
tapBodyToExpand: false,
|
tapBodyToExpand: false,
|
||||||
tapHeaderToExpand: false,
|
tapHeaderToExpand: false,
|
||||||
hasIcon: false,
|
hasIcon: false,
|
||||||
animationDuration: Duration(milliseconds: 450),
|
animationDuration: Duration(milliseconds: 450),
|
||||||
),
|
),
|
||||||
header: Row(
|
header: Row(
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
SizedBox(
|
SizedBox(
|
||||||
width: 40,
|
width: 40,
|
||||||
child: Image.memory(widget.icon, height: 40, width: 40),
|
child: Image.memory(widget.icon, height: 40, width: 40),
|
||||||
),
|
),
|
||||||
const SizedBox(width: 4),
|
const SizedBox(width: 4),
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.only(left: 15.0),
|
padding: const EdgeInsets.only(left: 15.0),
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: <Widget>[
|
|
||||||
Text(
|
|
||||||
widget.name.length > 12
|
|
||||||
? '${widget.name.substring(0, 12)}...'
|
|
||||||
: widget.name,
|
|
||||||
style: const TextStyle(
|
|
||||||
fontSize: 16,
|
|
||||||
fontWeight: FontWeight.w500,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Text(format(widget.patchDate)),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const Spacer(),
|
|
||||||
RotationTransition(
|
|
||||||
turns: Tween(begin: 0.0, end: 0.50)
|
|
||||||
.animate(_animationController),
|
|
||||||
child: const Padding(
|
|
||||||
padding: EdgeInsets.all(8.0),
|
|
||||||
child: Icon(Icons.arrow_drop_down),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(width: 8),
|
|
||||||
Column(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.end,
|
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
CustomMaterialButton(
|
Text(
|
||||||
label: widget.isUpdatableApp
|
widget.name.length > 12
|
||||||
? I18nText('applicationItem.patchButton')
|
? '${widget.name.substring(0, 12)}...'
|
||||||
: I18nText('applicationItem.infoButton'),
|
: widget.name,
|
||||||
onPressed: widget.onPressed,
|
style: const TextStyle(
|
||||||
|
fontSize: 16,
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
|
Text(format(widget.patchDate)),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
],
|
),
|
||||||
),
|
const Spacer(),
|
||||||
collapsed: const SizedBox(),
|
RotationTransition(
|
||||||
expanded: Padding(
|
turns:
|
||||||
padding: const EdgeInsets.only(
|
Tween(begin: 0.0, end: 0.50).animate(_animationController),
|
||||||
top: 16.0, left: 4.0, right: 4.0, bottom: 4.0),
|
child: const Padding(
|
||||||
child: Column(
|
padding: EdgeInsets.all(8.0),
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
child: Icon(Icons.arrow_drop_down),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 8),
|
||||||
|
Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.end,
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
I18nText(
|
CustomMaterialButton(
|
||||||
'applicationItem.changelogLabel',
|
label: widget.isUpdatableApp
|
||||||
child: const Text(
|
? I18nText('applicationItem.patchButton')
|
||||||
'',
|
: I18nText('applicationItem.infoButton'),
|
||||||
style: TextStyle(fontWeight: FontWeight.w700),
|
onPressed: widget.onPressed,
|
||||||
),
|
|
||||||
),
|
),
|
||||||
const SizedBox(height: 4),
|
|
||||||
Text('\u2022 ${widget.changelog.join('\n\u2022 ')}'),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
collapsed: const SizedBox(),
|
||||||
|
expanded: Padding(
|
||||||
|
padding: const EdgeInsets.only(
|
||||||
|
top: 16.0,
|
||||||
|
left: 4.0,
|
||||||
|
right: 4.0,
|
||||||
|
bottom: 4.0,
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: <Widget>[
|
||||||
|
I18nText(
|
||||||
|
'applicationItem.changelogLabel',
|
||||||
|
child: const Text(
|
||||||
|
'',
|
||||||
|
style: TextStyle(fontWeight: FontWeight.w700),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 4),
|
||||||
|
Text('\u2022 ${widget.changelog.join('\n\u2022 ')}'),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
));
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,21 +1,20 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
class CustomCard extends StatelessWidget {
|
class CustomCard extends StatelessWidget {
|
||||||
|
const CustomCard({
|
||||||
|
Key? key,
|
||||||
|
this.isFilled = true,
|
||||||
|
required this.child,
|
||||||
|
this.onTap,
|
||||||
|
this.padding,
|
||||||
|
this.backgroundColor,
|
||||||
|
}) : super(key: key);
|
||||||
final bool isFilled;
|
final bool isFilled;
|
||||||
final Widget child;
|
final Widget child;
|
||||||
final Function()? onTap;
|
final Function()? onTap;
|
||||||
final EdgeInsetsGeometry? padding;
|
final EdgeInsetsGeometry? padding;
|
||||||
final Color? backgroundColor;
|
final Color? backgroundColor;
|
||||||
|
|
||||||
const CustomCard(
|
|
||||||
{Key? key,
|
|
||||||
this.isFilled = true,
|
|
||||||
required this.child,
|
|
||||||
this.onTap,
|
|
||||||
this.padding,
|
|
||||||
this.backgroundColor})
|
|
||||||
: super(key: key);
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Material(
|
return Material(
|
||||||
|
|||||||
@@ -1,16 +1,15 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
class CustomChip extends StatelessWidget {
|
class CustomChip extends StatelessWidget {
|
||||||
final Widget label;
|
|
||||||
final bool isSelected;
|
|
||||||
final Function(bool)? onSelected;
|
|
||||||
|
|
||||||
const CustomChip({
|
const CustomChip({
|
||||||
Key? key,
|
Key? key,
|
||||||
required this.label,
|
required this.label,
|
||||||
this.isSelected = false,
|
this.isSelected = false,
|
||||||
this.onSelected,
|
this.onSelected,
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
final Widget label;
|
||||||
|
final bool isSelected;
|
||||||
|
final Function(bool)? onSelected;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
@@ -18,7 +17,7 @@ class CustomChip extends StatelessWidget {
|
|||||||
showCheckmark: false,
|
showCheckmark: false,
|
||||||
label: label,
|
label: label,
|
||||||
selected: isSelected,
|
selected: isSelected,
|
||||||
labelStyle: Theme.of(context).textTheme.subtitle2!.copyWith(
|
labelStyle: Theme.of(context).textTheme.titleSmall!.copyWith(
|
||||||
color: isSelected
|
color: isSelected
|
||||||
? Theme.of(context).colorScheme.primary
|
? Theme.of(context).colorScheme.primary
|
||||||
: Theme.of(context).colorScheme.secondary,
|
: Theme.of(context).colorScheme.secondary,
|
||||||
|
|||||||
@@ -1,11 +1,6 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
class CustomMaterialButton extends StatelessWidget {
|
class CustomMaterialButton extends StatelessWidget {
|
||||||
final Widget label;
|
|
||||||
final bool isFilled;
|
|
||||||
final bool isExpanded;
|
|
||||||
final Function()? onPressed;
|
|
||||||
|
|
||||||
const CustomMaterialButton({
|
const CustomMaterialButton({
|
||||||
Key? key,
|
Key? key,
|
||||||
required this.label,
|
required this.label,
|
||||||
@@ -13,6 +8,10 @@ class CustomMaterialButton extends StatelessWidget {
|
|||||||
this.isExpanded = false,
|
this.isExpanded = false,
|
||||||
required this.onPressed,
|
required this.onPressed,
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
final Widget label;
|
||||||
|
final bool isFilled;
|
||||||
|
final bool isExpanded;
|
||||||
|
final Function()? onPressed;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
@@ -28,7 +27,6 @@ class CustomMaterialButton extends StatelessWidget {
|
|||||||
side: isFilled
|
side: isFilled
|
||||||
? BorderSide.none
|
? BorderSide.none
|
||||||
: BorderSide(
|
: BorderSide(
|
||||||
width: 1,
|
|
||||||
color: Theme.of(context).colorScheme.primary,
|
color: Theme.of(context).colorScheme.primary,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -50,12 +48,6 @@ class CustomMaterialButton extends StatelessWidget {
|
|||||||
|
|
||||||
// ignore: must_be_immutable
|
// ignore: must_be_immutable
|
||||||
class TimerButton extends StatefulWidget {
|
class TimerButton extends StatefulWidget {
|
||||||
Widget label;
|
|
||||||
bool isFilled;
|
|
||||||
int seconds;
|
|
||||||
final bool isRunning;
|
|
||||||
final Function()? onTimerEnd;
|
|
||||||
|
|
||||||
TimerButton({
|
TimerButton({
|
||||||
Key? key,
|
Key? key,
|
||||||
required this.seconds,
|
required this.seconds,
|
||||||
@@ -64,6 +56,11 @@ class TimerButton extends StatefulWidget {
|
|||||||
this.label = const Text(''),
|
this.label = const Text(''),
|
||||||
this.isFilled = true,
|
this.isFilled = true,
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
Widget label;
|
||||||
|
bool isFilled;
|
||||||
|
int seconds;
|
||||||
|
final bool isRunning;
|
||||||
|
final Function()? onTimerEnd;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<TimerButton> createState() => _TimerButtonState();
|
State<TimerButton> createState() => _TimerButtonState();
|
||||||
@@ -101,7 +98,6 @@ class _TimerButtonState extends State<TimerButton> {
|
|||||||
side: widget.isFilled
|
side: widget.isFilled
|
||||||
? BorderSide.none
|
? BorderSide.none
|
||||||
: BorderSide(
|
: BorderSide(
|
||||||
width: 1,
|
|
||||||
color: Theme.of(context).colorScheme.primary,
|
color: Theme.of(context).colorScheme.primary,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -1,14 +1,13 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
class CustomPopupMenu extends StatelessWidget {
|
class CustomPopupMenu extends StatelessWidget {
|
||||||
final Function(dynamic) onSelected;
|
|
||||||
final Map<int, Widget> children;
|
|
||||||
|
|
||||||
const CustomPopupMenu({
|
const CustomPopupMenu({
|
||||||
Key? key,
|
Key? key,
|
||||||
required this.onSelected,
|
required this.onSelected,
|
||||||
required this.children,
|
required this.children,
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
final Function(dynamic) onSelected;
|
||||||
|
final Map<int, Widget> children;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
|||||||
@@ -1,12 +1,6 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
class CustomSliverAppBar extends StatelessWidget {
|
class CustomSliverAppBar extends StatelessWidget {
|
||||||
final Widget title;
|
|
||||||
final List<Widget>? actions;
|
|
||||||
final PreferredSizeWidget? bottom;
|
|
||||||
final bool isMainView;
|
|
||||||
final Function()? onBackButtonPressed;
|
|
||||||
|
|
||||||
const CustomSliverAppBar({
|
const CustomSliverAppBar({
|
||||||
Key? key,
|
Key? key,
|
||||||
required this.title,
|
required this.title,
|
||||||
@@ -15,13 +9,16 @@ class CustomSliverAppBar extends StatelessWidget {
|
|||||||
this.isMainView = false,
|
this.isMainView = false,
|
||||||
this.onBackButtonPressed,
|
this.onBackButtonPressed,
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
final Widget title;
|
||||||
|
final List<Widget>? actions;
|
||||||
|
final PreferredSizeWidget? bottom;
|
||||||
|
final bool isMainView;
|
||||||
|
final Function()? onBackButtonPressed;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return SliverAppBar(
|
return SliverAppBar(
|
||||||
pinned: true,
|
pinned: true,
|
||||||
snap: false,
|
|
||||||
floating: false,
|
|
||||||
expandedHeight: 100.0,
|
expandedHeight: 100.0,
|
||||||
automaticallyImplyLeading: !isMainView,
|
automaticallyImplyLeading: !isMainView,
|
||||||
flexibleSpace: FlexibleSpaceBar(
|
flexibleSpace: FlexibleSpaceBar(
|
||||||
@@ -36,7 +33,7 @@ class CustomSliverAppBar extends StatelessWidget {
|
|||||||
: IconButton(
|
: IconButton(
|
||||||
icon: Icon(
|
icon: Icon(
|
||||||
Icons.arrow_back,
|
Icons.arrow_back,
|
||||||
color: Theme.of(context).textTheme.headline6!.color,
|
color: Theme.of(context).textTheme.titleLarge!.color,
|
||||||
),
|
),
|
||||||
onPressed:
|
onPressed:
|
||||||
onBackButtonPressed ?? () => Navigator.of(context).pop(),
|
onBackButtonPressed ?? () => Navigator.of(context).pop(),
|
||||||
|
|||||||