Compare commits

...

138 Commits

Author SHA1 Message Date
Aunali321
13b7179941 fix: fix not asking for permission 2023-06-12 09:56:49 +05:30
Aunali321
da0d88d86f build: bump patcher 2023-06-12 09:08:29 +05:30
Aunali321
b7347c312a build: bump version to v1.3.4 2023-06-12 08:59:49 +05:30
Aunali321
c876f2f7e3 feat: add permission to manage storage 2023-06-12 08:59:14 +05:30
Aunali321
adad5fd8ff build: bump version to v1.3.3 2023-06-12 07:02:49 +05:30
Aunali321
2aaa7ae8c9 chore: merge dev to main (#935) 2023-06-12 07:01:12 +05:30
Aunali321
1f64ea37bd fix: using wrong string 2023-06-12 06:59:43 +05:30
Aunali321
cac1525da0 build: bump patcher 2023-06-12 06:02:16 +05:30
Aunali321
1ad906fedc refactor: improve code 2023-06-12 06:01:57 +05:30
Aabed Khan
20ffef39a3 fix: update button being shown as clickable on launch (#932) 2023-06-12 05:55:53 +05:30
Aabed Khan
185460c054 chore(fastlane): update description (#933)
Co-authored-by: Ushie <ushiekane@gmail.com>
2023-06-12 03:21:33 +03:00
Aabed Khan
0079e74d77 feat: clarify suggested version in app list (#934)
Co-authored-by: Aunali321 <48486084+Aunali321@users.noreply.github.com>
2023-06-12 05:35:02 +05:30
oSumAtrIX
a8e019482f ci: add workflow to update documentation repository 2023-06-10 23:41:36 +02:00
Yaros
f01b8e47aa fix: aborting message changed (#928)
Change aborting message from "aborting..." to "aborted..."

----------------------
Co-authored-by: Ushie <github@ushie.dev>
Co-authored-by: Ushie <ushiekane@gmail.com>
2023-06-09 17:34:55 +07:00
Pun
e43dfb7599 docs(troubleshooting): fix markdown styling (#929)
`docs`: `84a9168` - Self-explanatory, fix markdown styling issue
2023-06-09 17:24:31 +07:00
Ushie
6b8cfe2b49 build: bump version to v1.3.2 2023-06-09 11:42:06 +03:00
Ushie
7f7b14bae3 fix: use correct variable name for armv7 check 2023-06-09 11:40:54 +03:00
Ushie
810b02d9fd build: bump version to v1.3.1 2023-06-09 10:45:22 +03:00
Ushie
62813145b2 chore: merge dev to main (#926) 2023-06-09 01:59:18 +03:00
Ushie
4877058253 chore(fastlane): add banner 2023-06-09 01:58:02 +03:00
Ushie
2a9fd3abb8 chore(fastlane): add missing directory 2023-06-09 01:57:49 +03:00
Ushie
a244f7b598 chore: merge dev to main 2023-06-08 21:31:41 +03:00
Ushie
5396457ad5 chore: move fastlane to correct directory 2023-06-08 21:30:44 +03:00
Ushie
0186b6ea61 chore: merge dev to main (#924) 2023-06-07 20:18:21 +03:00
fe
f6e99f7e88 fix: return fetched patch version if non default patch repo is used (#922) 2023-06-07 20:16:25 +03:00
Pun
c24a3828be ci(analyze): don't run when pushed to repository (#920) 2023-06-07 20:15:43 +03:00
Pun
2e38a4567a feat: clarify architecture in about section
This commit changes `Arch` to `Supported Arch(s)`
2023-06-07 20:13:42 +03:00
Yaros
67c5d67a61 feat: update icons for the new logo
fixed #918
2023-06-07 20:10:14 +03:00
Aunali321
9592dde534 feat: add fastlane for F-Droid (#889)
Co-authored-by: Ushie <ushiekane@gmail.com>
2023-06-07 19:59:17 +03:00
oSumAtrIX
d030b0af70 docs: init (#911)
Co-authored-by: afn <_@afn.lol>
2023-06-07 12:01:36 +02:00
MrAngelos6
4ccb9ac94d fix: remove redundant "v" in the downloader screen (#895) 2023-05-26 13:54:52 +03:00
Ushie
8af62b917c build: bump version to 1.3.0 (#892) 2023-05-24 23:33:04 +03:00
Steven
311f114132 chore: update v1.3.0 2023-05-24 21:46:00 +02:00
Ushie
d015bd03f7 chore: merge dev to main (#891) 2023-05-24 22:17:16 +03:00
oSumAtrIX
a61b9de0fa chore(deps): bump revanced-patcher to 9.0.0 2023-05-24 02:02:59 +02:00
Ushie
ef1b283917 chore(deps): bump revanced-patcher to 8.0.0 (#880) 2023-05-24 01:07:22 +05:30
Ushie
c677f00105 build: bump version to v1.2.0 2023-05-20 02:30:18 +03:00
Ushie
8d2f778dfe chore: merge dev to main (#881) 2023-05-20 02:28:58 +03:00
Aunali321
c549d102f6 build: migrate to dart 3 (#871) 2023-05-20 04:07:53 +05:30
Aunali321
39a9ee4e9d chore: remove coreLibraryDesugaring (#873) 2023-05-20 04:05:46 +05:30
Sangam Shrestha
a27dc6ad1c feat: migrate to dart3 2023-05-19 14:23:48 +05:45
kitadai31
8ccb75fc8d chore: remove coreLibraryDesugaring 2023-05-17 15:38:03 +09:00
Ushie
8fc86dbe02 feat: allow selecting installed if app is full apk 2023-05-13 04:54:35 +03:00
Sebok Andras
359f052608 fix: export keystore not working in some conditions (#862) 2023-05-12 19:36:47 +03:00
Hokora Yinphine
4150e2265c fix: use correct version in update download dialog (#859) 2023-05-10 13:23:02 +03:00
Dhruvan Bhalara
b803ce7435 fix: system navigation overlapping UI (#853) 2023-05-10 13:18:51 +03:00
Aunali321
289c6cd7a9 chore: merge dev to main (#857) 2023-05-08 21:46:33 +05:30
Aunali321
31fc7b74c2 build: bump version to v1.1.0 2023-05-08 21:38:19 +05:30
Aunali321
3e565f25be fix(appselector): closing dialog closes app selector 2023-05-08 21:27:07 +05:30
Aunali321
e509be4e21 chore(deps): bump patcher to 7.1.1 2023-05-08 21:19:45 +05:30
Aunali321
170fc537ac fix: fix armv7 dialog shown for x86, x86_64 2023-05-07 04:14:57 +05:30
Aunali321
3fe5882145 feat: remove cronet 2023-05-06 05:39:46 +05:30
Aunali321
a290791410 chore: merge dev to main (#844) 2023-05-06 03:26:14 +05:30
Pun
2ebd38ff68 feat: add checksums verification to Gradle (#813) 2023-05-03 21:38:47 +03:00
Dhruvan Bhalara
cd987a5b19 fix: broken filename when saving files with the same name (#837) 2023-05-03 21:34:30 +03:00
Ushie
7f1dab7ee1 ci: temporarily restore old release workflow 2023-05-02 17:50:07 +03:00
SodaWithoutSparkles
bed3945aa5 feat: clarify warning and consequences for ARMv7 users (#836) 2023-05-02 16:41:11 +07:00
Aunali321
15a32a18b7 fix: fix broken manager update implmentation 2023-04-30 17:38:08 +07:00
Palm
d2e8e7dd5d chore: comply with review changes 2023-04-30 01:03:36 +07:00
Palm
ce12ec89c4 ci(release): automatically bump version codes 2023-04-29 20:53:54 +07:00
EvadeMaster
b286444ad9 fix(i18n): update translation for refreshSucess 2023-04-29 20:43:02 +07:00
EvadeMaster
941263102d refactor: change name from "recommendedVersion" to "suggestedVersion" 2023-04-29 20:43:02 +07:00
Palm
e2ed296dc7 chore(deps): constrain get_it to 7.2.0 2023-04-29 19:46:07 +07:00
Ushie
cfc866bef2 feat: change continue anyways to cancel 2023-04-29 15:37:30 +03:00
Sebok Andras
affba669ce feat: add continue anyway button to select from storage dialog (#810) 2023-04-29 15:36:25 +03:00
Palm
7230152ab8 chore: fix issues with gitignore [skip ci] 2023-04-29 17:42:19 +07:00
Palm
21fee7171f chore: update pubspec version
skip-checks: true
2023-04-29 15:40:25 +07:00
SodaWithoutSparkles
ad17995f28 feat: improve suggested app version text (#822) 2023-04-28 17:41:28 +03:00
Yaros
ac830cbe7f fix: resized monochrome icon to match the original (#789)
cherry-pick from 6281ae82876c69ff4194a782e4b08b398a1285d6
2023-04-27 18:12:48 +07:00
Aunali321
65da6af3f9 fix: update pubspec version 2023-04-21 14:56:31 +03:00
oSumAtrIX
3d90bf7588 ci: bypass push permission on protected branches with owner PAT 2023-04-21 14:55:29 +03:00
EvadeMaster
62ef1c88fe feat: resetting source to default dismiss the sources pop-up (#797)
* feat: resetting source to default dismiss the sources pop-up

* chore: format using `dart format`
2023-04-21 00:29:37 +05:30
Aunali321
d6918920b6 chore: add missing translations 2023-04-20 23:53:24 +05:30
Aunali321
cdfb09fbfa feat: warning for armv7 devices 2023-04-20 23:20:30 +05:30
Aunali321
bb681e31c9 feat: disable selecting installed apps for nonroot 2023-04-20 22:25:29 +05:30
Aunali321
c7483936ec docs: remove alpha disclaimer 2023-04-20 05:07:46 +05:30
Aunali321
0a1f2da33d feat: appreciation message for new contributors 2023-04-20 05:04:37 +05:30
Aunali321
f5aafdb7d6 feat: progress bar for manager updates 2023-04-20 04:45:46 +05:30
Aunali321
c9adf1c492 style: sort imports 2023-04-19 01:32:43 +05:30
Aunali321
4c9cb560e3 feat: auto select default patches 2023-04-19 01:21:08 +05:30
Yaros
f0b028279c fix: open contributor links externally (#791) 2023-04-18 19:46:16 +05:30
Aman Sikarwar
197770b68b chore: update dependencies (#772)
* chore: updated some dependencies

* refactor: reimplemented cache interceptor

* Revert "Updated dependencies & migrated breaking changes"

This reverts commit e6743b0d6b2552fdbf1c99d23e158e682362dd5d.

* chore: migrated flutter_local_notifications

* revert: reimplemented cache interceptor
2023-04-18 19:45:29 +05:30
EvadeMaster
37b583f560 feat: trim extra space when setting custom source (#771) 2023-04-18 17:15:04 +07:00
Sebok Andras
dca2d4fe12 feat: add option to import/export keystore (#776)
* feat: add option to import/export keystore

* change the order of import/export keystore buttons

* feat: add option to change the keystore password
2023-04-18 15:08:10 +05:30
Aunali321
3b677f8ae3 feat: improve ux (#752)
* feat: restart app toast when changing sources, api url

* fix: potentially fix manager stuck on black screen

* feat: remove select all patches chip

* feat: show all apps and recommended versions

* chore(i18n): remove unused strings

remove unused strings left out in 7e05bca

* feat: select install type before patching

* feat: update patches button (#782)

* feat: update patches button

* feat: show toast when force refresh

* chore: don't translate "ReVanced Manager" and "ReVanced Patches"

* Revert "feat: select install type before patching"

This reverts commit 74e0c09b54.

* feat: rename recommended patches to default patches

* feat: add missing localization

* feat: display restart app toast for resetting source

---------

Co-authored-by: EvadeMaster <93124920+EvadeMaster@users.noreply.github.com>
2023-04-18 13:27:26 +05:30
Palm
0b952578d1 ci(release): update semantic-release 2023-04-02 02:37:49 +07:00
EvadeMaster
054afbbedd feat: confirmation dialog for deleting keystore (#764)
* feat: confirmation dialog for deleting keystore

* refactor(i18n): apply suggestion from code-reviewer

Co-authored-by: Ushie <github@ushie.dev>

* refactor: apply suggestion from code-reviewer

Co-authored-by: Mipirakas <borismichiels@gmail.com>

---------

Co-authored-by: Ushie <github@ushie.dev>
Co-authored-by: Mipirakas <borismichiels@gmail.com>
2023-04-01 21:02:28 +07:00
Palm
866a6e4a44 chore(CHANGELOG): reset changelog 2023-03-30 23:29:26 +07:00
Palm
8ea7dd478b ci(release): update node dependencies 2023-03-30 23:20:31 +07:00
EvadeMaster
7839252934 ci(analyze): only run when necessary (#766)
* ci(analyser): only run on lib/`.dart` changes

* ci(analyser): runs on workflow changes

* ci(analyze): run on all dart file changes

---------

Co-authored-by: Palm <palmpasuthorn@gmail.com>
2023-03-29 20:51:08 +03:00
EvadeMaster
fa4063220f ci: dart analyser (#761)
* ci: dart analyser

* ci(analyser): clarify the dart analysing step

* refactor: ignore generated files

* ci(analyser): apply suggestion from code-reviewer

Co-Authored-By: Palm <palmpasuthorn@gmail.com>

* ci(analyser): apply suggestion from code-reviewer

Co-Authored-By: Palm <palmpasuthorn@gmail.com>

* ci(analyser): apply suggestion from code-reviewer

Co-authored-by: Palm <palmpasuthorn@gmail.com>

---------

Co-authored-by: Palm <palmpasuthorn@gmail.com>
2023-03-28 15:47:34 +07:00
Palm
d214a02abd ci(release): fix argument parsing of npm exec 2023-03-26 23:02:57 +07:00
Palm
d1c12edd1b ci(release): use appropriate npm commands for ci environment 2023-03-26 23:02:57 +07:00
Palm
ded1a44c37 ci(release): fix step not moving artifact to accessible path 2023-03-26 23:02:57 +07:00
Ushie
790a6cd1e3 ci(release): remove unnecessary variables in flutter build step 2023-03-26 18:57:27 +03:00
EvadeMaster
a79f883a0f ci: use caching to speed up workflows (#760)
Use caching to speed up workflows: 9-14 minutes (ish) to 6-8 minutes (ish)

Commits:
* ci(release): use GitHub cache

* ci(release): restore signing
2023-03-26 20:02:56 +07:00
Palm
d9c5a540a3 ci(release): fix wrong artifact name and ci not uploading build 2023-03-24 01:01:44 +07:00
Palm
276f33b9ec ci: use semantic-release (#746) 2023-03-23 17:34:52 +01:00
Ushie
ded59d2da0 feat(ci): update crowdin workflow to use new main branch 2023-03-20 22:39:29 +03:00
Ushie
62f7a820d8 feat: remove notice about stale development [skip ci] 2023-03-20 18:03:58 +03:00
Ushie
7063ffa013 build: bump version to v0.0.57 2023-03-14 21:00:48 +03:00
Ushie
bf4dc3c095 chore: bump stacked
stacked: ^3.0.0 -> ^3.2.0
stacked_generator: ^0.8.0 -> ^1.0.0
stacked_services: ^0.9.3 -> ^1.0.0
2023-03-14 21:00:28 +03:00
EvadeMaster
c10e5848bf Merge pull request #707 from revanced/dev
chore: merge branch `dev` to `flutter`
2023-03-14 22:07:35 +07:00
EvadeMaster
92a3b0d6e0 feat(style): use native switch & chip (#732)
* chore: remove useless themedata

* feat(style): new switch

* feat(style): use native chip components

* chore: remove unused import

* feat(accessibility): set tooltip

* chore: remove unneeded themedata

* chore: fix theme

* feat(i18n): add 3 new strings

* feat(style): correct material 3 theme on nondynamic
2023-03-14 21:53:42 +07:00
Aunali321
b475bd25c8 build: remove env setup step 2023-03-11 23:01:26 +05:30
Aunali321
d318224a6f fix: black screen after resetting custom sources 2023-03-06 18:08:48 +05:30
Aunali321
0074fee865 build: new ci (#731)
New Build CI

Commits:
* buid: re-do ci

* build: ignore tags

* build: get the latest flutter version automatically && formatting

---------

Co-authored-by: EvadeMaster <93124920+EvadeMaster@users.noreply.github.com>
2023-03-05 16:23:51 +07:00
Aunali321
5617535a63 refactor: remove sentry and crowdin (#730)
We no longer use sentry and crowdin.
2023-03-05 16:12:46 +07:00
EvadeMaster
68ccefc59f revert: "refactor: update deprecated and minor code refactors (#710)"
This reverts commit 6829d3cdea.

Signed-off-by: EvadeMaster <93124920+EvadeMaster@users.noreply.github.com>
2023-03-03 18:06:24 +07:00
Ushie
6d60541626 chore(deps): meet patcher breaking changes 2023-03-03 03:11:44 +03:00
EvadeMaster
a635e5b8d0 chore: addFiles -> addIntegrations (#725)
fix #721
2023-03-02 14:51:35 +07:00
EvadeMaster
48a10440fe ci(build): remove environment on PR build 2023-03-02 11:06:15 +07:00
EvadeMaster
8e3ba88318 chore: bump gradle 7.5-rc.1 -> 7.6.1 (#717)
* chore: upgrade gradle from 7.5-rc.1 -> 7.6.1

* chore: remove unrelated changes
2023-02-27 19:26:10 +07:00
EvadeMaster
ab8fccc544 chore(deps): bump patcher version from 6.4.3 -> 7.0.0 2023-02-27 19:19:19 +07:00
EvadeMaster
8319dc9164 fix: improperly sized monochrome icon (#715) 2023-02-27 19:07:44 +07:00
Sailesh Dahal
6829d3cdea refactor: update deprecated and minor code refactors (#710)
Improve code readability & additional refactoring

Commits:
chore: exclude generated from analyzer

refactor: add SharedPreferences to locator

refactor: access shared pref from locator, and code refactor

refactor: remove unwanted `await`

refactor: remove `const` from `CacheConfig`
2023-02-27 19:06:05 +07:00
EvadeMaster
3ae4d69110 chore: migrate deprecation code && code cleanup (#708)
Fixes all issues in `flutter analyze`.
<Reviewed>

Commits:
* chore: migrate deprecated text style

* chore: migrate `toggleableActiveColor` to individual theme

* chore: don't use 'BuildContext's across async gaps
2023-02-20 16:53:53 +07:00
LisoUseInAIKyrios
dc665f227e fix: use high resolution adaptive icons (#675)
Use high-resolution icons instead of the low-resolution ones on ReVanced Manager.

Commits:
* High resolution adaptive icons, built using revanced-logo-no-background.svg

Same icons currently used for ReVanced YouTube.

* tweak background color to match revanced-logo-no-background.svg

* recreated foreground using 'revanced-logo-shape.svg'

* updated full resolution icons, using same ratio as original SVG

* updated icons with gradient border
2023-02-17 19:28:01 +07:00
EvadeMaster
a83496568f chore: code cleanup (#681)
Improve the readability of the code.

Commits:
* chore: correct typos `fragement` to `fragment`

* chore: put a single newline at end of file.
2023-02-17 18:53:23 +07:00
Alex
12d25570af fix: Long patch description truncated (#702)
fix: long patch description truncated (#702)

Allow flexible height
2023-02-14 18:42:24 +07:00
EvadeMaster
378c947654 chore: bump version to v0.0.56 2023-02-12 13:04:34 +07:00
EvadeMaster
bd39a3140e feat: fix patch bundle version no longer displayed (#686) 2023-02-12 00:02:30 +07:00
Boris Michiels
7d3ca3dec1 fix: display patches version on first load (#687) 2023-02-12 00:00:01 +07:00
oSumAtrIX
1cb556c8f8 chore: bump version to 0.0.55 2023-02-11 23:59:53 +07:00
oSumAtrIX
8c8f96de1c build: bump patcher version 2023-02-11 23:59:47 +07:00
EvadeMaster
318cd87a9a feat(style): use the correct m3 theming (partially) (#680) 2023-02-07 15:46:29 +03:00
Aunali321
5d63d5c2d3 feat: potentially fix apps disappearing when update is available (#674) 2023-01-31 15:42:00 +03:00
Ushie
7d347fccc6 build: bump version to v0.0.54 2023-01-30 15:57:06 +03:00
Jay Gajjar
a54ca799b9 feat: update rules of analysis_options.yaml. and solved all problems (#665)
* update rules of analysis_options.yaml. and solved all problems

* refactor: fix remaining problems

---------

Co-authored-by: Ushie <ushiekane@gmail.com>
2023-01-30 15:35:06 +03:00
Mipirakas
f5bc1a996f feat: remove select all icon from searchbar (#669) 2023-01-30 15:13:25 +03:00
Mipirakas
8591bc4d01 chore: update cr_file_saver (#668)
Co-authored-by: Ushie <github@ushie.dev>
2023-01-30 15:11:31 +03:00
EvadeMaster
40888c07f3 feat: fix overflow text & separate version from patch name (#666)
* chore: Add note

* feat: Fix overflow text & seperate version from patch title

* feat: Fix overflow text & separate version from patch name
2023-01-30 15:07:25 +03:00
Mipirakas
1c965c3788 refactor: cleanup remember patches feature (#630) 2023-01-30 15:03:55 +03:00
Bennett
4df690c2a2 feat: monochrome icon + predictive back gesture (#591)
* Add monochrome icon

* Enable predictive back gesture

* feat: monochrome icon + predictive back gesture
2023-01-30 01:23:56 +03:00
Abhiram
d6abb61e2b ci(build): update workflow actions (#662)
- Bump upload-artifact to v3

- Update crowdin/github-action according to new versioning format [https://github.com/crowdin/github-action/releases/tag/v1.6.0]
2023-01-29 23:56:13 +03:00
oSumAtrIX
3434c862e9 feat: improve note [skip ci] 2023-01-28 07:49:57 +01:00
EvadeMaster
ea8af926fa chore: update broken documentation link (#659)
* chorus: fix invalid documentation link

* chorus: Update broken documentation link
2023-01-22 16:18:41 +03:00
EvadeMaster
c3df48174c feat: warn user for selecting all patches (#649) 2023-01-21 16:37:28 +01:00
133 changed files with 10028 additions and 1508 deletions

3
.env
View File

@@ -1,3 +0,0 @@
sentryDSN=
apiKey=
appId=

2
.github/config.yaml vendored Normal file
View File

@@ -0,0 +1,2 @@
firstPRMergeComment: >
Thank you for contributing to ReVanced. Join us on [Discord](https://revanced.app/discord) if you want to receive a contributor role.

27
.github/workflows/analyze.yml vendored Normal file
View File

@@ -0,0 +1,27 @@
name: Analyze Code
on:
pull_request:
branches: [ "main", "dev" ]
paths:
- "**.dart"
- ".github/workflows/analyze.yml"
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Setup Flutter
uses: subosito/flutter-action@v2
with:
channel: 'stable'
cache: true
- name: Install Flutter dependencies
run: flutter pub get
- name: Generate files with Builder
run: flutter packages pub run build_runner build --delete-conflicting-outputs
- name: Analyze code
uses: ValentinVignal/action-dart-analyze@v0.15
with:
fail-on: warning

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -15,15 +15,11 @@ jobs:
- name: Set up JDK 12
uses: actions/setup-java@v3
with:
java-version: '12'
distribution: 'zulu'
java-version: "12"
distribution: "zulu"
- uses: subosito/flutter-action@v2
with:
channel: 'stable'
- name: Set environment variables
run: echo $SECRETS | base64 -d > lib/utils/environment.dart
env:
SECRETS: ${{ secrets.SECRETS }}
channel: "stable"
- name: Set up Flutter
run: flutter pub get
- name: Generate files with Builder
@@ -51,4 +47,4 @@ jobs:
with:
repo_token: "${{ secrets.GITHUB_TOKEN }}"
prerelease: false
files: revanced-manager-${{ env.RELEASE_VERSION }}.apk
files: revanced-manager-${{ env.RELEASE_VERSION }}.apk

68
.github/workflows/release.yml vendored Normal file
View 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

View File

@@ -0,0 +1,19 @@
name: Update documentation
on:
push:
paths:
- docs/**
jobs:
trigger:
runs-on: ubuntu-latest
name: Dispatch event to documentation repository
if: github.ref == 'refs/heads/main'
steps:
- uses: peter-evans/repository-dispatch@v2
with:
token: ${{ secrets.DOCUMENTATION_REPO_ACCESS_TOKEN }}
repository: revanced/revanced-documentation
event-type: update-documentation
client-payload: '{"repo": "${{ github.event.repository.name }}", "ref": "${{ github.ref }}"}'

10
.gitignore vendored
View File

@@ -134,5 +134,11 @@ app.*.map.json
!/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages
!/dev/ci/**/Gemfile.lock
Firebase related
.firebase
# Firebase related
.firebase
# Dependency directories
node_modules/
# FVM
.fvm

75
.releaserc Normal file
View File

@@ -0,0 +1,75 @@
{
"branches": [
"main",
{
"name": "dev",
"prerelease": true
}
],
"plugins": [
"semantic-release-export-data",
"@semantic-release/commit-analyzer",
[
"@semantic-release/release-notes-generator",
{
"presetConfig": {
"types": [
{
"type": "build",
"section": "Dependency Updates"
},
{
"type": "chore",
"section": "Other Changes",
"hidden": false
},
{
"type": "perf",
"section": "Performance Improvements",
"hidden": false
},
{
"type": "refactor",
"section": "Code Improvements",
"hidden": false
}
]
}
}
],
"@semantic-release/changelog",
"semantic-release-flutter-plugin",
[
"@semantic-release/git",
{
"assets": [
"CHANGELOG.md",
"pubspec.yaml"
]
}
],
[
"@semantic-release/github",
{
"assets": [
{
"path": "build/app/outputs/apk/release/revanced-manager-*.apk"
}
],
"successComment": false
}
],
[
"@saithodev/semantic-release-backmerge",
{
"backmergeBranches": [
{
"from": "main",
"to": "dev"
}
],
"clearWorkspace": true
}
]
]
}

1
CHANGELOG.md Normal file
View File

@@ -0,0 +1 @@

View File

@@ -2,18 +2,12 @@
The official ReVanced Manager based on Flutter.
> **Warning**: This repository currently has no active maintainer. For that reason, development is stale. Active development continues for [revanced-cli](https://github.com/revanced/revanced-cli).
## 🔽 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).
@@ -30,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)

View File

@@ -11,23 +11,154 @@ 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_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

3
android/Gemfile Normal file
View File

@@ -0,0 +1,3 @@
source "https://rubygems.org"
gem "fastlane"

View File

@@ -71,14 +71,10 @@ dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
// ReVanced
implementation "app.revanced:revanced-patcher:6.4.2"
implementation "app.revanced:revanced-patcher:11.0.1"
// Signing & aligning
implementation("org.bouncycastle:bcpkix-jdk15on:1.70")
implementation("com.android.tools.build:apksig:7.2.2")
// MicroG cronet
implementation("org.microg:cronet-common:$cronetVersion")
implementation("org.microg:cronet-native:$cronetVersion")
}

View File

@@ -6,6 +6,8 @@
<uses-permission android:name="android.permission.READ_MEDIA_VIDEO" />
<uses-permission android:name="android.permission.READ_MEDIA_AUDIO" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" android:maxSdkVersion="32" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" />
<uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS" />
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
@@ -17,7 +19,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 +31,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"/>

View File

@@ -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}")
@@ -272,8 +271,8 @@ class MainActivity : FlutterActivity() {
"update",
mapOf(
"progress" to -100.0,
"header" to "Aborting...",
"log" to "An error occurred! Aborting\nError:\n$stack"
"header" to "Aborted...",
"log" to "An error occurred! Aborted\nError:\n$stack"
)
)
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 814 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.4 KiB

View File

@@ -0,0 +1,30 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt"
android:width="108dp"
android:height="108dp"
android:viewportWidth="256"
android:viewportHeight="256">
<group android:scaleX="0.23"
android:scaleY="0.23"
android:translateX="98.56"
android:translateY="98.56">
<path
android:pathData="M253.85,4.9C254.32,3.82 254.22,2.57 253.58,1.58C252.93,0.6 251.83,0 250.64,0C243.29,0 230.47,0 225.95,0C224.96,0 224.06,0.59 223.66,1.5C216.03,18.88 144.1,182.7 130.29,214.16C129.89,215.07 128.99,215.66 128,215.66C127.01,215.66 126.11,215.07 125.71,214.16C111.9,182.7 39.97,18.88 32.34,1.5C31.94,0.59 31.04,0 30.05,0C25.53,0 12.71,0 5.36,0C4.17,0 3.07,0.6 2.42,1.58C1.78,2.57 1.68,3.82 2.15,4.9C16.78,38.3 101.47,231.61 111.24,253.9C111.8,255.18 113.06,256 114.45,256C120.29,256 135.71,256 141.55,256C142.94,256 144.2,255.18 144.76,253.9C154.52,231.61 239.22,38.3 253.85,4.9Z"
android:fillColor="#ffffff"/>
<path
android:pathData="M130.59,131.75C130.06,132.68 129.07,133.25 128,133.25C126.93,133.25 125.94,132.68 125.4,131.75C113.45,111.06 63.88,25.19 51.93,4.5C51.4,3.57 51.4,2.43 51.93,1.5C52.47,0.57 53.46,-0 54.53,-0L201.47,-0C202.54,-0 203.53,0.57 204.06,1.5C204.6,2.43 204.6,3.57 204.06,4.5C192.12,25.19 142.54,111.06 130.59,131.75Z">
<aapt:attr name="android:fillColor">
<gradient
android:startX="128"
android:startY="-0"
android:endX="128"
android:endY="254.6"
android:type="linear">
<item android:offset="0" android:color="#FFF04E98"/>
<item android:offset="0.5" android:color="#FF5F65D4"/>
<item android:offset="1" android:color="#FF4E98F0"/>
</gradient>
</aapt:attr>
</path>
</group>
</vector>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 5.3 KiB

View File

@@ -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"/>
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
</adaptive-icon>
<background android:drawable="@color/ic_launcher_background"/>
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
<monochrome android:drawable="@drawable/ic_launcher_foreground"/>
</adaptive-icon>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.1 KiB

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.5 KiB

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.4 KiB

After

Width:  |  Height:  |  Size: 6.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 9.4 KiB

View File

@@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="ic_launcher_background">#1B1B1B</color>
</resources>
</resources>

View File

@@ -1,6 +1,5 @@
buildscript {
ext.cronetVersion = '102.5005.125'
ext.kotlin_version = '1.8.0'
ext.kotlin_version = '1.7.10'
repositories {
google()
mavenCentral()
@@ -32,6 +31,6 @@ subprojects {
project.evaluationDependsOn(':app')
}
task clean(type: Delete) {
tasks.register("clean", Delete) {
delete rootProject.buildDir
}

View File

@@ -1,6 +1,7 @@
#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
distributionSha256Sum=6147605a23b4eff6c334927a86ff3508cb5d6722cd624c97ded4c2e8640f1f87
networkTimeout=10000
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

View File

@@ -4,6 +4,8 @@
"updateButton": "Update",
"enabledLabel": "Enabled",
"disabledLabel": "Disabled",
"installed":"Installed: {version}",
"suggested":"Suggested: {version}",
"yesButton": "Yes",
"noButton": "No",
"warning": "Warning",
@@ -13,6 +15,7 @@
"settingsTab": "Settings"
},
"homeView": {
"refreshSuccess": "Refreshed successfully",
"widgetTitle": "Dashboard",
"updatesSubtitle": "Updates",
"patchedSubtitle": "Patched applications",
@@ -47,7 +50,8 @@
"patcherView": {
"widgetTitle": "Patcher",
"patchButton": "Patch",
"patchDialogText": "You have selected a resource patch and a split APK installation has been detected, so patching errors may occur.\nAre you sure you want to proceed?"
"patchDialogText": "You have selected a resource patch and a split APK installation has been detected, so patching errors may occur.\nAre you sure you want to proceed?",
"armv7WarningDialogText": "Patching on ARMv7 devices is not yet supported and might fail. Proceed anyways?"
},
"appSelectorCard": {
"widgetTitle": "Select an application",
@@ -55,7 +59,7 @@
"widgetSubtitle": "No application selected",
"noAppsLabel": "No applications found",
"currentVersion": "Current",
"recommendedVersion": "Recommended",
"suggestedVersion": "Suggested",
"anyVersion": "any"
},
"patchSelectorCard": {
@@ -71,20 +75,25 @@
"appSelectorView": {
"viewTitle": "Select an application",
"searchBarHint": "Search applications",
"selectFromStorageButton": "Select from storage",
"storageButton": "Storage",
"errorMessage": "Unable to use selected application"
"errorMessage": "Unable to use selected application",
"downloadToast": "Download function is not available yet",
"featureNotAvailable": "Feature not implemented",
"featureNotAvailableText": "This application is a split APK and cannot be selected. Unfortunately, this feature is only available for rooted users at the moment. However, you can still install the application by selecting its APK files from your device's storage instead"
},
"patchesSelectorView": {
"viewTitle": "Select patches",
"searchBarHint": "Search patches",
"doneButton": "Done",
"recommended": "Recommended",
"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 +154,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 +175,17 @@
"jsonSelectorErrorMessage": "Unable to use selected JSON file",
"deleteLogsLabel": "Delete logs",
"deleteLogsHint": "Delete collected manager logs",
"deletedLogs": "Logs deleted"
"deletedLogs": "Logs deleted",
"exportKeystoreLabel": "Export keystore",
"exportKeystoreHint": "Export keystore used to sign apps",
"exportedKeystore": "Keystore exported",
"noKeystoreExportFileFound": "No keystore to export",
"importKeystoreLabel": "Import keystore",
"importKeystoreHint": "Import keystore used to sign apps",
"importedKeystore": "Keystore imported",
"keystoreSelectorErrorMessage": "Unable to use selected KEYSTORE file",
"selectKeystorePassword": "Keystore Password",
"selectKeystorePasswordHint": "Select keystore password used to sign the apk"
},
"appInfoView": {
"widgetTitle": "App info",

16
docs/0_prerequisites.md Normal file
View File

@@ -0,0 +1,16 @@
# 💼 Prerequisites
In order to use ReVanced Manager, certain requirements must be met.
## 🤝 Requirements
- An Android device running Android 8 or higher
- Any device architecture except ARMv7[^1]
[^1]: This constraint only applies to patches, that require patching APK resources which is why some patches may or may not work on ARMv7 architecture. You can find out, which architectures your device supports here: [⚙️ Configuring ReVanced Manager](2_4_settings.md#%E2%84%B9%EF%B8%8F-about).
## ⏭️ What's next
The next page will guide you through patching an application.
Continue: [⬇️ Installation](1_installation.md)

14
docs/1_installation.md Normal file
View File

@@ -0,0 +1,14 @@
# ⬇️ Installation
In order to use ReVanced on your Android device, ReVanced Manager must be installed.
## 🪜 Installation steps
- Download the latest version of ReVanced Manager from [here](https://github.com/revanced/revanced-manager/releases/latest)
- Install ReVanced Manager
## ⏭️ What's next
The next page will guide you through patching an application.
Continue: [🪛 Usage](2_usage.md)

29
docs/2_1_patching.md Normal file
View File

@@ -0,0 +1,29 @@
# 🧩 Patching applications
The following pages will guide you through using ReVanced Manager to patch applications.
## 🪜 Steps to patch applications
1. Navigate to the **Patcher** tab from the bottom navigation bar
2. Tap on the **Select an application** card
3. Choose an application to patch[^1]
> **Note**: The application version suggested by ReVanced Manager is visible in each application's card. This is important in step 5. below, as most patches are compatible with this version.
4. Tap on the **Select patches** card and select the patches you want to apply[^2]
> **Warning**: If you see a warning icon next to a patch, it means that the patch is not compatible with the application version you selected in step 3. above. In this case, you should either go back and select a different application version or deselect the patch.
5. Tap on the **Done** button to confirm your selection
6. Tap on the **Patch** button to start patching
> **Warning**: The patching process can take anywhere from 2 to 5 minutes depending on your device. Refrain from exiting ReVanced Manager while the process is still running, otherwise the patching time will increase significantly.
7. After patching is complete, tap on the **Install** button to install the patched application
> **Note**: If you have root access on your device, you may have the option to mount the patched application on top of the original application.[^4]
> Optionally, you may tap on the three vertical dots in the top right to export the patched application to storage.
[^1]: You may be prompted to select an application from storage in case you have no root access on your device. In this case, select the APK file from your device storage.[^3]
[^2]: It is suggested to use the default set of patches by tapping on the **Default** chip above the list of patches.
[^3]: You can obtain `.apk` files from sites such as [APKMirror](https://www.apkmirror.com/).
[^4]: Mounting the patched application on top of the original application will only work if the installed application version matches the version of the application selected in step 3. above.
## ⏭️ What's next
The next page will bring you back to the usage page.
Continue: [🛠️ Usage](2_usage.md)

16
docs/2_2_managing.md Normal file
View File

@@ -0,0 +1,16 @@
# 🧰 Managing patched applications
After patching an application, you may want to manage it. This page will guide you through managing patched applications.
## 🪜 Steps to manage patched applications
1. Tap on the **Dashboard** tab in the bottom navigation bar
2. Select the **Installed** chip
3. Tap on the **Info** button for the application you want to manage
4. Choose one of the options from the menu
## ⏭️ What's next
The next page will bring you back to the usage page.
Continue: [🛠️ Usage](2_usage.md)

14
docs/2_3_updating.md Normal file
View File

@@ -0,0 +1,14 @@
# 🔄 Updating ReVanced Manager
In order to keep up with the latest features and bug fixes, it is recommended to keep ReVanced Manager up to date.
## 🪜 Updating steps
1. Navigate to the **Dashboard** tab from the bottom navigation bar
2. Tap on the **Update** button in the **Updates** section
## ⏭️ What's next
The next page will bring you back to the usage page.
Continue: [🛠️ Usage](2_usage.md)

39
docs/2_4_settings.md Normal file
View File

@@ -0,0 +1,39 @@
# ⚙️ Configuring ReVanced Manager
ReVanced Manager has settings that can be configured to your liking.
## 🪛 Essential settings
- ### 🔗 API URL
Specify the URL of the API to use. This is used to fetch ReVanced Patches and update ReVanced Manager.
- ### 🧬 Sources
Override the API and change the source of ReVanced Patches.
- ### 🧪 Experimental ReVanced Patches support
Lift application version constraints from ReVanced Patches. This allows you to patch any version of an application, even if the patch is not explicitly compatible with it.
- ### 🧑‍🔬 Experimental universal support
This will show or hide ReVanced Patches, which are not meant for any application in particular but rather for all applications but may not work on all applications.
- ### 🔑 Export, import or delete keystore
Manage the keystore used to sign patched applications.
- ### 📄 Export, import or reset ReVanced Patches selection
Manage the ReVanced Patches selection. This is useful if you want to share your ReVanced Patches selection with others or reset it to the default selection.
- ### About
View information about your device and ReVanced Manager. This includes the version of ReVanced Manager and supported architectures of your device.
## ⏭️ What's next
The next page will bring you back to the usage page.
Continue: [🛠️ Usage](2_usage.md)

16
docs/2_usage.md Normal file
View File

@@ -0,0 +1,16 @@
# 🛠️ Usage
The following pages will guide you through using ReVanced Manager to patch applications, manage patched applications, and update ReVanced Manager.
## 📖 Table of contents
1. [🧩 Patching applications](2_1_patching.md)
2. [🧰 Managing patched applications](2_2_managing.md)
3. [🔄 Updating ReVanced Manager](2_3_updating.md)
4. [⚙️ Configuring ReVanced Manager](2_4_settings.md)
## ⏭️ What's next
The next page will guide you through troubleshooting ReVanced Manager.
Continue: [🛟 Troubleshooting](3_troubleshooting.md)

27
docs/3_troubleshooting.md Normal file
View File

@@ -0,0 +1,27 @@
# 🛟 Troubleshooting
In case you encounter any issues while using ReVanced Manager, please refer to this page for possible solutions.
- ### 💥 App not installed as package conflicts with an existing package
An existing installation of the application you're trying to patch is conflicting with the patched application. Uninstall the existing application before installing the patched application.
- ### ❗Error code `135`, `139` or `1` when patching the application
Your device is not supported. Refer to the [Prerequisites](0_prerequisites.md) page for supported devices.
Alternatively, you can use [ReVanced CLI](https://github.com/revanced/revanced-cli) to patch the application.
- ### 🚫 Non-root install is not possible with the current patches selection
Select the **Default** chip when choosing patches.
- ### 🚨 Patched application crashes on launch
Select the **Default** chip when choosing patches.
## ⏭️ What's next
The next page will teach you how to build ReVanced Manager from source.
Continue: [🛠️ Building from source](4_building.md)

40
docs/4_building.md Normal file
View File

@@ -0,0 +1,40 @@
# 🛠️ Building from source
This page will guide you through building ReVanced Manager from source.
1. Setup the Flutter environment for your [platform](https://docs.flutter.dev/get-started/install)
2. Clone the repository
```sh
git clone https://github.com/revanced/revanced-manager.git && cd revanced-manager
```
3. Create a GitHub personal access token with the `read:packages` scope [here](https://github.com/settings/tokens/new?scopes=read:packages&description=ReVanced)
4. Add your GitHub username and the token to `~/.gradle/gradle.properties`
```properties
gpr.user = YourUsername
gpr.key = ghp_longrandomkey
```
5. Get dependencies
```sh
flutter pub get
```
6. Delete conflicting outputs
```sh
flutter packages pub run build_runner build --delete-conflicting-outputs
```
> **Note**: Must be run every time you sync your local repository with the remote repository.
7. Build the APK
```sh
flutter build apk
```

15
docs/README.md Normal file
View File

@@ -0,0 +1,15 @@
# 💊 ReVanced Manager
This documentation explains how to use [ReVanced Manager](https://github.com/revanced/revanced-manager).
## 📖 Table of contents
0. [💼 Prerequisites](0_prerequisites.md)
1. [⬇️ Installation](1_installation.md)
2. [🛠️ Usage](2_usage.md)
1. [🧩 Patching applications](2_1_patching.md)
2. [🧰 Managing patched applications](2_2_managing.md)
3. [🔄 Updating ReVanced Manager](2_3_updating.md)
4. [⚙️ Configuring ReVanced Manager](2_4_settings.md)
3. [🛟 Troubleshooting](3_troubleshooting.md)
4. [🛠 Building from source](4_building.md)

2
fastlane/Appfile Normal file
View File

@@ -0,0 +1,2 @@
json_key_file("") # Path to the json secret file - Follow https://docs.fastlane.tools/actions/supply/#setup to get one
package_name("app.revanced.manager.flutter") # e.g. com.krausefx.app

38
fastlane/Fastfile Normal file
View File

@@ -0,0 +1,38 @@
# This file contains the fastlane.tools configuration
# You can find the documentation at https://docs.fastlane.tools
#
# For a list of all available actions, check out
#
# https://docs.fastlane.tools/actions
#
# For a list of all available plugins, check out
#
# https://docs.fastlane.tools/plugins/available-plugins
#
# Uncomment the line if you want fastlane to automatically update itself
# update_fastlane
default_platform(:android)
platform :android do
desc "Runs all the tests"
lane :test do
gradle(task: "test")
end
desc "Submit a new Beta Build to Crashlytics Beta"
lane :beta do
gradle(task: "clean assembleRelease")
crashlytics
# sh "your_script.sh"
# You can also use other beta testing services here
end
desc "Deploy a new version to the Google Play"
lane :deploy do
gradle(task: "clean assembleRelease")
upload_to_play_store
end
end

48
fastlane/README.md Normal file
View File

@@ -0,0 +1,48 @@
fastlane documentation
----
# Installation
Make sure you have the latest version of the Xcode command line tools installed:
```sh
xcode-select --install
```
For _fastlane_ installation instructions, see [Installing _fastlane_](https://docs.fastlane.tools/#installing-fastlane)
# Available Actions
## Android
### android test
```sh
[bundle exec] fastlane android test
```
Runs all the tests
### android beta
```sh
[bundle exec] fastlane android beta
```
Submit a new Beta Build to Crashlytics Beta
### android deploy
```sh
[bundle exec] fastlane android deploy
```
Deploy a new version to the Google Play
----
This README.md is auto-generated and will be re-generated every time [_fastlane_](https://fastlane.tools) is run.
More information about _fastlane_ can be found on [fastlane.tools](https://fastlane.tools).
The documentation of _fastlane_ can be found on [docs.fastlane.tools](https://docs.fastlane.tools).

View File

@@ -0,0 +1 @@
ReVanced Manager is an Android application that uses ReVanced Patcher to add, remove, and modify existing functionalities in Android applications

Binary file not shown.

After

Width:  |  Height:  |  Size: 110 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 374 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 694 KiB

View File

@@ -0,0 +1 @@
Patch your favourite apps, right on your device.

View File

@@ -0,0 +1 @@
ReVanced Manager

20
fastlane/report.xml Normal file

File diff suppressed because one or more lines are too long

View File

@@ -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),
],
)

View File

@@ -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,

View File

@@ -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);
}

View File

@@ -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);

View File

@@ -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 [];
}
}
}

View File

@@ -1,22 +1,24 @@
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: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 +30,44 @@ class GithubAPI {
'com.spotify.music': 'spotify',
};
void initialize(String repoUrl) async {
Future<void> initialize(String repoUrl) async {
try {
_dio = Dio(BaseOptions(
baseUrl: repoUrl,
));
_dio.interceptors.add(_dioCacheManager.interceptor);
_dio.addSentry(
captureFailedRequests: true,
_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,37 +77,41 @@ 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] +
(commit) => commit['commit']['message'].split('\n')[0] +
' - ' +
commit['commit']['author']['name'] +
'\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 +121,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 +132,34 @@ 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';
}
}
}

View File

@@ -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,28 @@ 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');
} else {
// fetch from github
patchesVersion =
await _githubAPI.getLastestReleaseVersion(getPatchesRepo());
}
return patchesVersion ?? '0.0.0';
}
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 +324,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 +351,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 +371,6 @@ class ManagerAPI {
apkFilePath: application.apkFilePath,
icon: application.icon,
patchDate: DateTime.now(),
isRooted: false,
),
);
}
@@ -339,25 +380,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 +414,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 +425,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 +438,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 +466,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 {

View File

@@ -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()

View File

@@ -1,69 +1,64 @@
import 'dart:async';
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: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();
if (!isGMSInstalled) {
_dio = Dio(BaseOptions(
_dio = Dio(
BaseOptions(
baseUrl: apiUrl,
));
print('ReVanced API: Using default engine + $isGMSInstalled');
} else {
_dio = Dio(BaseOptions(
baseUrl: apiUrl,
))
..httpClientAdapter = NativeAdapter();
print('ReVanced API: Using CronetEngine + $isGMSInstalled');
}
_dio.interceptors.add(_dioCacheManager.interceptor);
_dio.addSentry(
captureFailedRequests: true,
),
);
} on Exception catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
_dio.interceptors.add(DioCacheInterceptor(options: _cacheOptions));
} on Exception catch (e) {
if (kDebugMode) {
print(e);
}
}
}
Future<void> clearAllCache() async {
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 +66,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 +82,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 +102,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;

View File

@@ -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"',
);

View File

@@ -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),
);

View File

@@ -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(

View File

@@ -1,10 +1,11 @@
import 'package:flutter/material.dart';
import 'package:flutter/material.dart' hide SearchBar;
import 'package:flutter_i18n/flutter_i18n.dart';
import 'package: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,28 +77,61 @@ class _AppSelectorViewState extends State<AppSelectorView> {
SliverToBoxAdapter(
child: model.noApps
? Center(
child: I18nText('appSelectorCard.noAppsLabel'),
child: I18nText(
'appSelectorCard.noAppsLabel',
child: Text(
'',
style: TextStyle(
color:
Theme.of(context).textTheme.titleLarge!.color,
),
),
),
)
: model.apps.isEmpty
? const AppSkeletonLoader()
: Padding(
padding: const EdgeInsets.symmetric(horizontal: 12.0)
.copyWith(bottom: 80),
.copyWith(
bottom:
MediaQuery.of(context).viewPadding.bottom + 8.0,
),
child: Column(
children: model
.getFilteredApps(_query)
.map((app) => InstalledAppItem(
children: [
...model
.getFilteredApps(_query)
.map(
(app) => InstalledAppItem(
name: app.appName,
pkgName: app.packageName,
icon: app.icon,
patchesCount:
model.patchesCount(app.packageName),
suggestedVersion:
model.getSuggestedVersion(
app.packageName,
),
installedVersion: app.versionName!,
onTap: () => model.canSelectInstalled(
context, app.packageName),
),
)
.toList(),
...model
.getFilteredAppsNames(_query)
.map(
(app) => NotInstalledAppItem(
name: app,
patchesCount: model.patchesCount(app),
suggestedVersion:
model.getSuggestedVersion(app),
onTap: () {
model.selectApp(app);
Navigator.of(context).pop();
model.showDownloadToast();
},
))
.toList(),
),
)
.toList(),
],
),
),
),

View File

@@ -1,37 +1,79 @@
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<bool> checkSplitApk(String packageName) async {
final app = await DeviceApps.getApp(packageName);
if (app != null) {
return app.isSplit;
}
return true;
}
Future<void> selectApp(ApplicationWithIcon application) async {
locator<PatcherViewModel>().selectedApp = PatchedApplication(
name: application.appName,
packageName: application.packageName,
@@ -42,27 +84,117 @@ class AppSelectorViewModel extends BaseViewModel {
patchDate: DateTime.now(),
);
locator<PatcherViewModel>().loadLastSelectedPatches();
locator<PatcherViewModel>().notifyListeners();
}
Future<void> canSelectInstalled(
BuildContext context,
String packageName,
) async {
final app =
await DeviceApps.getApp(packageName, true) as ApplicationWithIcon?;
if (app != null) {
if (await checkSplitApk(packageName) && !isRooted) {
return showSelectFromStorageDialog(context);
} else if (!await checkSplitApk(packageName) || isRooted) {
selectApp(app);
Navigator.pop(context);
}
}
}
Future showSelectFromStorageDialog(BuildContext context) async {
return showDialog(
context: context,
builder: (context) => SimpleDialog(
alignment: Alignment.center,
contentPadding:
const EdgeInsets.symmetric(horizontal: 20, vertical: 20),
children: [
const SizedBox(height: 10),
Icon(
Icons.block,
size: 28,
color: Theme.of(context).colorScheme.primary,
),
const SizedBox(height: 20),
I18nText(
'appSelectorView.featureNotAvailable',
child: const Text(
'',
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.w600,
wordSpacing: 1.5,
),
),
),
const SizedBox(height: 20),
I18nText(
'appSelectorView.featureNotAvailableText',
child: const Text(
'',
style: TextStyle(
fontSize: 14,
),
),
),
const SizedBox(height: 30),
CustomMaterialButton(
onPressed: () => selectAppFromStorage(context).then(
(_) {
Navigator.pop(context);
Navigator.pop(context);
},
),
label: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Icon(Icons.sd_card),
const SizedBox(width: 10),
I18nText('appSelectorView.selectFromStorageButton'),
],
),
),
const SizedBox(height: 10),
CustomMaterialButton(
isFilled: false,
onPressed: () {
Navigator.pop(context);
},
label: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const SizedBox(width: 10),
I18nText('cancelButton'),
],
),
),
],
),
);
}
Future<void> selectAppFromStorage(BuildContext context) async {
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 +210,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');
}

View File

@@ -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,
),
),
),
@@ -57,6 +57,7 @@ class ContributorsView extends StatelessWidget {
title: 'contributorsView.managerContributors',
contributors: model.managerContributors,
),
SizedBox(height: MediaQuery.of(context).viewPadding.bottom)
],
),
),

View File

@@ -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()] ?? [];

View File

@@ -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),

View File

@@ -1,26 +1,29 @@
// ignore_for_file: use_build_context_synchronously
import 'dart:async';
import 'dart:io';
import 'package:app_installer/app_installer.dart';
import 'package:cross_connectivity/cross_connectivity.dart';
import 'package:device_apps/device_apps.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_i18n/flutter_i18n.dart';
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
import 'package:injectable/injectable.dart';
import 'package:path_provider/path_provider.dart';
import 'package:revanced_manager/app/app.locator.dart';
import 'package:revanced_manager/app/app.router.dart';
import 'package:revanced_manager/models/patched_application.dart';
import 'package:revanced_manager/services/github_api.dart';
import 'package:revanced_manager/services/manager_api.dart';
import 'package:revanced_manager/services/patcher_api.dart';
import 'package:revanced_manager/services/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:stacked/stacked.dart';
import 'package:stacked_services/stacked_services.dart';
import 'package:timezone/timezone.dart' as tz;
@lazySingleton
class HomeViewModel extends BaseViewModel {
@@ -28,29 +31,52 @@ class HomeViewModel extends BaseViewModel {
final ManagerAPI _managerAPI = locator<ManagerAPI>();
final PatcherAPI _patcherAPI = locator<PatcherAPI>();
final GithubAPI _githubAPI = locator<GithubAPI>();
final RevancedAPI _revancedAPI = locator<RevancedAPI>();
final Toast _toast = locator<Toast>();
final flutterLocalNotificationsPlugin = FlutterLocalNotificationsPlugin();
DateTime? _lastUpdate;
bool showUpdatableApps = false;
List<PatchedApplication> patchedInstalledApps = [];
List<PatchedApplication> patchedUpdatableApps = [];
String? _latestManagerVersion = '';
Future<void> initialize(BuildContext context) async {
_latestManagerVersion = await _managerAPI.getLatestManagerVersion();
await flutterLocalNotificationsPlugin.initialize(
const InitializationSettings(
android: AndroidInitializationSettings('ic_notification'),
),
onSelectNotification: (p) =>
DeviceApps.openApp('app.revanced.manager.flutter'),
onDidReceiveNotificationResponse: (response) async {
if (response.id == 0) {
_toast.showBottom('homeView.installingMessage');
final File? managerApk = await _managerAPI.downloadManager();
if (managerApk != null) {
await AppInstaller.installApk(managerApk.path);
} else {
_toast.showBottom('homeView.errorDownloadMessage');
}
}
},
);
flutterLocalNotificationsPlugin
.resolvePlatformSpecificImplementation<
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 +93,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 +102,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 +111,158 @@ class HomeViewModel extends BaseViewModel {
}
Future<bool> hasManagerUpdates() async {
String? latestVersion = await _managerAPI.getLatestManagerVersion();
String currentVersion = await _managerAPI.getCurrentManagerVersion();
_latestManagerVersion = await _managerAPI.getLatestManagerVersion();
// add v to current version
if (!currentVersion.startsWith('v')) {
currentVersion = 'v$currentVersion';
}
if (_latestManagerVersion != currentVersion) {
return true;
}
return false;
}
Future<bool> hasPatchesUpdates() async {
final String? latestVersion = await _managerAPI.getLatestPatchesVersion();
final String? currentVersion = await _managerAPI.getCurrentPatchesVersion();
if (latestVersion != null) {
try {
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(
'$_latestManagerVersion',
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.w500,
color: Theme.of(context).colorScheme.secondary,
),
),
],
),
const SizedBox(height: 16.0),
StreamBuilder<double>(
initialData: 0.0,
stream: _revancedAPI.managerUpdateProgress.stream,
builder: (context, snapshot) {
return LinearProgressIndicator(
value: snapshot.data! * 0.01,
valueColor: AlwaysStoppedAnimation<Color>(
Theme.of(context).colorScheme.secondary,
),
);
},
),
const SizedBox(height: 16.0),
Align(
alignment: Alignment.centerRight,
child: CustomMaterialButton(
label: I18nText('cancelButton'),
onPressed: () {
_revancedAPI.disposeManagerUpdateProgress();
Navigator.of(context).pop();
},
),
),
],
),
],
),
);
final File? managerApk = await downloadManager();
if (managerApk != null) {
// await flutterLocalNotificationsPlugin.zonedSchedule(
// 0,
// FlutterI18n.translate(
// context,
// 'homeView.notificationTitle',
// ),
// FlutterI18n.translate(
// context,
// 'homeView.notificationText',
// ),
// tz.TZDateTime.now(tz.local).add(const Duration(seconds: 5)),
// const NotificationDetails(
// android: AndroidNotificationDetails(
// 'revanced_manager_channel',
// 'ReVanced Manager Channel',
// importance: Importance.max,
// priority: Priority.high,
// ticker: 'ticker',
// ),
// ),
// androidAllowWhileIdle: true,
// uiLocalNotificationDateInterpretation:
// UILocalNotificationDateInterpretation.absoluteTime,
// );
_toast.showBottom('homeView.installingMessage');
await AppInstaller.installApk(managerApk.path);
} else {
_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 +271,9 @@ class HomeViewModel extends BaseViewModel {
_toast.showBottom('homeView.updatesDisabled');
}
Future<void> showUpdateConfirmationDialog(BuildContext parentContext) {
Future<void> showUpdateConfirmationDialog(
BuildContext parentContext,
) {
return showModalBottomSheet(
context: parentContext,
isScrollControlled: true,
@@ -178,6 +302,7 @@ class HomeViewModel extends BaseViewModel {
_lastUpdate!.difference(DateTime.now()).inSeconds > 2) {
_managerAPI.clearAllData();
}
_toast.showBottom('homeView.refreshSuccess');
initialize(context);
}
}

View File

@@ -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,11 +15,12 @@ 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(
top: false,
bottom: false,
child: Scaffold(
body: CustomScrollView(
controller: model.scrollController,
@@ -28,7 +29,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 +49,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 +72,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),
@@ -153,6 +154,12 @@ class InstallerView extends StatelessWidget {
),
),
),
SliverFillRemaining(
hasScrollBody: false,
child: SizedBox(
height: MediaQuery.of(context).viewPadding.bottom,
),
),
],
),
),

View File

@@ -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,39 +132,44 @@ 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',
'Aborted...',
'An error occurred! Aborted\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');
update(-100.0, 'Aborted...', 'No app or patches selected! Aborted');
}
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);
}
}
}

View File

@@ -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: '',
),
],
),

View File

@@ -15,10 +15,14 @@ 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();
requestManageExternalStorage();
if (prefs.getBool('permissionsRequested') == null) {
await Permission.storage.request();
await Permission.manageExternalStorage.request();
await prefs.setBool('permissionsRequested', true);
RootAPI().hasRootPermissions().then(
(value) => Permission.requestInstallPackages.request().then(
@@ -26,8 +30,9 @@ class NavigationViewModel extends IndexTrackingViewModel {
),
);
}
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);
@@ -44,6 +49,17 @@ class NavigationViewModel extends IndexTrackingViewModel {
);
}
Future<void> requestManageExternalStorage() async {
final manageExternalStorageStatus =
await Permission.manageExternalStorage.status;
if (manageExternalStorageStatus.isDenied) {
await Permission.manageExternalStorage.request();
}
if (manageExternalStorageStatus.isPermanentlyDenied) {
await openAppSettings();
}
}
Widget getViewForIndex(int index) {
switch (index) {
case 0:

View File

@@ -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,
),
),
),

View File

@@ -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,69 @@ 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) {
final List<String> archs = info['supportedArch'];
final supportedAbis = ['arm64-v8a', 'x86', 'x86_64'];
return !archs.any((arch) => supportedAbis.contains(arch));
});
if (context.mounted && armv7) {
return showDialog(
context: context,
builder: (context) => AlertDialog(
title: I18nText('warning'),
backgroundColor: Theme.of(context).colorScheme.secondaryContainer,
content: I18nText('patcherView.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 +114,8 @@ class PatcherViewModel extends BaseViewModel {
],
),
);
} else {
navigateToInstaller();
}
}
@@ -88,31 +127,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

View File

@@ -1,10 +1,10 @@
import 'package:flutter/material.dart';
import 'package:flutter/material.dart' hide SearchBar;
import 'package:flutter_i18n/flutter_i18n.dart';
import 'package: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);
},
),
),
),
@@ -138,30 +129,32 @@ class _PatchesSelectorViewState extends State<PatchesSelectorView> {
),
)
: Padding(
padding: const EdgeInsets.symmetric(horizontal: 12.0)
.copyWith(bottom: 80),
padding:
const EdgeInsets.symmetric(horizontal: 12.0).copyWith(
bottom: MediaQuery.of(context).viewPadding.bottom + 8.0,
),
child: Column(
children: [
Row(
children: [
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 +171,7 @@ class _PatchesSelectorViewState extends State<PatchesSelectorView> {
packageVersion: model.getAppVersion(),
supportedPackageVersions:
model.getSupportedVersions(patch),
isUnsupported: !model.isPatchSupported(patch),
isUnsupported: !isPatchSupported(patch),
isSelected: model.isSelected(patch),
onChanged: (value) =>
model.selectPatch(patch, value),

View File

@@ -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');
}

View File

@@ -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();
},
)

View File

@@ -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),
);
}
}

View File

@@ -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();
},
)
],

View File

@@ -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();
}

View File

@@ -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,
),
},
),
),
),

View File

@@ -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(),
],
),

View File

@@ -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');
}

View 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(

View File

@@ -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 ')}';
}

View File

@@ -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)),
),
),

View File

@@ -1,22 +1,27 @@
import 'dart:typed_data';
import 'package:flutter/material.dart';
import 'package:flutter_i18n/flutter_i18n.dart';
import 'package:revanced_manager/ui/widgets/shared/custom_card.dart';
class InstalledAppItem extends StatefulWidget {
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,
required this.installedVersion,
this.onTap,
}) : super(key: key);
final String name;
final String pkgName;
final Uint8List icon;
final int patchesCount;
final String suggestedVersion;
final String installedVersion;
final Function()? onTap;
@override
State<InstalledAppItem> createState() => _InstalledAppItemState();
@@ -48,8 +53,7 @@ class _InstalledAppItemState extends State<InstalledAppItem> {
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
children: [
Text(
widget.name,
maxLines: 2,
@@ -59,20 +63,39 @@ class _InstalledAppItemState extends State<InstalledAppItem> {
fontWeight: FontWeight.w500,
),
),
const SizedBox(width: 6),
const SizedBox(width: 4),
I18nText(
widget.installedVersion.isEmpty
? 'All versions'
: 'v${widget.installedVersion}',
),
],
),
Text(widget.pkgName),
Row(
children: [
I18nText(
FlutterI18n.translate(
context,
'suggested',
translationParams: {
'version': widget.suggestedVersion.isEmpty
? 'All versions'
: 'v${widget.suggestedVersion}'
},
),
),
const SizedBox(width: 4),
Text(
widget.patchesCount == 1
? "${widget.patchesCount} patch"
: "${widget.patchesCount} patches",
? '${widget.patchesCount} patch'
: '${widget.patchesCount} patches',
style: TextStyle(
fontSize: 8,
color: Theme.of(context).colorScheme.secondary,
),
),
],
),
const SizedBox(height: 4),
Text(widget.pkgName),
],
),
),

View File

@@ -0,0 +1,83 @@
import 'package:flutter/material.dart';
import 'package:revanced_manager/ui/widgets/shared/custom_card.dart';
class NotInstalledAppItem extends StatefulWidget {
const NotInstalledAppItem({
Key? key,
required this.name,
required this.patchesCount,
required this.suggestedVersion,
this.onTap,
}) : super(key: key);
final String name;
final int patchesCount;
final String suggestedVersion;
final Function()? onTap;
@override
State<NotInstalledAppItem> createState() => _NotInstalledAppItem();
}
class _NotInstalledAppItem extends State<NotInstalledAppItem> {
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 4.0),
child: CustomCard(
onTap: widget.onTap,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Container(
height: 48,
padding: const EdgeInsets.symmetric(vertical: 4.0),
alignment: Alignment.center,
child: const CircleAvatar(
backgroundColor: Colors.transparent,
child: Icon(
Icons.square_rounded,
color: Colors.grey,
size: 44,
),
),
),
const SizedBox(width: 12),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text(
widget.name,
style: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.w500,
),
),
const SizedBox(height: 4),
const Text('App not installed.'),
const SizedBox(height: 4),
Row(
children: [
Text(
'Suggested: ${widget.suggestedVersion.isEmpty ? 'All versions' : 'v${widget.suggestedVersion}'}',
),
const SizedBox(width: 4),
Text(
widget.patchesCount == 1
? '${widget.patchesCount} patch'
: '${widget.patchesCount} patches',
style: TextStyle(
color: Theme.of(context).colorScheme.secondary,
),
),
],
),
],
),
),
],
),
),
);
}
}

View File

@@ -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(

View File

@@ -3,7 +3,6 @@ import 'package:flutter_i18n/flutter_i18n.dart';
import 'package:revanced_manager/app/app.locator.dart';
import 'package:revanced_manager/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,
// ),
// ),

View File

@@ -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,
),
),

View File

@@ -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',
),
),
const Row(
children: <Widget>[
Text('ReVanced Manager'),
],
),
const SizedBox(height: 4),
Row(
children: <Widget>[
FutureBuilder<String?>(
future: model.getLatestManagerReleaseTime(),
builder: (context, snapshot) =>
snapshot.hasData && snapshot.data!.isNotEmpty
? I18nText(
'latestCommitCard.timeagoLabel',
translationParams: {'time': snapshot.data!},
)
: I18nText('latestCommitCard.loadingLabel'),
),
],
),
],
),
const SizedBox(height: 4),
Row(
children: <Widget>[
I18nText('latestCommitCard.managerLabel'),
FutureBuilder<String?>(
future: model.getLatestManagerReleaseTime(),
builder: (context, snapshot) =>
snapshot.hasData && snapshot.data!.isNotEmpty
? I18nText(
'latestCommitCard.timeagoLabel',
translationParams: {'time': snapshot.data!},
)
: I18nText('latestCommitCard.loadingLabel'),
FutureBuilder<bool>(
future: model.hasManagerUpdates(),
initialData: false,
builder: (context, snapshot) => Opacity(
opacity: snapshot.hasData && snapshot.data! ? 1.0 : 0.25,
child: CustomMaterialButton(
label: I18nText('updateButton'),
onPressed: snapshot.hasData && snapshot.data!
? widget.onPressedManager
: () => {},
),
],
),
),
],
),
FutureBuilder<bool>(
future: locator<HomeViewModel>().hasManagerUpdates(),
initialData: false,
builder: (context, snapshot) => Opacity(
opacity: snapshot.hasData && snapshot.data! ? 1.0 : 0.25,
child: CustomMaterialButton(
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>[
const Row(
children: <Widget>[
Text('ReVanced Patches'),
],
),
const SizedBox(height: 4),
Row(
children: <Widget>[
FutureBuilder<String?>(
future: model.getLatestPatcherReleaseTime(),
builder: (context, snapshot) => Text(
snapshot.hasData && snapshot.data!.isNotEmpty
? FlutterI18n.translate(
context,
'latestCommitCard.timeagoLabel',
translationParams: {'time': snapshot.data!},
)
: FlutterI18n.translate(
context,
'latestCommitCard.loadingLabel',
),
),
),
],
),
],
),
),
FutureBuilder<bool>(
future: locator<HomeViewModel>().hasPatchesUpdates(),
initialData: false,
builder: (context, snapshot) => Opacity(
opacity: snapshot.hasData && snapshot.data! ? 1.0 : 0.25,
child: CustomMaterialButton(
label: I18nText('updateButton'),
onPressed: snapshot.hasData && snapshot.data!
? widget.onPressedPatches
: () => {},
),
),
),
],
),
],
),
),
],
);
}
}

View File

@@ -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'] ?? '',
),
),
],

Some files were not shown because too many files have changed in this diff Show More