Compare commits
93 Commits
v0.0.51
...
v1.0.0-dev
Author | SHA1 | Date | |
---|---|---|---|
1997e474f2 | |||
6492507d0e | |||
3091f441ea | |||
782c0127b1 | |||
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 | |||
d847a8e0b2 | |||
9668730b5d | |||
dc049cf26a |
2
.github/ISSUE_TEMPLATE/bug-issue.yml
vendored
@ -116,5 +116,5 @@ body:
|
||||
required: true
|
||||
- label: I filled out all of the requested information in this issue properly.
|
||||
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
|
||||
|
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:
|
||||
push:
|
||||
branches:
|
||||
- "flutter"
|
||||
- "dev"
|
||||
paths:
|
||||
- "assets/i18n/en_US.json"
|
||||
- ".github/workflows/crowdin.yml"
|
||||
@ -19,7 +19,7 @@ jobs:
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Crowdin
|
||||
uses: crowdin/github-action@1.5.0
|
||||
uses: crowdin/github-action@v1
|
||||
with:
|
||||
config: crowdin.yml
|
||||
upload_sources: true
|
||||
|
82
.github/workflows/pull-request-build.yml
vendored
@ -1,41 +1,41 @@
|
||||
name: "Android CI PR Build"
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches:
|
||||
- "**"
|
||||
|
||||
jobs:
|
||||
release:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Set env
|
||||
run: echo "RELEASE_VERSION=${GITHUB_REF#refs/*/}" >> $GITHUB_ENV
|
||||
- name: Set up JDK 12
|
||||
uses: actions/setup-java@v3
|
||||
with:
|
||||
java-version: '12'
|
||||
distribution: 'zulu'
|
||||
- uses: subosito/flutter-action@v2
|
||||
with:
|
||||
channel: 'stable'
|
||||
- name: Set 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 }}
|
||||
run: flutter build apk
|
||||
- name: Add version to APK
|
||||
run: mv build/app/outputs/flutter-apk/app-release.apk revanced-manager-${{ env.RELEASE_VERSION }}.apk
|
||||
- name: Upload APK
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: revanced-manager-${{ env.RELEASE_VERSION }}
|
||||
path: revanced-manager-${{ env.RELEASE_VERSION }}.apk
|
||||
name: PR Build
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Build
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
with:
|
||||
# Make sure the release step uses its own credentials:
|
||||
# https://github.com/cycjimmy/semantic-release-action#private-packages
|
||||
persist-credentials: false
|
||||
fetch-depth: 0
|
||||
- name: Setup JDK
|
||||
uses: actions/setup-java@v3
|
||||
with:
|
||||
java-version: '17'
|
||||
distribution: 'zulu'
|
||||
cache: 'gradle'
|
||||
- name: Setup Flutter
|
||||
uses: subosito/flutter-action@v2
|
||||
with:
|
||||
channel: 'stable'
|
||||
cache: true
|
||||
- name: Install Flutter dependencies
|
||||
run: flutter pub get
|
||||
- name: Generate files with Builder
|
||||
run: flutter packages pub run build_runner build --delete-conflicting-outputs
|
||||
- name: Build with Flutter
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: flutter build apk
|
||||
- name: Upload build
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: revanced-manager
|
||||
path: build/app/outputs/flutter-apk/app-release.apk
|
||||
|
54
.github/workflows/release-build.yml
vendored
@ -1,54 +0,0 @@
|
||||
name: "Release Build"
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- "v*"
|
||||
|
||||
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: Publish release APK
|
||||
uses: "marvinpinto/action-automatic-releases@latest"
|
||||
with:
|
||||
repo_token: "${{ secrets.GITHUB_TOKEN }}"
|
||||
prerelease: false
|
||||
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
|
7
.gitignore
vendored
@ -134,5 +134,8 @@ app.*.map.json
|
||||
!/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages
|
||||
!/dev/ci/**/Gemfile.lock
|
||||
|
||||
Firebase related
|
||||
.firebase
|
||||
# Firebase related
|
||||
.firebase
|
||||
|
||||
# Dependency directories
|
||||
node_modules/
|
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
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
74
CHANGELOG.md
Normal file
@ -0,0 +1,74 @@
|
||||
# [1.0.0-dev.1](https://github.com/revanced/revanced-manager/compare/v0.0.57...v1.0.0-dev.1) (2023-04-29)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* fix broken manager update implmentation ([6492507](https://github.com/revanced/revanced-manager/commit/6492507d0e0015b594f7d9e97bbadee9501b29d6))
|
||||
* **i18n:** update translation for `refreshSucess` ([b286444](https://github.com/revanced/revanced-manager/commit/b286444ad93fc6009412ac14b996ff6268069811))
|
||||
* open contributor links externally ([#791](https://github.com/revanced/revanced-manager/issues/791)) ([f0b0282](https://github.com/revanced/revanced-manager/commit/f0b028279c69f97817952063d84809d3e486ad6e))
|
||||
* resized monochrome icon to match the original ([#789](https://github.com/revanced/revanced-manager/issues/789)) ([ac830cb](https://github.com/revanced/revanced-manager/commit/ac830cbe7f3b1ebd7849e586b829d3c077436a0d))
|
||||
* update pubspec version ([65da6af](https://github.com/revanced/revanced-manager/commit/65da6af3f96550b138dcaf61832a5de23f529b32))
|
||||
|
||||
|
||||
* chore!: merge `dev` to `main` (#830) ([782c012](https://github.com/revanced/revanced-manager/commit/782c0127b1f6f5380bd8ca23c8c2f280e2bfc432)), closes [#830](https://github.com/revanced/revanced-manager/issues/830)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add continue anyway button to select from storage dialog ([#810](https://github.com/revanced/revanced-manager/issues/810)) ([affba66](https://github.com/revanced/revanced-manager/commit/affba669ce1ca6866a1dd1bd801e3f33e4bfe051))
|
||||
* add option to import/export keystore ([#776](https://github.com/revanced/revanced-manager/issues/776)) ([dca2d4f](https://github.com/revanced/revanced-manager/commit/dca2d4fe126a6966a094d335e0f27bb62d76c5e8))
|
||||
* appreciation message for new contributors ([0a1f2da](https://github.com/revanced/revanced-manager/commit/0a1f2da33da7d44f0613b19f3e6b2b7b50240548))
|
||||
* auto select default patches ([4c9cb56](https://github.com/revanced/revanced-manager/commit/4c9cb560e3e38295a5140419f2565b478cb6c497))
|
||||
* change `continue anyways` to `cancel` ([cfc866b](https://github.com/revanced/revanced-manager/commit/cfc866bef2497bc1675bf5dea834cea59d9cc969))
|
||||
* **ci:** update crowdin workflow to use new main branch ([ded59d2](https://github.com/revanced/revanced-manager/commit/ded59d2da0d193b2dea4a5a7f2fc8eefaceecc0a))
|
||||
* confirmation dialog for deleting keystore ([#764](https://github.com/revanced/revanced-manager/issues/764)) ([054afbb](https://github.com/revanced/revanced-manager/commit/054afbbedd70a1933d8241ff5b63a772f90b555f))
|
||||
* disable selecting installed apps for nonroot ([bb681e3](https://github.com/revanced/revanced-manager/commit/bb681e31c9c4e8a5b7b0c883edd1bc5c28505627))
|
||||
* improve suggested app version text ([#822](https://github.com/revanced/revanced-manager/issues/822)) ([ad17995](https://github.com/revanced/revanced-manager/commit/ad17995f2883682f67eb42b1f82ca865fba86ef9))
|
||||
* improve ux ([#752](https://github.com/revanced/revanced-manager/issues/752)) ([3b677f8](https://github.com/revanced/revanced-manager/commit/3b677f8ae3739c079e2116417fef6ed395c2ff06)), closes [#782](https://github.com/revanced/revanced-manager/issues/782)
|
||||
* progress bar for manager updates ([f5aafdb](https://github.com/revanced/revanced-manager/commit/f5aafdb7d6f51386b667abbccf7f2521ef664ba5))
|
||||
* remove notice about stale development [skip ci] ([62f7a82](https://github.com/revanced/revanced-manager/commit/62f7a820d8ee2506376306e119698d427de745ef))
|
||||
* resetting source to default dismiss the sources pop-up ([#797](https://github.com/revanced/revanced-manager/issues/797)) ([62ef1c8](https://github.com/revanced/revanced-manager/commit/62ef1c88fe0352d3962f8c73edff4b99ea347c0f))
|
||||
* trim extra space when setting custom source ([#771](https://github.com/revanced/revanced-manager/issues/771)) ([37b583f](https://github.com/revanced/revanced-manager/commit/37b583f560910c0b5da2a8e64de9b53f5a26bb56))
|
||||
* warning for armv7 devices ([cdfb09f](https://github.com/revanced/revanced-manager/commit/cdfb09fbfa8e74d84ddcc91565489c3c5b61dfa2))
|
||||
|
||||
|
||||
### BREAKING CHANGES
|
||||
|
||||
* this is to release as 1.0.0
|
||||
|
||||
# [1.0.0](https://github.com/revanced/revanced-manager/compare/v0.0.57...v1.0.0) (2023-04-29)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **i18n:** update translation for `refreshSucess` ([b286444](https://github.com/revanced/revanced-manager/commit/b286444ad93fc6009412ac14b996ff6268069811))
|
||||
* open contributor links externally ([#791](https://github.com/revanced/revanced-manager/issues/791)) ([f0b0282](https://github.com/revanced/revanced-manager/commit/f0b028279c69f97817952063d84809d3e486ad6e))
|
||||
* resized monochrome icon to match the original ([#789](https://github.com/revanced/revanced-manager/issues/789)) ([ac830cb](https://github.com/revanced/revanced-manager/commit/ac830cbe7f3b1ebd7849e586b829d3c077436a0d))
|
||||
* update pubspec version ([65da6af](https://github.com/revanced/revanced-manager/commit/65da6af3f96550b138dcaf61832a5de23f529b32))
|
||||
|
||||
|
||||
* chore!: merge `dev` to `main` (#830) ([782c012](https://github.com/revanced/revanced-manager/commit/782c0127b1f6f5380bd8ca23c8c2f280e2bfc432)), closes [#830](https://github.com/revanced/revanced-manager/issues/830)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add continue anyway button to select from storage dialog ([#810](https://github.com/revanced/revanced-manager/issues/810)) ([affba66](https://github.com/revanced/revanced-manager/commit/affba669ce1ca6866a1dd1bd801e3f33e4bfe051))
|
||||
* add option to import/export keystore ([#776](https://github.com/revanced/revanced-manager/issues/776)) ([dca2d4f](https://github.com/revanced/revanced-manager/commit/dca2d4fe126a6966a094d335e0f27bb62d76c5e8))
|
||||
* appreciation message for new contributors ([0a1f2da](https://github.com/revanced/revanced-manager/commit/0a1f2da33da7d44f0613b19f3e6b2b7b50240548))
|
||||
* auto select default patches ([4c9cb56](https://github.com/revanced/revanced-manager/commit/4c9cb560e3e38295a5140419f2565b478cb6c497))
|
||||
* change `continue anyways` to `cancel` ([cfc866b](https://github.com/revanced/revanced-manager/commit/cfc866bef2497bc1675bf5dea834cea59d9cc969))
|
||||
* **ci:** update crowdin workflow to use new main branch ([ded59d2](https://github.com/revanced/revanced-manager/commit/ded59d2da0d193b2dea4a5a7f2fc8eefaceecc0a))
|
||||
* confirmation dialog for deleting keystore ([#764](https://github.com/revanced/revanced-manager/issues/764)) ([054afbb](https://github.com/revanced/revanced-manager/commit/054afbbedd70a1933d8241ff5b63a772f90b555f))
|
||||
* disable selecting installed apps for nonroot ([bb681e3](https://github.com/revanced/revanced-manager/commit/bb681e31c9c4e8a5b7b0c883edd1bc5c28505627))
|
||||
* improve suggested app version text ([#822](https://github.com/revanced/revanced-manager/issues/822)) ([ad17995](https://github.com/revanced/revanced-manager/commit/ad17995f2883682f67eb42b1f82ca865fba86ef9))
|
||||
* improve ux ([#752](https://github.com/revanced/revanced-manager/issues/752)) ([3b677f8](https://github.com/revanced/revanced-manager/commit/3b677f8ae3739c079e2116417fef6ed395c2ff06)), closes [#782](https://github.com/revanced/revanced-manager/issues/782)
|
||||
* progress bar for manager updates ([f5aafdb](https://github.com/revanced/revanced-manager/commit/f5aafdb7d6f51386b667abbccf7f2521ef664ba5))
|
||||
* remove notice about stale development [skip ci] ([62f7a82](https://github.com/revanced/revanced-manager/commit/62f7a820d8ee2506376306e119698d427de745ef))
|
||||
* resetting source to default dismiss the sources pop-up ([#797](https://github.com/revanced/revanced-manager/issues/797)) ([62ef1c8](https://github.com/revanced/revanced-manager/commit/62ef1c88fe0352d3962f8c73edff4b99ea347c0f))
|
||||
* trim extra space when setting custom source ([#771](https://github.com/revanced/revanced-manager/issues/771)) ([37b583f](https://github.com/revanced/revanced-manager/commit/37b583f560910c0b5da2a8e64de9b53f5a26bb56))
|
||||
* warning for armv7 devices ([cdfb09f](https://github.com/revanced/revanced-manager/commit/cdfb09fbfa8e74d84ddcc91565489c3c5b61dfa2))
|
||||
|
||||
|
||||
### BREAKING CHANGES
|
||||
|
||||
* this is to release as 1.0.0
|
@ -3,15 +3,11 @@
|
||||
The official ReVanced Manager based on Flutter.
|
||||
|
||||
## 🔽 Download
|
||||
To download the Alpha version of Manager, go [here](https://github.com/revanced/revanced-manager/releases/latest) and install the provided APK file.
|
||||
To download latest Manager, go [here](https://github.com/revanced/revanced-manager/releases/latest) and install the provided APK file.
|
||||
|
||||
## 📝 Prerequisites
|
||||
1. Android 8 or higher
|
||||
2. Does not work on some armv7 devices
|
||||
3. [Vanced MicroG](https://github.com/TeamVanced/VancedMicroG/releases) required for YouTube and YouTube Music (Only for non-root)
|
||||
|
||||
## ⚠️ Disclaimer
|
||||
*Please note that even though we're releasing the Manager, it is an ALPHA version. There's a big chance that the Manager might not work at all for you.*
|
||||
|
||||
## 🔴 Issues
|
||||
For suggestions and bug reports, open an issue [here](https://github.com/revanced/revanced-manager/issues/new/choose).
|
||||
@ -28,7 +24,7 @@ If you wish to translate ReVanced Manager, we're accepting translations on [Crow
|
||||
## 🛠️ Building Manager from source
|
||||
1. Setup flutter environment for your [platform](https://docs.flutter.dev/get-started/install)
|
||||
2. Clone the repository locally
|
||||
3. Add your github token in gradle.properties like [this](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
|
||||
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)
|
||||
|
@ -11,23 +11,155 @@ include: package:flutter_lints/flutter.yaml
|
||||
|
||||
analyzer:
|
||||
exclude:
|
||||
- lib/utils/env_class.g.dart
|
||||
- lib/app/app.locator.dart
|
||||
- lib/app/app.router.dart
|
||||
- lib/models/patch.g.dart
|
||||
- lib/models/patched_application.g.dart
|
||||
|
||||
linter:
|
||||
# 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:
|
||||
# avoid_print: false # Uncomment to disable the `avoid_print` rule
|
||||
# prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule
|
||||
|
||||
# Additional information about this file can be found at
|
||||
# https://dart.dev/guides/language/analysis-options
|
||||
- always_declare_return_types
|
||||
- require_trailing_commas
|
||||
- always_put_control_body_on_new_line
|
||||
- always_require_non_null_named_parameters
|
||||
- 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_equal_for_default_values
|
||||
- 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
|
||||
|
@ -30,6 +30,7 @@ android {
|
||||
ndkVersion flutter.ndkVersion
|
||||
|
||||
compileOptions {
|
||||
coreLibraryDesugaringEnabled true
|
||||
sourceCompatibility JavaVersion.VERSION_11
|
||||
targetCompatibility JavaVersion.VERSION_11
|
||||
}
|
||||
@ -48,6 +49,7 @@ android {
|
||||
targetSdkVersion 33
|
||||
versionCode flutterVersionCode.toInteger()
|
||||
versionName flutterVersionName
|
||||
multiDexEnabled true
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
@ -71,7 +73,7 @@ dependencies {
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
|
||||
|
||||
// ReVanced
|
||||
implementation "app.revanced:revanced-patcher:6.3.1"
|
||||
implementation "app.revanced:revanced-patcher:7.0.0"
|
||||
|
||||
// Signing & aligning
|
||||
implementation("org.bouncycastle:bcpkix-jdk15on:1.70")
|
||||
@ -81,4 +83,11 @@ dependencies {
|
||||
implementation("org.microg:cronet-common:$cronetVersion")
|
||||
implementation("org.microg:cronet-native:$cronetVersion")
|
||||
|
||||
// Core libraries
|
||||
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.5'
|
||||
|
||||
// Window
|
||||
implementation 'androidx.window:window:1.0.0'
|
||||
implementation 'androidx.window:window-java:1.0.0'
|
||||
|
||||
}
|
||||
|
@ -17,7 +17,8 @@
|
||||
android:name="${applicationName}"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:largeHeap="true"
|
||||
android:extractNativeLibs="true">
|
||||
android:extractNativeLibs="true"
|
||||
android:enableOnBackInvokedCallback="true">
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
android:exported="true"
|
||||
@ -28,8 +29,7 @@
|
||||
android:windowSoftInputMode="adjustResize">
|
||||
<meta-data
|
||||
android:name="io.flutter.embedding.android.NormalTheme"
|
||||
android:resource="@style/NormalTheme"
|
||||
/>
|
||||
android:resource="@style/NormalTheme"/>
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN"/>
|
||||
<category android:name="android.intent.category.LAUNCHER"/>
|
||||
|
@ -43,8 +43,9 @@ class MainActivity : FlutterActivity() {
|
||||
val integrationsPath = call.argument<String>("integrationsPath")
|
||||
val selectedPatches = call.argument<List<String>>("selectedPatches")
|
||||
val cacheDirPath = call.argument<String>("cacheDirPath")
|
||||
val mergeIntegrations = call.argument<Boolean>("mergeIntegrations")
|
||||
val keyStoreFilePath = call.argument<String>("keyStoreFilePath")
|
||||
val keystorePassword = call.argument<String>("keystorePassword")
|
||||
|
||||
if (patchBundleFilePath != null &&
|
||||
originalFilePath != null &&
|
||||
inputFilePath != null &&
|
||||
@ -53,8 +54,8 @@ class MainActivity : FlutterActivity() {
|
||||
integrationsPath != null &&
|
||||
selectedPatches != null &&
|
||||
cacheDirPath != null &&
|
||||
mergeIntegrations != null &&
|
||||
keyStoreFilePath != null
|
||||
keyStoreFilePath != null &&
|
||||
keystorePassword != null
|
||||
) {
|
||||
runPatcher(
|
||||
result,
|
||||
@ -66,8 +67,8 @@ class MainActivity : FlutterActivity() {
|
||||
integrationsPath,
|
||||
selectedPatches,
|
||||
cacheDirPath,
|
||||
mergeIntegrations,
|
||||
keyStoreFilePath
|
||||
keyStoreFilePath,
|
||||
keystorePassword
|
||||
)
|
||||
} else {
|
||||
result.notImplemented()
|
||||
@ -88,8 +89,8 @@ class MainActivity : FlutterActivity() {
|
||||
integrationsPath: String,
|
||||
selectedPatches: List<String>,
|
||||
cacheDirPath: String,
|
||||
mergeIntegrations: Boolean,
|
||||
keyStoreFilePath: String
|
||||
keyStoreFilePath: String,
|
||||
keystorePassword: String
|
||||
) {
|
||||
val originalFile = File(originalFilePath)
|
||||
val inputFile = File(inputFilePath)
|
||||
@ -139,19 +140,17 @@ class MainActivity : FlutterActivity() {
|
||||
mapOf("progress" to 0.3, "header" to "", "log" to "")
|
||||
)
|
||||
}
|
||||
if (mergeIntegrations) {
|
||||
handler.post {
|
||||
installerChannel.invokeMethod(
|
||||
"update",
|
||||
mapOf(
|
||||
"progress" to 0.4,
|
||||
"header" to "Merging integrations...",
|
||||
"log" to "Merging integrations"
|
||||
)
|
||||
handler.post {
|
||||
installerChannel.invokeMethod(
|
||||
"update",
|
||||
mapOf(
|
||||
"progress" to 0.4,
|
||||
"header" to "Merging integrations...",
|
||||
"log" to "Merging integrations"
|
||||
)
|
||||
}
|
||||
patcher.addFiles(listOf(integrations)) {}
|
||||
)
|
||||
}
|
||||
patcher.addIntegrations(listOf(integrations)) {}
|
||||
|
||||
handler.post {
|
||||
installerChannel.invokeMethod(
|
||||
@ -248,7 +247,7 @@ class MainActivity : FlutterActivity() {
|
||||
// Signer("ReVanced", "s3cur3p@ssw0rd").signApk(patchedFile, outFile, keyStoreFile)
|
||||
|
||||
try {
|
||||
Signer("ReVanced", "s3cur3p@ssw0rd").signApk(patchedFile, outFile, keyStoreFile)
|
||||
Signer("ReVanced", keystorePassword).signApk(patchedFile, outFile, keyStoreFile)
|
||||
} catch (e: Exception) {
|
||||
//log to console
|
||||
print("Error signing apk: ${e.message}")
|
||||
|
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"?>
|
||||
<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"/>
|
||||
<monochrome android:drawable="@drawable/ic_launcher_monochrome"/>
|
||||
</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,6 @@
|
||||
buildscript {
|
||||
ext.cronetVersion = '102.5005.125'
|
||||
ext.kotlin_version = '1.7.20'
|
||||
ext.kotlin_version = '1.7.10'
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
|
@ -1,6 +1,6 @@
|
||||
#Mon May 09 12:07:41 MSK 2022
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-rc-1-bin.zip
|
||||
distributionPath=wrapper/dists
|
||||
zipStorePath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.6.1-bin.zip
|
||||
networkTimeout=10000
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
|
@ -1,6 +1,7 @@
|
||||
{
|
||||
"okButton": "موافق",
|
||||
"cancelButton": "إلغاء",
|
||||
"updateButton": "تحديث",
|
||||
"enabledLabel": "مفعّل",
|
||||
"disabledLabel": "معطّل",
|
||||
"yesButton": "نعم",
|
||||
@ -21,7 +22,7 @@
|
||||
"noInstallations": "لا توجد تطبيقات معدلة مثبتة",
|
||||
"installed": "مثبت",
|
||||
"updateDialogTitle": "تحديث المدير",
|
||||
"updateDialogText": "هل أنت متأكد من رغبتك في تنزيل وتحديث ReVanced Manager ؟",
|
||||
"updateChangelogTitle": "سجل التغييرات",
|
||||
"notificationTitle": "تم تنزيل التحديث",
|
||||
"notificationText": "أنقر لتثبيت التحديث",
|
||||
"downloadingMessage": "جاري تحميل التحديث...",
|
||||
@ -81,13 +82,13 @@
|
||||
"all": "الكل",
|
||||
"none": "بدون",
|
||||
"loadPatchesSelection": "تحميل التعديلات المحددة",
|
||||
"noSavedPatches": "لا توجد تعديلات محفوظة للتطبيق المحدد\nاضغط على تم لحفظ التحديد الحالي",
|
||||
"noSavedPatches": "لا توجد تعديلات محفوظة للتطبيق المختار.\nاضغط على تم لحفظ الاختيار الحالي.",
|
||||
"noPatchesFound": "لم يتم العثور على تعديلات للتطبيق المحدد",
|
||||
"selectAllPatchesWarningContent": "أنت على وشك تحديد جميع التعديلات، بما في ذلك التعديلات غير الموصى بها والتي يمكن أن تسبب سلوكاً غير مرغوب فيه."
|
||||
},
|
||||
"patchItem": {
|
||||
"unsupportedDialogText": "قد يؤدي تحديد هذا التعديل إلى حدوث أخطاء في عملية التعديل.\n\nإصدار التطبيق: {packageVersion}\nالإصدارات المدعومة حالياً:\n{supportedVersions}",
|
||||
"unsupportedPatchVersion": "التعديل غير مدعوم لهذا الإصدار من التطبيق. مكن التعديل التجريبي في الإعدادات للمتابعة."
|
||||
"unsupportedPatchVersion": "التعديل غير مدعوم لهذا الإصدار من التطبيق. فعِّل خِيار التعديل التجريبي في الإعدادات للمتابعة."
|
||||
},
|
||||
"installerView": {
|
||||
"widgetTitle": "المثبت",
|
||||
@ -121,6 +122,7 @@
|
||||
"englishOption": "الإنجليزية",
|
||||
"sourcesLabel": "المصادر",
|
||||
"sourcesLabelHint": "تكوين مصادرك المخصصة",
|
||||
"hostRepositoryLabel": "مستودع API",
|
||||
"orgPatchesLabel": "تنظيم التعديلات",
|
||||
"sourcesPatchesLabel": "مصدر التعديلات",
|
||||
"orgIntegrationsLabel": "تنظيم الدمج",
|
||||
@ -135,6 +137,8 @@
|
||||
"apiURLLabel": "رابط API",
|
||||
"apiURLHint": "تكوين رابط API الخاص بك",
|
||||
"selectApiURL": "رابط API",
|
||||
"experimentalUniversalPatchesLabel": "دعم التعديلات العامة التجريبية",
|
||||
"experimentalUniversalPatchesHint": "عرض جميع التطبيقات المراد استخدامها مع التعديلات العامة، قد يكون تحميل قائمة التطبيقات أبطأ",
|
||||
"experimentalPatchesLabel": "دعم التعديلات التجريبية",
|
||||
"experimentalPatchesHint": "تمكين استخدام التعديلات غير المدعومة في أي إصدار للتطبيق",
|
||||
"enabledExperimentalPatches": "تم تمكين دعم التعديلات التجريبية",
|
||||
|
200
assets/i18n/bg_BG.json
Normal file
@ -0,0 +1,200 @@
|
||||
{
|
||||
"okButton": "OK",
|
||||
"cancelButton": "Отказ",
|
||||
"updateButton": "Актуализация",
|
||||
"enabledLabel": "Включено",
|
||||
"disabledLabel": "Изключено",
|
||||
"yesButton": "Да",
|
||||
"noButton": "Не",
|
||||
"warning": "Внимание",
|
||||
"navigationView": {
|
||||
"dashboardTab": "Табло за управление",
|
||||
"patcherTab": "Пачър",
|
||||
"settingsTab": "Настройки"
|
||||
},
|
||||
"homeView": {
|
||||
"widgetTitle": "Табло за управление",
|
||||
"updatesSubtitle": "Актуализации",
|
||||
"patchedSubtitle": "Модифицирани приложения",
|
||||
"updatesAvailable": "Налични актуализации",
|
||||
"noUpdates": "Няма налични актуализации",
|
||||
"WIP": "В процес на разработка...",
|
||||
"noInstallations": "Няма инсталирани модифицирани приложения",
|
||||
"installed": "Инсталирани",
|
||||
"updateDialogTitle": "Обнови ReVanced Manager",
|
||||
"updateChangelogTitle": "Списък с промени",
|
||||
"notificationTitle": "Актуализацията е изтеглена",
|
||||
"notificationText": "Натиснете за да инсталирате актуализациите",
|
||||
"downloadingMessage": "Изтегляне на актуализация...",
|
||||
"installingMessage": "Инсталиране на актуализация...",
|
||||
"errorDownloadMessage": "Свалянето на актуализацията не беше успешно",
|
||||
"errorInstallMessage": "Инсталирането на актуализацията не беше успешно",
|
||||
"noConnection": "Няма връзка с интернет",
|
||||
"updatesDisabled": "Актуализацията на модифицирани приложения в момента не работи. Модифицирайте приложението наново."
|
||||
},
|
||||
"applicationItem": {
|
||||
"patchButton": "Модифицирай",
|
||||
"infoButton": "Информация",
|
||||
"changelogLabel": "Списък с промените"
|
||||
},
|
||||
"latestCommitCard": {
|
||||
"loadingLabel": "Зареждане...",
|
||||
"timeagoLabel": "Преди {time}",
|
||||
"patcherLabel": "Пачър: ",
|
||||
"managerLabel": "Мениджър: ",
|
||||
"updateButton": "Обнови ReVanced Manager"
|
||||
},
|
||||
"patcherView": {
|
||||
"widgetTitle": "Модификатор",
|
||||
"patchButton": "Модифицирай",
|
||||
"patchDialogText": "Избрали сте модификация за ресурси и разделен APK пакет, което може да доведе до грешки при пачването.\nСигурни ли сте, че искате да продължите?"
|
||||
},
|
||||
"appSelectorCard": {
|
||||
"widgetTitle": "Изберете приложение",
|
||||
"widgetTitleSelected": "Избрано приложение",
|
||||
"widgetSubtitle": "Няма избрано приложение",
|
||||
"noAppsLabel": "Няма намерени приложения",
|
||||
"currentVersion": "Текуща",
|
||||
"recommendedVersion": "Препоръчана",
|
||||
"anyVersion": "всяка"
|
||||
},
|
||||
"patchSelectorCard": {
|
||||
"widgetTitle": "Изберете модификации",
|
||||
"widgetTitleSelected": "Избрани модификации",
|
||||
"widgetSubtitle": "Първо изберете приложение",
|
||||
"widgetEmptySubtitle": "Няма избрани модификации"
|
||||
},
|
||||
"socialMediaCard": {
|
||||
"widgetTitle": "Социални мрежи",
|
||||
"widgetSubtitle": "Открийте ни онлайн!"
|
||||
},
|
||||
"appSelectorView": {
|
||||
"viewTitle": "Изберете приложение",
|
||||
"searchBarHint": "Търсене на приложение",
|
||||
"storageButton": "Хранилище",
|
||||
"errorMessage": "Избраното приложение не може да се използва"
|
||||
},
|
||||
"patchesSelectorView": {
|
||||
"viewTitle": "Изберете модификации",
|
||||
"searchBarHint": "Търсене на модификации",
|
||||
"doneButton": "Готово",
|
||||
"recommended": "Препоръчано",
|
||||
"all": "Всички",
|
||||
"none": "Нито един",
|
||||
"loadPatchesSelection": "Заредете избраните модификации",
|
||||
"noSavedPatches": "Няма запазени модификации за избраното приложение.\nНатиснете Готово за да запазите текущия избор.",
|
||||
"noPatchesFound": "Няма налични модификации за избраното приложение",
|
||||
"selectAllPatchesWarningContent": "Напът сте да изберете всички патчове, дори и тези, които не са препоръчителни, а това може да доведе до нежелани промени."
|
||||
},
|
||||
"patchItem": {
|
||||
"unsupportedDialogText": "Изборът на тази модификация може да доведе до грешки.\n\nВерсия: {packageVersion}\nПоддържани версии: {supportedVersions}",
|
||||
"unsupportedPatchVersion": "Модификацията не се поддържа за тази версия на приложението. Активираите експерименталната опция в настройките за да продължите."
|
||||
},
|
||||
"installerView": {
|
||||
"widgetTitle": "Инсталатор",
|
||||
"installButton": "Инсталиране",
|
||||
"installRootButton": "Инсталиране с root",
|
||||
"openButton": "Отвори",
|
||||
"shareButton": "Споделяне на файла",
|
||||
"notificationTitle": "ReVanced Мениджър модифицира",
|
||||
"notificationText": "Натиснете за да се върнете при инсталатора",
|
||||
"shareApkMenuOption": "Споделяне на APK",
|
||||
"exportApkMenuOption": "Експортиране на APK",
|
||||
"shareLogMenuOption": "Сподели логовете",
|
||||
"installErrorDialogTitle": "Грешка",
|
||||
"installErrorDialogText1": "Инсталиране с root не е възможно с настоящият избор на модификации.\nМодифицирайте приложението наново или изберете опция без root.",
|
||||
"installErrorDialogText2": "Инсталиране без root не е възможно с настоящият избор на модификации.\nМодифицирайте приложението наново или изберете опция с root, ако устройството ви има такъв.",
|
||||
"installErrorDialogText3": "Инсталиране с root не е възможно понеже АРК файла бе избран от хранилището.\nИзберете инсталирано приложение или опция без root.",
|
||||
"noExit": "Инсталаторът все още работи, не може да излезе..."
|
||||
},
|
||||
"settingsView": {
|
||||
"widgetTitle": "Настройки",
|
||||
"appearanceSectionTitle": "Облик",
|
||||
"teamSectionTitle": "Екип",
|
||||
"infoSectionTitle": "Информация",
|
||||
"advancedSectionTitle": "Разширени",
|
||||
"logsSectionTitle": "Логове",
|
||||
"darkThemeLabel": "Тъмен режим",
|
||||
"darkThemeHint": "Добре дошъл на тъмната страна",
|
||||
"dynamicThemeLabel": "Материална Тема",
|
||||
"dynamicThemeHint": "Насладете се на преживяване по-близо до устройството си",
|
||||
"languageLabel": "Език",
|
||||
"englishOption": "Английски",
|
||||
"sourcesLabel": "Източници",
|
||||
"sourcesLabelHint": "Конфигурирайте собствени източници",
|
||||
"hostRepositoryLabel": "API на хранилището",
|
||||
"orgPatchesLabel": "Организация на модификациите",
|
||||
"sourcesPatchesLabel": "Източник на модификациите",
|
||||
"orgIntegrationsLabel": "Организация на интеграциите",
|
||||
"sourcesIntegrationsLabel": "Източник на интеграциите",
|
||||
"sourcesResetDialogTitle": "Нулиране",
|
||||
"sourcesResetDialogText": "Искате ли да възстановите източниците до стойностите им по подразбиране?",
|
||||
"apiURLResetDialogText": "Искате ли да възстановите АРI URL до стойността му по подразбиране?",
|
||||
"contributorsLabel": "Хора, които допринесоха",
|
||||
"contributorsHint": "Списък с хората, допринесли за ReVanced",
|
||||
"logsLabel": "Логове",
|
||||
"logsHint": "Сподели логовете на мениджъра",
|
||||
"apiURLLabel": "API URL",
|
||||
"apiURLHint": "Конфигурирайте собствен API URL",
|
||||
"selectApiURL": "API URL",
|
||||
"experimentalUniversalPatchesLabel": "Поддръжка на експериментални модификации",
|
||||
"experimentalUniversalPatchesHint": "Показване на всички приложения за използване с универсални модификации, зареждането на списъкът с приложения може да е по-бавно",
|
||||
"experimentalPatchesLabel": "Поддръжка на експериментални модификации",
|
||||
"experimentalPatchesHint": "Разрешете използването на неподдържани модификации във всяка версия на приложението",
|
||||
"enabledExperimentalPatches": "Поддръжката на експериментални модификации е активирана",
|
||||
"exportSectionTitle": "Импортиране и експортиране",
|
||||
"aboutLabel": "Относно",
|
||||
"snackbarMessage": "Копирано",
|
||||
"sentryLabel": "Sentry логване",
|
||||
"sentryHint": "Пращайте анонимни логове за да ни помогнете да подобрим ReVanced Manager",
|
||||
"restartAppForChanges": "Рестартирайте приложението, за да се приложат промените",
|
||||
"deleteKeystoreLabel": "Изтриване на keystore",
|
||||
"deleteKeystoreHint": "Изтриване на keystore използван за подписване на приложението",
|
||||
"deletedKeystore": "Keystore изтрит",
|
||||
"deleteTempDirLabel": "Изтриване на временни файлове",
|
||||
"deleteTempDirHint": "Изтриване на неизползвани временни файлове",
|
||||
"deletedTempDir": "Временните файлове са изтрити",
|
||||
"exportPatchesLabel": "Експортиране на избраните модификации",
|
||||
"exportPatchesHint": "Експортиране на избраните модификации като JSON файл",
|
||||
"exportedPatches": "Избраните модификации са експортирани",
|
||||
"noExportFileFound": "Няма избрани модификации за експортиране",
|
||||
"importPatchesLabel": "Импортиране на избраните модификации",
|
||||
"importPatchesHint": "Импортиране на избраните модификации от JSON файл",
|
||||
"importedPatches": "Избраните модификации са импортирани",
|
||||
"resetStoredPatchesLabel": "Нулиране на модификациите",
|
||||
"resetStoredPatchesHint": "Нулиране на избраните запазени модификации",
|
||||
"resetStoredPatches": "Избраните модификации са нулирани",
|
||||
"jsonSelectorErrorMessage": "Избраният JSON файл не може да се изплозва",
|
||||
"deleteLogsLabel": "Изтриване на логовете",
|
||||
"deleteLogsHint": "Изтриване на събраните логове на мениджърът",
|
||||
"deletedLogs": "Логовете са изтрити"
|
||||
},
|
||||
"appInfoView": {
|
||||
"widgetTitle": "Информация за приложението",
|
||||
"openButton": "Отвори",
|
||||
"uninstallButton": "Деинсталирай",
|
||||
"patchButton": "Модифицирайте",
|
||||
"unpatchButton": "Премахнете модификации",
|
||||
"unpatchDialogText": "Сигурни ли сте че искате да премахнете модификациите?",
|
||||
"rootDialogTitle": "Грешка",
|
||||
"rootDialogText": "Приложението е инсталирано с superuser разрешения, но в момента ReVanced Manager няма разрешения. Моля, първо дайте superuser разрешения.",
|
||||
"packageNameLabel": "Име на пакета",
|
||||
"originalPackageNameLabel": "Оригинално име на пакета",
|
||||
"installTypeLabel": "Тип инсталация",
|
||||
"rootTypeLabel": "Вариант с root достъп",
|
||||
"nonRootTypeLabel": "Без root",
|
||||
"patchedDateLabel": "Дата на модификацията",
|
||||
"patchedDateHint": "на {date} в {time}",
|
||||
"appliedPatchesLabel": "Приложени модификации",
|
||||
"appliedPatchesHint": "{quantity} приложени модификации",
|
||||
"updateNotImplemented": "Тази функция все още не е внедрена"
|
||||
},
|
||||
"contributorsView": {
|
||||
"widgetTitle": "Хора, които допринесоха",
|
||||
"patcherContributors": "Допринесли към пачърът",
|
||||
"patchesContributors": "Допринесли към модификациите",
|
||||
"integrationsContributors": "Допринесли към интеграциите",
|
||||
"cliContributors": "Допринесли към CLI",
|
||||
"managerContributors": "Допринесли към мениджъра"
|
||||
}
|
||||
}
|
@ -1,6 +1,7 @@
|
||||
{
|
||||
"okButton": "OK",
|
||||
"cancelButton": "Afbryd",
|
||||
"updateButton": "Opdatér",
|
||||
"enabledLabel": "Aktiveret",
|
||||
"disabledLabel": "Deaktiveret",
|
||||
"yesButton": "Ja",
|
||||
@ -21,7 +22,7 @@
|
||||
"noInstallations": "Ingen patchede apps installeret",
|
||||
"installed": "Installeret",
|
||||
"updateDialogTitle": "Opdater Manager",
|
||||
"updateDialogText": "Er du sikker på, at du vil downloade og opdatere ReVanced Manager?",
|
||||
"updateChangelogTitle": "Ændringslog",
|
||||
"notificationTitle": "Opdatering downloadet",
|
||||
"notificationText": "Tryk for at installere opdateringen",
|
||||
"downloadingMessage": "Downloader opdatering...",
|
||||
@ -81,13 +82,13 @@
|
||||
"all": "Alle",
|
||||
"none": "Ingen",
|
||||
"loadPatchesSelection": "Indlæs patches udvælgelse",
|
||||
"noSavedPatches": "Ingen gemte patches til den valgte app\nTryk på Udført for at gemme det aktuelle valg",
|
||||
"noSavedPatches": "Ingen gemte patches til den valgte app.\nTryk på Udført for at gemme det aktuelle valg.",
|
||||
"noPatchesFound": "Ingen patches fundet til den valgte app",
|
||||
"selectAllPatchesWarningContent": "Du er ved at vælge alle patches, der inkluderer ikke-anbefalede patches, og kan forårsage uønsket adfærd."
|
||||
},
|
||||
"patchItem": {
|
||||
"unsupportedDialogText": "Valg af denne patch kan medføre patching-fejl.\n\nApp version: {packageVersion}\nUnderstøttede versioner:\n{supportedVersions}",
|
||||
"unsupportedPatchVersion": "Patch er ikke understøttet for denne app-version. Aktivér eksperimentel indstilling i indstillinger for at fortsætte."
|
||||
"unsupportedPatchVersion": "Patch er ikke understøttet for denne app-version. Aktivér den eksperimentelle indstilling i indstillinger for at fortsætte."
|
||||
},
|
||||
"installerView": {
|
||||
"widgetTitle": "Installationsprogram",
|
||||
@ -121,6 +122,7 @@
|
||||
"englishOption": "Engelsk",
|
||||
"sourcesLabel": "Kilder",
|
||||
"sourcesLabelHint": "Konfigurér dine brugerdefinerede kilder",
|
||||
"hostRepositoryLabel": "Repository API",
|
||||
"orgPatchesLabel": "Organisation for Patches",
|
||||
"sourcesPatchesLabel": "Kilde til Patches",
|
||||
"orgIntegrationsLabel": "Organisation for Integrationer",
|
||||
@ -135,6 +137,8 @@
|
||||
"apiURLLabel": "API URL",
|
||||
"apiURLHint": "Konfigurer din brugerdefineret API URL",
|
||||
"selectApiURL": "API URL",
|
||||
"experimentalUniversalPatchesLabel": "Understøttelse af eksperimentelle universale patches",
|
||||
"experimentalUniversalPatchesHint": "Vis alle applikationer der skal bruges med universelle patches, indlæsningslisten over apps kan være langsommere",
|
||||
"experimentalPatchesLabel": "Understøttelse af eksperimentelle patches",
|
||||
"experimentalPatchesHint": "Aktiver brugen af ikke-understøttede patches i enhver app version",
|
||||
"enabledExperimentalPatches": "Understøttelse af eksperimentelle patches aktiveret",
|
||||
@ -148,7 +152,7 @@
|
||||
"deleteKeystoreHint": "Slet keystore der bruges til at signere appen",
|
||||
"deletedKeystore": "Keystore slettet",
|
||||
"deleteTempDirLabel": "Slet midlertidige filer",
|
||||
"deleteTempDirHint": "Slet de ubrugte midlertidige filer",
|
||||
"deleteTempDirHint": "Slet ubrugte midlertidige filer",
|
||||
"deletedTempDir": "Midlertidige filer slettet",
|
||||
"exportPatchesLabel": "Eksportér valgte patches",
|
||||
"exportPatchesHint": "Eksportér valgte patches til en JSON-fil",
|
||||
|
@ -1,6 +1,7 @@
|
||||
{
|
||||
"okButton": "OK",
|
||||
"cancelButton": "Abbrechen",
|
||||
"updateButton": "Aktualisieren",
|
||||
"enabledLabel": "Aktiviert",
|
||||
"disabledLabel": "Deaktiviert",
|
||||
"yesButton": "Ja",
|
||||
@ -21,11 +22,11 @@
|
||||
"noInstallations": "Keine gepatchten Apps installiert",
|
||||
"installed": "Installiert",
|
||||
"updateDialogTitle": "Manager aktualisieren",
|
||||
"updateDialogText": "Bist du sicher, dass du den ReVanced Manager herunterladen und aktualisieren möchtest?",
|
||||
"updateChangelogTitle": "Änderungsverlauf",
|
||||
"notificationTitle": "Update heruntergeladen",
|
||||
"notificationText": "Tippen um Update zu installieren",
|
||||
"downloadingMessage": "Aktualisierung wird heruntergeladen...",
|
||||
"installingMessage": "Aktualisierung wird installiert...",
|
||||
"installingMessage": "Update wird installiert...",
|
||||
"errorDownloadMessage": "Aktualisierung konnte nicht heruntergeladen werden",
|
||||
"errorInstallMessage": "Aktualisierung konnte nicht installiert werden",
|
||||
"noConnection": "Keine Internetverbindung",
|
||||
@ -81,13 +82,13 @@
|
||||
"all": "Alle",
|
||||
"none": "Keine",
|
||||
"loadPatchesSelection": "Patchauswahl laden",
|
||||
"noSavedPatches": "Keine gespeicherten Patches für die ausgewählte App\nDrücken Sie Fertig, um die aktuelle Auswahl zu speichern",
|
||||
"noSavedPatches": "Keine gespeicherten Patches für die ausgewählte App. Drücke „Fertig” zum Speichern der aktuellen Auswahl.",
|
||||
"noPatchesFound": "Keine Patches für die ausgewählte App gefunden",
|
||||
"selectAllPatchesWarningContent": "Du bist dabei alle Patches auszuwählen, dies beinhaltet nicht empfohlene Patches und kann zu unerwünschtem Verhalten führen."
|
||||
},
|
||||
"patchItem": {
|
||||
"unsupportedDialogText": "Die Auswahl dieses Patches kann zu Fehlern beim Patchen führen.\n\nApp-Version: {packageVersion}\nUnterstützte Versionen:\n{supportedVersions}",
|
||||
"unsupportedPatchVersion": "Patch wird für diese App-Version nicht unterstützt. Aktiviere das experimentelle Feature in den Einstellungen, um fortzufahren."
|
||||
"unsupportedPatchVersion": "Der Patch unterstützt diese Version der App nicht. Aktiviere in den Einstellungen die experimentellen Funktionen, um fortzufahren."
|
||||
},
|
||||
"installerView": {
|
||||
"widgetTitle": "Installer",
|
||||
@ -121,6 +122,7 @@
|
||||
"englishOption": "Englisch",
|
||||
"sourcesLabel": "Quellen",
|
||||
"sourcesLabelHint": "Konfiguriere deine eigenen Quellen",
|
||||
"hostRepositoryLabel": "Repository-API",
|
||||
"orgPatchesLabel": "Patches Organisation",
|
||||
"sourcesPatchesLabel": "Quelle für Patche",
|
||||
"orgIntegrationsLabel": "Integrationen Organisation",
|
||||
@ -135,6 +137,8 @@
|
||||
"apiURLLabel": "API-URL",
|
||||
"apiURLHint": "Konfiguriere deine eigene API-URL",
|
||||
"selectApiURL": "API URL",
|
||||
"experimentalUniversalPatchesLabel": "Unterstützung für universelle Patches (experimentell)",
|
||||
"experimentalUniversalPatchesHint": "Zeigt alle Apps zur Verwendung mit universellen Patches, dadurch lädt die App-Liste evtl. langsamer",
|
||||
"experimentalPatchesLabel": "Experimentelle Patches aktiviert",
|
||||
"experimentalPatchesHint": "Erlaubt die Benutzung von nicht unterstützten Patches mit jeder App Version",
|
||||
"enabledExperimentalPatches": "Experimentelle Patches Unterstützung aktiviert",
|
||||
|
@ -1,6 +1,7 @@
|
||||
{
|
||||
"okButton": "Εντάξει",
|
||||
"cancelButton": "Ακύρωση",
|
||||
"updateButton": "Ενημέρωση",
|
||||
"enabledLabel": "Ενεργό",
|
||||
"disabledLabel": "Ανενεργό",
|
||||
"yesButton": "Ναι",
|
||||
@ -21,7 +22,7 @@
|
||||
"noInstallations": "Δεν έχουν εγκατασταθεί τροποποιημένες εφαρμογές",
|
||||
"installed": "Εγκατεστημένες",
|
||||
"updateDialogTitle": "Ενημέρωση του Manager",
|
||||
"updateDialogText": "Είστε βέβαιοι ότι θέλετε να κατεβάσετε και να ενημερώσετε το ReVanced Manager;",
|
||||
"updateChangelogTitle": "Τι νέο υπάρχει",
|
||||
"notificationTitle": "Η λήψη της ενημέρωσης ολοκληρώθηκε",
|
||||
"notificationText": "Πατήστε για την εγκατάσταση της ενημέρωσης",
|
||||
"downloadingMessage": "Λήψη ενημέρωσης...",
|
||||
@ -81,7 +82,7 @@
|
||||
"all": "Όλα",
|
||||
"none": "Κανένα",
|
||||
"loadPatchesSelection": "Φόρτωση επιλεγμένων τροποποιήσεων",
|
||||
"noSavedPatches": "Δεν υπάρχουν αποθηκευμένες τροποποιήσεις για την εφαρμογή που επιλέξατε.\nΠατήστε «Τέλος» για να αποθηκεύσετε τις τωρινές επιλογές σας",
|
||||
"noSavedPatches": "Δεν υπάρχουν αποθηκευμένες τροποποιήσεις για την εφαρμογή που επιλέξατε.\nΠατήστε «Τέλος» για να αποθηκεύσετε τις τωρινές επιλογές σας.",
|
||||
"noPatchesFound": "Δε βρέθηκαν τροποποιήσεις για την επιλεγμένη εφαρμογή",
|
||||
"selectAllPatchesWarningContent": "Πρόκειται να επιλέξτε όλες τις μη συνιστώμενες τροποποιήσεις, μπορεί να προκληθεί ανεπιθύμητη συμπεριφορά."
|
||||
},
|
||||
@ -121,6 +122,7 @@
|
||||
"englishOption": "Αγγλικά",
|
||||
"sourcesLabel": "Πηγές",
|
||||
"sourcesLabelHint": "Διαμορφώστε τις δικές σας προσαρμοσμένες πηγές",
|
||||
"hostRepositoryLabel": "Αποθετήριο API",
|
||||
"orgPatchesLabel": "Οργάνωση τροποποιήσεων",
|
||||
"sourcesPatchesLabel": "Πηγή τροποποιήσεων",
|
||||
"orgIntegrationsLabel": "Οργάνωση ενσωματώσεων",
|
||||
@ -135,6 +137,8 @@
|
||||
"apiURLLabel": "API URL",
|
||||
"apiURLHint": "Διαμορφώστε το δικό σας προσαρμοσμένο API URL",
|
||||
"selectApiURL": "Διεύθυνση URL API",
|
||||
"experimentalUniversalPatchesLabel": "Υποστήριξη γενικευμένων πειραματικών τροποποιήσεων",
|
||||
"experimentalUniversalPatchesHint": "Εμφάνιση όλων των εφαρμογών (για χρήση τροποποιήσεων που λειτουργούν σε κάθε εφαρμογή), η φόρτωση της λίστας εφαρμογών μπορεί να καθυστερεί περισσότερο",
|
||||
"experimentalPatchesLabel": "Πειραματική υποστήριξη τροποποιήσεων",
|
||||
"experimentalPatchesHint": "Ενεργοποιήστε το για χρήση μη υποστηριζόμενων τροποποιήσεων, ανεξαρτήτως έκδοσης εφαρμογής",
|
||||
"enabledExperimentalPatches": "Πειραματική υποστήριξη τροποποιήσεων ενεργή",
|
||||
|
@ -13,6 +13,7 @@
|
||||
"settingsTab": "Settings"
|
||||
},
|
||||
"homeView": {
|
||||
"refreshSuccess": "Refreshed successfully",
|
||||
"widgetTitle": "Dashboard",
|
||||
"updatesSubtitle": "Updates",
|
||||
"patchedSubtitle": "Patched applications",
|
||||
@ -47,7 +48,8 @@
|
||||
"patcherView": {
|
||||
"widgetTitle": "Patcher",
|
||||
"patchButton": "Patch",
|
||||
"patchDialogText": "You have selected a resource patch and a split APK installation has been detected, so patching errors may occur.\nAre you sure you want to proceed?"
|
||||
"patchDialogText": "You have selected a resource patch and a split APK installation has been detected, so patching errors may occur.\nAre you sure you want to proceed?",
|
||||
"armv7WarningDialogText": "Patching on ARMv7 devices is not yet supported. Proceed anyways?"
|
||||
},
|
||||
"appSelectorCard": {
|
||||
"widgetTitle": "Select an application",
|
||||
@ -55,7 +57,7 @@
|
||||
"widgetSubtitle": "No application selected",
|
||||
"noAppsLabel": "No applications found",
|
||||
"currentVersion": "Current",
|
||||
"recommendedVersion": "Recommended",
|
||||
"suggestedVersion": "Suggested",
|
||||
"anyVersion": "any"
|
||||
},
|
||||
"patchSelectorCard": {
|
||||
@ -71,20 +73,25 @@
|
||||
"appSelectorView": {
|
||||
"viewTitle": "Select an application",
|
||||
"searchBarHint": "Search applications",
|
||||
"selectFromStorageButton": "Select from storage",
|
||||
"storageButton": "Storage",
|
||||
"errorMessage": "Unable to use selected application"
|
||||
"errorMessage": "Unable to use selected application",
|
||||
"downloadToast": "Download function is not available yet",
|
||||
"featureNotAvailable": "Feature not implemented",
|
||||
"featureNotAvailableText": "This feature has not been added yet for non-root. You'll need to select APK files from storage for now."
|
||||
},
|
||||
"patchesSelectorView": {
|
||||
"viewTitle": "Select patches",
|
||||
"searchBarHint": "Search patches",
|
||||
"doneButton": "Done",
|
||||
"recommended": "Recommended",
|
||||
"all": "All",
|
||||
"default": "Default",
|
||||
"defaultTooltip": "Select all default patches",
|
||||
"none": "None",
|
||||
"noneTooltip": "Deselect all patches",
|
||||
"loadPatchesSelection": "Load patches selection",
|
||||
"noSavedPatches": "No saved patches for the selected app.\nPress Done to save current selection.",
|
||||
"noPatchesFound": "No patches found for the selected app",
|
||||
"selectAllPatchesWarningContent": "You are about to select all patches, that includes unrecommended patches and can cause unwanted behavior."
|
||||
"selectAllPatchesWarningContent": "You are about to select all patches, that includes non-suggested patches and can cause unwanted behavior."
|
||||
},
|
||||
"patchItem": {
|
||||
"unsupportedDialogText": "Selecting this patch may result in patching errors.\n\nApp version: {packageVersion}\nSupported versions:\n{supportedVersions}",
|
||||
@ -145,11 +152,10 @@
|
||||
"exportSectionTitle": "Import & export",
|
||||
"aboutLabel": "About",
|
||||
"snackbarMessage": "Copied to clipboard",
|
||||
"sentryLabel": "Sentry logging",
|
||||
"sentryHint": "Send anonymous logs to help us improve ReVanced Manager",
|
||||
"restartAppForChanges": "Restart the app to apply changes",
|
||||
"deleteKeystoreLabel": "Delete keystore",
|
||||
"deleteKeystoreHint": "Delete the keystore used to sign the app",
|
||||
"deleteKeystoreDialogText": "Are you sure you want to delete the keystore used to sign patched applications?",
|
||||
"deletedKeystore": "Keystore deleted",
|
||||
"deleteTempDirLabel": "Delete temporary files",
|
||||
"deleteTempDirHint": "Delete unused temporary files",
|
||||
@ -167,7 +173,17 @@
|
||||
"jsonSelectorErrorMessage": "Unable to use selected JSON file",
|
||||
"deleteLogsLabel": "Delete logs",
|
||||
"deleteLogsHint": "Delete collected manager logs",
|
||||
"deletedLogs": "Logs deleted"
|
||||
"deletedLogs": "Logs deleted",
|
||||
"exportKeystoreLabel": "Export keystore",
|
||||
"exportKeystoreHint": "Export keystore used to sign apps",
|
||||
"exportedKeystore": "Keystore exported",
|
||||
"noKeystoreExportFileFound": "No keystore to export",
|
||||
"importKeystoreLabel": "Import keystore",
|
||||
"importKeystoreHint": "Import keystore used to sign apps",
|
||||
"importedKeystore": "Keystore imported",
|
||||
"keystoreSelectorErrorMessage": "Unable to use selected KEYSTORE file",
|
||||
"selectKeystorePassword": "Keystore Password",
|
||||
"selectKeystorePasswordHint": "Select keystore password used to sign the apk"
|
||||
},
|
||||
"appInfoView": {
|
||||
"widgetTitle": "App info",
|
||||
|
@ -1,6 +1,7 @@
|
||||
{
|
||||
"okButton": "OK",
|
||||
"cancelButton": "Cancelar",
|
||||
"updateButton": "Actualizar",
|
||||
"enabledLabel": "Activado",
|
||||
"disabledLabel": "Desactivado",
|
||||
"yesButton": "Sí",
|
||||
@ -21,7 +22,7 @@
|
||||
"noInstallations": "No hay aplicaciones parcheadas instaladas",
|
||||
"installed": "Instalado",
|
||||
"updateDialogTitle": "Actualizar Manager",
|
||||
"updateDialogText": "¿Está seguro de que desea descargar y actualizar ReVanced Manager?",
|
||||
"updateChangelogTitle": "Registro de cambios",
|
||||
"notificationTitle": "Actualización descargada",
|
||||
"notificationText": "Toca para instalar la actualización",
|
||||
"downloadingMessage": "Descargando actualización...",
|
||||
@ -81,7 +82,7 @@
|
||||
"all": "Todos",
|
||||
"none": "Ninguno",
|
||||
"loadPatchesSelection": "Cargar selección de parches",
|
||||
"noSavedPatches": "No hay parches guardados para la app seleccionada\nPresione Hecho para guardar la selección actual",
|
||||
"noSavedPatches": "No hay parches guardados para la app seleccionada\nPresione Hecho para guardar la selección actual.",
|
||||
"noPatchesFound": "No se encontraron parches para la aplicación seleccionada",
|
||||
"selectAllPatchesWarningContent": "Está a punto de seleccionar todos los parches incluyendo parches no recomendados, lo cual puede causar comportamientos inesperados."
|
||||
},
|
||||
@ -121,6 +122,7 @@
|
||||
"englishOption": "Inglés",
|
||||
"sourcesLabel": "Fuentes",
|
||||
"sourcesLabelHint": "Configure sus fuentes personalizadas",
|
||||
"hostRepositoryLabel": "Repositorio API",
|
||||
"orgPatchesLabel": "Organización de los parches",
|
||||
"sourcesPatchesLabel": "Fuente de los parches",
|
||||
"orgIntegrationsLabel": "Organización de integraciones",
|
||||
@ -135,6 +137,8 @@
|
||||
"apiURLLabel": "URL de la API",
|
||||
"apiURLHint": "Configure su URL personalizada para la API",
|
||||
"selectApiURL": "URL de la API",
|
||||
"experimentalUniversalPatchesLabel": "Soporte experimental para parches universales",
|
||||
"experimentalUniversalPatchesHint": "Mostrar todas las aplicaciones a usar con parches universales, la carga de la lista de aplicaciones puede ser más lenta",
|
||||
"experimentalPatchesLabel": "Soporte para parches experimentales",
|
||||
"experimentalPatchesHint": "Habilite el uso de parches no compatibles en cualquier versión de la app",
|
||||
"enabledExperimentalPatches": "Soporte para parches experimentales activado",
|
||||
@ -148,7 +152,7 @@
|
||||
"deleteKeystoreHint": "Eliminar el keystore usado para firmar la app",
|
||||
"deletedKeystore": "Keystore eliminado",
|
||||
"deleteTempDirLabel": "Borrar archivos temporales",
|
||||
"deleteTempDirHint": "Elimina los archivos temporales no utilizados",
|
||||
"deleteTempDirHint": "Eliminar archivos temporales no utilizados",
|
||||
"deletedTempDir": "Archivos temporales eliminados",
|
||||
"exportPatchesLabel": "Exportar la selección de parches",
|
||||
"exportPatchesHint": "Exportar selección de parches a un archivo JSON",
|
||||
|
@ -1,6 +1,7 @@
|
||||
{
|
||||
"okButton": "OK",
|
||||
"cancelButton": "Annuler",
|
||||
"updateButton": "Mise à jour",
|
||||
"enabledLabel": "Activé",
|
||||
"disabledLabel": "Désactivé",
|
||||
"yesButton": "Oui",
|
||||
@ -21,9 +22,9 @@
|
||||
"noInstallations": "Aucune application patchée n'est installée",
|
||||
"installed": "Installée",
|
||||
"updateDialogTitle": "Mettre à jour le Manager",
|
||||
"updateDialogText": "Êtes-vous sûr de vouloir télécharger et mettre à jour ReVanced Manager ?",
|
||||
"updateChangelogTitle": "Liste des changements",
|
||||
"notificationTitle": "Mise à jour téléchargée",
|
||||
"notificationText": "Appuyer pour installer la mise à jour",
|
||||
"notificationText": "Appuyez sur pour installer la mise à jour",
|
||||
"downloadingMessage": "Téléchargement de la mise à jour...",
|
||||
"installingMessage": "Installation de la mise à jour...",
|
||||
"errorDownloadMessage": "Impossible de télécharger la mise à jour",
|
||||
@ -51,7 +52,7 @@
|
||||
"appSelectorCard": {
|
||||
"widgetTitle": "Sélectionner une application",
|
||||
"widgetTitleSelected": "Application sélectionnée",
|
||||
"widgetSubtitle": "Aucune application n'a été sélectionnée",
|
||||
"widgetSubtitle": "Aucune application sélectionnée",
|
||||
"noAppsLabel": "Aucune application trouvée",
|
||||
"currentVersion": "Version actuelle",
|
||||
"recommendedVersion": "Version recommandée",
|
||||
@ -80,14 +81,14 @@
|
||||
"recommended": "Recommandé",
|
||||
"all": "Tout",
|
||||
"none": "Aucun",
|
||||
"loadPatchesSelection": "Charger les patches sélectionnés",
|
||||
"noSavedPatches": "Aucun patch enregistré pour l'application sélectionnée\nAppuyez sur Terminé pour enregistrer la sélection actuelle",
|
||||
"loadPatchesSelection": "Charger les patchs sélectionnés",
|
||||
"noSavedPatches": "Aucun patch enregistré pour l'application sélectionnée.\nAppuyez sur Terminé pour enregistrer la sélection actuelle.",
|
||||
"noPatchesFound": "Aucun patch n'a été trouvé pour l'application sélectionnée",
|
||||
"selectAllPatchesWarningContent": "Vous êtes sur le point de sélectionner tous les patchs, cela inclut des patchs non recommandés et peut causer des comportements indésirables."
|
||||
},
|
||||
"patchItem": {
|
||||
"unsupportedDialogText": "Sélectionner ce patch pourrait entrainer des erreurs dans la modification.\n\nVersion de l'application: {packageVersion}\nVersions supportées:\n{supportedVersions}",
|
||||
"unsupportedPatchVersion": "Le patch n'est pas supporté pour cette version de l'application. Activez le commutateur expérimental dans les réglages pour continuer."
|
||||
"unsupportedDialogText": "La sélection de ce patch peut entraîner des erreurs.\n\nVersion de l'application : {packageVersion}\nVersions prises en charge :\n{supportedVersions}",
|
||||
"unsupportedPatchVersion": "Le correctif n'est pas pris en charge pour cette version de l'application. Activez le bouton expérimental dans les paramètres pour continuer."
|
||||
},
|
||||
"installerView": {
|
||||
"widgetTitle": "Installateur",
|
||||
@ -99,7 +100,7 @@
|
||||
"notificationText": "Appuyer pour revenir à l’installateur",
|
||||
"shareApkMenuOption": "Partager le fichier APK",
|
||||
"exportApkMenuOption": "Exporter l'APK",
|
||||
"shareLogMenuOption": "Partager le log",
|
||||
"shareLogMenuOption": "Partager le journal",
|
||||
"installErrorDialogTitle": "Erreur",
|
||||
"installErrorDialogText1": "L'installation Root n'est pas possible avec la sélection actuelle de patchs.\nRe-patchez votre application ou choisissez une installation non root.",
|
||||
"installErrorDialogText2": "L'installation Non-root n'est pas possible avec la sélection actuelle de patchs.\nRe-patchez votre application ou choisissez une installation en tant que Root.",
|
||||
@ -121,6 +122,7 @@
|
||||
"englishOption": "Anglais",
|
||||
"sourcesLabel": "Sources",
|
||||
"sourcesLabelHint": "Configurer vos sources personnalisées",
|
||||
"hostRepositoryLabel": "Dépôt de l'API",
|
||||
"orgPatchesLabel": "Organisation des patchs",
|
||||
"sourcesPatchesLabel": "Source des patches",
|
||||
"orgIntegrationsLabel": "Organisme d'intégration",
|
||||
@ -135,6 +137,8 @@
|
||||
"apiURLLabel": "URL de l'API",
|
||||
"apiURLHint": "Configurez l'URL de votre API personnalisée",
|
||||
"selectApiURL": "URL de l'API",
|
||||
"experimentalUniversalPatchesLabel": "Support expérimental des patchs universels",
|
||||
"experimentalUniversalPatchesHint": "Afficher toutes les applications à utiliser avec les patchs universels, le chargement de la liste des applications pourrait être lent",
|
||||
"experimentalPatchesLabel": "Support des patchs expérimentaux",
|
||||
"experimentalPatchesHint": "Activer l'utilisation des patchs non supportés dans n'importe quelle version de l'application",
|
||||
"enabledExperimentalPatches": "Support pour les patchs expérimentaux activé",
|
||||
|
@ -1,6 +1,7 @@
|
||||
{
|
||||
"okButton": "Oke",
|
||||
"cancelButton": "Batal",
|
||||
"updateButton": "Perbarui",
|
||||
"enabledLabel": "Aktif",
|
||||
"disabledLabel": "Nonaktif",
|
||||
"yesButton": "Ya",
|
||||
@ -21,7 +22,7 @@
|
||||
"noInstallations": "Tidak ada aplikasi termodifikasi yang terpasang",
|
||||
"installed": "Terpasang",
|
||||
"updateDialogTitle": "Perbarui Manager",
|
||||
"updateDialogText": "Apakah Anda yakin ingin mengunduh dan memperbarui ReVanced Manager?",
|
||||
"updateChangelogTitle": "Catatan perubahan",
|
||||
"notificationTitle": "Pembaruan sudah diunduh",
|
||||
"notificationText": "Ketuk untuk memasang pembaharuan",
|
||||
"downloadingMessage": "Mengunduh pembaruan...",
|
||||
@ -81,13 +82,13 @@
|
||||
"all": "Semua",
|
||||
"none": "Tidak ada",
|
||||
"loadPatchesSelection": "Tidak ada modifikasi yang terpilih",
|
||||
"noSavedPatches": "Tidak ada modifikasi tersimpan untuk aplikasi terpilih",
|
||||
"noSavedPatches": "Tidak ada tambalan tersimpan untuk aplikasi terpilih.\nTekan \"Sudah\" untuk menyimpan seleksi saat ini.",
|
||||
"noPatchesFound": "Modifikasi tidak ditemukan untuk aplikasi terpilih",
|
||||
"selectAllPatchesWarningContent": "Anda akan memilih semua modifikasi, termasuk modifikasi yang tidak direkomendasikan dan dapat mengakibatkan hal yang tidak diinginkan."
|
||||
},
|
||||
"patchItem": {
|
||||
"unsupportedDialogText": "Menggunakan modifikasi ini dapat menyebabkan error pada saat memodifikasi aplikasi\n\nVersi aplikasi: {packageVersion}\nVersi yang mendukung:\n{supportedVersions}",
|
||||
"unsupportedPatchVersion": "Modifikasi tidak didukung untuk versi aplikasi ini. Aktifkan eksperimental dalam pengaturan untuk melanjutkan."
|
||||
"unsupportedPatchVersion": "Patch tidak didukung untuk versi aplikasi ini. Aktifkan tombol eksperimental di pengaturan untuk melanjutkan."
|
||||
},
|
||||
"installerView": {
|
||||
"widgetTitle": "Pemasang",
|
||||
@ -121,6 +122,7 @@
|
||||
"englishOption": "Inggris",
|
||||
"sourcesLabel": "Sumber",
|
||||
"sourcesLabelHint": "Konfigurasi sumber kustom Anda",
|
||||
"hostRepositoryLabel": "API Repositori",
|
||||
"orgPatchesLabel": "Organisasi Modifikasi",
|
||||
"sourcesPatchesLabel": "Sumber Modifikasi",
|
||||
"orgIntegrationsLabel": "Organisasi Intergrasi",
|
||||
@ -135,6 +137,8 @@
|
||||
"apiURLLabel": "URL API",
|
||||
"apiURLHint": "Konfigurasi URL API kustom Anda",
|
||||
"selectApiURL": "URL API",
|
||||
"experimentalUniversalPatchesLabel": "Dukungan tambalan universal eksperimental",
|
||||
"experimentalUniversalPatchesHint": "Menampilkan semua aplikasi yang digunakan tambalan universal, pemuatan daftar aplikasi akan membuat lambat",
|
||||
"experimentalPatchesLabel": "Dukungan Modifikasi Eksperimental",
|
||||
"experimentalPatchesHint": "Aktifkan untuk menggunakan modifikasi yang tidak didukung di versi aplikasi apa pun",
|
||||
"enabledExperimentalPatches": "Modifikasi eksperimental diaktifkan",
|
||||
|
@ -1,6 +1,7 @@
|
||||
{
|
||||
"okButton": "OK",
|
||||
"cancelButton": "Annulla",
|
||||
"updateButton": "Aggiorna",
|
||||
"enabledLabel": "Attivo",
|
||||
"disabledLabel": "Disattivato",
|
||||
"yesButton": "Sì",
|
||||
@ -21,7 +22,7 @@
|
||||
"noInstallations": "Non è stata installata nessuna applicazione modificata",
|
||||
"installed": "Installate",
|
||||
"updateDialogTitle": "Aggiorna Manager",
|
||||
"updateDialogText": "Sei sicuro di voler scaricare e aggiornare ReVanced Manager?",
|
||||
"updateChangelogTitle": "Changelog",
|
||||
"notificationTitle": "Aggiornamento scaricato",
|
||||
"notificationText": "Tocca per installare l'aggiornamento",
|
||||
"downloadingMessage": "Sto scaricando l'aggiornamento...",
|
||||
@ -81,7 +82,7 @@
|
||||
"all": "Tutte",
|
||||
"none": "Nessuna",
|
||||
"loadPatchesSelection": "Carica selezione patch",
|
||||
"noSavedPatches": "Nessuna patch salvata per l'app selezionata\nPremi Fatto per salvare la selezione corrente",
|
||||
"noSavedPatches": "Nessuna patch salvata per l'app selezionata.\nPremi Fatto per salvare la selezione corrente.",
|
||||
"noPatchesFound": "Nessuna patch trovata per l'applicazione selezionata",
|
||||
"selectAllPatchesWarningContent": "Stai per selezionare tutte le patch, incluse patch non consigliate che potrebbero causare comportamenti indesiderati."
|
||||
},
|
||||
@ -121,6 +122,7 @@
|
||||
"englishOption": "English",
|
||||
"sourcesLabel": "Sorgenti",
|
||||
"sourcesLabelHint": "Configura le tue sorgenti personalizzate",
|
||||
"hostRepositoryLabel": "Repository API",
|
||||
"orgPatchesLabel": "Organizzazione Patch",
|
||||
"sourcesPatchesLabel": "Sorgente Patch",
|
||||
"orgIntegrationsLabel": "Organizzazione Integrazioni",
|
||||
@ -135,6 +137,8 @@
|
||||
"apiURLLabel": "URL API",
|
||||
"apiURLHint": "Configura il tuo URL delle API personalizzato",
|
||||
"selectApiURL": "URL API",
|
||||
"experimentalUniversalPatchesLabel": "Supporto per patch universali sperimentali",
|
||||
"experimentalUniversalPatchesHint": "Visualizza tutte le applicazioni da utilizzare con le patch universali, il caricamento dell'elenco delle app potrebbe essere più lento",
|
||||
"experimentalPatchesLabel": "Supporto per patch sperimentali",
|
||||
"experimentalPatchesHint": "Abilita l'utilizzo di patch non supportate in qualsiasi versione dell'app",
|
||||
"enabledExperimentalPatches": "Supporto patch sperimentali abilitato",
|
||||
|
200
assets/i18n/lt_LT.json
Normal file
@ -0,0 +1,200 @@
|
||||
{
|
||||
"okButton": "Gerai",
|
||||
"cancelButton": "Atšaukti",
|
||||
"updateButton": "Atnaujinti",
|
||||
"enabledLabel": "Įjungta",
|
||||
"disabledLabel": "Išjungta",
|
||||
"yesButton": "Taip",
|
||||
"noButton": "Ne",
|
||||
"warning": "Įspėjimas",
|
||||
"navigationView": {
|
||||
"dashboardTab": "Valdymo skydas",
|
||||
"patcherTab": "Patcher",
|
||||
"settingsTab": "Nustatymai"
|
||||
},
|
||||
"homeView": {
|
||||
"widgetTitle": "Valdymo skydas",
|
||||
"updatesSubtitle": "Atnaujinimai",
|
||||
"patchedSubtitle": "Modifikuotos programos",
|
||||
"updatesAvailable": "Galimi atnaujinimai",
|
||||
"noUpdates": "Atnaujinimų nėra",
|
||||
"WIP": "Vykdomi darbai...",
|
||||
"noInstallations": "Nėra įdiegtų modifikuotų programų",
|
||||
"installed": "Įdiegta",
|
||||
"updateDialogTitle": "Atnaujinti Manager",
|
||||
"updateChangelogTitle": "Pakeitimų sąrašas",
|
||||
"notificationTitle": "Atnaujinimas atsiųstas",
|
||||
"notificationText": "Paspauskite, kad įdiegtumėte naujinimą",
|
||||
"downloadingMessage": "Atsiunčiamas atnaujinimas...",
|
||||
"installingMessage": "Įdiegiamas atnaujinimas...",
|
||||
"errorDownloadMessage": "Nepavyksta atsisiųsti atnaujinimo",
|
||||
"errorInstallMessage": "Nepavyksta įdiegti atnaujinimo",
|
||||
"noConnection": "Nėra interneto ryšio",
|
||||
"updatesDisabled": "Modifikuotų programų atnaujinimas šiuo metu išjungtas. Vėl modifikuokite programą."
|
||||
},
|
||||
"applicationItem": {
|
||||
"patchButton": "Modifikuoti",
|
||||
"infoButton": "Info",
|
||||
"changelogLabel": "Pakeitimai"
|
||||
},
|
||||
"latestCommitCard": {
|
||||
"loadingLabel": "Kraunama...",
|
||||
"timeagoLabel": "Prieš {time}",
|
||||
"patcherLabel": "Patcher: ",
|
||||
"managerLabel": "Manager: ",
|
||||
"updateButton": "Atnaujinti Manager"
|
||||
},
|
||||
"patcherView": {
|
||||
"widgetTitle": "Patcher",
|
||||
"patchButton": "Modifikuoti",
|
||||
"patchDialogText": "Jūs pasirinkote resource modifikaciją ir padalinta APK instaliacija buvo aptikta, tai modifikavimo klaidos gali atsitikti.\nAr esate tikri kad norite tęsti modifikuoti padalytą APK?"
|
||||
},
|
||||
"appSelectorCard": {
|
||||
"widgetTitle": "Pasirinkite programą",
|
||||
"widgetTitleSelected": "Pasirinkta programa",
|
||||
"widgetSubtitle": "Nepasirinkta programa",
|
||||
"noAppsLabel": "Nerastos jokios programos",
|
||||
"currentVersion": "Dabartinė",
|
||||
"recommendedVersion": "Rekomenduojama",
|
||||
"anyVersion": "bet kokia"
|
||||
},
|
||||
"patchSelectorCard": {
|
||||
"widgetTitle": "Pasirinkite modifikacijas",
|
||||
"widgetTitleSelected": "Pasirinktos modifikacijos",
|
||||
"widgetSubtitle": "Pirmiausia pasirinkite programą",
|
||||
"widgetEmptySubtitle": "Nepasirinkta jokių modifikacijų"
|
||||
},
|
||||
"socialMediaCard": {
|
||||
"widgetTitle": "Socialiniai tinklai",
|
||||
"widgetSubtitle": "Mes esame internete!"
|
||||
},
|
||||
"appSelectorView": {
|
||||
"viewTitle": "Pasirinkite programą",
|
||||
"searchBarHint": "Ieškoti programų",
|
||||
"storageButton": "Saugykla",
|
||||
"errorMessage": "Neina naudoti parinktos programos"
|
||||
},
|
||||
"patchesSelectorView": {
|
||||
"viewTitle": "Pasirinkti modifikacijas",
|
||||
"searchBarHint": "Ieškoti modifikacijų",
|
||||
"doneButton": "Atlikta",
|
||||
"recommended": "Rekomenduojama",
|
||||
"all": "Visi",
|
||||
"none": "Nėra",
|
||||
"loadPatchesSelection": "Įkelti modifikacijų pasirinkimą",
|
||||
"noSavedPatches": "Nėra išsaugotų pasirinktos programos modifikacijų.\nPaspauskite Atlikta, kad išsaugotumėte dabartinį pasirinkimą.",
|
||||
"noPatchesFound": "Nerasta modifikacijų pasirinktai programai",
|
||||
"selectAllPatchesWarningContent": "Jūs pasirinksite visas modifikacijas, tai įskaito ir nerekomenduojamas modifikacijas ir tai gali sukelti nepageidaujama elgesį."
|
||||
},
|
||||
"patchItem": {
|
||||
"unsupportedDialogText": "Šios modifikacijos pasirinkimas gali sukelti modifikavimo klaidų.\n\nProgramos versija: {packageVersion}\npalaikomos versijos:\n{supportedVersions}",
|
||||
"unsupportedPatchVersion": "Modifikacija nepalaikoma šios programos versijai. Norėdami tęsti, nustatymuose įjunkite eksperimentinį jungiklį."
|
||||
},
|
||||
"installerView": {
|
||||
"widgetTitle": "Įdiegėjas",
|
||||
"installButton": "Įdiegti",
|
||||
"installRootButton": "Įdiegti kaip Root",
|
||||
"openButton": "Atidaryti",
|
||||
"shareButton": "Bendrinti failą",
|
||||
"notificationTitle": "ReVanced Manager modifikuoja programą",
|
||||
"notificationText": "Paspauskite sugrįžti į įdiegėją",
|
||||
"shareApkMenuOption": "Bendrinti APK",
|
||||
"exportApkMenuOption": "Eksportuoti APK",
|
||||
"shareLogMenuOption": "Bendrinti įrašus",
|
||||
"installErrorDialogTitle": "Klaida",
|
||||
"installErrorDialogText1": "Root instaliacija negalima su dabartinėm pasirinktom modifikacijom.\nPer modifikuok programą arba pasirink ne root instaliaciją.",
|
||||
"installErrorDialogText2": "Ne root instaliacija negalima su dabartinėm pasirinktom modifikacijom.\nPer modifikuok programą arba pasirink root instaliaciją jei tavo įrenginys turi root.",
|
||||
"installErrorDialogText3": "Root instaliacija negalima nes originalus APK buvo pasirinktas iš saugyklos.\nPasirink jau instaliuotą programą arba pasirink ne root instaliaciją.",
|
||||
"noExit": "Diegimo programa vis dar veikia, negalima išeiti..."
|
||||
},
|
||||
"settingsView": {
|
||||
"widgetTitle": "Nustatymai",
|
||||
"appearanceSectionTitle": "Išvaizda",
|
||||
"teamSectionTitle": "Komanda",
|
||||
"infoSectionTitle": "Info",
|
||||
"advancedSectionTitle": "Išplėstiniai nustatymai",
|
||||
"logsSectionTitle": "Įrašai",
|
||||
"darkThemeLabel": "Tamsus rėžimas",
|
||||
"darkThemeHint": "Sveikas atvykęs į tamsiąją pusę",
|
||||
"dynamicThemeLabel": "Material You",
|
||||
"dynamicThemeHint": "Mėgaukis patirtimi artimiau tavo įrenginiui",
|
||||
"languageLabel": "Kalba",
|
||||
"englishOption": "Anglų",
|
||||
"sourcesLabel": "Šaltiniai",
|
||||
"sourcesLabelHint": "Sukonfigūruoti tavo nurodytus šaltinius",
|
||||
"hostRepositoryLabel": "API saugykla",
|
||||
"orgPatchesLabel": "Modifikacijų organizacija",
|
||||
"sourcesPatchesLabel": "Modifikacijų šaltinis",
|
||||
"orgIntegrationsLabel": "Integracijų organizacija",
|
||||
"sourcesIntegrationsLabel": "Integracijų šaltinis",
|
||||
"sourcesResetDialogTitle": "Nustatyti iš naujo",
|
||||
"sourcesResetDialogText": "Ar esi tikras kad nori iš naujo nustatyti šaltinius į jų numatytas reikšmes?",
|
||||
"apiURLResetDialogText": "Ar esi tikras kad nori iš naujo nustatyti API URL adresą į numatytą reikšmę?",
|
||||
"contributorsLabel": "Prisidėjusieji žmonės",
|
||||
"contributorsHint": "Žmonės prisidėję prie ReVanced",
|
||||
"logsLabel": "Įrašai",
|
||||
"logsHint": "Bendrinti Manager įrašus",
|
||||
"apiURLLabel": "API URL",
|
||||
"apiURLHint": "Sukonfigūruoti tavo nurodytus API URL",
|
||||
"selectApiURL": "API URL",
|
||||
"experimentalUniversalPatchesLabel": "Eksperimentinių universalių modifikacijų palaikymas",
|
||||
"experimentalUniversalPatchesHint": "Rodyti visas programas, skirtas naudoti su universaliom modifikacijom, krovimas programų sąrašo gali būti lėtesnis",
|
||||
"experimentalPatchesLabel": "Eksperimentinių modifikacijų palaikymas",
|
||||
"experimentalPatchesHint": "Įgalinti naudoti nepalaikomas modifikacijas bet kurioje programos versijoje",
|
||||
"enabledExperimentalPatches": "Eksperimentinių modifikacijų palaikymas įjungtas",
|
||||
"exportSectionTitle": "Importuoti ir eksportuoti",
|
||||
"aboutLabel": "Apie",
|
||||
"snackbarMessage": "Nukopijuota į iškarpinę",
|
||||
"sentryLabel": "Klaidų rinkimo įrankio įrašai",
|
||||
"sentryHint": "Siųsti anoniminius įrašus, kad padėtumėte mums tobulinti ReVanced Manager",
|
||||
"restartAppForChanges": "Iš naujo paleiskite programą, kad modifikacijos įsigaliotų",
|
||||
"deleteKeystoreLabel": "Ištrinti keystore",
|
||||
"deleteKeystoreHint": "Ištrinti keystore kuris naudojamas pasirašyti programą",
|
||||
"deletedKeystore": "Keystore ištrintas",
|
||||
"deleteTempDirLabel": "Ištrinti laikinus failus",
|
||||
"deleteTempDirHint": "Ištrinti nenaudojamus laikinus failus",
|
||||
"deletedTempDir": "Laikini failai ištrinti",
|
||||
"exportPatchesLabel": "Eksportuoti modifikacijų pasirinkimą",
|
||||
"exportPatchesHint": "Eksportuoti modifikacijų pasirinkimą į JSON failą",
|
||||
"exportedPatches": "Modifikacijų pasirinkimai eksportuoti",
|
||||
"noExportFileFound": "Nėra pasirinktų modifikacijų eksportuoti",
|
||||
"importPatchesLabel": "Įkelti modifikacijų pasirinkimą",
|
||||
"importPatchesHint": "Įkelti modifikacijų pasirinkimą iš JSON failo",
|
||||
"importedPatches": "Modifikacijų pasirinkimai įkelti",
|
||||
"resetStoredPatchesLabel": "Atstatyti modifikacijas",
|
||||
"resetStoredPatchesHint": "Atstatyti išsaugotą modifikacijų pasirinkimą",
|
||||
"resetStoredPatches": "Modifikacijų pasirinkimas buvo atstatytas",
|
||||
"jsonSelectorErrorMessage": "Neina naudoti pasirinkto JSON failo",
|
||||
"deleteLogsLabel": "Ištrinti įrašus",
|
||||
"deleteLogsHint": "Ištrinti surinktus Manager įrašus",
|
||||
"deletedLogs": "Įrašai ištrinti"
|
||||
},
|
||||
"appInfoView": {
|
||||
"widgetTitle": "Programos informacija",
|
||||
"openButton": "Atidaryti",
|
||||
"uninstallButton": "Išdiegti",
|
||||
"patchButton": "Modifikuoti",
|
||||
"unpatchButton": "Išimti modifikacijas",
|
||||
"unpatchDialogText": "Ar esi tikras kad nori išimti modifikacijas iš šios programos?",
|
||||
"rootDialogTitle": "Klaida",
|
||||
"rootDialogText": "Programa buvo įdiegta su supervartotojo leidimais, bet ReVanced Manager neturi leidimų.\nPirmiausia suteikite supervartotojo leidimus.",
|
||||
"packageNameLabel": "Paketo pavadinimas",
|
||||
"originalPackageNameLabel": "Originalus paketo pavadinimas",
|
||||
"installTypeLabel": "Įdiegimo tipas",
|
||||
"rootTypeLabel": "Root",
|
||||
"nonRootTypeLabel": "Ne root",
|
||||
"patchedDateLabel": "Modifikavimo data",
|
||||
"patchedDateHint": "{date} {time}",
|
||||
"appliedPatchesLabel": "Uždėtos modifikacijos",
|
||||
"appliedPatchesHint": "{quantity} uždėtos modifikacijos",
|
||||
"updateNotImplemented": "Ši funkcija dar neįgyvendinta"
|
||||
},
|
||||
"contributorsView": {
|
||||
"widgetTitle": "Prisidėjusieji žmonės",
|
||||
"patcherContributors": "Prisidėjusieji prie Patcher",
|
||||
"patchesContributors": "Prisidėjusieji prie Patcher",
|
||||
"integrationsContributors": "Prisidėjusieji prie integracijų",
|
||||
"cliContributors": "Prisidėjusieji prie CLI",
|
||||
"managerContributors": "Prisidėjusieji prie Manager"
|
||||
}
|
||||
}
|
@ -1,6 +1,7 @@
|
||||
{
|
||||
"okButton": "OK",
|
||||
"cancelButton": "Anuluj",
|
||||
"updateButton": "Aktualizuj",
|
||||
"enabledLabel": "Włączone",
|
||||
"disabledLabel": "Wyłączone",
|
||||
"yesButton": "Tak",
|
||||
@ -21,7 +22,7 @@
|
||||
"noInstallations": "Nie zainstalowano żadnych zmodyfikowanych aplikacji",
|
||||
"installed": "Zainstalowane",
|
||||
"updateDialogTitle": "Zaktualizuj Managera",
|
||||
"updateDialogText": "Czy na pewno chcesz pobrać i zaktualizować ReVanced Manager?",
|
||||
"updateChangelogTitle": "Lista zmian",
|
||||
"notificationTitle": "Pobrano aktualizacje",
|
||||
"notificationText": "Kliknij, aby zainstalować aktualizację",
|
||||
"downloadingMessage": "Pobieranie aktualizacji...",
|
||||
@ -29,7 +30,7 @@
|
||||
"errorDownloadMessage": "Nie udało się pobrać aktualizacji",
|
||||
"errorInstallMessage": "Nie udało się zainstalować aktualizacji",
|
||||
"noConnection": "Brak połączenia z internetem",
|
||||
"updatesDisabled": "Aktualizowanie nowej wersji aplikacji jest obecnie niemożliwe. Spróbuj zaktualizować aplikację jeszcze raz."
|
||||
"updatesDisabled": "Aktualizowanie załatanej aplikacji jest obecnie niemożliwe. Spróbuj załatać aplikację jeszcze raz."
|
||||
},
|
||||
"applicationItem": {
|
||||
"patchButton": "Łataj",
|
||||
@ -75,33 +76,33 @@
|
||||
},
|
||||
"patchesSelectorView": {
|
||||
"viewTitle": "Wybierz łatki",
|
||||
"searchBarHint": "Wyszukaj aktualizacje",
|
||||
"searchBarHint": "Wyszukaj łatki",
|
||||
"doneButton": "Gotowe",
|
||||
"recommended": "Rekomendowane",
|
||||
"recommended": "Zalecane",
|
||||
"all": "Wszystkie",
|
||||
"none": "Żadne",
|
||||
"loadPatchesSelection": "Wczytaj wybór aktualizacji",
|
||||
"noSavedPatches": "Brak zapisanych aktualizacji dla wybranej aplikacji\nNaciśnij Gotowe, aby zapisać bieżący wybór",
|
||||
"loadPatchesSelection": "Załaduj wybrane łatki",
|
||||
"noSavedPatches": "Brak zapisanych łatek dla wybranej aplikacji.\nNaciśnij Gotowe, aby zapisać bieżący wybór.",
|
||||
"noPatchesFound": "Nie znaleziono żadnych łatek dla wybranej aplikacji",
|
||||
"selectAllPatchesWarningContent": "Zamierzasz wybrać wszystkie łatki, włącznie z tymi niezalecanymi, które mogą powodować niepożądane zachowania."
|
||||
},
|
||||
"patchItem": {
|
||||
"unsupportedDialogText": "Wybranie tej łatki może spowodować błędy podczas modyfikowania.\n\nWersja aplikacji: {packageVersion}\nAktualnie wspierana wersja:\n{supportedVersions}",
|
||||
"unsupportedPatchVersion": "Łatka nie jest obsługiwana dla tej wersji aplikacji. Włącz eksperymentalne przełączanie w ustawieniach, aby kontynuować."
|
||||
"unsupportedPatchVersion": "Łatka nie jest obsługiwana dla tej wersji aplikacji. Włącz opcje eksperymentalne w ustawieniach, aby kontynuować."
|
||||
},
|
||||
"installerView": {
|
||||
"widgetTitle": "Instalator",
|
||||
"installButton": "Zainstaluj",
|
||||
"installRootButton": "Instaluj jako Root",
|
||||
"installRootButton": "Zainstaluj jako Root",
|
||||
"openButton": "Otwórz",
|
||||
"shareButton": "Udostępnij plik",
|
||||
"notificationTitle": "ReVanced Manager jest aktualizowany",
|
||||
"notificationTitle": "ReVanced Manager patchuje",
|
||||
"notificationText": "Dotknij, aby powrócić do instalatora",
|
||||
"shareApkMenuOption": "Udostępnij plik APK",
|
||||
"exportApkMenuOption": "Eksportuj APK",
|
||||
"shareLogMenuOption": "Udostępnij logi",
|
||||
"installErrorDialogTitle": "Błąd",
|
||||
"installErrorDialogText1": "Instalacja za pomocą roota nie jest możliwa przy obecnym wyborze aktualizacji.\nZaktualizuj aplikacje ponownie lub wybierz instalacje bez roota.",
|
||||
"installErrorDialogText1": "Instalacja za pomocą roota nie jest możliwa przy obecnym wyborze łatek.\nZałataj aplikacje ponownie lub wybierz instalacje bez roota.",
|
||||
"installErrorDialogText2": "Instalacja bez roota nie jest możliwa przy obecnym wyborze łatek.\nZałataj aplikacje ponownie lub wybierz instalacje za pomocą roota, jeśli masz zrootowane urządzenie.",
|
||||
"installErrorDialogText3": "Instalacja jako root nie jest możliwa, ponieważ oryginalny plik APK został wybrany z pamięci.\nWybierz zainstalowaną aplikację lub wybierz instalację bez używania roota.",
|
||||
"noExit": "Instalator jest nadal uruchomiony, nie można zakończyć jego działania..."
|
||||
@ -121,6 +122,7 @@
|
||||
"englishOption": "Angielski",
|
||||
"sourcesLabel": "Źródła",
|
||||
"sourcesLabelHint": "Skonfiguruj własne źródła",
|
||||
"hostRepositoryLabel": "Repozytorium API",
|
||||
"orgPatchesLabel": "Organizacja łatek",
|
||||
"sourcesPatchesLabel": "Źródło łatek",
|
||||
"orgIntegrationsLabel": "Organizacja integracji",
|
||||
@ -135,6 +137,8 @@
|
||||
"apiURLLabel": "Adres API",
|
||||
"apiURLHint": "Ustaw własny adres API",
|
||||
"selectApiURL": "Adres API",
|
||||
"experimentalUniversalPatchesLabel": "Wsparcie dla uniwersalnych eksperymentalnych łatek",
|
||||
"experimentalUniversalPatchesHint": "Wyświetl wszystkie aplikacje do użycia z uniwersalnymi łatkami, ładowanie listy aplikacji może być wolniejsze",
|
||||
"experimentalPatchesLabel": "Wsparcie dla eksperymentalnych łatek",
|
||||
"experimentalPatchesHint": "Włącz użycie nieobsługiwanych łatek w dowolnej wersji aplikacji",
|
||||
"enabledExperimentalPatches": "Włączone wsparcie dla eksperymentalnych łatek",
|
||||
@ -150,16 +154,16 @@
|
||||
"deleteTempDirLabel": "Usuń pliki tymczasowe",
|
||||
"deleteTempDirHint": "Usuń nieużywane pliki tymczasowe",
|
||||
"deletedTempDir": "Pliki tymczasowe zostały usunięte",
|
||||
"exportPatchesLabel": "Eksportuj wybrane aktualizacje",
|
||||
"exportPatchesHint": "Eksportuj wybrane aktualizacje do pliku JSON",
|
||||
"exportedPatches": "Wyeksportowano wybrane aktualizacje",
|
||||
"noExportFileFound": "Brak wybranych aktualizacji do wyeksportowania",
|
||||
"importPatchesLabel": "Importuj wybrane aktualizacje",
|
||||
"importPatchesHint": "Importuj wybrane aktualizacje z pliku JSON",
|
||||
"importedPatches": "Zaimportowano wybrane aktualizacje",
|
||||
"resetStoredPatchesLabel": "Zresetuj aktualizacje",
|
||||
"resetStoredPatchesHint": "Zresetuj wybrane aktualizacje",
|
||||
"resetStoredPatches": "Wybrane aktualizacje zostały zresetowane",
|
||||
"exportPatchesLabel": "Eksportuj wybrane łatki",
|
||||
"exportPatchesHint": "Eksportuj wybrane łatki do pliku JSON",
|
||||
"exportedPatches": "Wyeksportowano wybór łatek",
|
||||
"noExportFileFound": "Brak wybranych łatek do wyeksportowania",
|
||||
"importPatchesLabel": "Importuj wybrane łatki",
|
||||
"importPatchesHint": "Importuj wybrane łatki z pliku JSON",
|
||||
"importedPatches": "Zaimportowano wybrane łatki",
|
||||
"resetStoredPatchesLabel": "Resetuj łatki",
|
||||
"resetStoredPatchesHint": "Zresetuj wybrane przechowywane łatki",
|
||||
"resetStoredPatches": "Wybrane łatki zostały zresetowane",
|
||||
"jsonSelectorErrorMessage": "Nie można użyć wybranego pliku JSON",
|
||||
"deleteLogsLabel": "Usuń logi",
|
||||
"deleteLogsHint": "Usuń logi zebrane przez menadżera",
|
||||
@ -178,17 +182,17 @@
|
||||
"originalPackageNameLabel": "Oryginalna nazwa pakietu",
|
||||
"installTypeLabel": "Typ instalacji",
|
||||
"rootTypeLabel": "Root",
|
||||
"nonRootTypeLabel": "Bez Roota",
|
||||
"nonRootTypeLabel": "Bez roota",
|
||||
"patchedDateLabel": "Data modyfikacji",
|
||||
"patchedDateHint": "{date} o godzinie {time}",
|
||||
"appliedPatchesLabel": "Zastosowane aktualizacje",
|
||||
"appliedPatchesHint": "Zastosowano {quantity} aktualizacji",
|
||||
"appliedPatchesLabel": "Zastosowane łatki",
|
||||
"appliedPatchesHint": "Zastosowano {quantity} łatek",
|
||||
"updateNotImplemented": "Ta funkcja nie została jeszcze zaimplementowana"
|
||||
},
|
||||
"contributorsView": {
|
||||
"widgetTitle": "Współtwórcy",
|
||||
"patcherContributors": "Współtwórcy patcher'a",
|
||||
"patchesContributors": "Współtwórcy aktualizacji",
|
||||
"patchesContributors": "Współtwórcy łatek",
|
||||
"integrationsContributors": "Współtwórcy integracji",
|
||||
"cliContributors": "Współtwórcy CLI",
|
||||
"managerContributors": "Współtwórcy menedżera"
|
||||
|
200
assets/i18n/pt_PT.json
Normal file
@ -0,0 +1,200 @@
|
||||
{
|
||||
"okButton": "OK",
|
||||
"cancelButton": "Cancelar",
|
||||
"updateButton": "Atualizar",
|
||||
"enabledLabel": "Ativado",
|
||||
"disabledLabel": "Desativado",
|
||||
"yesButton": "Sim",
|
||||
"noButton": "Não",
|
||||
"warning": "Aviso",
|
||||
"navigationView": {
|
||||
"dashboardTab": "Painel de controlo",
|
||||
"patcherTab": "Patcher",
|
||||
"settingsTab": "Definições"
|
||||
},
|
||||
"homeView": {
|
||||
"widgetTitle": "Painel de controlo",
|
||||
"updatesSubtitle": "Atualizações",
|
||||
"patchedSubtitle": "Aplicações Modificadas",
|
||||
"updatesAvailable": "Atualizações disponíveis",
|
||||
"noUpdates": "Nenhuma atualização disponível",
|
||||
"WIP": "Em desenvolvimento...",
|
||||
"noInstallations": "Nenhuma aplicação modificada instalada",
|
||||
"installed": "Instalado",
|
||||
"updateDialogTitle": "Atualizar Manager",
|
||||
"updateChangelogTitle": "Alterações",
|
||||
"notificationTitle": "Atualização transferida",
|
||||
"notificationText": "Toque para instalar a atualização",
|
||||
"downloadingMessage": "A transferir a atualização...",
|
||||
"installingMessage": "A instalar atualização...",
|
||||
"errorDownloadMessage": "Não é possível transferir a atualização",
|
||||
"errorInstallMessage": "Não foi possível instalar a atualização",
|
||||
"noConnection": "Sem ligação à Internet",
|
||||
"updatesDisabled": "Atualizar uma aplicação modificada está atualmente desabilitado. Volta a modificar a aplicação."
|
||||
},
|
||||
"applicationItem": {
|
||||
"patchButton": "Modificar",
|
||||
"infoButton": "Informação",
|
||||
"changelogLabel": "Alterações"
|
||||
},
|
||||
"latestCommitCard": {
|
||||
"loadingLabel": "A carregar...",
|
||||
"timeagoLabel": "há {time}",
|
||||
"patcherLabel": "Patcher: ",
|
||||
"managerLabel": "Manager: ",
|
||||
"updateButton": "Atualizar Manager"
|
||||
},
|
||||
"patcherView": {
|
||||
"widgetTitle": "Patcher",
|
||||
"patchButton": "Modificar",
|
||||
"patchDialogText": "Selecionou uma modificação de recurso e uma instalação dividida de APK foi detetada, logo podem ocorrer erros de modificação.\nTem a certeza que deseja prosseguir?"
|
||||
},
|
||||
"appSelectorCard": {
|
||||
"widgetTitle": "Selecione uma aplicação",
|
||||
"widgetTitleSelected": "Aplicação selecionada",
|
||||
"widgetSubtitle": "Nenhuma aplicação selecionada",
|
||||
"noAppsLabel": "Não foram encontradas aplicações",
|
||||
"currentVersion": "Atual",
|
||||
"recommendedVersion": "Recomendada",
|
||||
"anyVersion": "qualquer"
|
||||
},
|
||||
"patchSelectorCard": {
|
||||
"widgetTitle": "Selecionar modificações",
|
||||
"widgetTitleSelected": "Modificações selecionadas",
|
||||
"widgetSubtitle": "Selecione uma aplicação primeiro",
|
||||
"widgetEmptySubtitle": "Nenhuma modificação selecionada"
|
||||
},
|
||||
"socialMediaCard": {
|
||||
"widgetTitle": "Redes sociais",
|
||||
"widgetSubtitle": "Estamos online!"
|
||||
},
|
||||
"appSelectorView": {
|
||||
"viewTitle": "Selecione uma aplicação",
|
||||
"searchBarHint": "Procurar aplicações",
|
||||
"storageButton": "Armazenamento",
|
||||
"errorMessage": "Não é possível usar a aplicação selecionada"
|
||||
},
|
||||
"patchesSelectorView": {
|
||||
"viewTitle": "Selecionar modificações",
|
||||
"searchBarHint": "Procurar modificações",
|
||||
"doneButton": "Concluído",
|
||||
"recommended": "Recomendadas",
|
||||
"all": "Todas",
|
||||
"none": "Nenhuma",
|
||||
"loadPatchesSelection": "Carregar seleção de modificações",
|
||||
"noSavedPatches": "Nenhuma correção salva para o aplicativo selecionado.\nPressione \"Concluir\" para salvar a seleção atual.",
|
||||
"noPatchesFound": "Nenhuma modificação encontrada para a aplicação selecionada",
|
||||
"selectAllPatchesWarningContent": "Está prestes a selecionar todos os patches que incluem correções não recomendadas e podem causar comportamentos indesejados."
|
||||
},
|
||||
"patchItem": {
|
||||
"unsupportedDialogText": "Selecionar esta modificação pode resultar em erros.\n\nVersão da aplicação: {packageVersion}\nVersões suportadas:\n{supportedVersions}",
|
||||
"unsupportedPatchVersion": "Modificar não é suportado para esta versão da aplicação. Ativa a opção experimental nas definições para proceder."
|
||||
},
|
||||
"installerView": {
|
||||
"widgetTitle": "Instalador",
|
||||
"installButton": "Instalar",
|
||||
"installRootButton": "Instalar como Root",
|
||||
"openButton": "Abrir",
|
||||
"shareButton": "Partilhar ficheiro",
|
||||
"notificationTitle": "O ReVanced Manager está a fazer as modificações",
|
||||
"notificationText": "Toca para voltar ao instalador",
|
||||
"shareApkMenuOption": "Partilhar APK",
|
||||
"exportApkMenuOption": "Exportar APK",
|
||||
"shareLogMenuOption": "Partilhar registo",
|
||||
"installErrorDialogTitle": "Erro",
|
||||
"installErrorDialogText1": "A instalação com root não é possível com a seleção de modificações atuais.\nVolta a fazer a modificação da tua aplicação ou escolhe uma instalação sem root.",
|
||||
"installErrorDialogText2": "A instalação sem root não é possível com a seleção de modificações atuais.\nVolta a fazer a modificação da tua aplicação ou escolhe uma instalação com root se o teu dispositivo estiver rooted.",
|
||||
"installErrorDialogText3": "Não é possível instalar com root, pois o APK original foi selecionado do armazenamento.\nSelecione uma aplicação instalada ou escolha uma instalação sem root.",
|
||||
"noExit": "O instalador ainda está em execução, não é possível sair..."
|
||||
},
|
||||
"settingsView": {
|
||||
"widgetTitle": "Definições",
|
||||
"appearanceSectionTitle": "Aparência",
|
||||
"teamSectionTitle": "Equipa",
|
||||
"infoSectionTitle": "Informação",
|
||||
"advancedSectionTitle": "Opções avançadas",
|
||||
"logsSectionTitle": "Registos",
|
||||
"darkThemeLabel": "Modo escuro",
|
||||
"darkThemeHint": "Bem-vindo ao lado negro",
|
||||
"dynamicThemeLabel": "Material You",
|
||||
"dynamicThemeHint": "Aproveite uma experiência mais próxima do tema do seu dispositivo",
|
||||
"languageLabel": "Idioma",
|
||||
"englishOption": "Inglês",
|
||||
"sourcesLabel": "Fontes",
|
||||
"sourcesLabelHint": "Configure as suas fontes personalizadas",
|
||||
"hostRepositoryLabel": "API do Repositório",
|
||||
"orgPatchesLabel": "Organização de Modificações",
|
||||
"sourcesPatchesLabel": "Fonte das Modificações",
|
||||
"orgIntegrationsLabel": "Organização de Integrações",
|
||||
"sourcesIntegrationsLabel": "Fonte das Integrações",
|
||||
"sourcesResetDialogTitle": "Repor",
|
||||
"sourcesResetDialogText": "Tem certeza que deseja redefinir as fontes personalizadas para os seus valores padrão?",
|
||||
"apiURLResetDialogText": "Tem certeza de que deseja repor o URL da API para o seu valor padrão?",
|
||||
"contributorsLabel": "Contribuidores",
|
||||
"contributorsHint": "Uma lista de contribuidores do ReVanced",
|
||||
"logsLabel": "Registos",
|
||||
"logsHint": "Compartilhar registos do Manager",
|
||||
"apiURLLabel": "URL da API",
|
||||
"apiURLHint": "Configura a tua URL de API personalizada",
|
||||
"selectApiURL": "URL da API",
|
||||
"experimentalUniversalPatchesLabel": "Suporte experimental para modificações universais",
|
||||
"experimentalUniversalPatchesHint": "Mostrar todas as aplicações a usar com as modificações universais, a lista de carregamento de apps pode ser mais lenta",
|
||||
"experimentalPatchesLabel": "Suporte para modificações experimentais",
|
||||
"experimentalPatchesHint": "Ativa o uso de modificações não suportadas em qualquer versão das aplicações",
|
||||
"enabledExperimentalPatches": "Suporte para modificações experimentais ativo",
|
||||
"exportSectionTitle": "Importar e exportar",
|
||||
"aboutLabel": "Sobre",
|
||||
"snackbarMessage": "Copiado para a área de transferência",
|
||||
"sentryLabel": "Registo do coletor de erros",
|
||||
"sentryHint": "Enviar registos anónimos para nos ajudar a melhorar o ReVanced Manager",
|
||||
"restartAppForChanges": "Reinicia a aplicação para aplicar as alterações",
|
||||
"deleteKeystoreLabel": "Excluir keystore",
|
||||
"deleteKeystoreHint": "Excluir a keystore usada para assinar a aplicação",
|
||||
"deletedKeystore": "Keystore excluída",
|
||||
"deleteTempDirLabel": "Apagar arquivos temporários",
|
||||
"deleteTempDirHint": "Apagar arquivos temporários não utilizados",
|
||||
"deletedTempDir": "Arquivos temporários apagados",
|
||||
"exportPatchesLabel": "Exportar seleção de modificações",
|
||||
"exportPatchesHint": "Exporta a seleção de modificações para um arquivo JSON",
|
||||
"exportedPatches": "Seleção de modificações exportada",
|
||||
"noExportFileFound": "Nenhuma seleção de modificações para exportar",
|
||||
"importPatchesLabel": "Importar seleção de modificações",
|
||||
"importPatchesHint": "Importa a seleção de modificações de um arquivo JSON",
|
||||
"importedPatches": "Seleção de modificações importada",
|
||||
"resetStoredPatchesLabel": "Redefinir modificações",
|
||||
"resetStoredPatchesHint": "Redefinir a seleção de modificações armazenada",
|
||||
"resetStoredPatches": "A seleção de modificações foi redefinida",
|
||||
"jsonSelectorErrorMessage": "Não é possível usar o arquivo JSON selecionado",
|
||||
"deleteLogsLabel": "Eliminar registos",
|
||||
"deleteLogsHint": "Exclui registos do manager coletados",
|
||||
"deletedLogs": "Registos excluídos"
|
||||
},
|
||||
"appInfoView": {
|
||||
"widgetTitle": "Informações da aplicação",
|
||||
"openButton": "Abrir",
|
||||
"uninstallButton": "Desinstalar",
|
||||
"patchButton": "Modificar",
|
||||
"unpatchButton": "Desalterar",
|
||||
"unpatchDialogText": "Tens a certeza que queres desalterar esta app?",
|
||||
"rootDialogTitle": "Erro",
|
||||
"rootDialogText": "A aplicação foi instalada com permissões de superutilizador, mas atualmente o ReVanced Manager não tem permissões.\nPor favor, conceda permissões de superutilizador primeiro.",
|
||||
"packageNameLabel": "Nome do pacote",
|
||||
"originalPackageNameLabel": "Nome original do pacote",
|
||||
"installTypeLabel": "Tipo de instalação",
|
||||
"rootTypeLabel": "Root",
|
||||
"nonRootTypeLabel": "Sem root",
|
||||
"patchedDateLabel": "Data da Modificação",
|
||||
"patchedDateHint": "{date} às {time}",
|
||||
"appliedPatchesLabel": "Modificações aplicadas",
|
||||
"appliedPatchesHint": "{quantity} modificação/ões aplicada/s",
|
||||
"updateNotImplemented": "Este recurso ainda não foi implementado"
|
||||
},
|
||||
"contributorsView": {
|
||||
"widgetTitle": "Contribuidores",
|
||||
"patcherContributors": "Contribuidores do Modificador",
|
||||
"patchesContributors": "Contribuidores das Modificações",
|
||||
"integrationsContributors": "Contribuidores das Integrações",
|
||||
"cliContributors": "Contribuidores do CLI",
|
||||
"managerContributors": "Contribuidores do Manager"
|
||||
}
|
||||
}
|
@ -1,6 +1,7 @@
|
||||
{
|
||||
"okButton": "OK",
|
||||
"cancelButton": "Anulează",
|
||||
"updateButton": "Actualizare",
|
||||
"enabledLabel": "Activat",
|
||||
"disabledLabel": "Dezactivat",
|
||||
"yesButton": "Da",
|
||||
@ -21,7 +22,7 @@
|
||||
"noInstallations": "Nu sunt instalate aplicații patch-uite",
|
||||
"installed": "Instalat",
|
||||
"updateDialogTitle": "Manager Actualizări",
|
||||
"updateDialogText": "Sunteți sigur că doriți să descărcați și să actualizați ReVanced Manager?",
|
||||
"updateChangelogTitle": "Istoric schimbări",
|
||||
"notificationTitle": "Actualizare descărcată",
|
||||
"notificationText": "Apăsați pentru a instala actualizarea",
|
||||
"downloadingMessage": "Se descarcă actualizarea...",
|
||||
@ -81,13 +82,13 @@
|
||||
"all": "Toate",
|
||||
"none": "Niciunul",
|
||||
"loadPatchesSelection": "Încărcați selecția de patch-uri",
|
||||
"noSavedPatches": "Nu există patch-uri salvate pentru aplicația selectată\nApăsați Terminat pentru a salva selecția curentă",
|
||||
"noSavedPatches": "Nu există patch-uri salvate pentru aplicația selectată.\nApăsați Terminat pentru a salva selecția curentă.",
|
||||
"noPatchesFound": "Nu s-au găsit patch-uri pentru aplicația selectată",
|
||||
"selectAllPatchesWarningContent": "Urmează să selectați toate patch-urile, acestea incluzând patch-uri nerecomandate care pot cauza funcționarea necorespunzătoare a aplicației."
|
||||
},
|
||||
"patchItem": {
|
||||
"unsupportedDialogText": "Selectarea acestui patch poate rezulta în erori de patch-uire.\n\nVersiunea aplicației: {packageVersion}\nVersiuni compatibile:\n{supportedVersions}",
|
||||
"unsupportedPatchVersion": "Patch-ul nu este compatibil cu versiunea curentă a aplicației.\nActivați comutatorul experimental din setări pentru a continua."
|
||||
"unsupportedPatchVersion": "Patch-ul nu este compatibil cu această versiune a aplicației. Activați comutatorul experimental din setări pentru a continua."
|
||||
},
|
||||
"installerView": {
|
||||
"widgetTitle": "Instalator",
|
||||
@ -121,6 +122,7 @@
|
||||
"englishOption": "Engleză",
|
||||
"sourcesLabel": "Surse",
|
||||
"sourcesLabelHint": "Configurați-vă sursele personalizate",
|
||||
"hostRepositoryLabel": "Repository API",
|
||||
"orgPatchesLabel": "Organizarea patch-urilor",
|
||||
"sourcesPatchesLabel": "Sursă patch-uri",
|
||||
"orgIntegrationsLabel": "Organizare integrări",
|
||||
@ -135,6 +137,8 @@
|
||||
"apiURLLabel": "API URL",
|
||||
"apiURLHint": "Configurați-vă API URL-ul personalizat",
|
||||
"selectApiURL": "API URL",
|
||||
"experimentalUniversalPatchesLabel": "Suport pentru patch-uri experimentale universale",
|
||||
"experimentalUniversalPatchesHint": "Afișează toate aplicațiile de utilizat cu patch-uri universale, lista de aplicații poate fi mai lentă",
|
||||
"experimentalPatchesLabel": "Suport pentru patch-uri experimentale",
|
||||
"experimentalPatchesHint": "Activați utilizarea de patch-uri nesuportate în orice versiune a aplicației",
|
||||
"enabledExperimentalPatches": "Suport pentru patch-uri experimentale",
|
||||
@ -148,7 +152,7 @@
|
||||
"deleteKeystoreHint": "Ștergeți keystore-ul utilizat pentru a însemna aplicația",
|
||||
"deletedKeystore": "Keystore șters",
|
||||
"deleteTempDirLabel": "Ștergeți fișierele temporare",
|
||||
"deleteTempDirHint": "Ștergeți fișierele temporare neutilizate",
|
||||
"deleteTempDirHint": "Șterge fișierele temporare neutilizate",
|
||||
"deletedTempDir": "Fișierele temporare au fost șterse",
|
||||
"exportPatchesLabel": "Exportă selecția patch-urilor",
|
||||
"exportPatchesHint": "Exportă selecția patch-urilor într-un fișier JSON",
|
||||
|
@ -1,6 +1,7 @@
|
||||
{
|
||||
"okButton": "ОК",
|
||||
"cancelButton": "Отмена",
|
||||
"updateButton": "Обновить",
|
||||
"enabledLabel": "Включено",
|
||||
"disabledLabel": "Выключено",
|
||||
"yesButton": "Да",
|
||||
@ -21,7 +22,7 @@
|
||||
"noInstallations": "Не установлено пропатченных приложений",
|
||||
"installed": "Установлено",
|
||||
"updateDialogTitle": "Обновить Manager",
|
||||
"updateDialogText": "Вы уверены, что хотите загрузить и обновить ReVanced Manager?",
|
||||
"updateChangelogTitle": "Список изменений",
|
||||
"notificationTitle": "Обновление загружено",
|
||||
"notificationText": "Нажмите, чтобы установить обновление",
|
||||
"downloadingMessage": "Скачивается обновление...",
|
||||
@ -77,17 +78,17 @@
|
||||
"viewTitle": "Выберите патчи",
|
||||
"searchBarHint": "Искать патчи",
|
||||
"doneButton": "Готово",
|
||||
"recommended": "Рекомендуемые",
|
||||
"recommended": "Рекомендуется",
|
||||
"all": "Все",
|
||||
"none": "Никакие",
|
||||
"loadPatchesSelection": "Загрузить выбор патчей",
|
||||
"noSavedPatches": "Нет сохранённых патчей для выбранного приложения\nНажмите «Готово» для сохранения текущего выбора",
|
||||
"noSavedPatches": "Нет сохранённых патчей для выбранного приложения.\nНажмите «Готово» для сохранения текущего выбора.",
|
||||
"noPatchesFound": "Для выбранного приложения не найдены патчи",
|
||||
"selectAllPatchesWarningContent": "Вы собираетесь выбрать все патчи, в том числе и нерекомендуемые патчи, которые могут вызвать нежелательное поведение."
|
||||
},
|
||||
"patchItem": {
|
||||
"unsupportedDialogText": "Выбор этого патча может привести к ошибкам во время патча.\n\nВерсия приложения: {packageVersion}\nПоддерживаемые версии:\n{supportedVersions}",
|
||||
"unsupportedPatchVersion": "Патч не поддерживается этой версией приложения. Для продолжения включите опцию экспериментальной поддержки в настройках."
|
||||
"unsupportedPatchVersion": "Патч не поддерживается для этой версии приложения. Чтобы продолжить, включите опцию экспериментальной поддержки в настройках."
|
||||
},
|
||||
"installerView": {
|
||||
"widgetTitle": "Установщик",
|
||||
@ -101,7 +102,7 @@
|
||||
"exportApkMenuOption": "Экспорт APK",
|
||||
"shareLogMenuOption": "Отправить лог",
|
||||
"installErrorDialogTitle": "Ошибка",
|
||||
"installErrorDialogText1": "Root-установка невозможна с выбранными патчами.\nПерепатчите приложение, или выберите non-root установку.",
|
||||
"installErrorDialogText1": "Root-установка невозможна с выбранными патчами.\nПерепатчите приложение или выберите non-root установку.",
|
||||
"installErrorDialogText2": "Non-root установка невозможна с выбранными патчами.\nПерепатчите приложение, или выберите root-установку, если на вашем устройстве есть root.",
|
||||
"installErrorDialogText3": "Root-установка невозможна, так как оригинальный APK был выбран из хранилища.\nВыберите установленное приложение, или выберите non-root установку.",
|
||||
"noExit": "Установщик все еще запущен, выход невозможен..."
|
||||
@ -121,6 +122,7 @@
|
||||
"englishOption": "Английский",
|
||||
"sourcesLabel": "Источники",
|
||||
"sourcesLabelHint": "Настройте свои источники",
|
||||
"hostRepositoryLabel": "API репозитория",
|
||||
"orgPatchesLabel": "Организация патчей",
|
||||
"sourcesPatchesLabel": "Репозиторий патчей",
|
||||
"orgIntegrationsLabel": "Организация интеграций",
|
||||
@ -135,6 +137,8 @@
|
||||
"apiURLLabel": "API-ссылка",
|
||||
"apiURLHint": "Настройте свою API-ссылку",
|
||||
"selectApiURL": "API-ссылка",
|
||||
"experimentalUniversalPatchesLabel": "Экспериментальная поддержка универсальных патчей",
|
||||
"experimentalUniversalPatchesHint": "Отображать все приложения, поддерживающие универсальные патчи; загрузка списка приложений может быть медленнее",
|
||||
"experimentalPatchesLabel": "Экспериментальная поддержка патчей",
|
||||
"experimentalPatchesHint": "Разрешить использование несовместимых патчей в любой версии приложения",
|
||||
"enabledExperimentalPatches": "Экспериментальная поддержка патчей включена",
|
||||
|
@ -1,6 +1,7 @@
|
||||
{
|
||||
"okButton": "OK",
|
||||
"cancelButton": "Zrušiť",
|
||||
"updateButton": "Aktualizovať",
|
||||
"enabledLabel": "Zapnuté",
|
||||
"disabledLabel": "Vypnuté",
|
||||
"yesButton": "Áno",
|
||||
@ -21,7 +22,7 @@
|
||||
"noInstallations": "Žiadne zaplátané aplikácie nie sú nainštalované",
|
||||
"installed": "Nainštalované",
|
||||
"updateDialogTitle": "Aktualizovať Manažéra",
|
||||
"updateDialogText": "Ste si istý, že chcete stiahnuť a aktualizovať ReVanced manažér?",
|
||||
"updateChangelogTitle": "Zoznam zmien",
|
||||
"notificationTitle": "Aktualizácia bola stiahnutá",
|
||||
"notificationText": "Klepnutím nainštalujete aktualizáciu",
|
||||
"downloadingMessage": "Sťahovanie aktualizácie...",
|
||||
@ -81,13 +82,13 @@
|
||||
"all": "Všetky",
|
||||
"none": "Žiadne",
|
||||
"loadPatchesSelection": "Načítať výber záplat",
|
||||
"noSavedPatches": "Žiadne uložené záplaty pre vybranú aplikáciu\nStlačením tlačidla Hotovo uložíte aktuálny výber",
|
||||
"noSavedPatches": "Žiadne uložené záplaty pre vybranú aplikáciu\nStlačením tlačidla Hotovo uložíte aktuálny výber.",
|
||||
"noPatchesFound": "Neboli nájdené žiadne záplaty pre zvolenú aplikáciu",
|
||||
"selectAllPatchesWarningContent": "Týmto vyberiete všetky záplaty, aj tie ktoré nie sú odporúčané. Toto môže viesť k nežiadúcim účinkom."
|
||||
},
|
||||
"patchItem": {
|
||||
"unsupportedDialogText": "Výber tejto záplaty môže spôsobiť chyby.\n\nVerzia aplikácie: {packageVersion}\nPodporované verzie:\n{supportedVersions}",
|
||||
"unsupportedPatchVersion": "Záplata nie je pre túto verziu aplikácie podporovaná. Ak chcete pokračovať, povoľte v nastaveniach prepínač experimental."
|
||||
"unsupportedPatchVersion": "Záplata nie je pre túto verziu aplikácie podporovaná. Ak chcete pokračovať, povoľte v nastaveniach experimentálny prepínač."
|
||||
},
|
||||
"installerView": {
|
||||
"widgetTitle": "Inštalátor",
|
||||
@ -121,6 +122,7 @@
|
||||
"englishOption": "Anglicky",
|
||||
"sourcesLabel": "Zdroje",
|
||||
"sourcesLabelHint": "Nastavte vlastné zdroje",
|
||||
"hostRepositoryLabel": "API repozitára",
|
||||
"orgPatchesLabel": "Autor záplaty",
|
||||
"sourcesPatchesLabel": "Zdroj záplaty",
|
||||
"orgIntegrationsLabel": "Autor integrácie",
|
||||
@ -135,6 +137,8 @@
|
||||
"apiURLLabel": "API URL",
|
||||
"apiURLHint": "Nastaviť vlastnú API URL",
|
||||
"selectApiURL": "API URL",
|
||||
"experimentalUniversalPatchesLabel": "Experimentálna podpora univerzálnych záplat",
|
||||
"experimentalUniversalPatchesHint": "Zobrazenie všetkých aplikácií na použitie s univerzálnymi záplatami, načítanie zoznamu aplikácií môže byť pomalšie",
|
||||
"experimentalPatchesLabel": "Podpora experimentálnych záplat",
|
||||
"experimentalPatchesHint": "Povoliť používanie nepodporovaných záplat v akejkoľvek verzii aplikácie",
|
||||
"enabledExperimentalPatches": "Podpora experimentálnych záplat povolená",
|
||||
|
200
assets/i18n/sw_KE.json
Normal file
@ -0,0 +1,200 @@
|
||||
{
|
||||
"okButton": "Sawa",
|
||||
"cancelButton": "Ghairi",
|
||||
"updateButton": "Sasisho",
|
||||
"enabledLabel": "Imewashwa",
|
||||
"disabledLabel": "Imezimwa",
|
||||
"yesButton": "Ndiyo",
|
||||
"noButton": "Hapana",
|
||||
"warning": "Onyo",
|
||||
"navigationView": {
|
||||
"dashboardTab": "Dashibodi",
|
||||
"patcherTab": "Kirekebishaji",
|
||||
"settingsTab": "Mpangilio"
|
||||
},
|
||||
"homeView": {
|
||||
"widgetTitle": "Dashibodi",
|
||||
"updatesSubtitle": "Sasisho",
|
||||
"patchedSubtitle": "Programu zilizo na viraka",
|
||||
"updatesAvailable": "Sasisho Zinapatikana",
|
||||
"noUpdates": "Masasisho Hayapatikani",
|
||||
"WIP": "Kazi inaendelea...",
|
||||
"noInstallations": "Hakuna programu zilizo na viraka zilizosakinishwa",
|
||||
"installed": "Imesakinishwa",
|
||||
"updateDialogTitle": "Kidhibiti cha Usasishaji",
|
||||
"updateChangelogTitle": "Changelog",
|
||||
"notificationTitle": "Sasisho limepakuliwa",
|
||||
"notificationText": "Gusa ili kusakinisha sasisho",
|
||||
"downloadingMessage": "Inapakua sasisho...",
|
||||
"installingMessage": "Inasakinisha sasisho...",
|
||||
"errorDownloadMessage": "Imeshindwa kupakua sasisho",
|
||||
"errorInstallMessage": "Imeshindwa kusakinisha sasisho",
|
||||
"noConnection": "Hakuna muunganisho wa mtandao",
|
||||
"updatesDisabled": "Kusasisha programu iliyobanwa kumezimwa kwa sasa. Rekebisha programu tena."
|
||||
},
|
||||
"applicationItem": {
|
||||
"patchButton": "Rekebisha",
|
||||
"infoButton": "Taarifa",
|
||||
"changelogLabel": "Changelog"
|
||||
},
|
||||
"latestCommitCard": {
|
||||
"loadingLabel": "Inapakia...",
|
||||
"timeagoLabel": "{time} iliyopita",
|
||||
"patcherLabel": "Kirekebishaji: ",
|
||||
"managerLabel": "Meneja: ",
|
||||
"updateButton": "Kidhibiti cha Usasishaji"
|
||||
},
|
||||
"patcherView": {
|
||||
"widgetTitle": "Kirekebishaji",
|
||||
"patchButton": "Rekebisha",
|
||||
"patchDialogText": "Umechagua kiraka cha rasilimali na usakinishaji umegunduliwa ya split APK, kwa hivyo makosa ya kuweka viraka yanaweza kutokea.\nJe, una uhakika unataka kuendelea?"
|
||||
},
|
||||
"appSelectorCard": {
|
||||
"widgetTitle": "Chagua programu",
|
||||
"widgetTitleSelected": "Programu imechaguliwa",
|
||||
"widgetSubtitle": "Hakuna programu iliyochaguliwa",
|
||||
"noAppsLabel": "Hakuna programu zilizopatikana",
|
||||
"currentVersion": "Sasa",
|
||||
"recommendedVersion": "Imependekezwa",
|
||||
"anyVersion": "yoyote"
|
||||
},
|
||||
"patchSelectorCard": {
|
||||
"widgetTitle": "Chagua viraka",
|
||||
"widgetTitleSelected": "Viraka vilivyochaguliwa",
|
||||
"widgetSubtitle": "Chagua programu kwanza",
|
||||
"widgetEmptySubtitle": "Hakuna viraka vilivyochaguliwa"
|
||||
},
|
||||
"socialMediaCard": {
|
||||
"widgetTitle": "Mtandao wa kijamii",
|
||||
"widgetSubtitle": "Tuko mtandaoni!"
|
||||
},
|
||||
"appSelectorView": {
|
||||
"viewTitle": "Chagua programu",
|
||||
"searchBarHint": "Tafuta programu",
|
||||
"storageButton": "Hifadhi",
|
||||
"errorMessage": "Haiwezi kutumia programu iliyochaguliwa"
|
||||
},
|
||||
"patchesSelectorView": {
|
||||
"viewTitle": "Chagua viraka",
|
||||
"searchBarHint": "Tafuta viraka",
|
||||
"doneButton": "Imekamilika",
|
||||
"recommended": "Imependekezwa",
|
||||
"all": "Yote",
|
||||
"none": "Hakuna",
|
||||
"loadPatchesSelection": "Ingiza Uteuzi wa viraka",
|
||||
"noSavedPatches": "Hakuna viraka vilivyohifadhiwa kwa programu iliyochaguliwa.\nBonyeza Nimemaliza ili kuhifadhi chaguo la sasa.",
|
||||
"noPatchesFound": "Hakuna viraka vilivyopatikana kwa programu iliyochaguliwa",
|
||||
"selectAllPatchesWarningContent": "Unakaribia kuchagua viraka vyote, vinavyojumuisha viraka visivyopendekezwa na vinaweza kusababisha tabia isiyotakikana."
|
||||
},
|
||||
"patchItem": {
|
||||
"unsupportedDialogText": "Toleo la programu: {packageVersion}\nMatoleo yanayotumika:{supportedVersions}",
|
||||
"unsupportedPatchVersion": "Kiraka hakitumiki kwa toleo hili la programu. Washa ugeuzaji wa majaribio katika mipangilio ili kuendelea."
|
||||
},
|
||||
"installerView": {
|
||||
"widgetTitle": "Kisakinishi",
|
||||
"installButton": "Sakinisha",
|
||||
"installRootButton": "Sakinisha kama mtumiaji mkuu",
|
||||
"openButton": "Fungua",
|
||||
"shareButton": "Gawana faili",
|
||||
"notificationTitle": "Revanced Manager inatia viraka",
|
||||
"notificationText": "Gusa ili urudi kwa kisakinishi",
|
||||
"shareApkMenuOption": "Gawa APK",
|
||||
"exportApkMenuOption": "Hamisha APK",
|
||||
"shareLogMenuOption": "Gawa jarida",
|
||||
"installErrorDialogTitle": "Hitilafu",
|
||||
"installErrorDialogText1": "Usakinishaji kama mtumiaji mkuu hauwezekani kwa uteuzi wa viraka vya sasa.",
|
||||
"installErrorDialogText2": "Usakinishaji usio wa msimamizi hauwezekani kwa uteuzi wa sasa wa viraka.",
|
||||
"installErrorDialogText3": "Usakinishaji kama mtumiaji mkuu hauwezekani kwansababu APK ule wa asili ilichaguliwa kutoka kwa hifadhi.\nChagua programu iliyosakinishwa au chagua usakinishaji usio wa msimamizi.",
|
||||
"noExit": "Kisakinishi bado kinafanya kazi, hakiwezi kutoka..."
|
||||
},
|
||||
"settingsView": {
|
||||
"widgetTitle": "Mipangilio",
|
||||
"appearanceSectionTitle": "Mwonekano",
|
||||
"teamSectionTitle": "Timu",
|
||||
"infoSectionTitle": "Taarifa",
|
||||
"advancedSectionTitle": "Ziada",
|
||||
"logsSectionTitle": "Kumbukumbu",
|
||||
"darkThemeLabel": "Hali ya giza",
|
||||
"darkThemeHint": "Karibu katika upande wa giza",
|
||||
"dynamicThemeLabel": "Material You",
|
||||
"dynamicThemeHint": "Furahia matumizi karibu na kifaa chako",
|
||||
"languageLabel": "Lugha",
|
||||
"englishOption": "Kiingereza",
|
||||
"sourcesLabel": "Vyanzo",
|
||||
"sourcesLabelHint": "Sanidi vyanzo vyako maalum",
|
||||
"hostRepositoryLabel": "HIfadhi ya API",
|
||||
"orgPatchesLabel": "Shirika la viraka",
|
||||
"sourcesPatchesLabel": "Chanzo cha viraka",
|
||||
"orgIntegrationsLabel": "Shirika la ujumuishaji",
|
||||
"sourcesIntegrationsLabel": "Chanzo cha ujumuishaji",
|
||||
"sourcesResetDialogTitle": "Anzisha",
|
||||
"sourcesResetDialogText": "Je, una uhakika unataka kuweka upya vyanzo maalum kwa thamani zao chaguomsingi?",
|
||||
"apiURLResetDialogText": "Je, una uhakika unataka kuweka upya URL ya API hadi thamani yake chaguomsingi?",
|
||||
"contributorsLabel": "Wachangiaji",
|
||||
"contributorsHint": "Orodha ya wachangiaji wa Revanced",
|
||||
"logsLabel": "Kumbukumbu",
|
||||
"logsHint": "Gawa kumbukumbu za meneja",
|
||||
"apiURLLabel": "API URL",
|
||||
"apiURLHint": "Sanidi URL yako ya API maalum",
|
||||
"selectApiURL": "API URL",
|
||||
"experimentalUniversalPatchesLabel": "Usaidizi wa viraka vya majaribio kwa wote",
|
||||
"experimentalUniversalPatchesHint": "Onyesha programu zote za kutumia na viraka vya ulimwengu wote, orodha ya upakiaji inaweza kuwa polepole",
|
||||
"experimentalPatchesLabel": "Usaidizi wa viraka vya majaribio",
|
||||
"experimentalPatchesHint": "Washa matumizi ya viraka visivyotumika katika toleo lolote la programu",
|
||||
"enabledExperimentalPatches": "Usaidizi wa viraka vya majaribio umewashwa",
|
||||
"exportSectionTitle": "Kuagiza na kuuza nje",
|
||||
"aboutLabel": "Kuhusu",
|
||||
"snackbarMessage": "Imenakiliwa kwenye ubao wa kunakili",
|
||||
"sentryLabel": "Sentry logging",
|
||||
"sentryHint": "Tuma kumbukumbu bila majina ili utusaidie kuboresha Revanced Manager",
|
||||
"restartAppForChanges": "Anzisha tena programu ili kutekeleza mabadiliko",
|
||||
"deleteKeystoreLabel": "Futa Duka la Ufunguo",
|
||||
"deleteKeystoreHint": "Futa Duka la Ufunguo iliyotumika kusaini programu",
|
||||
"deletedKeystore": "Duka la ufunguo imefutwa",
|
||||
"deleteTempDirLabel": "Futa faili za muda",
|
||||
"deleteTempDirHint": "Futa faili za muda ambazo hazijatumiwa",
|
||||
"deletedTempDir": "Faili za muda zimefutwa",
|
||||
"exportPatchesLabel": "Hamisha uteuzi wa viraka",
|
||||
"exportPatchesHint": "Hamisha uteuzi wa viraka kwenye faili ya JSON",
|
||||
"exportedPatches": "Uteuzi wa viraka umehamishwa",
|
||||
"noExportFileFound": "Hakuna viraka vilivyochaguliwa kuhamishwa",
|
||||
"importPatchesLabel": "Ingiza uteuzi wa viraka",
|
||||
"importPatchesHint": "Ingiza uteuzi wa viraka kutoka kwa faili ya JSON",
|
||||
"importedPatches": "Uteuzi wa viraka umeingizwa",
|
||||
"resetStoredPatchesLabel": "Weka upya viraka",
|
||||
"resetStoredPatchesHint": "Weka upya uteuzi wa viraka vilivyohifadhiwa",
|
||||
"resetStoredPatches": "Uteuzi wa viraka umewekwa upya",
|
||||
"jsonSelectorErrorMessage": "Imeshindwa kutumia faili ya JSON iliyochaguliwa",
|
||||
"deleteLogsLabel": "Futa kumbukumbu",
|
||||
"deleteLogsHint": "Futa kumbukumbu za wasimamizi zilizokusanywa",
|
||||
"deletedLogs": "Kumbukumbu zimefutwa"
|
||||
},
|
||||
"appInfoView": {
|
||||
"widgetTitle": "Maelezo ya programu",
|
||||
"openButton": "Fungua",
|
||||
"uninstallButton": "Sanidua",
|
||||
"patchButton": "Rekebisha",
|
||||
"unpatchButton": "Ondoa kibandiko",
|
||||
"unpatchDialogText": "Je, una uhakika unataka kubandua programu hii?",
|
||||
"rootDialogTitle": "Hitilafu",
|
||||
"rootDialogText": "Programu ilisakinishwa kwa ruhusa za mtumiaji mkuu, lakini kwa sasa ReVanced Manager haina ruhusa..\nTafadhali toa ruhusa za mtumiaji mkuu kwanza.",
|
||||
"packageNameLabel": "Jina la kifurushi",
|
||||
"originalPackageNameLabel": "Jina halisi la kifurushi",
|
||||
"installTypeLabel": "Aina ya usakinishaji",
|
||||
"rootTypeLabel": "Root",
|
||||
"nonRootTypeLabel": "Sio mtumiaji mkuu",
|
||||
"patchedDateLabel": "Tarehe iliyowekwa viraka",
|
||||
"patchedDateHint": "{date} katika {time}",
|
||||
"appliedPatchesLabel": "Viraka vilivyotumika",
|
||||
"appliedPatchesHint": "{quantity} viraka vilivyotumika",
|
||||
"updateNotImplemented": "Kipengele hiki bado hakijatekelezwa"
|
||||
},
|
||||
"contributorsView": {
|
||||
"widgetTitle": "Wachangiaji",
|
||||
"patcherContributors": "Wachangiaji wa Kirekebishaji",
|
||||
"patchesContributors": "Wachangiaji wa Viraka",
|
||||
"integrationsContributors": "Wachangiaji wa ujumuishaji",
|
||||
"cliContributors": "Wachangiaji wa CLI",
|
||||
"managerContributors": "Wachangiaji wa meneja"
|
||||
}
|
||||
}
|
@ -1,6 +1,7 @@
|
||||
{
|
||||
"okButton": "Tamam",
|
||||
"cancelButton": "İptal",
|
||||
"updateButton": "Güncelleme",
|
||||
"enabledLabel": "Etkinleştirildi",
|
||||
"disabledLabel": "Devre dışı",
|
||||
"yesButton": "Evet",
|
||||
@ -21,7 +22,7 @@
|
||||
"noInstallations": "Yamalanmış uygulama bulunmamaktadır",
|
||||
"installed": "Yüklendi",
|
||||
"updateDialogTitle": "Manager'ı güncelle",
|
||||
"updateDialogText": "ReVanced Manager'ın güncellemesini indirmek ve yüklemek istediğinize emin misiniz?",
|
||||
"updateChangelogTitle": "Değişiklik Notları",
|
||||
"notificationTitle": "Güncelleme indirildi",
|
||||
"notificationText": "Güncellemeyi yüklemek için dokunun",
|
||||
"downloadingMessage": "Güncelleme indiriliyor...",
|
||||
@ -40,7 +41,7 @@
|
||||
"loadingLabel": "Yükleniyor...",
|
||||
"timeagoLabel": "{time} önce",
|
||||
"patcherLabel": "Yamalayıcı: ",
|
||||
"managerLabel": "Manager: ",
|
||||
"managerLabel": "Yönetici: ",
|
||||
"updateButton": "Manager'ı güncelle"
|
||||
},
|
||||
"patcherView": {
|
||||
@ -81,7 +82,7 @@
|
||||
"all": "Hepsi",
|
||||
"none": "Hiçbiri",
|
||||
"loadPatchesSelection": "Yama seçimlerini yükle",
|
||||
"noSavedPatches": "Seçilen uygulama için kaydedilmiş yama yok\nMevcut seçimi kaydetmek için Tamam'a tıklayın",
|
||||
"noSavedPatches": "Seçilen uygulama için kaydedilmiş yama yok.\nMevcut seçimi kaydetmek için Tamam'a tıklayın.",
|
||||
"noPatchesFound": "Seçili uygulama için yama bulunamadı",
|
||||
"selectAllPatchesWarningContent": "Tavsiye edilmeyenler de dahil bütün yamaları seçmek üzeresiniz, bu istenmeyen davranışlara sebep olabilir."
|
||||
},
|
||||
@ -121,6 +122,7 @@
|
||||
"englishOption": "İngilizce",
|
||||
"sourcesLabel": "Kaynaklar",
|
||||
"sourcesLabelHint": "Özel kaynaklarınızı yapılandırın",
|
||||
"hostRepositoryLabel": "Depo API",
|
||||
"orgPatchesLabel": "Yama organizasyonu",
|
||||
"sourcesPatchesLabel": "Yama kaynağı",
|
||||
"orgIntegrationsLabel": "Entegrasyon organizasyonu",
|
||||
@ -135,6 +137,8 @@
|
||||
"apiURLLabel": "API URL",
|
||||
"apiURLHint": "Özel API URL'inizi yapılandırın",
|
||||
"selectApiURL": "API URL",
|
||||
"experimentalUniversalPatchesLabel": "Deneysel evrensel yama desteği",
|
||||
"experimentalUniversalPatchesHint": "Evrensel yamalarla kullanılacak tüm uygulamaları görüntüleyin, uygulamaların yüklenmesi daha yavaş olabilir",
|
||||
"experimentalPatchesLabel": "Deneysel yama desteği",
|
||||
"experimentalPatchesHint": "Herhangi bir uygulama sürümünde desteklenmeyen yamaları kullanmayı etkinleştir",
|
||||
"enabledExperimentalPatches": "Deneysel yama desteği etkin",
|
||||
|
@ -1,6 +1,7 @@
|
||||
{
|
||||
"okButton": "Đồng ý",
|
||||
"cancelButton": "Hủy",
|
||||
"updateButton": "Cập nhật",
|
||||
"enabledLabel": "Đã bật",
|
||||
"disabledLabel": "Đã tắt",
|
||||
"yesButton": "Có",
|
||||
@ -21,7 +22,7 @@
|
||||
"noInstallations": "Không có ứng dụng đã vá nào được cài đặt",
|
||||
"installed": "Đã cài đặt",
|
||||
"updateDialogTitle": "Update Manager",
|
||||
"updateDialogText": "Bạn có muốn tải về và cập nhật ReVanced Manager?",
|
||||
"updateChangelogTitle": "Nhật ký thay đổi",
|
||||
"notificationTitle": "Cập nhật đã được tải xong",
|
||||
"notificationText": "Nhấn để cài đặt bản cập nhật",
|
||||
"downloadingMessage": "Đang tải xuống bản cập nhật...",
|
||||
@ -81,7 +82,7 @@
|
||||
"all": "Tất cả",
|
||||
"none": "Không có",
|
||||
"loadPatchesSelection": "Nạp các bản vá được chọn",
|
||||
"noSavedPatches": "Không có bản vá cho ứng dụng được chọn\nNhấn Hoàn tất để lưu lựa chọn hiện tại",
|
||||
"noSavedPatches": "Không có bản vá cho ứng dụng được chọn\nNhấn Hoàn tất để lưu lựa chọn hiện tại.",
|
||||
"noPatchesFound": "Không tìm thấy bản vá cho ứng dụng đã chọn",
|
||||
"selectAllPatchesWarningContent": "Bạn sắp lựa chọn tất cả bản vá, bao gồm những bản vá không được khuyến nghị và có thể gây ra những hành vi không mong muốn."
|
||||
},
|
||||
@ -121,6 +122,7 @@
|
||||
"englishOption": "Tiếng Anh",
|
||||
"sourcesLabel": "Nguồn",
|
||||
"sourcesLabelHint": "Cấu hình nguồn tùy chỉnh của bạn",
|
||||
"hostRepositoryLabel": "API Kho lưu trữ",
|
||||
"orgPatchesLabel": "Tác giả bản vá",
|
||||
"sourcesPatchesLabel": "Nguồn bản vá",
|
||||
"orgIntegrationsLabel": "Tác giá bản tích hợp",
|
||||
@ -135,6 +137,8 @@
|
||||
"apiURLLabel": "Địa chỉ URL của API",
|
||||
"apiURLHint": "Cấu hình API URL tùy chỉnh của bạn",
|
||||
"selectApiURL": "Địa chỉ URL của API",
|
||||
"experimentalUniversalPatchesLabel": "Hỗ trợ các bản vá thử nghiệm phổ biến",
|
||||
"experimentalUniversalPatchesHint": "Hiển thị tất cả các ứng dụng dùng với các bản vá phổ biến, việc tải các danh sách ứng dụng có thể chậm hơn",
|
||||
"experimentalPatchesLabel": "Hỗ trợ các bản vá thử nghiệm",
|
||||
"experimentalPatchesHint": "Bật sử dụng các bản vá không được hỗ trợ trong bất kỳ phiên bản nào của ứng dụng",
|
||||
"enabledExperimentalPatches": "Đã bật hỗ trợ các bản vá thử nghiệm",
|
||||
|
@ -1,6 +1,7 @@
|
||||
{
|
||||
"okButton": "確定",
|
||||
"cancelButton": "取消",
|
||||
"updateButton": "更新",
|
||||
"enabledLabel": "已啟用",
|
||||
"disabledLabel": "已停用",
|
||||
"yesButton": "是",
|
||||
@ -21,7 +22,7 @@
|
||||
"noInstallations": "沒有安裝已修補的應用程式",
|
||||
"installed": "已安裝",
|
||||
"updateDialogTitle": "更新 Manager",
|
||||
"updateDialogText": "確定要下載並更新 ReVanced Manager 嗎?",
|
||||
"updateChangelogTitle": "更新日誌",
|
||||
"notificationTitle": "已下載更新",
|
||||
"notificationText": "輕觸以安裝更新",
|
||||
"downloadingMessage": "正在下載更新...",
|
||||
@ -81,13 +82,13 @@
|
||||
"all": "全部",
|
||||
"none": "無",
|
||||
"loadPatchesSelection": "載入修補檔選取",
|
||||
"noSavedPatches": "選取的應用程式尚無已儲存的修補檔\n按下 [完成] 以儲存目前選取",
|
||||
"noSavedPatches": "選取的應用程式尚無已儲存的修補檔。\n按下 [完成] 以儲存目前選取。",
|
||||
"noPatchesFound": "找不到適合所選應用程式的修補檔",
|
||||
"selectAllPatchesWarningContent": "即將選取所有修補檔——之中包含不推薦的修補檔,且可能造成非預期的結果。"
|
||||
},
|
||||
"patchItem": {
|
||||
"unsupportedDialogText": "選取此修補檔可能導致修補錯誤。\n應用程式版本: {packageVersion}\n支援的版本: {supportedVersions}",
|
||||
"unsupportedPatchVersion": "不支援此應用程式的版本的修補作業。請在設定中啟用實驗性選項以繼續進行。"
|
||||
"unsupportedPatchVersion": "修補檔不支援這個版本的應用程式。需啟用設定中的實驗性選項才可繼續下一步。"
|
||||
},
|
||||
"installerView": {
|
||||
"widgetTitle": "安裝程式",
|
||||
@ -121,6 +122,7 @@
|
||||
"englishOption": "English",
|
||||
"sourcesLabel": "來源",
|
||||
"sourcesLabelHint": "設定自訂來源",
|
||||
"hostRepositoryLabel": "儲存庫 API",
|
||||
"orgPatchesLabel": "修補檔組織",
|
||||
"sourcesPatchesLabel": "修補檔來源",
|
||||
"orgIntegrationsLabel": "整合組織",
|
||||
@ -135,6 +137,8 @@
|
||||
"apiURLLabel": "API URL",
|
||||
"apiURLHint": "設定自訂 API URL",
|
||||
"selectApiURL": "API URL",
|
||||
"experimentalUniversalPatchesLabel": "支援實驗性通用型修補檔",
|
||||
"experimentalUniversalPatchesHint": "顯示所有可使用通用型修補檔的應用程式,載入應用程式清單的時長可能會變得更慢",
|
||||
"experimentalPatchesLabel": "支援實驗性修補檔",
|
||||
"experimentalPatchesHint": "啟用以在任何應用程式版本中使用不支援的修補檔",
|
||||
"enabledExperimentalPatches": "已啟用支援實驗性修補檔",
|
||||
@ -183,7 +187,7 @@
|
||||
"patchedDateHint": "{date} {time}",
|
||||
"appliedPatchesLabel": "已套用修補檔",
|
||||
"appliedPatchesHint": "已套用 {quantity} 個修補檔",
|
||||
"updateNotImplemented": "這項功能還沒有實現"
|
||||
"updateNotImplemented": "這項功能尚未實作"
|
||||
},
|
||||
"contributorsView": {
|
||||
"widgetTitle": "貢獻者",
|
||||
|
@ -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/manager_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: RevancedAPI),
|
||||
LazySingleton(classType: GithubAPI),
|
||||
LazySingleton(classType: CrowdinAPI),
|
||||
LazySingleton(classType: Toast),
|
||||
],
|
||||
)
|
||||
|
@ -1,18 +1,17 @@
|
||||
import 'dart:developer';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_i18n/flutter_i18n.dart';
|
||||
import 'package:flutter_localizations/flutter_localizations.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/manager_api.dart';
|
||||
import 'package:revanced_manager/services/patcher_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/views/navigation/navigation_view.dart';
|
||||
import 'package:revanced_manager/utils/environment.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
import 'package:stacked_themes/stacked_themes.dart';
|
||||
import 'package:sentry_flutter/sentry_flutter.dart';
|
||||
import 'package:timezone/data/latest.dart' as tz;
|
||||
|
||||
late SharedPreferences prefs;
|
||||
@ -21,38 +20,14 @@ Future main() async {
|
||||
await setupLocator();
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
await locator<ManagerAPI>().initialize();
|
||||
String apiUrl = locator<ManagerAPI>().getApiUrl();
|
||||
final String apiUrl = locator<ManagerAPI>().getApiUrl();
|
||||
await locator<RevancedAPI>().initialize(apiUrl);
|
||||
await locator<CrowdinAPI>().initialize();
|
||||
bool isSentryEnabled = locator<ManagerAPI>().isSentryEnabled();
|
||||
String repoUrl = locator<ManagerAPI>().getRepoUrl();
|
||||
final String repoUrl = locator<ManagerAPI>().getRepoUrl();
|
||||
locator<GithubAPI>().initialize(repoUrl);
|
||||
await locator<PatcherAPI>().initialize();
|
||||
tz.initializeTimeZones();
|
||||
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());
|
||||
}
|
||||
|
||||
@ -65,7 +40,7 @@ class MyApp extends StatelessWidget {
|
||||
// String replaceLocale = rawLocale.replaceAll('_', '-');
|
||||
// List<String> localeList = replaceLocale.split('-');
|
||||
// Locale locale = Locale(localeList[0], localeList[1]);
|
||||
Locale locale = const Locale('en', 'US');
|
||||
const Locale locale = Locale('en', 'US');
|
||||
|
||||
return DynamicThemeBuilder(
|
||||
title: 'ReVanced Manager',
|
||||
@ -79,8 +54,9 @@ class MyApp extends StatelessWidget {
|
||||
useCountryCode: true,
|
||||
),
|
||||
missingTranslationHandler: (key, locale) {
|
||||
print(
|
||||
'--> Missing translation: key: $key, languageCode: ${locale?.languageCode}');
|
||||
log(
|
||||
'--> Missing translation: key: $key, languageCode: ${locale?.languageCode}',
|
||||
);
|
||||
},
|
||||
),
|
||||
GlobalMaterialLocalizations.delegate,
|
||||
|
@ -5,13 +5,6 @@ part 'patch.g.dart';
|
||||
|
||||
@JsonSerializable()
|
||||
class Patch {
|
||||
final String name;
|
||||
final String description;
|
||||
final String version;
|
||||
final bool excluded;
|
||||
final List<String> dependencies;
|
||||
final List<Package> compatiblePackages;
|
||||
|
||||
Patch({
|
||||
required this.name,
|
||||
required this.description,
|
||||
@ -22,6 +15,12 @@ class Patch {
|
||||
});
|
||||
|
||||
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);
|
||||
|
||||
@ -37,9 +36,6 @@ class Patch {
|
||||
|
||||
@JsonSerializable()
|
||||
class Package {
|
||||
final String name;
|
||||
final List<String> versions;
|
||||
|
||||
Package({
|
||||
required this.name,
|
||||
required this.versions,
|
||||
@ -47,6 +43,8 @@ class Package {
|
||||
|
||||
factory Package.fromJson(Map<String, dynamic> json) =>
|
||||
_$PackageFromJson(json);
|
||||
final String name;
|
||||
final List<String> versions;
|
||||
|
||||
Map toJson() => _$PackageToJson(this);
|
||||
}
|
||||
|
@ -6,23 +6,6 @@ part 'patched_application.g.dart';
|
||||
|
||||
@JsonSerializable()
|
||||
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({
|
||||
required this.name,
|
||||
required this.packageName,
|
||||
@ -40,6 +23,22 @@ class PatchedApplication {
|
||||
|
||||
factory PatchedApplication.fromJson(Map<String, dynamic> 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);
|
||||
|
||||
|
@ -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,25 @@
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:dio_http_cache_lts/dio_http_cache_lts.dart';
|
||||
import 'package:dio_cache_interceptor/dio_cache_interceptor.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter_cache_manager/flutter_cache_manager.dart';
|
||||
import 'package:injectable/injectable.dart';
|
||||
import 'package:native_dio_adapter/native_dio_adapter.dart';
|
||||
import 'package:revanced_manager/models/patch.dart';
|
||||
import 'package:sentry_flutter/sentry_flutter.dart';
|
||||
import 'package:sentry_dio/sentry_dio.dart';
|
||||
|
||||
@lazySingleton
|
||||
class GithubAPI {
|
||||
late Dio _dio = Dio();
|
||||
final DioCacheManager _dioCacheManager = DioCacheManager(CacheConfig());
|
||||
final Options _cacheOptions = buildCacheOptions(
|
||||
const Duration(hours: 6),
|
||||
|
||||
final _cacheOptions = CacheOptions(
|
||||
store: MemCacheStore(),
|
||||
maxStale: const Duration(days: 1),
|
||||
priority: CachePriority.high,
|
||||
);
|
||||
|
||||
final Map<String, String> repoAppPath = {
|
||||
'com.google.android.youtube': 'youtube',
|
||||
'com.google.android.apps.youtube.music': 'music',
|
||||
@ -28,38 +31,60 @@ class GithubAPI {
|
||||
'com.spotify.music': 'spotify',
|
||||
};
|
||||
|
||||
void initialize(String repoUrl) async {
|
||||
Future<void> initialize(String repoUrl) async {
|
||||
try {
|
||||
_dio = Dio(BaseOptions(
|
||||
baseUrl: repoUrl,
|
||||
));
|
||||
if (Platform.isIOS || Platform.isMacOS || Platform.isAndroid) {
|
||||
final CronetEngine androidCronetEngine = await CronetEngine.build(
|
||||
userAgent: 'ReVanced Manager',
|
||||
enableBrotli: true,
|
||||
enableQuic: true,
|
||||
);
|
||||
_dio.httpClientAdapter =
|
||||
NativeAdapter(androidCronetEngine: androidCronetEngine);
|
||||
|
||||
_dio.interceptors.add(_dioCacheManager.interceptor);
|
||||
_dio.addSentry(
|
||||
captureFailedRequests: true,
|
||||
_dio = Dio(
|
||||
BaseOptions(
|
||||
baseUrl: repoUrl,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
_dio = Dio(
|
||||
BaseOptions(
|
||||
baseUrl: repoUrl,
|
||||
),
|
||||
);
|
||||
} 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 {
|
||||
try {
|
||||
await _dioCacheManager.clearAll();
|
||||
} on Exception catch (e, s) {
|
||||
await Sentry.captureException(e, stackTrace: s);
|
||||
await _cacheOptions.store!.clean();
|
||||
} on Exception catch (e) {
|
||||
if (kDebugMode) {
|
||||
print(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Future<Map<String, dynamic>?> getLatestRelease(String repoName) async {
|
||||
Future<Map<String, dynamic>?> getLatestRelease(
|
||||
String repoName,
|
||||
) async {
|
||||
try {
|
||||
var response = await _dio.get(
|
||||
final response = await _dio.get(
|
||||
'/repos/$repoName/releases',
|
||||
options: _cacheOptions,
|
||||
);
|
||||
return response.data[0];
|
||||
} on Exception catch (e, s) {
|
||||
await Sentry.captureException(e, stackTrace: s);
|
||||
} on Exception catch (e) {
|
||||
if (kDebugMode) {
|
||||
print(e);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@ -69,18 +94,17 @@ class GithubAPI {
|
||||
String repoName,
|
||||
DateTime since,
|
||||
) async {
|
||||
String path =
|
||||
final String path =
|
||||
'src/main/kotlin/app/revanced/patches/${repoAppPath[packageName]}';
|
||||
try {
|
||||
var response = await _dio.get(
|
||||
final response = await _dio.get(
|
||||
'/repos/$repoName/commits',
|
||||
queryParameters: {
|
||||
'path': path,
|
||||
'since': since.toIso8601String(),
|
||||
},
|
||||
options: _cacheOptions,
|
||||
);
|
||||
List<dynamic> commits = response.data;
|
||||
final List<dynamic> commits = response.data;
|
||||
return commits
|
||||
.map(
|
||||
(commit) => (commit['commit']['message']).split('\n')[0] +
|
||||
@ -89,17 +113,23 @@ class GithubAPI {
|
||||
'\n' as String,
|
||||
)
|
||||
.toList();
|
||||
} on Exception catch (e, s) {
|
||||
await Sentry.captureException(e, stackTrace: s);
|
||||
return List.empty();
|
||||
} on Exception catch (e) {
|
||||
if (kDebugMode) {
|
||||
print(e);
|
||||
}
|
||||
}
|
||||
return [];
|
||||
}
|
||||
|
||||
Future<File?> getLatestReleaseFile(String extension, String repoName) async {
|
||||
Future<File?> getLatestReleaseFile(
|
||||
String extension,
|
||||
String repoName,
|
||||
) async {
|
||||
try {
|
||||
Map<String, dynamic>? release = await getLatestRelease(repoName);
|
||||
final Map<String, dynamic>? release =
|
||||
await getLatestRelease(repoName);
|
||||
if (release != null) {
|
||||
Map<String, dynamic>? asset =
|
||||
final Map<String, dynamic>? asset =
|
||||
(release['assets'] as List<dynamic>).firstWhereOrNull(
|
||||
(asset) => (asset['name'] as String).endsWith(extension),
|
||||
);
|
||||
@ -109,9 +139,10 @@ class GithubAPI {
|
||||
);
|
||||
}
|
||||
}
|
||||
} on Exception catch (e, s) {
|
||||
await Sentry.captureException(e, stackTrace: s);
|
||||
return null;
|
||||
} on Exception catch (e) {
|
||||
if (kDebugMode) {
|
||||
print(e);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
@ -119,29 +150,35 @@ class GithubAPI {
|
||||
Future<List<Patch>> getPatches(String repoName) async {
|
||||
List<Patch> patches = [];
|
||||
try {
|
||||
File? f = await getLatestReleaseFile('.json', repoName);
|
||||
final File? f = await getLatestReleaseFile('.json', repoName);
|
||||
if (f != null) {
|
||||
List<dynamic> list = jsonDecode(f.readAsStringSync());
|
||||
final List<dynamic> list = jsonDecode(f.readAsStringSync());
|
||||
patches = list.map((patch) => Patch.fromJson(patch)).toList();
|
||||
}
|
||||
} on Exception catch (e, s) {
|
||||
await Sentry.captureException(e, stackTrace: s);
|
||||
return List.empty();
|
||||
} on Exception catch (e) {
|
||||
if (kDebugMode) {
|
||||
print(e);
|
||||
}
|
||||
}
|
||||
|
||||
return patches;
|
||||
}
|
||||
|
||||
Future<String> getLastestReleaseVersion(String repoName) async {
|
||||
try {
|
||||
Map<String, dynamic>? release = await getLatestRelease(repoName);
|
||||
final Map<String, dynamic>? release =
|
||||
await getLatestRelease(repoName);
|
||||
if (release != null) {
|
||||
return release['tag_name'];
|
||||
} else {
|
||||
return 'Unknown';
|
||||
}
|
||||
} on Exception catch (e, s) {
|
||||
await Sentry.captureException(e, stackTrace: s);
|
||||
return '';
|
||||
} on Exception catch (e) {
|
||||
if (kDebugMode) {
|
||||
print(e);
|
||||
}
|
||||
|
||||
return 'Unknown';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
import 'package:device_apps/device_apps.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:injectable/injectable.dart';
|
||||
import 'package:package_info_plus/package_info_plus.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/revanced_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';
|
||||
|
||||
@lazySingleton
|
||||
@ -20,8 +21,12 @@ class ManagerAPI {
|
||||
final RootAPI _rootAPI = RootAPI();
|
||||
final String patcherRepo = 'revanced-patcher';
|
||||
final String cliRepo = 'revanced-cli';
|
||||
late String storedPatchesFile = '/selected-patches.json';
|
||||
late SharedPreferences _prefs;
|
||||
bool isRooted = false;
|
||||
String storedPatchesFile = '/selected-patches.json';
|
||||
String keystoreFile =
|
||||
'/sdcard/Android/data/app.revanced.manager.flutter/files/revanced-manager.keystore';
|
||||
String defaultKeystorePassword = 's3cur3p@ssw0rd';
|
||||
String defaultApiUrl = 'https://releases.revanced.app/';
|
||||
String defaultRepoUrl = 'https://api.github.com';
|
||||
String defaultPatcherRepo = 'revanced/revanced-patcher';
|
||||
@ -29,9 +34,14 @@ class ManagerAPI {
|
||||
String defaultIntegrationsRepo = 'revanced/revanced-integrations';
|
||||
String defaultCliRepo = 'revanced/revanced-cli';
|
||||
String defaultManagerRepo = 'revanced/revanced-manager';
|
||||
String? patchesVersion = '';
|
||||
bool isDefaultPatchesRepo() {
|
||||
return getPatchesRepo() == 'revanced/revanced-patches';
|
||||
}
|
||||
|
||||
Future<void> initialize() async {
|
||||
_prefs = await SharedPreferences.getInstance();
|
||||
isRooted = await _rootAPI.isRooted();
|
||||
storedPatchesFile =
|
||||
(await getApplicationDocumentsDirectory()).path + storedPatchesFile;
|
||||
}
|
||||
@ -98,14 +108,6 @@ class ManagerAPI {
|
||||
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() {
|
||||
return _prefs.getBool('universalPatchesEnabled') ?? false;
|
||||
}
|
||||
@ -122,6 +124,14 @@ class ManagerAPI {
|
||||
await _prefs.setBool('experimentalPatchesEnabled', value);
|
||||
}
|
||||
|
||||
Future<void> setKeystorePassword(String password) async {
|
||||
await _prefs.setString('keystorePassword', password);
|
||||
}
|
||||
|
||||
String getKeystorePassword() {
|
||||
return _prefs.getString('keystorePassword') ?? defaultKeystorePassword;
|
||||
}
|
||||
|
||||
Future<void> deleteTempFolder() async {
|
||||
final Directory dir = Directory('/data/local/tmp/revanced-manager');
|
||||
if (await dir.exists()) {
|
||||
@ -131,29 +141,34 @@ class ManagerAPI {
|
||||
|
||||
Future<void> deleteKeystore() async {
|
||||
final File keystore = File(
|
||||
'/sdcard/Android/data/app.revanced.manager.flutter/files/revanced-manager.keystore');
|
||||
keystoreFile,
|
||||
);
|
||||
if (await keystore.exists()) {
|
||||
await keystore.delete();
|
||||
}
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
Future<void> setPatchedApps(List<PatchedApplication> patchedApps) async {
|
||||
Future<void> setPatchedApps(
|
||||
List<PatchedApplication> patchedApps,
|
||||
) async {
|
||||
if (patchedApps.length > 1) {
|
||||
patchedApps.sort((a, b) => a.name.compareTo(b.name));
|
||||
}
|
||||
await _prefs.setStringList('patchedApps',
|
||||
patchedApps.map((a) => json.encode(a.toJson())).toList());
|
||||
await _prefs.setStringList(
|
||||
'patchedApps',
|
||||
patchedApps.map((a) => json.encode(a.toJson())).toList(),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> savePatchedApp(PatchedApplication app) async {
|
||||
List<PatchedApplication> patchedApps = getPatchedApps();
|
||||
final List<PatchedApplication> patchedApps = getPatchedApps();
|
||||
patchedApps.removeWhere((a) => a.packageName == app.packageName);
|
||||
ApplicationWithIcon? installed = await DeviceApps.getApp(
|
||||
final ApplicationWithIcon? installed = await DeviceApps.getApp(
|
||||
app.packageName,
|
||||
true,
|
||||
) as ApplicationWithIcon?;
|
||||
@ -167,17 +182,19 @@ class ManagerAPI {
|
||||
}
|
||||
|
||||
Future<void> deletePatchedApp(PatchedApplication app) async {
|
||||
List<PatchedApplication> patchedApps = getPatchedApps();
|
||||
final List<PatchedApplication> patchedApps = getPatchedApps();
|
||||
patchedApps.removeWhere((a) => a.packageName == app.packageName);
|
||||
await setPatchedApps(patchedApps);
|
||||
}
|
||||
|
||||
void clearAllData() async {
|
||||
Future<void> clearAllData() async {
|
||||
try {
|
||||
_revancedAPI.clearAllCache();
|
||||
_githubAPI.clearAllCache();
|
||||
} on Exception catch (e, s) {
|
||||
await Sentry.captureException(e, stackTrace: s);
|
||||
} on Exception catch (e) {
|
||||
if (kDebugMode) {
|
||||
print(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -187,21 +204,23 @@ class ManagerAPI {
|
||||
|
||||
Future<List<Patch>> getPatches() async {
|
||||
try {
|
||||
String repoName = getPatchesRepo();
|
||||
final String repoName = getPatchesRepo();
|
||||
if (repoName == defaultPatchesRepo) {
|
||||
return await _revancedAPI.getPatches();
|
||||
} else {
|
||||
return await _githubAPI.getPatches(repoName);
|
||||
}
|
||||
} on Exception catch (e, s) {
|
||||
await Sentry.captureException(e, stackTrace: s);
|
||||
} on Exception catch (e) {
|
||||
if (kDebugMode) {
|
||||
print(e);
|
||||
}
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
Future<File?> downloadPatches() async {
|
||||
try {
|
||||
String repoName = getPatchesRepo();
|
||||
final String repoName = getPatchesRepo();
|
||||
if (repoName == defaultPatchesRepo) {
|
||||
return await _revancedAPI.getLatestReleaseFile(
|
||||
'.jar',
|
||||
@ -210,15 +229,17 @@ class ManagerAPI {
|
||||
} else {
|
||||
return await _githubAPI.getLatestReleaseFile('.jar', repoName);
|
||||
}
|
||||
} on Exception catch (e, s) {
|
||||
await Sentry.captureException(e, stackTrace: s);
|
||||
} on Exception catch (e) {
|
||||
if (kDebugMode) {
|
||||
print(e);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
Future<File?> downloadIntegrations() async {
|
||||
try {
|
||||
String repoName = getIntegrationsRepo();
|
||||
final String repoName = getIntegrationsRepo();
|
||||
if (repoName == defaultIntegrationsRepo) {
|
||||
return await _revancedAPI.getLatestReleaseFile(
|
||||
'.apk',
|
||||
@ -227,22 +248,33 @@ class ManagerAPI {
|
||||
} else {
|
||||
return await _githubAPI.getLatestReleaseFile('.apk', repoName);
|
||||
}
|
||||
} on Exception catch (e, s) {
|
||||
await Sentry.captureException(e, stackTrace: s);
|
||||
} on Exception catch (e) {
|
||||
if (kDebugMode) {
|
||||
print(e);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
Future<File?> downloadManager() async {
|
||||
return await _revancedAPI.getLatestReleaseFile('.apk', defaultManagerRepo);
|
||||
return await _revancedAPI.getLatestReleaseFile(
|
||||
'.apk',
|
||||
defaultManagerRepo,
|
||||
);
|
||||
}
|
||||
|
||||
Future<String?> getLatestPatcherReleaseTime() async {
|
||||
return await _revancedAPI.getLatestReleaseTime('.gz', defaultPatcherRepo);
|
||||
return await _revancedAPI.getLatestReleaseTime(
|
||||
'.gz',
|
||||
defaultPatcherRepo,
|
||||
);
|
||||
}
|
||||
|
||||
Future<String?> getLatestManagerReleaseTime() async {
|
||||
return await _revancedAPI.getLatestReleaseTime('.apk', defaultManagerRepo);
|
||||
return await _revancedAPI.getLatestReleaseTime(
|
||||
'.apk',
|
||||
defaultManagerRepo,
|
||||
);
|
||||
}
|
||||
|
||||
Future<String?> getLatestManagerVersion() async {
|
||||
@ -260,16 +292,29 @@ class ManagerAPI {
|
||||
}
|
||||
|
||||
Future<String> getCurrentManagerVersion() async {
|
||||
PackageInfo packageInfo = await PackageInfo.fromPlatform();
|
||||
final PackageInfo packageInfo = await PackageInfo.fromPlatform();
|
||||
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(
|
||||
List<PatchedApplication> patchedApps,
|
||||
) async {
|
||||
List<PatchedApplication> toRemove = [];
|
||||
for (PatchedApplication app in patchedApps) {
|
||||
bool isRemove = await isAppUninstalled(app);
|
||||
final List<PatchedApplication> toRemove = [];
|
||||
for (final PatchedApplication app in patchedApps) {
|
||||
final bool isRemove = await isAppUninstalled(app);
|
||||
if (isRemove) {
|
||||
toRemove.add(app);
|
||||
}
|
||||
@ -280,13 +325,13 @@ class ManagerAPI {
|
||||
Future<List<PatchedApplication>> getUnsavedApps(
|
||||
List<PatchedApplication> patchedApps,
|
||||
) async {
|
||||
List<PatchedApplication> unsavedApps = [];
|
||||
bool hasRootPermissions = await _rootAPI.hasRootPermissions();
|
||||
final List<PatchedApplication> unsavedApps = [];
|
||||
final bool hasRootPermissions = await _rootAPI.hasRootPermissions();
|
||||
if (hasRootPermissions) {
|
||||
List<String> installedApps = await _rootAPI.getInstalledApps();
|
||||
for (String packageName in installedApps) {
|
||||
final List<String> installedApps = await _rootAPI.getInstalledApps();
|
||||
for (final String packageName in installedApps) {
|
||||
if (!patchedApps.any((app) => app.packageName == packageName)) {
|
||||
ApplicationWithIcon? application = await DeviceApps.getApp(
|
||||
final ApplicationWithIcon? application = await DeviceApps.getApp(
|
||||
packageName,
|
||||
true,
|
||||
) as ApplicationWithIcon?;
|
||||
@ -307,15 +352,13 @@ class ManagerAPI {
|
||||
}
|
||||
}
|
||||
}
|
||||
List<Application> userApps = await DeviceApps.getInstalledApplications(
|
||||
includeSystemApps: false,
|
||||
includeAppIcons: false,
|
||||
);
|
||||
for (Application app in userApps) {
|
||||
final List<Application> userApps =
|
||||
await DeviceApps.getInstalledApplications();
|
||||
for (final Application app in userApps) {
|
||||
if (app.packageName.startsWith('app.revanced') &&
|
||||
!app.packageName.startsWith('app.revanced.manager.') &&
|
||||
!patchedApps.any((uapp) => uapp.packageName == app.packageName)) {
|
||||
ApplicationWithIcon? application = await DeviceApps.getApp(
|
||||
final ApplicationWithIcon? application = await DeviceApps.getApp(
|
||||
app.packageName,
|
||||
true,
|
||||
) as ApplicationWithIcon?;
|
||||
@ -329,7 +372,6 @@ class ManagerAPI {
|
||||
apkFilePath: application.apkFilePath,
|
||||
icon: application.icon,
|
||||
patchDate: DateTime.now(),
|
||||
isRooted: false,
|
||||
),
|
||||
);
|
||||
}
|
||||
@ -339,25 +381,29 @@ class ManagerAPI {
|
||||
}
|
||||
|
||||
Future<void> reAssessSavedApps() async {
|
||||
List<PatchedApplication> patchedApps = getPatchedApps();
|
||||
List<PatchedApplication> unsavedApps = await getUnsavedApps(patchedApps);
|
||||
final List<PatchedApplication> patchedApps = getPatchedApps();
|
||||
final List<PatchedApplication> unsavedApps =
|
||||
await getUnsavedApps(patchedApps);
|
||||
patchedApps.addAll(unsavedApps);
|
||||
List<PatchedApplication> toRemove = await getAppsToRemove(patchedApps);
|
||||
final List<PatchedApplication> toRemove =
|
||||
await getAppsToRemove(patchedApps);
|
||||
patchedApps.removeWhere((a) => toRemove.contains(a));
|
||||
for (PatchedApplication app in patchedApps) {
|
||||
for (final PatchedApplication app in patchedApps) {
|
||||
app.hasUpdates =
|
||||
await hasAppUpdates(app.originalPackageName, app.patchDate);
|
||||
app.changelog =
|
||||
await getAppChangelog(app.originalPackageName, app.patchDate);
|
||||
if (!app.hasUpdates) {
|
||||
String? currentInstalledVersion =
|
||||
final String? currentInstalledVersion =
|
||||
(await DeviceApps.getApp(app.packageName))?.versionName;
|
||||
if (currentInstalledVersion != null) {
|
||||
String currentSavedVersion = app.version;
|
||||
int currentInstalledVersionInt = int.parse(
|
||||
currentInstalledVersion.replaceAll(RegExp('[^0-9]'), ''));
|
||||
int currentSavedVersionInt =
|
||||
int.parse(currentSavedVersion.replaceAll(RegExp('[^0-9]'), ''));
|
||||
final String currentSavedVersion = app.version;
|
||||
final int currentInstalledVersionInt = int.parse(
|
||||
currentInstalledVersion.replaceAll(RegExp('[^0-9]'), ''),
|
||||
);
|
||||
final int currentSavedVersionInt = int.parse(
|
||||
currentSavedVersion.replaceAll(RegExp('[^0-9]'), ''),
|
||||
);
|
||||
if (currentInstalledVersionInt > currentSavedVersionInt) {
|
||||
app.hasUpdates = true;
|
||||
}
|
||||
@ -369,9 +415,9 @@ class ManagerAPI {
|
||||
|
||||
Future<bool> isAppUninstalled(PatchedApplication app) async {
|
||||
bool existsRoot = false;
|
||||
bool existsNonRoot = await DeviceApps.isAppInstalled(app.packageName);
|
||||
final bool existsNonRoot = await DeviceApps.isAppInstalled(app.packageName);
|
||||
if (app.isRooted) {
|
||||
bool hasRootPermissions = await _rootAPI.hasRootPermissions();
|
||||
final bool hasRootPermissions = await _rootAPI.hasRootPermissions();
|
||||
if (hasRootPermissions) {
|
||||
existsRoot = await _rootAPI.isAppInstalled(app.packageName);
|
||||
}
|
||||
@ -380,8 +426,11 @@ class ManagerAPI {
|
||||
return !existsNonRoot;
|
||||
}
|
||||
|
||||
Future<bool> hasAppUpdates(String packageName, DateTime patchDate) async {
|
||||
List<String> commits = await _githubAPI.getCommits(
|
||||
Future<bool> hasAppUpdates(
|
||||
String packageName,
|
||||
DateTime patchDate,
|
||||
) async {
|
||||
final List<String> commits = await _githubAPI.getCommits(
|
||||
packageName,
|
||||
getPatchesRepo(),
|
||||
patchDate,
|
||||
@ -390,7 +439,9 @@ class ManagerAPI {
|
||||
}
|
||||
|
||||
Future<List<String>> getAppChangelog(
|
||||
String packageName, DateTime patchDate) async {
|
||||
String packageName,
|
||||
DateTime patchDate,
|
||||
) async {
|
||||
List<String> newCommits = await _githubAPI.getCommits(
|
||||
packageName,
|
||||
getPatchesRepo(),
|
||||
@ -416,38 +467,59 @@ class ManagerAPI {
|
||||
return app != null && app.isSplit;
|
||||
}
|
||||
|
||||
Future<void> setSelectedPatches(String app, List<String> patches) async {
|
||||
Future<void> setSelectedPatches(
|
||||
String app,
|
||||
List<String> patches,
|
||||
) async {
|
||||
final File selectedPatchesFile = File(storedPatchesFile);
|
||||
Map<String, dynamic> patchesMap = await readSelectedPatchesFile();
|
||||
final Map<String, dynamic> patchesMap = await readSelectedPatchesFile();
|
||||
if (patches.isEmpty) {
|
||||
patchesMap.remove(app);
|
||||
} else {
|
||||
patchesMap[app] = patches;
|
||||
}
|
||||
if (selectedPatchesFile.existsSync()) {
|
||||
selectedPatchesFile.createSync(recursive: true);
|
||||
}
|
||||
selectedPatchesFile.writeAsString(jsonEncode(patchesMap));
|
||||
}
|
||||
|
||||
Future<List<String>> getSelectedPatches(String app) async {
|
||||
Map<String, dynamic> patchesMap = await readSelectedPatchesFile();
|
||||
if (patchesMap.isNotEmpty) {
|
||||
final List<String> patches =
|
||||
List.from(patchesMap.putIfAbsent(app, () => List.empty()));
|
||||
return patches;
|
||||
// get default patches for app
|
||||
Future<List<String>> getDefaultPatches() async {
|
||||
final List<Patch> patches = await getPatches();
|
||||
final List<String> defaultPatches = [];
|
||||
if (areExperimentalPatchesEnabled() == false) {
|
||||
defaultPatches.addAll(
|
||||
patches
|
||||
.where(
|
||||
(element) =>
|
||||
element.excluded == false && isPatchSupported(element),
|
||||
)
|
||||
.map((p) => p.name),
|
||||
);
|
||||
} else {
|
||||
defaultPatches.addAll(
|
||||
patches
|
||||
.where((element) => isPatchSupported(element))
|
||||
.map((p) => p.name),
|
||||
);
|
||||
}
|
||||
return 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 {
|
||||
final File selectedPatchesFile = File(storedPatchesFile);
|
||||
if (selectedPatchesFile.existsSync()) {
|
||||
String string = selectedPatchesFile.readAsStringSync();
|
||||
if (string.trim().isEmpty) return {};
|
||||
return json.decode(string);
|
||||
if (!selectedPatchesFile.existsSync()) {
|
||||
return {};
|
||||
}
|
||||
return {};
|
||||
final String string = selectedPatchesFile.readAsStringSync();
|
||||
if (string.trim().isEmpty) {
|
||||
return {};
|
||||
}
|
||||
return jsonDecode(string);
|
||||
}
|
||||
|
||||
Future<void> resetLastSelectedPatches() async {
|
||||
|
@ -1,7 +1,10 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:app_installer/app_installer.dart';
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:cr_file_saver/file_saver.dart';
|
||||
import 'package:device_apps/device_apps.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:injectable/injectable.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/services/manager_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:cr_file_saver/file_saver.dart';
|
||||
|
||||
@lazySingleton
|
||||
class PatcherAPI {
|
||||
@ -29,7 +30,7 @@ class PatcherAPI {
|
||||
|
||||
Future<void> initialize() async {
|
||||
await _loadPatches();
|
||||
Directory appCache = await getTemporaryDirectory();
|
||||
final Directory appCache = await getTemporaryDirectory();
|
||||
_dataDir = await getExternalStorageDirectory() ?? appCache;
|
||||
_tmpDir = Directory('${appCache.path}/patcher');
|
||||
_keyStoreFile = File('${_dataDir.path}/revanced-manager.keystore');
|
||||
@ -47,24 +48,29 @@ class PatcherAPI {
|
||||
if (_patches.isEmpty) {
|
||||
_patches = await _managerAPI.getPatches();
|
||||
}
|
||||
} on Exception catch (e, s) {
|
||||
await Sentry.captureException(e, stackTrace: s);
|
||||
} on Exception catch (e) {
|
||||
if (kDebugMode) {
|
||||
print(e);
|
||||
}
|
||||
_patches = List.empty();
|
||||
}
|
||||
}
|
||||
|
||||
Future<List<ApplicationWithIcon>> getFilteredInstalledApps(bool showUniversalPatches) async {
|
||||
List<ApplicationWithIcon> filteredApps = [];
|
||||
bool? allAppsIncluded =
|
||||
_patches.any((patch) => patch.compatiblePackages.isEmpty) && showUniversalPatches;
|
||||
Future<List<ApplicationWithIcon>> getFilteredInstalledApps(
|
||||
bool showUniversalPatches,
|
||||
) async {
|
||||
final List<ApplicationWithIcon> filteredApps = [];
|
||||
final bool allAppsIncluded =
|
||||
_patches.any((patch) => patch.compatiblePackages.isEmpty) &&
|
||||
showUniversalPatches;
|
||||
if (allAppsIncluded) {
|
||||
var allPackages = await DeviceApps.getInstalledApplications(
|
||||
final allPackages = await DeviceApps.getInstalledApplications(
|
||||
includeAppIcons: true,
|
||||
onlyAppsWithLaunchIntent: true,
|
||||
);
|
||||
allPackages.forEach((pkg) async {
|
||||
for (final pkg in allPackages) {
|
||||
if (!filteredApps.any((app) => app.packageName == pkg.packageName)) {
|
||||
var appInfo = await DeviceApps.getApp(
|
||||
final appInfo = await DeviceApps.getApp(
|
||||
pkg.packageName,
|
||||
true,
|
||||
) as ApplicationWithIcon?;
|
||||
@ -72,13 +78,13 @@ class PatcherAPI {
|
||||
filteredApps.add(appInfo);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
for (Patch patch in _patches) {
|
||||
for (Package package in patch.compatiblePackages) {
|
||||
for (final Patch patch in _patches) {
|
||||
for (final Package package in patch.compatiblePackages) {
|
||||
try {
|
||||
if (!filteredApps.any((app) => app.packageName == package.name)) {
|
||||
ApplicationWithIcon? app = await DeviceApps.getApp(
|
||||
final ApplicationWithIcon? app = await DeviceApps.getApp(
|
||||
package.name,
|
||||
true,
|
||||
) as ApplicationWithIcon?;
|
||||
@ -86,9 +92,10 @@ class PatcherAPI {
|
||||
filteredApps.add(app);
|
||||
}
|
||||
}
|
||||
} on Exception catch (e, s) {
|
||||
await Sentry.captureException(e, stackTrace: s);
|
||||
continue;
|
||||
} on Exception catch (e) {
|
||||
if (kDebugMode) {
|
||||
print(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -97,44 +104,31 @@ class PatcherAPI {
|
||||
|
||||
List<Patch> getFilteredPatches(String packageName) {
|
||||
if (!filteredPatches.keys.contains(packageName)) {
|
||||
List<Patch> patches = _patches
|
||||
.where((patch) =>
|
||||
patch.compatiblePackages.isEmpty ||
|
||||
!patch.name.contains('settings') &&
|
||||
patch.compatiblePackages
|
||||
.any((pack) => pack.name == packageName))
|
||||
final List<Patch> patches = _patches
|
||||
.where(
|
||||
(patch) =>
|
||||
patch.compatiblePackages.isEmpty ||
|
||||
!patch.name.contains('settings') &&
|
||||
patch.compatiblePackages
|
||||
.any((pack) => pack.name == packageName),
|
||||
)
|
||||
.toList();
|
||||
filteredPatches[packageName] = patches;
|
||||
}
|
||||
return filteredPatches[packageName];
|
||||
}
|
||||
|
||||
Future<List<Patch>> getAppliedPatches(List<String> appliedPatches) async {
|
||||
Future<List<Patch>> getAppliedPatches(
|
||||
List<String> appliedPatches,
|
||||
) async {
|
||||
return _patches
|
||||
.where((patch) => appliedPatches.contains(patch.name))
|
||||
.toList();
|
||||
}
|
||||
|
||||
bool dependencyNeedsIntegrations(String name) {
|
||||
return name.contains('integrations') ||
|
||||
_patches.any(
|
||||
(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 {
|
||||
Future<bool> needsResourcePatching(
|
||||
List<Patch> selectedPatches,
|
||||
) async {
|
||||
return selectedPatches.any(
|
||||
(patch) => patch.dependencies.any(
|
||||
(dep) => dep.contains('resource-'),
|
||||
@ -155,7 +149,7 @@ class PatcherAPI {
|
||||
String originalFilePath,
|
||||
) async {
|
||||
try {
|
||||
bool hasRootPermissions = await _rootAPI.hasRootPermissions();
|
||||
final bool hasRootPermissions = await _rootAPI.hasRootPermissions();
|
||||
if (hasRootPermissions) {
|
||||
originalFilePath = await _rootAPI.getOriginalFilePath(
|
||||
packageName,
|
||||
@ -163,8 +157,10 @@ class PatcherAPI {
|
||||
);
|
||||
}
|
||||
return originalFilePath;
|
||||
} on Exception catch (e, s) {
|
||||
await Sentry.captureException(e, stackTrace: s);
|
||||
} on Exception catch (e) {
|
||||
if (kDebugMode) {
|
||||
print(e);
|
||||
}
|
||||
return originalFilePath;
|
||||
}
|
||||
}
|
||||
@ -174,11 +170,10 @@ class PatcherAPI {
|
||||
String originalFilePath,
|
||||
List<Patch> selectedPatches,
|
||||
) async {
|
||||
bool mergeIntegrations = await needsIntegrations(selectedPatches);
|
||||
bool includeSettings = await needsSettingsPatch(selectedPatches);
|
||||
final bool includeSettings = await needsSettingsPatch(selectedPatches);
|
||||
if (includeSettings) {
|
||||
try {
|
||||
Patch? settingsPatch = _patches.firstWhereOrNull(
|
||||
final Patch? settingsPatch = _patches.firstWhereOrNull(
|
||||
(patch) =>
|
||||
patch.name.contains('settings') &&
|
||||
patch.compatiblePackages.any((pack) => pack.name == packageName),
|
||||
@ -186,24 +181,22 @@ class PatcherAPI {
|
||||
if (settingsPatch != null) {
|
||||
selectedPatches.add(settingsPatch);
|
||||
}
|
||||
} on Exception catch (e, s) {
|
||||
await Sentry.captureException(e, stackTrace: s);
|
||||
// ignore
|
||||
} on Exception catch (e) {
|
||||
if (kDebugMode) {
|
||||
print(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
File? patchBundleFile = await _managerAPI.downloadPatches();
|
||||
File? integrationsFile;
|
||||
if (mergeIntegrations) {
|
||||
integrationsFile = await _managerAPI.downloadIntegrations();
|
||||
}
|
||||
final File? patchBundleFile = await _managerAPI.downloadPatches();
|
||||
final File? integrationsFile = await _managerAPI.downloadIntegrations();
|
||||
if (patchBundleFile != null) {
|
||||
_dataDir.createSync();
|
||||
_tmpDir.createSync();
|
||||
Directory workDir = _tmpDir.createTempSync('tmp-');
|
||||
File inputFile = File('${workDir.path}/base.apk');
|
||||
File patchedFile = File('${workDir.path}/patched.apk');
|
||||
final Directory workDir = _tmpDir.createTempSync('tmp-');
|
||||
final File inputFile = File('${workDir.path}/base.apk');
|
||||
final File patchedFile = File('${workDir.path}/patched.apk');
|
||||
_outFile = File('${workDir.path}/out.apk');
|
||||
Directory cacheDir = Directory('${workDir.path}/cache');
|
||||
final Directory cacheDir = Directory('${workDir.path}/cache');
|
||||
cacheDir.createSync();
|
||||
try {
|
||||
await patcherChannel.invokeMethod(
|
||||
@ -217,16 +210,17 @@ class PatcherAPI {
|
||||
'inputFilePath': inputFile.path,
|
||||
'patchedFilePath': patchedFile.path,
|
||||
'outFilePath': _outFile!.path,
|
||||
'integrationsPath': mergeIntegrations ? integrationsFile!.path : '',
|
||||
'integrationsPath': integrationsFile!.path,
|
||||
'selectedPatches': selectedPatches.map((p) => p.name).toList(),
|
||||
'cacheDirPath': cacheDir.path,
|
||||
'mergeIntegrations': mergeIntegrations,
|
||||
'keyStoreFilePath': _keyStoreFile.path,
|
||||
'keystorePassword': _managerAPI.getKeystorePassword(),
|
||||
},
|
||||
);
|
||||
} on Exception catch (e, s) {
|
||||
print(e);
|
||||
throw await Sentry.captureException(e, stackTrace: s);
|
||||
} on Exception catch (e) {
|
||||
if (kDebugMode) {
|
||||
print(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -235,7 +229,7 @@ class PatcherAPI {
|
||||
if (_outFile != null) {
|
||||
try {
|
||||
if (patchedApp.isRooted) {
|
||||
bool hasRootPermissions = await _rootAPI.hasRootPermissions();
|
||||
final bool hasRootPermissions = await _rootAPI.hasRootPermissions();
|
||||
if (hasRootPermissions) {
|
||||
return _rootAPI.installApp(
|
||||
patchedApp.packageName,
|
||||
@ -245,10 +239,14 @@ class PatcherAPI {
|
||||
}
|
||||
} else {
|
||||
await AppInstaller.installApk(_outFile!.path);
|
||||
return await DeviceApps.isAppInstalled(patchedApp.packageName);
|
||||
return await DeviceApps.isAppInstalled(
|
||||
patchedApp.packageName,
|
||||
);
|
||||
}
|
||||
} on Exception catch (e) {
|
||||
if (kDebugMode) {
|
||||
print(e);
|
||||
}
|
||||
} on Exception catch (e, s) {
|
||||
await Sentry.captureException(e, stackTrace: s);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@ -258,67 +256,68 @@ class PatcherAPI {
|
||||
void exportPatchedFile(String appName, String version) {
|
||||
try {
|
||||
if (_outFile != null) {
|
||||
String newName = _getFileName(appName, version);
|
||||
|
||||
// This is temporary workaround to populate initial file name
|
||||
// ref: https://github.com/Cleveroad/cr_file_saver/issues/7
|
||||
int lastSeparator = _outFile!.path.lastIndexOf('/');
|
||||
String newSourcePath =
|
||||
_outFile!.path.substring(0, lastSeparator + 1) + newName;
|
||||
_outFile!.copySync(newSourcePath);
|
||||
|
||||
CRFileSaver.saveFileWithDialog(SaveFileDialogParams(
|
||||
sourceFilePath: newSourcePath, destinationFileName: newName));
|
||||
final String newName = _getFileName(appName, version);
|
||||
CRFileSaver.saveFileWithDialog(
|
||||
SaveFileDialogParams(
|
||||
sourceFilePath: _outFile!.path,
|
||||
destinationFileName: newName,
|
||||
),
|
||||
);
|
||||
}
|
||||
} on Exception catch (e) {
|
||||
if (kDebugMode) {
|
||||
print(e);
|
||||
}
|
||||
} on Exception catch (e, s) {
|
||||
Sentry.captureException(e, stackTrace: s);
|
||||
}
|
||||
}
|
||||
|
||||
void sharePatchedFile(String appName, String version) {
|
||||
try {
|
||||
if (_outFile != null) {
|
||||
String newName = _getFileName(appName, version);
|
||||
int lastSeparator = _outFile!.path.lastIndexOf('/');
|
||||
String newPath =
|
||||
final String newName = _getFileName(appName, version);
|
||||
final int lastSeparator = _outFile!.path.lastIndexOf('/');
|
||||
final String newPath =
|
||||
_outFile!.path.substring(0, lastSeparator + 1) + newName;
|
||||
File shareFile = _outFile!.copySync(newPath);
|
||||
final File shareFile = _outFile!.copySync(newPath);
|
||||
ShareExtend.share(shareFile.path, 'file');
|
||||
}
|
||||
} on Exception catch (e, s) {
|
||||
Sentry.captureException(e, stackTrace: s);
|
||||
} on Exception catch (e) {
|
||||
if (kDebugMode) {
|
||||
print(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
String _getFileName(String appName, String version) {
|
||||
String prefix = appName.toLowerCase().replaceAll(' ', '-');
|
||||
String newName = '$prefix-revanced_v$version.apk';
|
||||
final String prefix = appName.toLowerCase().replaceAll(' ', '-');
|
||||
final String newName = '$prefix-revanced_v$version.apk';
|
||||
return newName;
|
||||
}
|
||||
|
||||
Future<void> sharePatcherLog(String logs) async {
|
||||
Directory appCache = await getTemporaryDirectory();
|
||||
Directory logDir = Directory('${appCache.path}/logs');
|
||||
final Directory appCache = await getTemporaryDirectory();
|
||||
final Directory logDir = Directory('${appCache.path}/logs');
|
||||
logDir.createSync();
|
||||
String dateTime = DateTime.now()
|
||||
final String dateTime = DateTime.now()
|
||||
.toIso8601String()
|
||||
.replaceAll('-', '')
|
||||
.replaceAll(':', '')
|
||||
.replaceAll('T', '')
|
||||
.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);
|
||||
ShareExtend.share(log.path, 'file');
|
||||
}
|
||||
|
||||
String getRecommendedVersion(String packageName) {
|
||||
Map<String, int> versions = {};
|
||||
for (Patch patch in _patches) {
|
||||
Package? package = patch.compatiblePackages.firstWhereOrNull(
|
||||
String getSuggestedVersion(String packageName) {
|
||||
final Map<String, int> versions = {};
|
||||
for (final Patch patch in _patches) {
|
||||
final Package? package = patch.compatiblePackages.firstWhereOrNull(
|
||||
(pack) => pack.name == packageName,
|
||||
);
|
||||
if (package != null) {
|
||||
for (String version in package.versions) {
|
||||
for (final String version in package.versions) {
|
||||
versions.update(
|
||||
version,
|
||||
(value) => versions[version]! + 1,
|
||||
@ -328,7 +327,7 @@ class PatcherAPI {
|
||||
}
|
||||
}
|
||||
if (versions.isNotEmpty) {
|
||||
var entries = versions.entries.toList()
|
||||
final entries = versions.entries.toList()
|
||||
..sort((a, b) => a.value.compareTo(b.value));
|
||||
versions
|
||||
..clear()
|
||||
|
@ -1,69 +1,89 @@
|
||||
import 'dart:async';
|
||||
import 'dart:developer';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:native_dio_client/native_dio_client.dart';
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:dio_http_cache_lts/dio_http_cache_lts.dart';
|
||||
import 'package:dio_cache_interceptor/dio_cache_interceptor.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter_cache_manager/flutter_cache_manager.dart';
|
||||
import 'package:injectable/injectable.dart';
|
||||
import 'package:native_dio_adapter/native_dio_adapter.dart';
|
||||
import 'package:revanced_manager/models/patch.dart';
|
||||
import 'package:revanced_manager/utils/check_for_gms.dart';
|
||||
import 'package:timeago/timeago.dart';
|
||||
import 'package:sentry_flutter/sentry_flutter.dart';
|
||||
import 'package:sentry_dio/sentry_dio.dart';
|
||||
|
||||
@lazySingleton
|
||||
class RevancedAPI {
|
||||
late Dio _dio = Dio();
|
||||
final DioCacheManager _dioCacheManager = DioCacheManager(CacheConfig());
|
||||
final Options _cacheOptions = buildCacheOptions(
|
||||
const Duration(hours: 6),
|
||||
|
||||
final _cacheOptions = CacheOptions(
|
||||
store: MemCacheStore(),
|
||||
maxStale: const Duration(days: 1),
|
||||
priority: CachePriority.high,
|
||||
);
|
||||
|
||||
Future<void> initialize(String apiUrl) async {
|
||||
try {
|
||||
bool isGMSInstalled = await checkForGMS();
|
||||
final bool isGMSInstalled = await checkForGMS();
|
||||
|
||||
if (!isGMSInstalled) {
|
||||
_dio = Dio(BaseOptions(
|
||||
baseUrl: apiUrl,
|
||||
));
|
||||
print('ReVanced API: Using default engine + $isGMSInstalled');
|
||||
_dio = Dio(
|
||||
BaseOptions(
|
||||
baseUrl: apiUrl,
|
||||
),
|
||||
);
|
||||
log('ReVanced API: Using default engine + $isGMSInstalled');
|
||||
} else {
|
||||
_dio = Dio(BaseOptions(
|
||||
baseUrl: apiUrl,
|
||||
))
|
||||
..httpClientAdapter = NativeAdapter();
|
||||
print('ReVanced API: Using CronetEngine + $isGMSInstalled');
|
||||
if (Platform.isIOS || Platform.isMacOS || Platform.isAndroid) {
|
||||
final CronetEngine androidCronetEngine = await CronetEngine.build(
|
||||
userAgent: 'ReVanced Manager',
|
||||
enableBrotli: true,
|
||||
enableQuic: true,
|
||||
);
|
||||
_dio.httpClientAdapter =
|
||||
NativeAdapter(androidCronetEngine: androidCronetEngine);
|
||||
|
||||
_dio = Dio(
|
||||
BaseOptions(
|
||||
baseUrl: apiUrl,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
log('ReVanced API: Using CronetEngine + $isGMSInstalled');
|
||||
}
|
||||
_dio.interceptors.add(DioCacheInterceptor(options: _cacheOptions));
|
||||
} on Exception catch (e) {
|
||||
if (kDebugMode) {
|
||||
print(e);
|
||||
}
|
||||
_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);
|
||||
await _cacheOptions.store!.clean();
|
||||
} on Exception catch (e) {
|
||||
if (kDebugMode) {
|
||||
print(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Future<Map<String, List<dynamic>>> getContributors() async {
|
||||
Map<String, List<dynamic>> contributors = {};
|
||||
final Map<String, List<dynamic>> contributors = {};
|
||||
try {
|
||||
var response = await _dio.get('/contributors', options: _cacheOptions);
|
||||
List<dynamic> repositories = response.data['repositories'];
|
||||
for (Map<String, dynamic> repo in repositories) {
|
||||
String name = repo['name'];
|
||||
final response = await _dio.get('/contributors');
|
||||
final List<dynamic> repositories = response.data['repositories'];
|
||||
for (final Map<String, dynamic> repo in repositories) {
|
||||
final String name = repo['name'];
|
||||
contributors[name] = repo['contributors'];
|
||||
}
|
||||
} on Exception catch (e, s) {
|
||||
await Sentry.captureException(e, stackTrace: s);
|
||||
} on Exception catch (e) {
|
||||
if (kDebugMode) {
|
||||
print(e);
|
||||
}
|
||||
return {};
|
||||
}
|
||||
return contributors;
|
||||
@ -71,11 +91,13 @@ class RevancedAPI {
|
||||
|
||||
Future<List<Patch>> getPatches() async {
|
||||
try {
|
||||
var response = await _dio.get('/patches', options: _cacheOptions);
|
||||
List<dynamic> patches = response.data;
|
||||
final response = await _dio.get('/patches');
|
||||
final List<dynamic> patches = response.data;
|
||||
return patches.map((patch) => Patch.fromJson(patch)).toList();
|
||||
} on Exception catch (e, s) {
|
||||
await Sentry.captureException(e, stackTrace: s);
|
||||
} on Exception catch (e) {
|
||||
if (kDebugMode) {
|
||||
print(e);
|
||||
}
|
||||
return List.empty();
|
||||
}
|
||||
}
|
||||
@ -85,15 +107,17 @@ class RevancedAPI {
|
||||
String repoName,
|
||||
) async {
|
||||
try {
|
||||
var response = await _dio.get('/tools', options: _cacheOptions);
|
||||
List<dynamic> tools = response.data['tools'];
|
||||
final response = await _dio.get('/tools');
|
||||
final List<dynamic> tools = response.data['tools'];
|
||||
return tools.firstWhereOrNull(
|
||||
(t) =>
|
||||
t['repository'] == repoName &&
|
||||
(t['name'] as String).endsWith(extension),
|
||||
);
|
||||
} on Exception catch (e, s) {
|
||||
await Sentry.captureException(e, stackTrace: s);
|
||||
} on Exception catch (e) {
|
||||
if (kDebugMode) {
|
||||
print(e);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@ -103,52 +127,99 @@ class RevancedAPI {
|
||||
String repoName,
|
||||
) async {
|
||||
try {
|
||||
Map<String, dynamic>? release = await _getLatestRelease(
|
||||
final Map<String, dynamic>? release = await _getLatestRelease(
|
||||
extension,
|
||||
repoName,
|
||||
);
|
||||
if (release != null) {
|
||||
return release['version'];
|
||||
}
|
||||
} on Exception catch (e, s) {
|
||||
await Sentry.captureException(e, stackTrace: s);
|
||||
} on Exception catch (e) {
|
||||
if (kDebugMode) {
|
||||
print(e);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
Future<File?> getLatestReleaseFile(String extension, String repoName) async {
|
||||
Future<File?> getLatestReleaseFile(
|
||||
String extension,
|
||||
String repoName,
|
||||
) async {
|
||||
try {
|
||||
Map<String, dynamic>? release = await _getLatestRelease(
|
||||
final Map<String, dynamic>? release = await _getLatestRelease(
|
||||
extension,
|
||||
repoName,
|
||||
);
|
||||
if (release != null) {
|
||||
String url = release['browser_download_url'];
|
||||
final String url = release['browser_download_url'];
|
||||
return await DefaultCacheManager().getSingleFile(url);
|
||||
}
|
||||
} on Exception catch (e, s) {
|
||||
await Sentry.captureException(e, stackTrace: s);
|
||||
} on Exception catch (e) {
|
||||
if (kDebugMode) {
|
||||
print(e);
|
||||
}
|
||||
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(
|
||||
String extension,
|
||||
String repoName,
|
||||
) async {
|
||||
try {
|
||||
Map<String, dynamic>? release = await _getLatestRelease(
|
||||
final Map<String, dynamic>? release = await _getLatestRelease(
|
||||
extension,
|
||||
repoName,
|
||||
);
|
||||
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');
|
||||
}
|
||||
} on Exception catch (e, s) {
|
||||
await Sentry.captureException(e, stackTrace: s);
|
||||
} on Exception catch (e) {
|
||||
if (kDebugMode) {
|
||||
print(e);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
return null;
|
||||
|
@ -1,5 +1,5 @@
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:root/root.dart';
|
||||
import 'package:sentry_flutter/sentry_flutter.dart';
|
||||
|
||||
class RootAPI {
|
||||
final String _managerDirPath = '/data/local/tmp/revanced-manager';
|
||||
@ -8,10 +8,12 @@ class RootAPI {
|
||||
|
||||
Future<bool> isRooted() async {
|
||||
try {
|
||||
bool? isRooted = await Root.isRootAvailable();
|
||||
final bool? isRooted = await Root.isRootAvailable();
|
||||
return isRooted != null && isRooted;
|
||||
} on Exception catch (e, s) {
|
||||
await Sentry.captureException(e, stackTrace: s);
|
||||
} on Exception catch (e) {
|
||||
if (kDebugMode) {
|
||||
print(e);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@ -24,8 +26,10 @@ class RootAPI {
|
||||
return isRooted != null && isRooted;
|
||||
}
|
||||
return false;
|
||||
} on Exception catch (e, s) {
|
||||
await Sentry.captureException(e, stackTrace: s);
|
||||
} on Exception catch (e) {
|
||||
if (kDebugMode) {
|
||||
print(e);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@ -70,16 +74,18 @@ class RootAPI {
|
||||
|
||||
Future<List<String>> getInstalledApps() async {
|
||||
try {
|
||||
String? res = await Root.exec(
|
||||
final String? res = await Root.exec(
|
||||
cmd: 'ls "$_managerDirPath"',
|
||||
);
|
||||
if (res != null) {
|
||||
List<String> apps = res.split('\n');
|
||||
final List<String> apps = res.split('\n');
|
||||
apps.removeWhere((pack) => pack.isEmpty);
|
||||
return apps.map((pack) => pack.trim()).toList();
|
||||
}
|
||||
} on Exception catch (e, s) {
|
||||
await Sentry.captureException(e, stackTrace: s);
|
||||
} on Exception catch (e) {
|
||||
if (kDebugMode) {
|
||||
print(e);
|
||||
}
|
||||
return List.empty();
|
||||
}
|
||||
return List.empty();
|
||||
@ -125,19 +131,21 @@ class RootAPI {
|
||||
await installApk(packageName, patchedFilePath);
|
||||
await mountApk(packageName, originalFilePath);
|
||||
return true;
|
||||
} on Exception catch (e, s) {
|
||||
await Sentry.captureException(e, stackTrace: s);
|
||||
} on Exception catch (e) {
|
||||
if (kDebugMode) {
|
||||
print(e);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
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'
|
||||
'base_path=$_managerDirPath/$packageName/base.apk\n'
|
||||
'stock_path=\$(pm path $packageName | grep base | sed \'"\'"\'s/package://g\'"\'"\')\n'
|
||||
'[ ! -z \$stock_path ] && mount -o bind \$base_path \$stock_path';
|
||||
String scriptFilePath = '$_serviceDDirPath/$packageName.sh';
|
||||
r'[ ! -z $stock_path ] && mount -o bind $base_path $stock_path';
|
||||
final String scriptFilePath = '$_serviceDDirPath/$packageName.sh';
|
||||
await Root.exec(
|
||||
cmd: 'echo \'$content\' > "$scriptFilePath"',
|
||||
);
|
||||
@ -145,10 +153,10 @@ class RootAPI {
|
||||
}
|
||||
|
||||
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'
|
||||
'[ ! -z \$stock_path ] && umount -l \$stock_path';
|
||||
String scriptFilePath = '$_postFsDataDirPath/$packageName.sh';
|
||||
r'[ ! -z $stock_path ] && umount -l $stock_path';
|
||||
final String scriptFilePath = '$_postFsDataDirPath/$packageName.sh';
|
||||
await Root.exec(
|
||||
cmd: 'echo \'$content\' > "$scriptFilePath"',
|
||||
);
|
||||
@ -156,7 +164,7 @@ class RootAPI {
|
||||
}
|
||||
|
||||
Future<void> installApk(String packageName, String patchedFilePath) async {
|
||||
String newPatchedFilePath = '$_managerDirPath/$packageName/base.apk';
|
||||
final String newPatchedFilePath = '$_managerDirPath/$packageName/base.apk';
|
||||
await Root.exec(
|
||||
cmd: 'cp "$patchedFilePath" "$newPatchedFilePath"',
|
||||
);
|
||||
@ -169,7 +177,7 @@ class RootAPI {
|
||||
}
|
||||
|
||||
Future<void> mountApk(String packageName, String originalFilePath) async {
|
||||
String newPatchedFilePath = '$_managerDirPath/$packageName/base.apk';
|
||||
final String newPatchedFilePath = '$_managerDirPath/$packageName/base.apk';
|
||||
await Root.exec(
|
||||
cmd: 'am force-stop "$packageName"',
|
||||
);
|
||||
@ -182,7 +190,7 @@ class RootAPI {
|
||||
}
|
||||
|
||||
Future<bool> isMounted(String packageName) async {
|
||||
String? res = await Root.exec(
|
||||
final String? res = await Root.exec(
|
||||
cmd: 'cat /proc/mounts | grep $packageName',
|
||||
);
|
||||
return res != null && res.isNotEmpty;
|
||||
@ -192,7 +200,7 @@ class RootAPI {
|
||||
String packageName,
|
||||
String originalFilePath,
|
||||
) async {
|
||||
bool isInstalled = await isAppInstalled(packageName);
|
||||
final bool isInstalled = await isAppInstalled(packageName);
|
||||
if (isInstalled && await isMounted(packageName)) {
|
||||
originalFilePath = '$_managerDirPath/$packageName/original.apk';
|
||||
await setPermissions(
|
||||
@ -209,7 +217,8 @@ class RootAPI {
|
||||
String packageName,
|
||||
String originalFilePath,
|
||||
) async {
|
||||
String originalRootPath = '$_managerDirPath/$packageName/original.apk';
|
||||
final String originalRootPath =
|
||||
'$_managerDirPath/$packageName/original.apk';
|
||||
await Root.exec(
|
||||
cmd: 'mkdir -p "$_managerDirPath/$packageName"',
|
||||
);
|
||||
|
@ -3,7 +3,6 @@ import 'package:google_fonts/google_fonts.dart';
|
||||
|
||||
var lightCustomColorScheme = ColorScheme.fromSeed(
|
||||
seedColor: Colors.blue,
|
||||
brightness: Brightness.light,
|
||||
primary: const Color(0xff1B73E8),
|
||||
);
|
||||
|
||||
@ -13,7 +12,7 @@ var lightCustomTheme = ThemeData(
|
||||
navigationBarTheme: NavigationBarThemeData(
|
||||
labelTextStyle: MaterialStateProperty.all(
|
||||
TextStyle(
|
||||
color: lightCustomColorScheme.secondary,
|
||||
color: lightCustomColorScheme.onSurface,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
@ -34,13 +33,12 @@ var darkCustomTheme = ThemeData(
|
||||
navigationBarTheme: NavigationBarThemeData(
|
||||
labelTextStyle: MaterialStateProperty.all(
|
||||
TextStyle(
|
||||
color: darkCustomColorScheme.secondary,
|
||||
color: darkCustomColorScheme.onSurface,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
),
|
||||
canvasColor: const Color(0xff1B1A1D),
|
||||
scaffoldBackgroundColor: const Color(0xff1B1A1D),
|
||||
toggleableActiveColor: const Color(0xffA5CAFF),
|
||||
textTheme: GoogleFonts.robotoTextTheme(ThemeData.dark().textTheme),
|
||||
);
|
||||
|
@ -7,65 +7,44 @@ import 'package:revanced_manager/theme.dart';
|
||||
import 'package:stacked_services/stacked_services.dart';
|
||||
|
||||
class DynamicThemeBuilder extends StatelessWidget {
|
||||
final String title;
|
||||
final Widget home;
|
||||
final Iterable<LocalizationsDelegate> localizationsDelegates;
|
||||
|
||||
const DynamicThemeBuilder({
|
||||
Key? key,
|
||||
required this.title,
|
||||
required this.home,
|
||||
required this.localizationsDelegates,
|
||||
}) : super(key: key);
|
||||
final String title;
|
||||
final Widget home;
|
||||
final Iterable<LocalizationsDelegate> localizationsDelegates;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return DynamicColorBuilder(
|
||||
builder: (lightColorScheme, darkColorScheme) {
|
||||
ThemeData lightDynamicTheme = ThemeData(
|
||||
final ThemeData lightDynamicTheme = ThemeData(
|
||||
useMaterial3: true,
|
||||
canvasColor: lightColorScheme?.background,
|
||||
navigationBarTheme: NavigationBarThemeData(
|
||||
backgroundColor: lightColorScheme?.background,
|
||||
indicatorColor: lightColorScheme?.primary.withAlpha(150),
|
||||
labelTextStyle: MaterialStateProperty.all(
|
||||
GoogleFonts.roboto(
|
||||
color: lightColorScheme?.secondary,
|
||||
color: lightColorScheme?.onSurface,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
iconTheme: MaterialStateProperty.all(
|
||||
IconThemeData(
|
||||
color: lightColorScheme?.secondary,
|
||||
),
|
||||
),
|
||||
),
|
||||
scaffoldBackgroundColor: lightColorScheme?.background,
|
||||
colorScheme: lightColorScheme?.harmonized(),
|
||||
toggleableActiveColor: lightColorScheme?.primary,
|
||||
textTheme: GoogleFonts.robotoTextTheme(ThemeData.light().textTheme),
|
||||
);
|
||||
ThemeData darkDynamicTheme = ThemeData(
|
||||
final ThemeData darkDynamicTheme = ThemeData(
|
||||
useMaterial3: true,
|
||||
canvasColor: darkColorScheme?.background,
|
||||
navigationBarTheme: NavigationBarThemeData(
|
||||
backgroundColor: darkColorScheme?.background,
|
||||
indicatorColor: darkColorScheme?.primary.withOpacity(0.4),
|
||||
labelTextStyle: MaterialStateProperty.all(
|
||||
GoogleFonts.roboto(
|
||||
color: darkColorScheme?.secondary,
|
||||
color: darkColorScheme?.onSurface,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
iconTheme: MaterialStateProperty.all(
|
||||
IconThemeData(
|
||||
color: darkColorScheme?.secondary,
|
||||
),
|
||||
),
|
||||
),
|
||||
scaffoldBackgroundColor: darkColorScheme?.background,
|
||||
colorScheme: darkColorScheme?.harmonized(),
|
||||
toggleableActiveColor: darkColorScheme?.primary,
|
||||
textTheme: GoogleFonts.robotoTextTheme(ThemeData.dark().textTheme),
|
||||
);
|
||||
return DynamicTheme(
|
||||
|
@ -1,10 +1,11 @@
|
||||
import 'package:flutter/material.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/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 {
|
||||
const AppSelectorView({Key? key}) : super(key: key);
|
||||
@ -19,7 +20,7 @@ class _AppSelectorViewState extends State<AppSelectorView> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ViewModelBuilder<AppSelectorViewModel>.reactive(
|
||||
onModelReady: (model) => model.initialize(),
|
||||
onViewModelReady: (model) => model.initialize(),
|
||||
viewModelBuilder: () => AppSelectorViewModel(),
|
||||
builder: (context, model, child) => Scaffold(
|
||||
resizeToAvoidBottomInset: false,
|
||||
@ -36,20 +37,19 @@ class _AppSelectorViewState extends State<AppSelectorView> {
|
||||
SliverAppBar(
|
||||
pinned: true,
|
||||
floating: true,
|
||||
snap: false,
|
||||
title: I18nText(
|
||||
'appSelectorView.viewTitle',
|
||||
child: Text(
|
||||
'',
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).textTheme.headline6!.color,
|
||||
color: Theme.of(context).textTheme.titleLarge!.color,
|
||||
),
|
||||
),
|
||||
),
|
||||
leading: IconButton(
|
||||
icon: Icon(
|
||||
Icons.arrow_back,
|
||||
color: Theme.of(context).textTheme.headline6!.color,
|
||||
color: Theme.of(context).textTheme.titleLarge!.color,
|
||||
),
|
||||
onPressed: () => Navigator.of(context).pop(),
|
||||
),
|
||||
@ -61,7 +61,6 @@ class _AppSelectorViewState extends State<AppSelectorView> {
|
||||
horizontal: 12.0,
|
||||
),
|
||||
child: SearchBar(
|
||||
showSelectIcon: false,
|
||||
hintText: FlutterI18n.translate(
|
||||
context,
|
||||
'appSelectorView.searchBarHint',
|
||||
@ -78,7 +77,16 @@ class _AppSelectorViewState extends State<AppSelectorView> {
|
||||
SliverToBoxAdapter(
|
||||
child: model.noApps
|
||||
? Center(
|
||||
child: I18nText('appSelectorCard.noAppsLabel'),
|
||||
child: I18nText(
|
||||
'appSelectorView.noApps',
|
||||
child: Text(
|
||||
'',
|
||||
style: TextStyle(
|
||||
color:
|
||||
Theme.of(context).textTheme.titleLarge!.color,
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
: model.apps.isEmpty
|
||||
? const AppSkeletonLoader()
|
||||
@ -86,20 +94,48 @@ class _AppSelectorViewState extends State<AppSelectorView> {
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12.0)
|
||||
.copyWith(bottom: 80),
|
||||
child: Column(
|
||||
children: model
|
||||
.getFilteredApps(_query)
|
||||
.map((app) => InstalledAppItem(
|
||||
children: [
|
||||
...model
|
||||
.getFilteredApps(_query)
|
||||
.map(
|
||||
(app) => InstalledAppItem(
|
||||
name: app.appName,
|
||||
pkgName: app.packageName,
|
||||
icon: app.icon,
|
||||
patchesCount:
|
||||
model.patchesCount(app.packageName),
|
||||
suggestedVersion:
|
||||
model.getSuggestedVersion(
|
||||
app.packageName,
|
||||
),
|
||||
onTap: () {
|
||||
model.selectApp(app);
|
||||
Navigator.of(context).pop();
|
||||
model.isRooted
|
||||
? model.selectApp(app).then(
|
||||
(_) => Navigator.of(context)
|
||||
.pop(),
|
||||
)
|
||||
: model.showSelectFromStorageDialog(
|
||||
context,
|
||||
);
|
||||
},
|
||||
))
|
||||
.toList(),
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
...model
|
||||
.getFilteredAppsNames(_query)
|
||||
.map(
|
||||
(app) => NotInstalledAppItem(
|
||||
name: app,
|
||||
patchesCount: model.patchesCount(app),
|
||||
suggestedVersion:
|
||||
model.getSuggestedVersion(app),
|
||||
onTap: () {
|
||||
model.showDownloadToast();
|
||||
},
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
|
@ -1,37 +1,71 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:device_apps/device_apps.dart';
|
||||
import 'package:file_picker/file_picker.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_i18n/flutter_i18n.dart';
|
||||
import 'package:revanced_manager/app/app.locator.dart';
|
||||
import 'package:revanced_manager/models/patch.dart';
|
||||
import 'package:revanced_manager/models/patched_application.dart';
|
||||
import 'package:revanced_manager/services/manager_api.dart';
|
||||
import 'package:revanced_manager/services/patcher_api.dart';
|
||||
import 'package:revanced_manager/services/revanced_api.dart';
|
||||
import 'package:revanced_manager/services/toast.dart';
|
||||
import 'package:revanced_manager/ui/views/patcher/patcher_viewmodel.dart';
|
||||
import 'package:sentry_flutter/sentry_flutter.dart';
|
||||
import 'package:revanced_manager/ui/widgets/shared/custom_material_button.dart';
|
||||
import 'package:stacked/stacked.dart';
|
||||
import '../../../services/manager_api.dart';
|
||||
|
||||
class AppSelectorViewModel extends BaseViewModel {
|
||||
final PatcherAPI _patcherAPI = locator<PatcherAPI>();
|
||||
final ManagerAPI _managerAPI = locator<ManagerAPI>();
|
||||
final RevancedAPI _revancedAPI = locator<RevancedAPI>();
|
||||
final Toast _toast = locator<Toast>();
|
||||
final List<ApplicationWithIcon> apps = [];
|
||||
List<String> allApps = [];
|
||||
bool noApps = false;
|
||||
bool isRooted = false;
|
||||
int patchesCount(String packageName) {
|
||||
return _patcherAPI.getFilteredPatches(packageName).length;
|
||||
}
|
||||
|
||||
List<Patch> patches = [];
|
||||
|
||||
Future<void> initialize() async {
|
||||
apps.addAll(await _patcherAPI.getFilteredInstalledApps(_managerAPI.areUniversalPatchesEnabled()));
|
||||
apps.sort(((a, b) => _patcherAPI
|
||||
.getFilteredPatches(b.packageName)
|
||||
.length
|
||||
.compareTo(_patcherAPI.getFilteredPatches(a.packageName).length)));
|
||||
patches = await _revancedAPI.getPatches();
|
||||
isRooted = _managerAPI.isRooted;
|
||||
|
||||
apps.addAll(
|
||||
await _patcherAPI
|
||||
.getFilteredInstalledApps(_managerAPI.areUniversalPatchesEnabled()),
|
||||
);
|
||||
apps.sort(
|
||||
(a, b) => _patcherAPI
|
||||
.getFilteredPatches(b.packageName)
|
||||
.length
|
||||
.compareTo(_patcherAPI.getFilteredPatches(a.packageName).length),
|
||||
);
|
||||
noApps = apps.isEmpty;
|
||||
getAllApps();
|
||||
|
||||
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<void> selectApp(ApplicationWithIcon application) async {
|
||||
locator<PatcherViewModel>().selectedApp = PatchedApplication(
|
||||
name: application.appName,
|
||||
packageName: application.packageName,
|
||||
@ -42,27 +76,102 @@ class AppSelectorViewModel extends BaseViewModel {
|
||||
patchDate: DateTime.now(),
|
||||
);
|
||||
locator<PatcherViewModel>().loadLastSelectedPatches();
|
||||
locator<PatcherViewModel>().notifyListeners();
|
||||
}
|
||||
|
||||
Future showSelectFromStorageDialog(BuildContext context) async {
|
||||
return showDialog(
|
||||
context: context,
|
||||
builder: (context) => SimpleDialog(
|
||||
alignment: Alignment.center,
|
||||
contentPadding:
|
||||
const EdgeInsets.symmetric(horizontal: 20, vertical: 20),
|
||||
children: [
|
||||
const SizedBox(height: 10),
|
||||
Icon(
|
||||
Icons.block,
|
||||
size: 28,
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
I18nText(
|
||||
'appSelectorView.featureNotAvailable',
|
||||
child: const Text(
|
||||
'',
|
||||
textAlign: TextAlign.center,
|
||||
style: TextStyle(
|
||||
fontSize: 20,
|
||||
fontWeight: FontWeight.w600,
|
||||
wordSpacing: 1.5,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
I18nText(
|
||||
'appSelectorView.featureNotAvailableText',
|
||||
child: const Text(
|
||||
'',
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 30),
|
||||
CustomMaterialButton(
|
||||
onPressed: () => selectAppFromStorage(context).then(
|
||||
(_) {
|
||||
Navigator.pop(context);
|
||||
Navigator.pop(context);
|
||||
},
|
||||
),
|
||||
label: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
const Icon(Icons.sd_card),
|
||||
const SizedBox(width: 10),
|
||||
I18nText('appSelectorView.selectFromStorageButton'),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
CustomMaterialButton(
|
||||
isFilled: false,
|
||||
onPressed: () {
|
||||
Navigator.pop(context);
|
||||
Navigator.pop(context);
|
||||
},
|
||||
label: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
const SizedBox(width: 10),
|
||||
I18nText('cancelButton'),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> selectAppFromStorage(BuildContext context) async {
|
||||
try {
|
||||
FilePickerResult? result = await FilePicker.platform.pickFiles(
|
||||
final FilePickerResult? result = await FilePicker.platform.pickFiles(
|
||||
type: FileType.custom,
|
||||
allowedExtensions: ['apk'],
|
||||
);
|
||||
if (result != null && result.files.single.path != null) {
|
||||
File apkFile = File(result.files.single.path!);
|
||||
List<String> pathSplit = result.files.single.path!.split("/");
|
||||
final File apkFile = File(result.files.single.path!);
|
||||
final List<String> pathSplit = result.files.single.path!.split('/');
|
||||
pathSplit.removeLast();
|
||||
Directory filePickerCacheDir = Directory(pathSplit.join("/"));
|
||||
Iterable<File> deletableFiles =
|
||||
final Directory filePickerCacheDir = Directory(pathSplit.join('/'));
|
||||
final Iterable<File> deletableFiles =
|
||||
(await filePickerCacheDir.list().toList()).whereType<File>();
|
||||
for (var file in deletableFiles) {
|
||||
if (file.path != apkFile.path && file.path.endsWith(".apk"))
|
||||
for (final file in deletableFiles) {
|
||||
if (file.path != apkFile.path && file.path.endsWith('.apk')) {
|
||||
file.delete();
|
||||
}
|
||||
}
|
||||
ApplicationWithIcon? application = await DeviceApps.getAppFromStorage(
|
||||
final ApplicationWithIcon? application =
|
||||
await DeviceApps.getAppFromStorage(
|
||||
apkFile.path,
|
||||
true,
|
||||
) as ApplicationWithIcon?;
|
||||
@ -78,21 +187,38 @@ class AppSelectorViewModel extends BaseViewModel {
|
||||
isFromStorage: true,
|
||||
);
|
||||
locator<PatcherViewModel>().loadLastSelectedPatches();
|
||||
locator<PatcherViewModel>().notifyListeners();
|
||||
}
|
||||
}
|
||||
} on Exception catch (e, s) {
|
||||
await Sentry.captureException(e, stackTrace: s);
|
||||
} on Exception catch (e) {
|
||||
if (kDebugMode) {
|
||||
print(e);
|
||||
}
|
||||
_toast.showBottom('appSelectorView.errorMessage');
|
||||
}
|
||||
}
|
||||
|
||||
List<ApplicationWithIcon> getFilteredApps(String query) {
|
||||
return apps
|
||||
.where((app) =>
|
||||
query.isEmpty ||
|
||||
query.length < 2 ||
|
||||
app.appName.toLowerCase().contains(query.toLowerCase()))
|
||||
.where(
|
||||
(app) =>
|
||||
query.isEmpty ||
|
||||
query.length < 2 ||
|
||||
app.appName.toLowerCase().contains(query.toLowerCase()),
|
||||
)
|
||||
.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) {
|
||||
return ViewModelBuilder<ContributorsViewModel>.reactive(
|
||||
viewModelBuilder: () => ContributorsViewModel(),
|
||||
onModelReady: (model) => model.getContributors(),
|
||||
onViewModelReady: (model) => model.getContributors(),
|
||||
builder: (context, model, child) => Scaffold(
|
||||
body: CustomScrollView(
|
||||
slivers: <Widget>[
|
||||
@ -23,7 +23,7 @@ class ContributorsView extends StatelessWidget {
|
||||
child: Text(
|
||||
'',
|
||||
style: GoogleFonts.inter(
|
||||
color: Theme.of(context).textTheme.headline6!.color,
|
||||
color: Theme.of(context).textTheme.titleLarge!.color,
|
||||
),
|
||||
),
|
||||
),
|
||||
|
@ -11,7 +11,7 @@ class ContributorsViewModel extends BaseViewModel {
|
||||
List<dynamic> managerContributors = [];
|
||||
|
||||
Future<void> getContributors() async {
|
||||
Map<String, List<dynamic>> contributors =
|
||||
final Map<String, List<dynamic>> contributors =
|
||||
await _managerAPI.getContributors();
|
||||
patcherContributors = contributors[_managerAPI.defaultPatcherRepo] ?? [];
|
||||
patchesContributors = contributors[_managerAPI.getPatchesRepo()] ?? [];
|
||||
|
@ -1,13 +1,12 @@
|
||||
import 'package:animations/animations.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_i18n/flutter_i18n.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/ui/views/home/home_viewmodel.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/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:stacked/stacked.dart';
|
||||
|
||||
@ -18,12 +17,11 @@ class HomeView extends StatelessWidget {
|
||||
Widget build(BuildContext context) {
|
||||
return ViewModelBuilder<HomeViewModel>.reactive(
|
||||
disposeViewModel: false,
|
||||
onModelReady: (model) => model.initialize(context),
|
||||
fireOnViewModelReadyOnce: true,
|
||||
onViewModelReady: (model) => model.initialize(context),
|
||||
viewModelBuilder: () => locator<HomeViewModel>(),
|
||||
builder: (context, model, child) => Scaffold(
|
||||
body: RefreshIndicator(
|
||||
color: Theme.of(context).colorScheme.secondary,
|
||||
backgroundColor: Theme.of(context).colorScheme.secondaryContainer,
|
||||
onRefresh: () => model.forceRefresh(context),
|
||||
child: CustomScrollView(
|
||||
slivers: <Widget>[
|
||||
@ -34,7 +32,7 @@ class HomeView extends StatelessWidget {
|
||||
child: Text(
|
||||
'',
|
||||
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',
|
||||
child: Text(
|
||||
'',
|
||||
style: Theme.of(context).textTheme.headline6!,
|
||||
style: Theme.of(context).textTheme.titleLarge,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
LatestCommitCard(
|
||||
onPressed: () =>
|
||||
onPressedManager: () =>
|
||||
model.showUpdateConfirmationDialog(context),
|
||||
onPressedPatches: () => model.forceRefresh(context),
|
||||
),
|
||||
const SizedBox(height: 23),
|
||||
I18nText(
|
||||
'homeView.patchedSubtitle',
|
||||
child: Text(
|
||||
'',
|
||||
style: Theme.of(context).textTheme.headline6!,
|
||||
style: Theme.of(context).textTheme.titleLarge,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Row(
|
||||
children: <Widget>[
|
||||
CustomChip(
|
||||
ActionChip(
|
||||
avatar: const Icon(Icons.grid_view),
|
||||
label: I18nText('homeView.installed'),
|
||||
isSelected: !model.showUpdatableApps,
|
||||
onSelected: (value) {
|
||||
side: BorderSide(
|
||||
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);
|
||||
},
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
CustomChip(
|
||||
ActionChip(
|
||||
avatar: const Icon(Icons.update),
|
||||
label: I18nText('homeView.updatesAvailable'),
|
||||
isSelected: model.showUpdatableApps,
|
||||
onSelected: (value) {
|
||||
side: BorderSide(
|
||||
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);
|
||||
},
|
||||
)
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 14),
|
||||
|
@ -1,26 +1,30 @@
|
||||
// ignore_for_file: use_build_context_synchronously
|
||||
import 'dart:async';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:app_installer/app_installer.dart';
|
||||
import 'package:cross_connectivity/cross_connectivity.dart';
|
||||
import 'package:device_apps/device_apps.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_i18n/flutter_i18n.dart';
|
||||
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
|
||||
import 'package:injectable/injectable.dart';
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
import 'package:revanced_manager/app/app.locator.dart';
|
||||
import 'package:revanced_manager/app/app.router.dart';
|
||||
import 'package:revanced_manager/models/patched_application.dart';
|
||||
import 'package:revanced_manager/services/github_api.dart';
|
||||
import 'package:revanced_manager/services/manager_api.dart';
|
||||
import 'package:revanced_manager/services/patcher_api.dart';
|
||||
import 'package:revanced_manager/services/github_api.dart';
|
||||
import 'package:revanced_manager/services/revanced_api.dart';
|
||||
import 'package:revanced_manager/services/toast.dart';
|
||||
import 'package:revanced_manager/ui/views/navigation/navigation_viewmodel.dart';
|
||||
import 'package:revanced_manager/ui/views/patcher/patcher_viewmodel.dart';
|
||||
import 'package:revanced_manager/ui/widgets/homeView/update_confirmation_dialog.dart';
|
||||
import 'package: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_services/stacked_services.dart';
|
||||
import 'package:timezone/timezone.dart' as tz;
|
||||
|
||||
@lazySingleton
|
||||
class HomeViewModel extends BaseViewModel {
|
||||
@ -28,29 +32,55 @@ class HomeViewModel extends BaseViewModel {
|
||||
final ManagerAPI _managerAPI = locator<ManagerAPI>();
|
||||
final PatcherAPI _patcherAPI = locator<PatcherAPI>();
|
||||
final GithubAPI _githubAPI = locator<GithubAPI>();
|
||||
final RevancedAPI _revancedAPI = locator<RevancedAPI>();
|
||||
final Toast _toast = locator<Toast>();
|
||||
final flutterLocalNotificationsPlugin = FlutterLocalNotificationsPlugin();
|
||||
DateTime? _lastUpdate;
|
||||
bool showUpdatableApps = false;
|
||||
List<PatchedApplication> patchedInstalledApps = [];
|
||||
List<PatchedApplication> patchedUpdatableApps = [];
|
||||
String _managerVersion = '';
|
||||
|
||||
Future<void> initialize(BuildContext context) async {
|
||||
_managerVersion = await AboutInfo.getInfo().then(
|
||||
(value) => value.keys.contains('version') ? value['version']! : '',
|
||||
);
|
||||
_managerVersion = await _managerAPI.getCurrentManagerVersion();
|
||||
await flutterLocalNotificationsPlugin.initialize(
|
||||
const InitializationSettings(
|
||||
android: AndroidInitializationSettings('ic_notification'),
|
||||
),
|
||||
onSelectNotification: (p) =>
|
||||
DeviceApps.openApp('app.revanced.manager.flutter'),
|
||||
onDidReceiveNotificationResponse: (response) async {
|
||||
if (response.id == 0) {
|
||||
_toast.showBottom('homeView.installingMessage');
|
||||
final File? managerApk = await _managerAPI.downloadManager();
|
||||
if (managerApk != null) {
|
||||
await AppInstaller.installApk(managerApk.path);
|
||||
} else {
|
||||
_toast.showBottom('homeView.errorDownloadMessage');
|
||||
}
|
||||
}
|
||||
},
|
||||
);
|
||||
flutterLocalNotificationsPlugin
|
||||
.resolvePlatformSpecificImplementation<
|
||||
AndroidFlutterLocalNotificationsPlugin>()
|
||||
?.requestPermission();
|
||||
bool isConnected = await Connectivity().checkConnection();
|
||||
final bool isConnected = await Connectivity().checkConnection();
|
||||
if (!isConnected) {
|
||||
_toast.showBottom('homeView.noConnection');
|
||||
}
|
||||
final NotificationAppLaunchDetails? notificationAppLaunchDetails =
|
||||
await flutterLocalNotificationsPlugin.getNotificationAppLaunchDetails();
|
||||
if (notificationAppLaunchDetails?.didNotificationLaunchApp ?? false) {
|
||||
_toast.showBottom('homeView.installingMessage');
|
||||
final File? managerApk = await _managerAPI.downloadManager();
|
||||
if (managerApk != null) {
|
||||
await AppInstaller.installApk(managerApk.path);
|
||||
} else {
|
||||
_toast.showBottom('homeView.errorDownloadMessage');
|
||||
}
|
||||
}
|
||||
_getPatchedApps();
|
||||
_managerAPI.reAssessSavedApps().then((_) => _getPatchedApps());
|
||||
}
|
||||
@ -67,7 +97,7 @@ class HomeViewModel extends BaseViewModel {
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
void navigateToPatcher(PatchedApplication app) async {
|
||||
Future<void> navigateToPatcher(PatchedApplication app) async {
|
||||
locator<PatcherViewModel>().selectedApp = app;
|
||||
locator<PatcherViewModel>().selectedPatches =
|
||||
await _patcherAPI.getAppliedPatches(app.appliedPatches);
|
||||
@ -76,10 +106,7 @@ class HomeViewModel extends BaseViewModel {
|
||||
}
|
||||
|
||||
void _getPatchedApps() {
|
||||
patchedInstalledApps = _managerAPI
|
||||
.getPatchedApps()
|
||||
.where((app) => app.hasUpdates == false)
|
||||
.toList();
|
||||
patchedInstalledApps = _managerAPI.getPatchedApps().toList();
|
||||
patchedUpdatableApps = _managerAPI
|
||||
.getPatchedApps()
|
||||
.where((app) => app.hasUpdates == true)
|
||||
@ -88,59 +115,158 @@ class HomeViewModel extends BaseViewModel {
|
||||
}
|
||||
|
||||
Future<bool> hasManagerUpdates() async {
|
||||
String? latestVersion = await _managerAPI.getLatestManagerVersion();
|
||||
final String? latestVersion = await _managerAPI.getLatestManagerVersion();
|
||||
String currentVersion = await _managerAPI.getCurrentManagerVersion();
|
||||
|
||||
// add v to current version
|
||||
if (!currentVersion.startsWith('v')) {
|
||||
currentVersion = 'v$currentVersion';
|
||||
}
|
||||
|
||||
if (latestVersion != currentVersion) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
Future<bool> hasPatchesUpdates() async {
|
||||
final String? latestVersion = await _managerAPI.getLatestPatchesVersion();
|
||||
final String? currentVersion = await _managerAPI.getCurrentPatchesVersion();
|
||||
if (latestVersion != null) {
|
||||
try {
|
||||
int latestVersionInt =
|
||||
final int latestVersionInt =
|
||||
int.parse(latestVersion.replaceAll(RegExp('[^0-9]'), ''));
|
||||
int currentVersionInt =
|
||||
int.parse(currentVersion.replaceAll(RegExp('[^0-9]'), ''));
|
||||
final int currentVersionInt =
|
||||
int.parse(currentVersion!.replaceAll(RegExp('[^0-9]'), ''));
|
||||
return latestVersionInt > currentVersionInt;
|
||||
} on Exception catch (e, s) {
|
||||
await Sentry.captureException(e, stackTrace: s);
|
||||
} on Exception catch (e) {
|
||||
if (kDebugMode) {
|
||||
print(e);
|
||||
}
|
||||
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 {
|
||||
try {
|
||||
_toast.showBottom('homeView.downloadingMessage');
|
||||
File? managerApk = await _managerAPI.downloadManager();
|
||||
if (managerApk != null) {
|
||||
await flutterLocalNotificationsPlugin.zonedSchedule(
|
||||
0,
|
||||
FlutterI18n.translate(
|
||||
context,
|
||||
'homeView.notificationTitle',
|
||||
),
|
||||
FlutterI18n.translate(
|
||||
context,
|
||||
'homeView.notificationText',
|
||||
),
|
||||
tz.TZDateTime.now(tz.local).add(const Duration(seconds: 5)),
|
||||
const NotificationDetails(
|
||||
android: AndroidNotificationDetails(
|
||||
'revanced_manager_channel',
|
||||
'ReVanced Manager Channel',
|
||||
importance: Importance.max,
|
||||
priority: Priority.high,
|
||||
ticker: 'ticker',
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) => SimpleDialog(
|
||||
contentPadding: const EdgeInsets.all(16.0),
|
||||
title: I18nText(
|
||||
'homeView.downloadingMessage',
|
||||
child: Text(
|
||||
'',
|
||||
style: TextStyle(
|
||||
fontSize: 20,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Theme.of(context).colorScheme.secondary,
|
||||
),
|
||||
),
|
||||
),
|
||||
androidAllowWhileIdle: true,
|
||||
uiLocalNotificationDateInterpretation:
|
||||
UILocalNotificationDateInterpretation.absoluteTime,
|
||||
);
|
||||
children: [
|
||||
Column(
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Icon(
|
||||
Icons.new_releases_outlined,
|
||||
color: Theme.of(context).colorScheme.secondary,
|
||||
),
|
||||
const SizedBox(width: 8.0),
|
||||
Text(
|
||||
'v$_managerVersion',
|
||||
style: TextStyle(
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Theme.of(context).colorScheme.secondary,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 16.0),
|
||||
StreamBuilder<double>(
|
||||
initialData: 0.0,
|
||||
stream: _revancedAPI.managerUpdateProgress.stream,
|
||||
builder: (context, snapshot) {
|
||||
return LinearProgressIndicator(
|
||||
value: snapshot.data! * 0.01,
|
||||
valueColor: AlwaysStoppedAnimation<Color>(
|
||||
Theme.of(context).colorScheme.secondary,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
const SizedBox(height: 16.0),
|
||||
Align(
|
||||
alignment: Alignment.centerRight,
|
||||
child: CustomMaterialButton(
|
||||
label: I18nText('cancelButton'),
|
||||
onPressed: () {
|
||||
_revancedAPI.disposeManagerUpdateProgress();
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
final File? managerApk = await downloadManager();
|
||||
if (managerApk != null) {
|
||||
// await flutterLocalNotificationsPlugin.zonedSchedule(
|
||||
// 0,
|
||||
// FlutterI18n.translate(
|
||||
// context,
|
||||
// 'homeView.notificationTitle',
|
||||
// ),
|
||||
// FlutterI18n.translate(
|
||||
// context,
|
||||
// 'homeView.notificationText',
|
||||
// ),
|
||||
// tz.TZDateTime.now(tz.local).add(const Duration(seconds: 5)),
|
||||
// const NotificationDetails(
|
||||
// android: AndroidNotificationDetails(
|
||||
// 'revanced_manager_channel',
|
||||
// 'ReVanced Manager Channel',
|
||||
// importance: Importance.max,
|
||||
// priority: Priority.high,
|
||||
// ticker: 'ticker',
|
||||
// ),
|
||||
// ),
|
||||
// androidAllowWhileIdle: true,
|
||||
// uiLocalNotificationDateInterpretation:
|
||||
// UILocalNotificationDateInterpretation.absoluteTime,
|
||||
// );
|
||||
_toast.showBottom('homeView.installingMessage');
|
||||
await AppInstaller.installApk(managerApk.path);
|
||||
} else {
|
||||
_toast.showBottom('homeView.errorDownloadMessage');
|
||||
}
|
||||
} on Exception catch (e, s) {
|
||||
await Sentry.captureException(e, stackTrace: s);
|
||||
} on Exception catch (e) {
|
||||
if (kDebugMode) {
|
||||
print(e);
|
||||
}
|
||||
_toast.showBottom('homeView.errorInstallMessage');
|
||||
}
|
||||
}
|
||||
@ -149,7 +275,9 @@ class HomeViewModel extends BaseViewModel {
|
||||
_toast.showBottom('homeView.updatesDisabled');
|
||||
}
|
||||
|
||||
Future<void> showUpdateConfirmationDialog(BuildContext parentContext) {
|
||||
Future<void> showUpdateConfirmationDialog(
|
||||
BuildContext parentContext,
|
||||
) {
|
||||
return showModalBottomSheet(
|
||||
context: parentContext,
|
||||
isScrollControlled: true,
|
||||
@ -178,6 +306,7 @@ class HomeViewModel extends BaseViewModel {
|
||||
_lastUpdate!.difference(DateTime.now()).inSeconds > 2) {
|
||||
_managerAPI.clearAllData();
|
||||
}
|
||||
_toast.showBottom('homeView.refreshSuccess');
|
||||
initialize(context);
|
||||
}
|
||||
}
|
||||
|
@ -2,9 +2,9 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter_i18n/flutter_i18n.dart';
|
||||
import 'package:google_fonts/google_fonts.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/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_sliver_app_bar.dart';
|
||||
import 'package:stacked/stacked.dart';
|
||||
@ -15,7 +15,7 @@ class InstallerView extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ViewModelBuilder<InstallerViewModel>.reactive(
|
||||
onModelReady: (model) => model.initialize(context),
|
||||
onViewModelReady: (model) => model.initialize(context),
|
||||
viewModelBuilder: () => InstallerViewModel(),
|
||||
builder: (context, model, child) => WillPopScope(
|
||||
child: SafeArea(
|
||||
@ -28,7 +28,7 @@ class InstallerView extends StatelessWidget {
|
||||
title: Text(
|
||||
model.headerLogs,
|
||||
style: GoogleFonts.inter(
|
||||
color: Theme.of(context).textTheme.headline6!.color,
|
||||
color: Theme.of(context).textTheme.titleLarge!.color,
|
||||
),
|
||||
),
|
||||
onBackButtonPressed: () => model.onWillPop(context),
|
||||
@ -48,15 +48,15 @@ class InstallerView extends StatelessWidget {
|
||||
),
|
||||
),
|
||||
),
|
||||
1: I18nText(
|
||||
'installerView.exportApkMenuOption',
|
||||
child: const Text(
|
||||
'',
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
1: I18nText(
|
||||
'installerView.exportApkMenuOption',
|
||||
child: const Text(
|
||||
'',
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
),
|
||||
2: I18nText(
|
||||
'installerView.shareLogMenuOption',
|
||||
child: const Text(
|
||||
@ -71,9 +71,9 @@ class InstallerView extends StatelessWidget {
|
||||
),
|
||||
],
|
||||
bottom: PreferredSize(
|
||||
preferredSize: const Size(double.infinity, 1.0),
|
||||
child:
|
||||
GradientProgressIndicator(progress: model.progress!)),
|
||||
preferredSize: const Size(double.infinity, 1.0),
|
||||
child: GradientProgressIndicator(progress: model.progress),
|
||||
),
|
||||
),
|
||||
SliverPadding(
|
||||
padding: const EdgeInsets.all(20.0),
|
||||
|
@ -1,5 +1,6 @@
|
||||
// ignore_for_file: use_build_context_synchronously
|
||||
import 'package:device_apps/device_apps.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.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/ui/views/patcher/patcher_viewmodel.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:wakelock/wakelock.dart';
|
||||
|
||||
@ -51,16 +51,15 @@ class InstallerViewModel extends BaseViewModel {
|
||||
context,
|
||||
'installerView.notificationText',
|
||||
),
|
||||
notificationImportance: AndroidNotificationImportance.Default,
|
||||
notificationIcon: const AndroidResource(
|
||||
name: 'ic_notification',
|
||||
defType: 'drawable',
|
||||
),
|
||||
),
|
||||
).then((value) => FlutterBackground.enableBackgroundExecution());
|
||||
} on Exception catch (e, s) {
|
||||
await Sentry.captureException(e, stackTrace: s);
|
||||
// ignore
|
||||
} on Exception catch (e) {
|
||||
if (kDebugMode) {
|
||||
print(e);
|
||||
} // ignore
|
||||
}
|
||||
}
|
||||
await Wakelock.enable();
|
||||
@ -73,10 +72,10 @@ class InstallerViewModel extends BaseViewModel {
|
||||
switch (call.method) {
|
||||
case 'update':
|
||||
if (call.arguments != null) {
|
||||
Map<dynamic, dynamic> arguments = call.arguments;
|
||||
double progress = arguments['progress'];
|
||||
String header = arguments['header'];
|
||||
String log = arguments['log'];
|
||||
final Map<dynamic, dynamic> arguments = call.arguments;
|
||||
final double progress = arguments['progress'];
|
||||
final String header = arguments['header'];
|
||||
final String log = arguments['log'];
|
||||
update(progress, header, log);
|
||||
}
|
||||
break;
|
||||
@ -133,14 +132,15 @@ class InstallerViewModel extends BaseViewModel {
|
||||
_app.apkFilePath,
|
||||
_patches,
|
||||
);
|
||||
} on Exception catch (e, s) {
|
||||
} on Exception catch (e) {
|
||||
update(
|
||||
-100.0,
|
||||
'Aborting...',
|
||||
'An error occurred! Aborting\nError:\n$e',
|
||||
);
|
||||
await Sentry.captureException(e, stackTrace: s);
|
||||
throw await Sentry.captureException(e, stackTrace: s);
|
||||
if (kDebugMode) {
|
||||
print(e);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
update(-100.0, 'Aborting...', 'No app or patches selected! Aborting');
|
||||
@ -148,24 +148,28 @@ class InstallerViewModel extends BaseViewModel {
|
||||
if (FlutterBackground.isBackgroundExecutionEnabled) {
|
||||
try {
|
||||
FlutterBackground.disableBackgroundExecution();
|
||||
} on Exception catch (e, s) {
|
||||
await Sentry.captureException(e, stackTrace: s);
|
||||
// ignore
|
||||
} on Exception catch (e) {
|
||||
if (kDebugMode) {
|
||||
print(e);
|
||||
} // ignore
|
||||
}
|
||||
}
|
||||
await Wakelock.disable();
|
||||
} on Exception catch (e, s) {
|
||||
await Sentry.captureException(e, stackTrace: s);
|
||||
} on Exception catch (e) {
|
||||
if (kDebugMode) {
|
||||
print(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void installResult(BuildContext context, bool installAsRoot) async {
|
||||
Future<void> installResult(BuildContext context, bool installAsRoot) async {
|
||||
try {
|
||||
_app.isRooted = installAsRoot;
|
||||
bool hasMicroG = _patches.any((p) => p.name.endsWith('microg-support'));
|
||||
bool rootMicroG = installAsRoot && hasMicroG;
|
||||
bool rootFromStorage = installAsRoot && _app.isFromStorage;
|
||||
bool ytWithoutRootMicroG =
|
||||
final bool hasMicroG =
|
||||
_patches.any((p) => p.name.endsWith('microg-support'));
|
||||
final bool rootMicroG = installAsRoot && hasMicroG;
|
||||
final bool rootFromStorage = installAsRoot && _app.isFromStorage;
|
||||
final bool ytWithoutRootMicroG =
|
||||
!installAsRoot && !hasMicroG && _app.packageName.contains('youtube');
|
||||
if (rootMicroG || rootFromStorage || ytWithoutRootMicroG) {
|
||||
return showDialog(
|
||||
@ -212,24 +216,30 @@ class InstallerViewModel extends BaseViewModel {
|
||||
await _managerAPI.savePatchedApp(_app);
|
||||
}
|
||||
}
|
||||
} on Exception catch (e, s) {
|
||||
await Sentry.captureException(e, stackTrace: s);
|
||||
} on Exception catch (e) {
|
||||
if (kDebugMode) {
|
||||
print(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void exportResult() {
|
||||
try {
|
||||
_patcherAPI.exportPatchedFile(_app.name, _app.version);
|
||||
} on Exception catch (e, s) {
|
||||
Sentry.captureException(e, stackTrace: s);
|
||||
} on Exception catch (e) {
|
||||
if (kDebugMode) {
|
||||
print(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void shareResult() {
|
||||
try {
|
||||
_patcherAPI.sharePatchedFile(_app.name, _app.version);
|
||||
} on Exception catch (e, s) {
|
||||
Sentry.captureException(e, stackTrace: s);
|
||||
} on Exception catch (e) {
|
||||
if (kDebugMode) {
|
||||
print(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -243,8 +253,10 @@ class InstallerViewModel extends BaseViewModel {
|
||||
locator<PatcherViewModel>().selectedApp = null;
|
||||
locator<PatcherViewModel>().selectedPatches.clear();
|
||||
locator<PatcherViewModel>().notifyListeners();
|
||||
} on Exception catch (e, s) {
|
||||
await Sentry.captureException(e, stackTrace: s);
|
||||
} on Exception catch (e) {
|
||||
if (kDebugMode) {
|
||||
print(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -11,7 +11,7 @@ class NavigationView extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ViewModelBuilder<NavigationViewModel>.reactive(
|
||||
onModelReady: (model) => model.initialize(context),
|
||||
onViewModelReady: (model) => model.initialize(context),
|
||||
viewModelBuilder: () => locator<NavigationViewModel>(),
|
||||
builder: (context, model, child) => Scaffold(
|
||||
body: PageTransitionSwitcher(
|
||||
@ -42,7 +42,6 @@ class NavigationView extends StatelessWidget {
|
||||
context,
|
||||
'navigationView.dashboardTab',
|
||||
),
|
||||
tooltip: '',
|
||||
),
|
||||
NavigationDestination(
|
||||
icon: model.isIndexSelected(1)
|
||||
@ -52,7 +51,6 @@ class NavigationView extends StatelessWidget {
|
||||
context,
|
||||
'navigationView.patcherTab',
|
||||
),
|
||||
tooltip: '',
|
||||
),
|
||||
NavigationDestination(
|
||||
icon: model.isIndexSelected(2)
|
||||
@ -62,7 +60,6 @@ class NavigationView extends StatelessWidget {
|
||||
context,
|
||||
'navigationView.settingsTab',
|
||||
),
|
||||
tooltip: '',
|
||||
),
|
||||
],
|
||||
),
|
||||
|
@ -15,19 +15,21 @@ import 'package:stacked/stacked.dart';
|
||||
|
||||
@lazySingleton
|
||||
class NavigationViewModel extends IndexTrackingViewModel {
|
||||
void initialize(BuildContext context) async {
|
||||
Future<void> initialize(BuildContext context) async {
|
||||
locator<Toast>().initialize(context);
|
||||
SharedPreferences prefs = await SharedPreferences.getInstance();
|
||||
final SharedPreferences prefs =
|
||||
await SharedPreferences.getInstance();
|
||||
if (prefs.getBool('permissionsRequested') == null) {
|
||||
await prefs.setBool('permissionsRequested', true);
|
||||
RootAPI().hasRootPermissions().then(
|
||||
(value) => Permission.requestInstallPackages.request().then(
|
||||
(value) => Permission.ignoreBatteryOptimizations.request(),
|
||||
(value) =>
|
||||
Permission.ignoreBatteryOptimizations.request(),
|
||||
),
|
||||
);
|
||||
}
|
||||
if (prefs.getBool('useDarkTheme') == null) {
|
||||
bool isDark =
|
||||
final bool isDark =
|
||||
MediaQuery.of(context).platformBrightness != Brightness.light;
|
||||
await prefs.setBool('useDarkTheme', isDark);
|
||||
await DynamicTheme.of(context)!.setTheme(isDark ? 1 : 0);
|
||||
@ -37,7 +39,8 @@ class NavigationViewModel extends IndexTrackingViewModel {
|
||||
SystemUiOverlayStyle(
|
||||
systemNavigationBarColor: Colors.transparent,
|
||||
systemNavigationBarIconBrightness:
|
||||
DynamicTheme.of(context)!.theme.brightness == Brightness.light
|
||||
DynamicTheme.of(context)!.theme.brightness ==
|
||||
Brightness.light
|
||||
? Brightness.dark
|
||||
: Brightness.light,
|
||||
),
|
||||
|
@ -34,7 +34,7 @@ class PatcherView extends StatelessWidget {
|
||||
child: Text(
|
||||
'',
|
||||
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_i18n/flutter_i18n.dart';
|
||||
import 'package:injectable/injectable.dart';
|
||||
@ -8,6 +10,7 @@ import 'package:revanced_manager/models/patched_application.dart';
|
||||
import 'package:revanced_manager/services/manager_api.dart';
|
||||
import 'package:revanced_manager/services/patcher_api.dart';
|
||||
import 'package:revanced_manager/ui/widgets/shared/custom_material_button.dart';
|
||||
import 'package:revanced_manager/utils/about_info.dart';
|
||||
import 'package:stacked/stacked.dart';
|
||||
import 'package:stacked_services/stacked_services.dart';
|
||||
|
||||
@ -40,35 +43,71 @@ class PatcherViewModel extends BaseViewModel {
|
||||
}
|
||||
|
||||
Future<bool> isValidPatchConfig() async {
|
||||
bool needsResourcePatching = await _patcherAPI.needsResourcePatching(
|
||||
final bool needsResourcePatching = await _patcherAPI.needsResourcePatching(
|
||||
selectedPatches,
|
||||
);
|
||||
if (needsResourcePatching && selectedApp != null) {
|
||||
bool isSplit = await _managerAPI.isSplitApk(selectedApp!);
|
||||
final bool isSplit = await _managerAPI.isSplitApk(selectedApp!);
|
||||
return !isSplit;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
Future<void> showPatchConfirmationDialog(BuildContext context) async {
|
||||
bool isValid = await isValidPatchConfig();
|
||||
if (isValid) {
|
||||
navigateToInstaller();
|
||||
} else {
|
||||
final bool isValid = await isValidPatchConfig();
|
||||
if (context.mounted) {
|
||||
if (isValid) {
|
||||
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) =>
|
||||
info['arch'] != null &&
|
||||
info['arch']!.contains('armeabi-v7a') &&
|
||||
!info['arch']!.contains('arm64-v8a'),
|
||||
);
|
||||
|
||||
if (context.mounted && armv7) {
|
||||
return showDialog(
|
||||
context: context,
|
||||
builder: (context) => AlertDialog(
|
||||
title: I18nText('warning'),
|
||||
backgroundColor: Theme.of(context).colorScheme.secondaryContainer,
|
||||
content: I18nText('patcherView.patchDialogText'),
|
||||
content: I18nText('patcherView.armv7WarningDialogText'),
|
||||
actions: <Widget>[
|
||||
CustomMaterialButton(
|
||||
isFilled: false,
|
||||
label: I18nText('noButton'),
|
||||
onPressed: () => Navigator.of(context).pop(),
|
||||
),
|
||||
CustomMaterialButton(
|
||||
label: I18nText('yesButton'),
|
||||
isFilled: false,
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
navigateToInstaller();
|
||||
@ -77,6 +116,8 @@ class PatcherViewModel extends BaseViewModel {
|
||||
],
|
||||
),
|
||||
);
|
||||
} else {
|
||||
navigateToInstaller();
|
||||
}
|
||||
}
|
||||
|
||||
@ -88,31 +129,31 @@ class PatcherViewModel extends BaseViewModel {
|
||||
return text;
|
||||
}
|
||||
|
||||
String getRecommendedVersionString(BuildContext context) {
|
||||
String recommendedVersion =
|
||||
_patcherAPI.getRecommendedVersion(selectedApp!.packageName);
|
||||
if (recommendedVersion.isEmpty) {
|
||||
recommendedVersion = FlutterI18n.translate(
|
||||
String getSuggestedVersionString(BuildContext context) {
|
||||
String suggestedVersion =
|
||||
_patcherAPI.getSuggestedVersion(selectedApp!.packageName);
|
||||
if (suggestedVersion.isEmpty) {
|
||||
suggestedVersion = FlutterI18n.translate(
|
||||
context,
|
||||
'appSelectorCard.anyVersion',
|
||||
);
|
||||
} else {
|
||||
recommendedVersion = 'v$recommendedVersion';
|
||||
suggestedVersion = 'v$suggestedVersion';
|
||||
}
|
||||
return '${FlutterI18n.translate(
|
||||
context,
|
||||
'appSelectorCard.currentVersion',
|
||||
)}: v${selectedApp!.version}\n${FlutterI18n.translate(
|
||||
context,
|
||||
'appSelectorCard.recommendedVersion',
|
||||
)}: $recommendedVersion';
|
||||
'appSelectorCard.suggestedVersion',
|
||||
)}: $suggestedVersion';
|
||||
}
|
||||
|
||||
Future<void> loadLastSelectedPatches() async {
|
||||
this.selectedPatches.clear();
|
||||
List<String> selectedPatches =
|
||||
final List<String> selectedPatches =
|
||||
await _managerAPI.getSelectedPatches(selectedApp!.originalPackageName);
|
||||
List<Patch> patches =
|
||||
final List<Patch> patches =
|
||||
_patcherAPI.getFilteredPatches(selectedApp!.originalPackageName);
|
||||
this
|
||||
.selectedPatches
|
||||
|
@ -2,9 +2,9 @@ import 'package:flutter/material.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/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/search_bar.dart';
|
||||
import 'package:revanced_manager/utils/check_for_supported_patch.dart';
|
||||
import 'package:stacked/stacked.dart';
|
||||
|
||||
class PatchesSelectorView extends StatefulWidget {
|
||||
@ -20,7 +20,7 @@ class _PatchesSelectorViewState extends State<PatchesSelectorView> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ViewModelBuilder<PatchesSelectorViewModel>.reactive(
|
||||
onModelReady: (model) => model.initialize(),
|
||||
onViewModelReady: (model) => model.initialize(),
|
||||
viewModelBuilder: () => PatchesSelectorViewModel(),
|
||||
builder: (context, model, child) => Scaffold(
|
||||
resizeToAvoidBottomInset: false,
|
||||
@ -45,26 +45,24 @@ class _PatchesSelectorViewState extends State<PatchesSelectorView> {
|
||||
SliverAppBar(
|
||||
pinned: true,
|
||||
floating: true,
|
||||
snap: false,
|
||||
title: I18nText(
|
||||
'patchesSelectorView.viewTitle',
|
||||
child: Text(
|
||||
'',
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).textTheme.headline6!.color,
|
||||
color: Theme.of(context).textTheme.titleLarge!.color,
|
||||
),
|
||||
),
|
||||
),
|
||||
leading: IconButton(
|
||||
icon: Icon(
|
||||
Icons.arrow_back,
|
||||
color: Theme.of(context).textTheme.headline6!.color,
|
||||
color: Theme.of(context).textTheme.titleLarge!.color,
|
||||
),
|
||||
onPressed: () => Navigator.of(context).pop(),
|
||||
),
|
||||
actions: [
|
||||
Container(
|
||||
height: 2,
|
||||
margin: const EdgeInsets.only(top: 12, bottom: 12),
|
||||
padding:
|
||||
const EdgeInsets.symmetric(horizontal: 6, vertical: 6),
|
||||
@ -76,7 +74,7 @@ class _PatchesSelectorViewState extends State<PatchesSelectorView> {
|
||||
child: Text(
|
||||
model.patchesVersion!,
|
||||
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,
|
||||
),
|
||||
child: SearchBar(
|
||||
showSelectIcon: true,
|
||||
hintText: FlutterI18n.translate(
|
||||
context,
|
||||
'patchesSelectorView.searchBarHint',
|
||||
@ -113,12 +110,6 @@ class _PatchesSelectorViewState extends State<PatchesSelectorView> {
|
||||
_query = searchQuery;
|
||||
});
|
||||
},
|
||||
onSelectAll: (value) {
|
||||
if (value) {
|
||||
model.selectAllPatcherWarning(context);
|
||||
}
|
||||
model.selectAllPatches(value);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
@ -144,24 +135,24 @@ class _PatchesSelectorViewState extends State<PatchesSelectorView> {
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
CustomChip(
|
||||
label:
|
||||
I18nText('patchesSelectorView.recommended'),
|
||||
onSelected: (value) {
|
||||
model.selectRecommendedPatches();
|
||||
ActionChip(
|
||||
label: I18nText('patchesSelectorView.default'),
|
||||
tooltip: FlutterI18n.translate(
|
||||
context,
|
||||
'patchesSelectorView.defaultTooltip',
|
||||
),
|
||||
onPressed: () {
|
||||
model.selectDefaultPatches();
|
||||
},
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
CustomChip(
|
||||
label: I18nText('patchesSelectorView.all'),
|
||||
onSelected: (value) {
|
||||
model.selectAllPatches(true);
|
||||
},
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
CustomChip(
|
||||
ActionChip(
|
||||
label: I18nText('patchesSelectorView.none'),
|
||||
onSelected: (value) {
|
||||
tooltip: FlutterI18n.translate(
|
||||
context,
|
||||
'patchesSelectorView.noneTooltip',
|
||||
),
|
||||
onPressed: () {
|
||||
model.clearPatches();
|
||||
},
|
||||
),
|
||||
@ -178,7 +169,7 @@ class _PatchesSelectorViewState extends State<PatchesSelectorView> {
|
||||
packageVersion: model.getAppVersion(),
|
||||
supportedPackageVersions:
|
||||
model.getSupportedVersions(patch),
|
||||
isUnsupported: !model.isPatchSupported(patch),
|
||||
isUnsupported: !isPatchSupported(patch),
|
||||
isSelected: model.isSelected(patch),
|
||||
onChanged: (value) =>
|
||||
model.selectPatch(patch, value),
|
||||
|
@ -1,5 +1,4 @@
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:flutter_i18n/widgets/I18nText.dart';
|
||||
import 'package:revanced_manager/app/app.locator.dart';
|
||||
import 'package:revanced_manager/models/patch.dart';
|
||||
import 'package:revanced_manager/models/patched_application.dart';
|
||||
@ -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/toast.dart';
|
||||
import 'package:revanced_manager/ui/views/patcher/patcher_viewmodel.dart';
|
||||
import 'package:revanced_manager/ui/widgets/shared/custom_material_button.dart';
|
||||
import 'package:revanced_manager/utils/check_for_supported_patch.dart';
|
||||
import 'package:stacked/stacked.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class PatchesSelectorViewModel extends BaseViewModel {
|
||||
final PatcherAPI _patcherAPI = locator<PatcherAPI>();
|
||||
@ -25,10 +23,12 @@ class PatchesSelectorViewModel extends BaseViewModel {
|
||||
}
|
||||
|
||||
Future<void> initialize() async {
|
||||
getPatchesVersion();
|
||||
patches.addAll(_patcherAPI.getFilteredPatches(
|
||||
locator<PatcherViewModel>().selectedApp!.originalPackageName,
|
||||
));
|
||||
getPatchesVersion().whenComplete(() => notifyListeners());
|
||||
patches.addAll(
|
||||
_patcherAPI.getFilteredPatches(
|
||||
locator<PatcherViewModel>().selectedApp!.originalPackageName,
|
||||
),
|
||||
);
|
||||
patches.sort((a, b) => a.name.compareTo(b.name));
|
||||
notifyListeners();
|
||||
}
|
||||
@ -48,44 +48,15 @@ class PatchesSelectorViewModel extends BaseViewModel {
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
Future<void> selectAllPatcherWarning(BuildContext context) {
|
||||
return showDialog(
|
||||
context: context,
|
||||
builder: (context) => AlertDialog(
|
||||
title: I18nText('warning'),
|
||||
backgroundColor: Theme.of(context).colorScheme.secondaryContainer,
|
||||
content: I18nText('patchesSelectorView.selectAllPatchesWarningContent'),
|
||||
actions: <Widget>[
|
||||
CustomMaterialButton(
|
||||
label: I18nText('okButton'),
|
||||
onPressed: () => Navigator.of(context).pop(),
|
||||
)
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void selectAllPatches(bool isSelected) {
|
||||
selectedPatches.clear();
|
||||
|
||||
if (isSelected && _managerAPI.areExperimentalPatchesEnabled() == false) {
|
||||
selectedPatches
|
||||
.addAll(patches.where((element) => isPatchSupported(element)));
|
||||
}
|
||||
|
||||
if (isSelected && _managerAPI.areExperimentalPatchesEnabled()) {
|
||||
selectedPatches.addAll(patches);
|
||||
}
|
||||
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
void selectRecommendedPatches() {
|
||||
void selectDefaultPatches() {
|
||||
selectedPatches.clear();
|
||||
|
||||
if (_managerAPI.areExperimentalPatchesEnabled() == false) {
|
||||
selectedPatches.addAll(patches.where(
|
||||
(element) => element.excluded == false && isPatchSupported(element)));
|
||||
selectedPatches.addAll(
|
||||
patches.where(
|
||||
(element) => element.excluded == false && isPatchSupported(element),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
if (_managerAPI.areExperimentalPatchesEnabled()) {
|
||||
@ -117,15 +88,18 @@ class PatchesSelectorViewModel extends BaseViewModel {
|
||||
patchesVersion = await _githubAPI
|
||||
.getLastestReleaseVersion(_managerAPI.getPatchesRepo());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
List<Patch> getQueriedPatches(String query) {
|
||||
return patches
|
||||
.where((patch) =>
|
||||
query.isEmpty ||
|
||||
query.length < 2 ||
|
||||
patch.name.toLowerCase().contains(query.toLowerCase()) ||
|
||||
patch.getSimpleName().toLowerCase().contains(query.toLowerCase()))
|
||||
.where(
|
||||
(patch) =>
|
||||
query.isEmpty ||
|
||||
query.length < 2 ||
|
||||
patch.name.toLowerCase().contains(query.toLowerCase()) ||
|
||||
patch.getSimpleName().toLowerCase().contains(query.toLowerCase()),
|
||||
)
|
||||
.toList();
|
||||
}
|
||||
|
||||
@ -134,8 +108,8 @@ class PatchesSelectorViewModel extends BaseViewModel {
|
||||
}
|
||||
|
||||
List<String> getSupportedVersions(Patch patch) {
|
||||
PatchedApplication app = locator<PatcherViewModel>().selectedApp!;
|
||||
Package? package = patch.compatiblePackages.firstWhereOrNull(
|
||||
final PatchedApplication app = locator<PatcherViewModel>().selectedApp!;
|
||||
final Package? package = patch.compatiblePackages.firstWhereOrNull(
|
||||
(pack) => pack.name == app.packageName,
|
||||
);
|
||||
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) {
|
||||
switch (value) {
|
||||
case 0:
|
||||
@ -161,20 +128,23 @@ class PatchesSelectorViewModel extends BaseViewModel {
|
||||
}
|
||||
|
||||
Future<void> saveSelectedPatches() async {
|
||||
List<String> selectedPatches =
|
||||
final List<String> selectedPatches =
|
||||
this.selectedPatches.map((patch) => patch.name).toList();
|
||||
await _managerAPI.setSelectedPatches(
|
||||
locator<PatcherViewModel>().selectedApp!.originalPackageName,
|
||||
selectedPatches);
|
||||
locator<PatcherViewModel>().selectedApp!.originalPackageName,
|
||||
selectedPatches,
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> loadSelectedPatches() async {
|
||||
List<String> selectedPatches = await _managerAPI.getSelectedPatches(
|
||||
locator<PatcherViewModel>().selectedApp!.originalPackageName);
|
||||
final List<String> selectedPatches = await _managerAPI.getSelectedPatches(
|
||||
locator<PatcherViewModel>().selectedApp!.originalPackageName,
|
||||
);
|
||||
if (selectedPatches.isNotEmpty) {
|
||||
this.selectedPatches.clear();
|
||||
this.selectedPatches.addAll(
|
||||
patches.where((patch) => selectedPatches.contains(patch.name)));
|
||||
patches.where((patch) => selectedPatches.contains(patch.name)),
|
||||
);
|
||||
} else {
|
||||
locator<Toast>().showBottom('patchesSelectorView.noSavedPatches');
|
||||
}
|
||||
|
@ -4,6 +4,7 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter_i18n/flutter_i18n.dart';
|
||||
import 'package:revanced_manager/app/app.locator.dart';
|
||||
import 'package:revanced_manager/services/manager_api.dart';
|
||||
import 'package:revanced_manager/services/toast.dart';
|
||||
import 'package:revanced_manager/ui/widgets/settingsView/custom_text_field.dart';
|
||||
import 'package:revanced_manager/ui/widgets/settingsView/settings_tile_dialog.dart';
|
||||
import 'package:revanced_manager/ui/widgets/shared/custom_material_button.dart';
|
||||
@ -11,11 +12,12 @@ import 'package:stacked/stacked.dart';
|
||||
|
||||
class SManageApiUrl extends BaseViewModel {
|
||||
final ManagerAPI _managerAPI = locator<ManagerAPI>();
|
||||
final Toast _toast = locator<Toast>();
|
||||
|
||||
final TextEditingController _apiUrlController = TextEditingController();
|
||||
|
||||
Future<void> showApiUrlDialog(BuildContext context) async {
|
||||
String apiUrl = _managerAPI.getApiUrl();
|
||||
final String apiUrl = _managerAPI.getApiUrl();
|
||||
_apiUrlController.text = apiUrl.replaceAll('https://', '');
|
||||
return showDialog(
|
||||
context: context,
|
||||
@ -90,7 +92,7 @@ class SManageApiUrl extends BaseViewModel {
|
||||
label: I18nText('yesButton'),
|
||||
onPressed: () {
|
||||
_managerAPI.setApiUrl('');
|
||||
Navigator.of(context).pop();
|
||||
_toast.showBottom('settingsView.restartAppForChanges');
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
)
|
@ -0,0 +1,86 @@
|
||||
// ignore_for_file: use_build_context_synchronously
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_i18n/flutter_i18n.dart';
|
||||
import 'package:revanced_manager/app/app.locator.dart';
|
||||
import 'package:revanced_manager/services/manager_api.dart';
|
||||
import 'package:revanced_manager/ui/widgets/settingsView/custom_text_field.dart';
|
||||
import 'package:revanced_manager/ui/widgets/settingsView/settings_tile_dialog.dart';
|
||||
import 'package:revanced_manager/ui/widgets/shared/custom_material_button.dart';
|
||||
import 'package:stacked/stacked.dart';
|
||||
|
||||
class SManageKeystorePassword extends BaseViewModel {
|
||||
final ManagerAPI _managerAPI = locator<ManagerAPI>();
|
||||
|
||||
final TextEditingController _keystorePasswordController =
|
||||
TextEditingController();
|
||||
|
||||
Future<void> showKeystoreDialog(BuildContext context) async {
|
||||
final String keystorePasswordText = _managerAPI.getKeystorePassword();
|
||||
_keystorePasswordController.text = keystorePasswordText;
|
||||
return showDialog(
|
||||
context: context,
|
||||
builder: (context) => AlertDialog(
|
||||
title: Row(
|
||||
children: <Widget>[
|
||||
I18nText('settingsView.selectKeystorePassword'),
|
||||
const Spacer(),
|
||||
IconButton(
|
||||
icon: const Icon(Icons.manage_history_outlined),
|
||||
onPressed: () => _keystorePasswordController.text =
|
||||
_managerAPI.defaultKeystorePassword,
|
||||
color: Theme.of(context).colorScheme.secondary,
|
||||
)
|
||||
],
|
||||
),
|
||||
backgroundColor: Theme.of(context).colorScheme.secondaryContainer,
|
||||
content: SingleChildScrollView(
|
||||
child: Column(
|
||||
children: <Widget>[
|
||||
CustomTextField(
|
||||
inputController: _keystorePasswordController,
|
||||
label: I18nText('settingsView.selectKeystorePassword'),
|
||||
hint: '',
|
||||
onChanged: (value) => notifyListeners(),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
actions: <Widget>[
|
||||
CustomMaterialButton(
|
||||
isFilled: false,
|
||||
label: I18nText('cancelButton'),
|
||||
onPressed: () {
|
||||
_keystorePasswordController.clear();
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
),
|
||||
CustomMaterialButton(
|
||||
label: I18nText('okButton'),
|
||||
onPressed: () {
|
||||
final String passwd = _keystorePasswordController.text;
|
||||
_managerAPI.setKeystorePassword(passwd);
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
)
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
final sManageKeystorePassword = SManageKeystorePassword();
|
||||
|
||||
class SManageKeystorePasswordUI extends StatelessWidget {
|
||||
const SManageKeystorePasswordUI({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SettingsTileDialog(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 20.0),
|
||||
title: 'settingsView.selectKeystorePassword',
|
||||
subtitle: 'settingsView.selectKeystorePasswordHint',
|
||||
onTap: () => sManageKeystorePassword.showKeystoreDialog(context),
|
||||
);
|
||||
}
|
||||
}
|
@ -4,6 +4,7 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter_i18n/flutter_i18n.dart';
|
||||
import 'package:revanced_manager/app/app.locator.dart';
|
||||
import 'package:revanced_manager/services/manager_api.dart';
|
||||
import 'package:revanced_manager/services/toast.dart';
|
||||
import 'package:revanced_manager/ui/widgets/settingsView/custom_text_field.dart';
|
||||
import 'package:revanced_manager/ui/widgets/settingsView/settings_tile_dialog.dart';
|
||||
import 'package:revanced_manager/ui/widgets/shared/custom_material_button.dart';
|
||||
@ -11,6 +12,7 @@ import 'package:stacked/stacked.dart';
|
||||
|
||||
class SManageSources extends BaseViewModel {
|
||||
final ManagerAPI _managerAPI = locator<ManagerAPI>();
|
||||
final Toast _toast = locator<Toast>();
|
||||
|
||||
final TextEditingController _hostSourceController = TextEditingController();
|
||||
final TextEditingController _orgPatSourceController = TextEditingController();
|
||||
@ -19,9 +21,9 @@ class SManageSources extends BaseViewModel {
|
||||
final TextEditingController _intSourceController = TextEditingController();
|
||||
|
||||
Future<void> showSourcesDialog(BuildContext context) async {
|
||||
String hostRepository = _managerAPI.getRepoUrl();
|
||||
String patchesRepo = _managerAPI.getPatchesRepo();
|
||||
String integrationsRepo = _managerAPI.getIntegrationsRepo();
|
||||
final String hostRepository = _managerAPI.getRepoUrl();
|
||||
final String patchesRepo = _managerAPI.getPatchesRepo();
|
||||
final String integrationsRepo = _managerAPI.getIntegrationsRepo();
|
||||
_hostSourceController.text = hostRepository;
|
||||
_orgPatSourceController.text = patchesRepo.split('/')[0];
|
||||
_patSourceController.text = patchesRepo.split('/')[1];
|
||||
@ -117,13 +119,14 @@ class SManageSources extends BaseViewModel {
|
||||
CustomMaterialButton(
|
||||
label: I18nText('okButton'),
|
||||
onPressed: () {
|
||||
_managerAPI.setRepoUrl(_hostSourceController.text);
|
||||
_managerAPI.setRepoUrl(_hostSourceController.text.trim());
|
||||
_managerAPI.setPatchesRepo(
|
||||
'${_orgPatSourceController.text}/${_patSourceController.text}',
|
||||
'${_orgPatSourceController.text.trim()}/${_patSourceController.text.trim()}',
|
||||
);
|
||||
_managerAPI.setIntegrationsRepo(
|
||||
'${_orgIntSourceController.text}/${_intSourceController.text}',
|
||||
'${_orgIntSourceController.text.trim()}/${_intSourceController.text.trim()}',
|
||||
);
|
||||
_toast.showBottom('settingsView.restartAppForChanges');
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
)
|
||||
@ -151,9 +154,10 @@ class SManageSources extends BaseViewModel {
|
||||
_managerAPI.setRepoUrl('');
|
||||
_managerAPI.setPatchesRepo('');
|
||||
_managerAPI.setIntegrationsRepo('');
|
||||
Navigator.of(context).pop();
|
||||
Navigator.of(context).pop();
|
||||
Navigator.of(context).pop();
|
||||
_toast.showBottom('settingsView.restartAppForChanges');
|
||||
Navigator.of(context)
|
||||
..pop()
|
||||
..pop();
|
||||
},
|
||||
)
|
||||
],
|
@ -4,7 +4,6 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter_i18n/flutter_i18n.dart';
|
||||
import 'package:revanced_manager/app/app.locator.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/ui/views/navigation/navigation_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();
|
||||
|
||||
class SUpdateLanguage extends BaseViewModel {
|
||||
final CrowdinAPI _crowdinAPI = locator<CrowdinAPI>();
|
||||
final Toast _toast = locator<Toast>();
|
||||
late SharedPreferences _prefs;
|
||||
String selectedLanguage = 'English';
|
||||
@ -43,7 +41,6 @@ class SUpdateLanguage extends BaseViewModel {
|
||||
}
|
||||
|
||||
Future<void> initLang() async {
|
||||
languages = await _crowdinAPI.getLanguages();
|
||||
languages.sort((a, b) => a['name'].compareTo(b['name']));
|
||||
notifyListeners();
|
||||
}
|
@ -7,7 +7,6 @@ import 'package:flutter_i18n/widgets/I18nText.dart';
|
||||
import 'package:revanced_manager/app/app.locator.dart';
|
||||
import 'package:revanced_manager/services/manager_api.dart';
|
||||
import 'package:revanced_manager/ui/views/settings/settings_viewmodel.dart';
|
||||
import 'package:revanced_manager/ui/widgets/settingsView/custom_switch_tile.dart';
|
||||
import 'package:revanced_manager/ui/widgets/settingsView/settings_section.dart';
|
||||
import 'package:stacked/stacked.dart';
|
||||
|
||||
@ -23,9 +22,9 @@ class SUpdateTheme extends BaseViewModel {
|
||||
return _managerAPI.getUseDynamicTheme();
|
||||
}
|
||||
|
||||
void setUseDynamicTheme(BuildContext context, bool value) async {
|
||||
Future<void> setUseDynamicTheme(BuildContext context, bool value) async {
|
||||
await _managerAPI.setUseDynamicTheme(value);
|
||||
int currentTheme = DynamicTheme.of(context)!.themeId;
|
||||
final int currentTheme = DynamicTheme.of(context)!.themeId;
|
||||
if (currentTheme.isEven) {
|
||||
await DynamicTheme.of(context)!.setTheme(value ? 2 : 0);
|
||||
} else {
|
||||
@ -38,9 +37,9 @@ class SUpdateTheme extends BaseViewModel {
|
||||
return _managerAPI.getUseDarkTheme();
|
||||
}
|
||||
|
||||
void setUseDarkTheme(BuildContext context, bool value) async {
|
||||
Future<void> setUseDarkTheme(BuildContext context, bool value) async {
|
||||
await _managerAPI.setUseDarkTheme(value);
|
||||
int currentTheme = DynamicTheme.of(context)!.themeId;
|
||||
final int currentTheme = DynamicTheme.of(context)!.themeId;
|
||||
if (currentTheme < 2) {
|
||||
await DynamicTheme.of(context)!.setTheme(value ? 1 : 0);
|
||||
} else {
|
||||
@ -64,8 +63,8 @@ class SUpdateThemeUI extends StatelessWidget {
|
||||
return SettingsSection(
|
||||
title: 'settingsView.appearanceSectionTitle',
|
||||
children: <Widget>[
|
||||
CustomSwitchTile(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 20.0),
|
||||
SwitchListTile(
|
||||
contentPadding: const EdgeInsets.symmetric(horizontal: 20.0),
|
||||
title: I18nText(
|
||||
'settingsView.darkThemeLabel',
|
||||
child: const Text(
|
||||
@ -78,7 +77,7 @@ class SUpdateThemeUI extends StatelessWidget {
|
||||
),
|
||||
subtitle: I18nText('settingsView.darkThemeHint'),
|
||||
value: SUpdateTheme().getDarkThemeStatus(),
|
||||
onTap: (value) => SUpdateTheme().setUseDarkTheme(
|
||||
onChanged: (value) => SUpdateTheme().setUseDarkTheme(
|
||||
context,
|
||||
value,
|
||||
),
|
||||
@ -88,8 +87,8 @@ class SUpdateThemeUI extends StatelessWidget {
|
||||
builder: (context, snapshot) => Visibility(
|
||||
visible:
|
||||
snapshot.hasData && snapshot.data! >= ANDROID_12_SDK_VERSION,
|
||||
child: CustomSwitchTile(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 20.0),
|
||||
child: SwitchListTile(
|
||||
contentPadding: const EdgeInsets.symmetric(horizontal: 20.0),
|
||||
title: I18nText(
|
||||
'settingsView.dynamicThemeLabel',
|
||||
child: const Text(
|
||||
@ -102,11 +101,12 @@ class SUpdateThemeUI extends StatelessWidget {
|
||||
),
|
||||
subtitle: I18nText('settingsView.dynamicThemeHint'),
|
||||
value: _settingViewModel.sUpdateTheme.getDynamicThemeStatus(),
|
||||
onTap: (value) =>
|
||||
_settingViewModel.sUpdateTheme.setUseDynamicTheme(
|
||||
context,
|
||||
value,
|
||||
),
|
||||
onChanged: (value) => {
|
||||
_settingViewModel.sUpdateTheme.setUseDynamicTheme(
|
||||
context,
|
||||
value,
|
||||
),
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
@ -3,8 +3,7 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_i18n/flutter_i18n.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/settingsFragement/settings_update_theme.dart';
|
||||
import 'package:revanced_manager/ui/views/settings/settingsFragment/settings_update_theme.dart';
|
||||
import 'package:revanced_manager/ui/views/settings/settings_viewmodel.dart';
|
||||
import 'package:revanced_manager/ui/widgets/settingsView/settings_advanced_section.dart';
|
||||
import 'package:revanced_manager/ui/widgets/settingsView/settings_export_section.dart';
|
||||
@ -33,7 +32,7 @@ class SettingsView extends StatelessWidget {
|
||||
child: Text(
|
||||
'',
|
||||
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,
|
||||
SExportSection(),
|
||||
_settingsDivider,
|
||||
// SLoggingSection(),
|
||||
// _settingsDivider,
|
||||
SInfoSection(),
|
||||
],
|
||||
),
|
||||
|
@ -2,6 +2,7 @@ import 'dart:io';
|
||||
import 'package:cr_file_saver/file_saver.dart';
|
||||
import 'package:device_info_plus/device_info_plus.dart';
|
||||
import 'package:file_picker/file_picker.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:logcat/logcat.dart';
|
||||
import 'package:path_provider/path_provider.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/toast.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/settingsFragement/settings_update_theme.dart';
|
||||
import 'package:sentry_flutter/sentry_flutter.dart';
|
||||
import 'package:revanced_manager/ui/views/settings/settingsFragment/settings_update_language.dart';
|
||||
import 'package:revanced_manager/ui/views/settings/settingsFragment/settings_update_theme.dart';
|
||||
import 'package:share_extend/share_extend.dart';
|
||||
import 'package:stacked/stacked.dart';
|
||||
import 'package:stacked_services/stacked_services.dart';
|
||||
|
||||
class SettingsViewModel extends BaseViewModel {
|
||||
final NavigationService _navigationService = locator<NavigationService>();
|
||||
final NavigationService _navigationService =
|
||||
locator<NavigationService>();
|
||||
final ManagerAPI _managerAPI = locator<ManagerAPI>();
|
||||
final Toast _toast = locator<Toast>();
|
||||
|
||||
@ -28,16 +29,6 @@ class SettingsViewModel extends BaseViewModel {
|
||||
_navigationService.navigateTo(Routes.contributorsView);
|
||||
}
|
||||
|
||||
bool isSentryEnabled() {
|
||||
return _managerAPI.isSentryEnabled();
|
||||
}
|
||||
|
||||
void useSentry(bool value) {
|
||||
_managerAPI.setSentryStatus(value);
|
||||
_toast.showBottom('settingsView.restartAppForChanges');
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
bool areUniversalPatchesEnabled() {
|
||||
return _managerAPI.areUniversalPatchesEnabled();
|
||||
}
|
||||
@ -70,47 +61,91 @@ class SettingsViewModel extends BaseViewModel {
|
||||
|
||||
Future<void> exportPatches() async {
|
||||
try {
|
||||
File outFile = File(_managerAPI.storedPatchesFile);
|
||||
final File outFile = File(_managerAPI.storedPatchesFile);
|
||||
if (outFile.existsSync()) {
|
||||
String dateTime =
|
||||
DateTime.now().toString().replaceAll(' ', '_').split('.').first;
|
||||
String tempFilePath =
|
||||
'${outFile.path.substring(0, outFile.path.lastIndexOf('/') + 1)}selected_patches_$dateTime.json';
|
||||
outFile.copySync(tempFilePath);
|
||||
await CRFileSaver.saveFileWithDialog(SaveFileDialogParams(
|
||||
sourceFilePath: tempFilePath, destinationFileName: ''));
|
||||
File(tempFilePath).delete();
|
||||
locator<Toast>().showBottom('settingsView.exportedPatches');
|
||||
final String dateTime = DateTime.now()
|
||||
.toString()
|
||||
.replaceAll(' ', '_')
|
||||
.split('.')
|
||||
.first;
|
||||
await CRFileSaver.saveFileWithDialog(
|
||||
SaveFileDialogParams(
|
||||
sourceFilePath: outFile.path,
|
||||
destinationFileName: 'selected_patches_$dateTime.json',
|
||||
),
|
||||
);
|
||||
_toast.showBottom('settingsView.exportedPatches');
|
||||
} 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 {
|
||||
try {
|
||||
FilePickerResult? result = await FilePicker.platform.pickFiles(
|
||||
final FilePickerResult? result =
|
||||
await FilePicker.platform.pickFiles(
|
||||
type: FileType.custom,
|
||||
allowedExtensions: ['json'],
|
||||
);
|
||||
if (result != null && result.files.single.path != null) {
|
||||
File inFile = File(result.files.single.path!);
|
||||
final File storedPatchesFile = File(_managerAPI.storedPatchesFile);
|
||||
if (!storedPatchesFile.existsSync()) {
|
||||
storedPatchesFile.createSync(recursive: true);
|
||||
}
|
||||
inFile.copySync(storedPatchesFile.path);
|
||||
final File inFile = File(result.files.single.path!);
|
||||
inFile.copySync(_managerAPI.storedPatchesFile);
|
||||
inFile.delete();
|
||||
if (locator<PatcherViewModel>().selectedApp != null) {
|
||||
locator<PatcherViewModel>().loadLastSelectedPatches();
|
||||
}
|
||||
locator<Toast>().showBottom('settingsView.importedPatches');
|
||||
_toast.showBottom('settingsView.importedPatches');
|
||||
}
|
||||
} on Exception catch (e, s) {
|
||||
await Sentry.captureException(e, stackTrace: s);
|
||||
locator<Toast>().showBottom('settingsView.jsonSelectorErrorMessage');
|
||||
} on Exception catch (e) {
|
||||
if (kDebugMode) {
|
||||
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 {
|
||||
AndroidDeviceInfo info = await DeviceInfoPlugin().androidInfo;
|
||||
return info.version.sdkInt ?? -1;
|
||||
final AndroidDeviceInfo info = await DeviceInfoPlugin().androidInfo;
|
||||
return info.version.sdkInt;
|
||||
}
|
||||
|
||||
Future<void> deleteLogs() async {
|
||||
Directory appCacheDir = await getTemporaryDirectory();
|
||||
Directory logsDir = Directory('${appCacheDir.path}/logs');
|
||||
final Directory appCacheDir = await getTemporaryDirectory();
|
||||
final Directory logsDir = Directory('${appCacheDir.path}/logs');
|
||||
if (logsDir.existsSync()) {
|
||||
logsDir.deleteSync(recursive: true);
|
||||
}
|
||||
@ -134,17 +169,18 @@ class SettingsViewModel extends BaseViewModel {
|
||||
}
|
||||
|
||||
Future<void> exportLogcatLogs() async {
|
||||
Directory appCache = await getTemporaryDirectory();
|
||||
Directory logDir = Directory('${appCache.path}/logs');
|
||||
final Directory appCache = await getTemporaryDirectory();
|
||||
final Directory logDir = Directory('${appCache.path}/logs');
|
||||
logDir.createSync();
|
||||
String dateTime = DateTime.now()
|
||||
final String dateTime = DateTime.now()
|
||||
.toIso8601String()
|
||||
.replaceAll('-', '')
|
||||
.replaceAll(':', '')
|
||||
.replaceAll('T', '')
|
||||
.replaceAll('.', '');
|
||||
File logcat = File('${logDir.path}/revanced-manager_logcat_$dateTime.log');
|
||||
String logs = await Logcat.execute();
|
||||
final File logcat =
|
||||
File('${logDir.path}/revanced-manager_logcat_$dateTime.log');
|
||||
final String logs = await Logcat.execute();
|
||||
logcat.writeAsStringSync(logs);
|
||||
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';
|
||||
|
||||
class AppInfoView extends StatelessWidget {
|
||||
final PatchedApplication app;
|
||||
|
||||
const AppInfoView({
|
||||
Key? key,
|
||||
required this.app,
|
||||
}) : super(key: key);
|
||||
final PatchedApplication app;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@ -28,7 +27,7 @@ class AppInfoView extends StatelessWidget {
|
||||
child: Text(
|
||||
'',
|
||||
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(
|
||||
app.name,
|
||||
textAlign: TextAlign.center,
|
||||
style: Theme.of(context).textTheme.headline6,
|
||||
style: Theme.of(context).textTheme.titleLarge,
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
app.version,
|
||||
textAlign: TextAlign.center,
|
||||
style: Theme.of(context).textTheme.subtitle1,
|
||||
style: Theme.of(context).textTheme.titleLarge,
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
Padding(
|
||||
|
@ -29,7 +29,7 @@ class AppInfoViewModel extends BaseViewModel {
|
||||
) async {
|
||||
bool isUninstalled = true;
|
||||
if (app.isRooted) {
|
||||
bool hasRootPermissions = await _rootAPI.hasRootPermissions();
|
||||
final bool hasRootPermissions = await _rootAPI.hasRootPermissions();
|
||||
if (hasRootPermissions) {
|
||||
await _rootAPI.deleteApp(app.packageName, app.apkFilePath);
|
||||
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>().selectedPatches =
|
||||
await _patcherAPI.getAppliedPatches(app.appliedPatches);
|
||||
@ -62,7 +62,7 @@ class AppInfoViewModel extends BaseViewModel {
|
||||
PatchedApplication app,
|
||||
bool onlyUnpatch,
|
||||
) async {
|
||||
bool hasRootPermissions = await _rootAPI.hasRootPermissions();
|
||||
final bool hasRootPermissions = await _rootAPI.hasRootPermissions();
|
||||
if (app.isRooted && !hasRootPermissions) {
|
||||
return showDialog(
|
||||
context: context,
|
||||
@ -134,7 +134,8 @@ class AppInfoViewModel extends BaseViewModel {
|
||||
title: I18nText('appInfoView.appliedPatchesLabel'),
|
||||
backgroundColor: Theme.of(context).colorScheme.secondaryContainer,
|
||||
content: SingleChildScrollView(
|
||||
child: Text(getAppliedPatchesString(app.appliedPatches))),
|
||||
child: Text(getAppliedPatchesString(app.appliedPatches)),
|
||||
),
|
||||
actions: <Widget>[
|
||||
CustomMaterialButton(
|
||||
label: I18nText('okButton'),
|
||||
@ -146,13 +147,15 @@ class AppInfoViewModel extends BaseViewModel {
|
||||
}
|
||||
|
||||
String getAppliedPatchesString(List<String> appliedPatches) {
|
||||
List<String> names = appliedPatches
|
||||
.map((p) => p
|
||||
.replaceAll('-', ' ')
|
||||
.split('-')
|
||||
.join(' ')
|
||||
.toTitleCase()
|
||||
.replaceFirst('Microg', 'MicroG'))
|
||||
final List<String> names = appliedPatches
|
||||
.map(
|
||||
(p) => p
|
||||
.replaceAll('-', ' ')
|
||||
.split('-')
|
||||
.join(' ')
|
||||
.toTitleCase()
|
||||
.replaceFirst('Microg', 'MicroG'),
|
||||
)
|
||||
.toList();
|
||||
return '\u2022 ${names.join('\n\u2022 ')}';
|
||||
}
|
||||
|
@ -21,7 +21,6 @@ class AppSkeletonLoader extends StatelessWidget {
|
||||
style: SkeletonAvatarStyle(
|
||||
width: screenWidth * 0.15,
|
||||
height: screenWidth * 0.15,
|
||||
shape: BoxShape.rectangle,
|
||||
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';
|
||||
|
||||
class InstalledAppItem extends StatefulWidget {
|
||||
final String name;
|
||||
final String pkgName;
|
||||
final Uint8List icon;
|
||||
final int patchesCount;
|
||||
final Function()? onTap;
|
||||
|
||||
const InstalledAppItem({
|
||||
Key? key,
|
||||
required this.name,
|
||||
required this.pkgName,
|
||||
required this.icon,
|
||||
required this.patchesCount,
|
||||
required this.suggestedVersion,
|
||||
this.onTap,
|
||||
}) : super(key: key);
|
||||
final String name;
|
||||
final String pkgName;
|
||||
final Uint8List icon;
|
||||
final int patchesCount;
|
||||
final String suggestedVersion;
|
||||
final Function()? onTap;
|
||||
|
||||
@override
|
||||
State<InstalledAppItem> createState() => _InstalledAppItemState();
|
||||
@ -47,32 +48,35 @@ class _InstalledAppItemState extends State<InstalledAppItem> {
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
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(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: <Widget>[
|
||||
children: [
|
||||
Text(
|
||||
widget.name,
|
||||
maxLines: 2,
|
||||
overflow: TextOverflow.visible,
|
||||
style: const TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
widget.suggestedVersion.isEmpty
|
||||
? 'All versions'
|
||||
: widget.suggestedVersion,
|
||||
),
|
||||
const SizedBox(width: 6),
|
||||
const SizedBox(width: 4),
|
||||
Text(
|
||||
widget.patchesCount == 1
|
||||
? "${widget.patchesCount} patch"
|
||||
: "${widget.patchesCount} patches",
|
||||
? '• ${widget.patchesCount} patch'
|
||||
: '• ${widget.patchesCount} patches',
|
||||
style: TextStyle(
|
||||
fontSize: 8,
|
||||
color: Theme.of(context).colorScheme.secondary,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Text(widget.pkgName),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
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';
|
||||
|
||||
class ContributorsCard extends StatefulWidget {
|
||||
final String title;
|
||||
final List<dynamic> contributors;
|
||||
|
||||
const ContributorsCard({
|
||||
Key? key,
|
||||
required this.title,
|
||||
required this.contributors,
|
||||
}) : super(key: key);
|
||||
final String title;
|
||||
final List<dynamic> contributors;
|
||||
|
||||
@override
|
||||
State<ContributorsCard> createState() => _ContributorsCardState();
|
||||
@ -56,6 +55,7 @@ class _ContributorsCardState extends State<ContributorsCard> {
|
||||
Uri.parse(
|
||||
widget.contributors[index]['html_url'],
|
||||
),
|
||||
mode: LaunchMode.externalApplication,
|
||||
),
|
||||
child: FutureBuilder<File?>(
|
||||
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/models/patched_application.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';
|
||||
|
||||
class AvailableUpdatesCard extends StatelessWidget {
|
||||
@ -29,7 +28,7 @@ class AvailableUpdatesCard extends StatelessWidget {
|
||||
child: Text(
|
||||
'',
|
||||
textAlign: TextAlign.center,
|
||||
style: Theme.of(context).textTheme.subtitle1!.copyWith(
|
||||
style: Theme.of(context).textTheme.titleMedium!.copyWith(
|
||||
color: Theme.of(context).colorScheme.secondary,
|
||||
),
|
||||
),
|
||||
@ -54,7 +53,7 @@ class AvailableUpdatesCard extends StatelessWidget {
|
||||
// child: Text(
|
||||
// '',
|
||||
// textAlign: TextAlign.center,
|
||||
// style: Theme.of(context).textTheme.subtitle1!.copyWith(
|
||||
// style: Theme.of(context).textTheme.titleMedium!.copyWith(
|
||||
// color: Theme.of(context).colorScheme.secondary,
|
||||
// ),
|
||||
// ),
|
||||
|
@ -30,7 +30,7 @@ class InstalledAppsCard extends StatelessWidget {
|
||||
child: Text(
|
||||
'',
|
||||
textAlign: TextAlign.center,
|
||||
style: Theme.of(context).textTheme.subtitle1!.copyWith(
|
||||
style: Theme.of(context).textTheme.titleMedium!.copyWith(
|
||||
color: Theme.of(context).colorScheme.secondary,
|
||||
),
|
||||
),
|
||||
|
@ -2,16 +2,17 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter_i18n/flutter_i18n.dart';
|
||||
import 'package:revanced_manager/app/app.locator.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_material_button.dart';
|
||||
|
||||
class LatestCommitCard extends StatefulWidget {
|
||||
final Function() onPressed;
|
||||
|
||||
const LatestCommitCard({
|
||||
Key? key,
|
||||
required this.onPressed,
|
||||
required this.onPressedManager,
|
||||
required this.onPressedPatches,
|
||||
}) : super(key: key);
|
||||
final Function() onPressedManager;
|
||||
final Function() onPressedPatches;
|
||||
|
||||
@override
|
||||
State<LatestCommitCard> createState() => _LatestCommitCardState();
|
||||
@ -22,67 +23,109 @@ class _LatestCommitCardState extends State<LatestCommitCard> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return CustomCard(
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: <Widget>[
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
return Column(
|
||||
children: [
|
||||
// ReVanced Manager
|
||||
CustomCard(
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: <Widget>[
|
||||
Row(
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
I18nText('latestCommitCard.patcherLabel'),
|
||||
FutureBuilder<String?>(
|
||||
future: model.getLatestPatcherReleaseTime(),
|
||||
builder: (context, snapshot) => Text(
|
||||
snapshot.hasData && snapshot.data!.isNotEmpty
|
||||
? FlutterI18n.translate(
|
||||
context,
|
||||
'latestCommitCard.timeagoLabel',
|
||||
translationParams: {'time': snapshot.data!},
|
||||
)
|
||||
: FlutterI18n.translate(
|
||||
context,
|
||||
'latestCommitCard.loadingLabel',
|
||||
),
|
||||
),
|
||||
Row(
|
||||
children: const <Widget>[
|
||||
Text('ReVanced Manager'),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Row(
|
||||
children: <Widget>[
|
||||
FutureBuilder<String?>(
|
||||
future: model.getLatestManagerReleaseTime(),
|
||||
builder: (context, snapshot) =>
|
||||
snapshot.hasData && snapshot.data!.isNotEmpty
|
||||
? I18nText(
|
||||
'latestCommitCard.timeagoLabel',
|
||||
translationParams: {'time': snapshot.data!},
|
||||
)
|
||||
: I18nText('latestCommitCard.loadingLabel'),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Row(
|
||||
children: <Widget>[
|
||||
I18nText('latestCommitCard.managerLabel'),
|
||||
FutureBuilder<String?>(
|
||||
future: model.getLatestManagerReleaseTime(),
|
||||
builder: (context, snapshot) =>
|
||||
snapshot.hasData && snapshot.data!.isNotEmpty
|
||||
? I18nText(
|
||||
'latestCommitCard.timeagoLabel',
|
||||
translationParams: {'time': snapshot.data!},
|
||||
)
|
||||
: I18nText('latestCommitCard.loadingLabel'),
|
||||
FutureBuilder<bool>(
|
||||
future: model.hasManagerUpdates(),
|
||||
initialData: false,
|
||||
builder: (context, snapshot) => Opacity(
|
||||
opacity: snapshot.hasData && snapshot.data! ? 1.0 : 0.25,
|
||||
child: CustomMaterialButton(
|
||||
label: I18nText('updateButton'),
|
||||
onPressed: snapshot.hasData && snapshot.data!
|
||||
? widget.onPressedManager
|
||||
: () => {},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
FutureBuilder<bool>(
|
||||
future: locator<HomeViewModel>().hasManagerUpdates(),
|
||||
initialData: false,
|
||||
builder: (context, snapshot) => Opacity(
|
||||
opacity: snapshot.hasData && snapshot.data! ? 1.0 : 0.25,
|
||||
child: CustomMaterialButton(
|
||||
isExpanded: false,
|
||||
label: I18nText('latestCommitCard.updateButton'),
|
||||
onPressed: snapshot.hasData && snapshot.data!
|
||||
? widget.onPressed
|
||||
: () => {},
|
||||
),
|
||||
|
||||
const SizedBox(height: 16),
|
||||
|
||||
// ReVanced Patches
|
||||
CustomCard(
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: <Widget>[
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
Row(
|
||||
children: const <Widget>[
|
||||
Text('ReVanced Patches'),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Row(
|
||||
children: <Widget>[
|
||||
FutureBuilder<String?>(
|
||||
future: model.getLatestPatcherReleaseTime(),
|
||||
builder: (context, snapshot) => Text(
|
||||
snapshot.hasData && snapshot.data!.isNotEmpty
|
||||
? FlutterI18n.translate(
|
||||
context,
|
||||
'latestCommitCard.timeagoLabel',
|
||||
translationParams: {'time': snapshot.data!},
|
||||
)
|
||||
: FlutterI18n.translate(
|
||||
context,
|
||||
'latestCommitCard.loadingLabel',
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
FutureBuilder<bool>(
|
||||
future: locator<HomeViewModel>().hasPatchesUpdates(),
|
||||
initialData: false,
|
||||
builder: (context, snapshot) => Opacity(
|
||||
opacity: snapshot.hasData && snapshot.data! ? 1.0 : 0.25,
|
||||
child: CustomMaterialButton(
|
||||
label: I18nText('updateButton'),
|
||||
onPressed: snapshot.hasData && snapshot.data!
|
||||
? widget.onPressedPatches
|
||||
: () => {},
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -14,15 +14,14 @@ class UpdateConfirmationDialog extends StatelessWidget {
|
||||
|
||||
return DraggableScrollableSheet(
|
||||
expand: false,
|
||||
initialChildSize: 0.5,
|
||||
snap: true,
|
||||
snapSizes: const [0.5],
|
||||
builder: (context, scrollController) => SingleChildScrollView(
|
||||
builder: (_, scrollController) => SingleChildScrollView(
|
||||
controller: scrollController,
|
||||
child: SafeArea(
|
||||
child: FutureBuilder<Map<String, dynamic>?>(
|
||||
future: model.getLatestManagerRelease(),
|
||||
builder: (context, snapshot) {
|
||||
builder: (_, snapshot) {
|
||||
if (!snapshot.hasData) {
|
||||
return const SizedBox(
|
||||
height: 300,
|
||||
@ -37,20 +36,26 @@ class UpdateConfirmationDialog extends StatelessWidget {
|
||||
children: [
|
||||
Padding(
|
||||
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(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
crossAxisAlignment:
|
||||
CrossAxisAlignment.start,
|
||||
children: [
|
||||
I18nText(
|
||||
'homeView.updateDialogTitle',
|
||||
child: const Text(
|
||||
"",
|
||||
'',
|
||||
style: TextStyle(
|
||||
fontSize: 24,
|
||||
fontWeight: FontWeight.bold),
|
||||
fontSize: 24,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 4.0),
|
||||
@ -58,12 +63,14 @@ class UpdateConfirmationDialog extends StatelessWidget {
|
||||
children: [
|
||||
Icon(
|
||||
Icons.new_releases_outlined,
|
||||
color:
|
||||
Theme.of(context).colorScheme.secondary,
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
.secondary,
|
||||
),
|
||||
const SizedBox(width: 8.0),
|
||||
Text(
|
||||
snapshot.data!["tag_name"] ?? "Unknown",
|
||||
snapshot.data!['tag_name'] ??
|
||||
'Unknown',
|
||||
style: TextStyle(
|
||||
fontSize: 20,
|
||||
fontWeight: FontWeight.w500,
|
||||
@ -89,31 +96,36 @@ class UpdateConfirmationDialog extends StatelessWidget {
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(left: 24.0, bottom: 12.0),
|
||||
padding:
|
||||
const EdgeInsets.only(left: 24.0, bottom: 12.0),
|
||||
child: I18nText(
|
||||
'homeView.updateChangelogTitle',
|
||||
child: Text(
|
||||
"",
|
||||
'',
|
||||
style: TextStyle(
|
||||
fontSize: 20,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
.onSecondaryContainer),
|
||||
fontSize: 20,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
.onSecondaryContainer,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
Container(
|
||||
margin: const EdgeInsets.symmetric(horizontal: 24.0),
|
||||
margin:
|
||||
const EdgeInsets.symmetric(horizontal: 24.0),
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).colorScheme.secondaryContainer,
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
.secondaryContainer,
|
||||
borderRadius: BorderRadius.circular(12.0),
|
||||
),
|
||||
child: Markdown(
|
||||
shrinkWrap: true,
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
padding: const EdgeInsets.all(20.0),
|
||||
data: snapshot.data!["body"] ?? "",
|
||||
data: snapshot.data!['body'] ?? '',
|
||||
),
|
||||
),
|
||||
],
|
||||
|
@ -1,8 +1,8 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class GradientProgressIndicator extends StatefulWidget {
|
||||
final double? progress;
|
||||
const GradientProgressIndicator({required this.progress, super.key});
|
||||
final double? progress;
|
||||
|
||||
@override
|
||||
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';
|
||||
|
||||
class AppSelectorCard extends StatelessWidget {
|
||||
final Function() onPressed;
|
||||
|
||||
const AppSelectorCard({
|
||||
Key? key,
|
||||
required this.onPressed,
|
||||
}) : super(key: key);
|
||||
final Function() onPressed;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@ -33,40 +32,41 @@ class AppSelectorCard extends StatelessWidget {
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
locator<PatcherViewModel>().selectedApp == null
|
||||
? I18nText('appSelectorCard.widgetSubtitle')
|
||||
: Row(
|
||||
children: <Widget>[
|
||||
SizedBox(
|
||||
height: 18.0,
|
||||
child: ClipOval(
|
||||
child: Image.memory(
|
||||
locator<PatcherViewModel>().selectedApp == null
|
||||
? Uint8List(0)
|
||||
: locator<PatcherViewModel>().selectedApp!.icon,
|
||||
fit: BoxFit.cover,
|
||||
),
|
||||
),
|
||||
if (locator<PatcherViewModel>().selectedApp == null)
|
||||
I18nText('appSelectorCard.widgetSubtitle')
|
||||
else
|
||||
Row(
|
||||
children: <Widget>[
|
||||
SizedBox(
|
||||
height: 18.0,
|
||||
child: ClipOval(
|
||||
child: Image.memory(
|
||||
locator<PatcherViewModel>().selectedApp == null
|
||||
? Uint8List(0)
|
||||
: 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
|
||||
? Container()
|
||||
: Column(
|
||||
children: [
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
locator<PatcherViewModel>()
|
||||
.getRecommendedVersionString(context),
|
||||
),
|
||||
],
|
||||
const SizedBox(width: 6),
|
||||
Text(
|
||||
locator<PatcherViewModel>().getAppSelectionString(),
|
||||
style: const TextStyle(fontWeight: FontWeight.w600),
|
||||
),
|
||||
],
|
||||
),
|
||||
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';
|
||||
|
||||
class PatchSelectorCard extends StatelessWidget {
|
||||
final Function() onPressed;
|
||||
|
||||
const PatchSelectorCard({
|
||||
Key? key,
|
||||
required this.onPressed,
|
||||
}) : super(key: key);
|
||||
final Function() onPressed;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@ -46,11 +45,12 @@ class PatchSelectorCard extends StatelessWidget {
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
locator<PatcherViewModel>().selectedApp == null
|
||||
? I18nText('patchSelectorCard.widgetSubtitle')
|
||||
: locator<PatcherViewModel>().selectedPatches.isEmpty
|
||||
? I18nText('patchSelectorCard.widgetEmptySubtitle')
|
||||
: Text(_getPatchesSelection()),
|
||||
if (locator<PatcherViewModel>().selectedApp == null)
|
||||
I18nText('patchSelectorCard.widgetSubtitle')
|
||||
else
|
||||
locator<PatcherViewModel>().selectedPatches.isEmpty
|
||||
? I18nText('patchSelectorCard.widgetEmptySubtitle')
|
||||
: Text(_getPatchesSelection()),
|
||||
],
|
||||
),
|
||||
);
|
||||
@ -58,7 +58,7 @@ class PatchSelectorCard extends StatelessWidget {
|
||||
|
||||
String _getPatchesSelection() {
|
||||
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';
|
||||
}
|
||||
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/services/manager_api.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_material_button.dart';
|
||||
|
||||
// ignore: must_be_immutable
|
||||
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 simpleName;
|
||||
final String description;
|
||||
@ -21,20 +34,6 @@ class PatchItem extends StatefulWidget {
|
||||
final toast = locator<Toast>();
|
||||
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
|
||||
State<PatchItem> createState() => _PatchItemState();
|
||||
}
|
||||
@ -75,30 +74,31 @@ class _PatchItemState extends State<PatchItem> {
|
||||
Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.end,
|
||||
children: <Widget>[
|
||||
Text(
|
||||
widget.simpleName,
|
||||
maxLines: 2,
|
||||
overflow: TextOverflow.visible,
|
||||
style: const TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 6),
|
||||
Text(
|
||||
widget.version,
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
color: Theme.of(context).colorScheme.secondary,
|
||||
Expanded(
|
||||
child: Text(
|
||||
widget.simpleName,
|
||||
maxLines: 2,
|
||||
overflow: TextOverflow.visible,
|
||||
style: const TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 1),
|
||||
Text(
|
||||
widget.version,
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
color: Theme.of(context).colorScheme.secondary,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
widget.description,
|
||||
softWrap: true,
|
||||
maxLines: 3,
|
||||
overflow: TextOverflow.visible,
|
||||
style: TextStyle(
|
||||
fontSize: 14,
|
||||
@ -139,38 +139,37 @@ class _PatchItemState extends State<PatchItem> {
|
||||
)
|
||||
],
|
||||
),
|
||||
widget.isUnsupported
|
||||
? Row(
|
||||
children: <Widget>[
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 8),
|
||||
child: TextButton.icon(
|
||||
label: I18nText('warning'),
|
||||
icon: const Icon(Icons.warning, size: 20.0),
|
||||
onPressed: () => _showUnsupportedWarningDialog(),
|
||||
style: ButtonStyle(
|
||||
shape: MaterialStateProperty.all(
|
||||
RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
side: BorderSide(
|
||||
width: 1,
|
||||
color:
|
||||
Theme.of(context).colorScheme.secondary,
|
||||
),
|
||||
),
|
||||
),
|
||||
backgroundColor: MaterialStateProperty.all(
|
||||
Colors.transparent,
|
||||
),
|
||||
foregroundColor: MaterialStateProperty.all(
|
||||
Theme.of(context).colorScheme.secondary,
|
||||
if (widget.isUnsupported)
|
||||
Row(
|
||||
children: <Widget>[
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 8),
|
||||
child: TextButton.icon(
|
||||
label: I18nText('warning'),
|
||||
icon: const Icon(Icons.warning, size: 20.0),
|
||||
onPressed: () => _showUnsupportedWarningDialog(),
|
||||
style: ButtonStyle(
|
||||
shape: MaterialStateProperty.all(
|
||||
RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
side: BorderSide(
|
||||
color: 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(),
|
||||
],
|
||||
),
|
||||
|
@ -3,8 +3,8 @@ import 'package:flutter_i18n/flutter_i18n.dart';
|
||||
import 'package:google_fonts/google_fonts.dart';
|
||||
|
||||
class OptionsTextField extends StatelessWidget {
|
||||
final String hint;
|
||||
const OptionsTextField({Key? key, required this.hint}) : super(key: key);
|
||||
final String hint;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@ -12,7 +12,7 @@ class OptionsTextField extends StatelessWidget {
|
||||
final sWidth = MediaQuery.of(context).size.width;
|
||||
return Container(
|
||||
margin: const EdgeInsets.only(top: 12, bottom: 6),
|
||||
padding: const EdgeInsets.all(0),
|
||||
padding: EdgeInsets.zero,
|
||||
child: TextField(
|
||||
decoration: InputDecoration(
|
||||
constraints: BoxConstraints(
|
||||
@ -28,9 +28,9 @@ class OptionsTextField extends StatelessWidget {
|
||||
}
|
||||
|
||||
class OptionsFilePicker extends StatelessWidget {
|
||||
final String optionName;
|
||||
const OptionsFilePicker({Key? key, required this.optionName})
|
||||
: super(key: key);
|
||||
final String optionName;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@ -61,7 +61,7 @@ class OptionsFilePicker extends StatelessWidget {
|
||||
child: Text(
|
||||
'Select File',
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).textTheme.bodyText1?.color,
|
||||
color: Theme.of(context).textTheme.bodyLarge?.color,
|
||||
),
|
||||
),
|
||||
),
|
||||
|