chore: merge dev to main (#844)

This commit is contained in:
Aunali321 2023-05-06 03:26:14 +05:30 committed by GitHub
commit a290791410
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
46 changed files with 8543 additions and 495 deletions

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.

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

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

View File

@ -1,141 +0,0 @@
name: Android Release
on:
push:
branches:
- "**"
tags-ignore:
- "v*"
pull_request:
branches:
- "**"
workflow_dispatch:
jobs:
version:
name: Create version number
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Fetch all history for all tags and branches
run: |
git config remote.origin.url https://x-access-token:${{ secrets.GITHUB_TOKEN }}@github.com/${{ github.repository }}
git fetch --prune --depth=10000
- name: Install GitVersion
uses: gittools/actions/gitversion/setup@v0.9.7
with:
versionSpec: "5.x"
- name: Use GitVersion
id: gitversion
uses: gittools/actions/gitversion/execute@v0.9.7
- name: Create version.txt with nuGetVersion
run: echo ${{ steps.gitversion.outputs.nuGetVersion }} > version.txt
- name: Upload version.txt
uses: actions/upload-artifact@v2
with:
name: gitversion
path: version.txt
build:
name: Create Android Build
needs: version
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Get version.txt
uses: actions/download-artifact@v2
with:
name: gitversion
- name: Create new file without newline char from version.txt
run: tr -d '\n' < version.txt > version1.txt
- name: Read version
id: version
uses: juliangruber/read-file-action@v1
with:
path: version1.txt
- name: Update version in YAML
run: sed -i 's/99.99.99+99/${{ steps.version.outputs.content }}+${{ github.run_number }}/g' pubspec.yaml
- name: Download Android keystore
id: android_keystore
uses: timheuer/base64-to-file@v1.0.3
with:
fileName: upload-keystore.jks
encodedString: ${{ secrets.SIGNING_KEYSTORE }}
- name: Create key.properties
run: |
echo "storeFile=${{ steps.android_keystore.outputs.filePath }}" > android/key.properties
echo "storePassword=${{ secrets.SIGNING_KEYSTORE_PASSWORD }}" >> android/key.properties
echo "keyPassword=${{ secrets.SIGNING_KEY_PASSWORD }}" >> android/key.properties
echo "keyAlias=${{ secrets.SIGNING_KEY_ALIAS }}" >> android/key.properties
- uses: actions/setup-java@v3
with:
distribution: 'zulu'
java-version: "12.x"
cache: gradle
- uses: subosito/flutter-action@v2
with:
channel: 'stable'
cache: true
- name: Get dependencies
run: flutter pub get
- name: Generate files with Builder
run: flutter packages pub run build_runner build --delete-conflicting-outputs
- name: Build with Flutter
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
SIGNING_KEY_ALIAS: ${{ secrets.SIGNING_KEY_ALIAS }}
SIGNING_KEY_PASSWORD: ${{ secrets.SIGNING_KEY_PASSWORD }}
SIGNING_STORE_PASSWORD: ${{ secrets.SIGNING_KEYSTORE_PASSWORD }}
run: flutter build apk
- name: Sign APK
id: sign_apk
uses: ilharp/sign-android-release@v1
with:
releaseDir: build/app/outputs/apk/release
signingKey: ${{ secrets.SIGNING_KEYSTORE }}
keyStorePassword: ${{ secrets.SIGNING_KEYSTORE_PASSWORD }}
keyAlias: ${{ secrets.SIGNING_KEY_ALIAS }}
keyPassword: ${{ secrets.SIGNING_KEY_PASSWORD }}
- name: Add version to APK
run: mv ${{ steps.sign_apk.outputs.signedFile }} revanced-manager-${{ env.RELEASE_VERSION }}.apk
- name: Upload APK
uses: actions/upload-artifact@v3
with:
name: revanced-manager-${{ env.RELEASE_VERSION }}
path: revanced-manager-${{ env.RELEASE_VERSION }}.apk
# deploy:
# name: Deploy Android Build
# needs: build
# runs-on: ubuntu-latest
# steps:
# - uses: actions/checkout@v1
# - name: Get Android Build from artifacts
# uses: actions/download-artifact@v2
# with:
# name: android-release
env:
ACTIONS_ALLOW_UNSECURE_COMMANDS: true

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"

View File

@ -1,37 +1,41 @@
name: "Android CI PR Build"
on:
pull_request:
branches:
- "**"
jobs:
release:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set env
run: echo "RELEASE_VERSION=${GITHUB_REF#refs/*/}" >> $GITHUB_ENV
- name: Set up JDK 12
uses: actions/setup-java@v3
with:
java-version: '12'
distribution: 'zulu'
- uses: subosito/flutter-action@v2
with:
channel: 'stable'
- name: Set up Flutter
run: flutter pub get
- name: Generate files with Builder
run: flutter packages pub run build_runner build --delete-conflicting-outputs
- name: Build with Flutter
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: flutter build apk
- name: Add version to APK
run: mv build/app/outputs/flutter-apk/app-release.apk revanced-manager-${{ env.RELEASE_VERSION }}.apk
- name: Upload APK
uses: actions/upload-artifact@v3
with:
name: revanced-manager-${{ env.RELEASE_VERSION }}
path: revanced-manager-${{ env.RELEASE_VERSION }}.apk
name: PR Build
on:
workflow_dispatch:
jobs:
build:
name: Build
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
with:
# Make sure the release step uses its own credentials:
# https://github.com/cycjimmy/semantic-release-action#private-packages
persist-credentials: false
fetch-depth: 0
- name: Setup JDK
uses: actions/setup-java@v3
with:
java-version: '17'
distribution: 'zulu'
cache: 'gradle'
- name: Setup Flutter
uses: subosito/flutter-action@v2
with:
channel: 'stable'
cache: true
- name: Install Flutter dependencies
run: flutter pub get
- name: Generate files with Builder
run: flutter packages pub run build_runner build --delete-conflicting-outputs
- name: Build with Flutter
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: flutter build apk
- name: Upload build
uses: actions/upload-artifact@v3
with:
name: revanced-manager
path: build/app/outputs/flutter-apk/app-release.apk

View File

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

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

7
.gitignore vendored
View File

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

75
.releaserc Normal file
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). If you are interested in maintaining this repository, please let us know at manager@revanced.app.
## 🔽 Download
To download the Alpha version of Manager, go [here](https://github.com/revanced/revanced-manager/releases/latest) and install the provided APK file.
To download latest Manager, go [here](https://github.com/revanced/revanced-manager/releases/latest) and install the provided APK file.
## 📝 Prerequisites
1. Android 8 or higher
2. Does not work on some armv7 devices
3. [Vanced MicroG](https://github.com/TeamVanced/VancedMicroG/releases) required for YouTube and YouTube Music (Only for non-root)
## ⚠️ Disclaimer
*Please note that even though we're releasing the Manager, it is an ALPHA version. There's a big chance that the Manager might not work at all for you.*
## 🔴 Issues
For suggestions and bug reports, open an issue [here](https://github.com/revanced/revanced-manager/issues/new/choose).

View File

@ -11,7 +11,10 @@ include: package:flutter_lints/flutter.yaml
analyzer:
exclude:
- lib/utils/env_class.g.dart
- lib/app/app.locator.dart
- lib/app/app.router.dart
- lib/models/patch.g.dart
- lib/models/patched_application.g.dart
linter:
rules:

View File

@ -30,6 +30,7 @@ android {
ndkVersion flutter.ndkVersion
compileOptions {
coreLibraryDesugaringEnabled true
sourceCompatibility JavaVersion.VERSION_11
targetCompatibility JavaVersion.VERSION_11
}
@ -48,6 +49,7 @@ android {
targetSdkVersion 33
versionCode flutterVersionCode.toInteger()
versionName flutterVersionName
multiDexEnabled true
}
buildTypes {
@ -81,4 +83,11 @@ dependencies {
implementation("org.microg:cronet-common:$cronetVersion")
implementation("org.microg:cronet-native:$cronetVersion")
// Core libraries
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.5'
// Window
implementation 'androidx.window:window:1.0.0'
implementation 'androidx.window:window-java:1.0.0'
}

View File

@ -44,6 +44,8 @@ class MainActivity : FlutterActivity() {
val selectedPatches = call.argument<List<String>>("selectedPatches")
val cacheDirPath = call.argument<String>("cacheDirPath")
val keyStoreFilePath = call.argument<String>("keyStoreFilePath")
val keystorePassword = call.argument<String>("keystorePassword")
if (patchBundleFilePath != null &&
originalFilePath != null &&
inputFilePath != null &&
@ -52,7 +54,8 @@ class MainActivity : FlutterActivity() {
integrationsPath != null &&
selectedPatches != null &&
cacheDirPath != null &&
keyStoreFilePath != null
keyStoreFilePath != null &&
keystorePassword != null
) {
runPatcher(
result,
@ -64,7 +67,8 @@ class MainActivity : FlutterActivity() {
integrationsPath,
selectedPatches,
cacheDirPath,
keyStoreFilePath
keyStoreFilePath,
keystorePassword
)
} else {
result.notImplemented()
@ -85,7 +89,8 @@ class MainActivity : FlutterActivity() {
integrationsPath: String,
selectedPatches: List<String>,
cacheDirPath: String,
keyStoreFilePath: String
keyStoreFilePath: String,
keystorePassword: String
) {
val originalFile = File(originalFilePath)
val inputFile = File(inputFilePath)
@ -242,7 +247,7 @@ class MainActivity : FlutterActivity() {
// Signer("ReVanced", "s3cur3p@ssw0rd").signApk(patchedFile, outFile, keyStoreFile)
try {
Signer("ReVanced", "s3cur3p@ssw0rd").signApk(patchedFile, outFile, keyStoreFile)
Signer("ReVanced", keystorePassword).signApk(patchedFile, outFile, keyStoreFile)
} catch (e: Exception) {
//log to console
print("Error signing apk: ${e.message}")

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.2 KiB

After

Width:  |  Height:  |  Size: 5.1 KiB

View File

@ -1,6 +1,7 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-7.6.1-bin.zip
distributionSha256Sum=6147605a23b4eff6c334927a86ff3508cb5d6722cd624c97ded4c2e8640f1f87
networkTimeout=10000
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

View File

@ -13,6 +13,7 @@
"settingsTab": "Settings"
},
"homeView": {
"refreshSuccess": "Refreshed successfully",
"widgetTitle": "Dashboard",
"updatesSubtitle": "Updates",
"patchedSubtitle": "Patched applications",
@ -47,7 +48,8 @@
"patcherView": {
"widgetTitle": "Patcher",
"patchButton": "Patch",
"patchDialogText": "You have selected a resource patch and a split APK installation has been detected, so patching errors may occur.\nAre you sure you want to proceed?"
"patchDialogText": "You have selected a resource patch and a split APK installation has been detected, so patching errors may occur.\nAre you sure you want to proceed?",
"armv7WarningDialogText": "Patching on ARMv7 devices is not yet supported and might fail. Proceed anyways?"
},
"appSelectorCard": {
"widgetTitle": "Select an application",
@ -55,7 +57,7 @@
"widgetSubtitle": "No application selected",
"noAppsLabel": "No applications found",
"currentVersion": "Current",
"recommendedVersion": "Recommended",
"suggestedVersion": "Suggested",
"anyVersion": "any"
},
"patchSelectorCard": {
@ -71,23 +73,25 @@
"appSelectorView": {
"viewTitle": "Select an application",
"searchBarHint": "Search applications",
"selectFromStorageButton": "Select from storage",
"storageButton": "Storage",
"errorMessage": "Unable to use selected application"
"errorMessage": "Unable to use selected application",
"downloadToast": "Download function is not available yet",
"featureNotAvailable": "Feature not implemented",
"featureNotAvailableText": "This feature has not been added yet for non-root. You'll need to select APK files from storage for now."
},
"patchesSelectorView": {
"viewTitle": "Select patches",
"searchBarHint": "Search patches",
"doneButton": "Done",
"recommended": "Recommended",
"recommendedTooltip": "Select all recommended patches",
"all": "All",
"allTooltip": "Select all patches",
"default": "Default",
"defaultTooltip": "Select all default patches",
"none": "None",
"noneTooltip": "Deselect all patches",
"loadPatchesSelection": "Load patches selection",
"noSavedPatches": "No saved patches for the selected app.\nPress Done to save current selection.",
"noPatchesFound": "No patches found for the selected app",
"selectAllPatchesWarningContent": "You are about to select all patches, that includes unrecommended patches and can cause unwanted behavior."
"selectAllPatchesWarningContent": "You are about to select all patches, that includes non-suggested patches and can cause unwanted behavior."
},
"patchItem": {
"unsupportedDialogText": "Selecting this patch may result in patching errors.\n\nApp version: {packageVersion}\nSupported versions:\n{supportedVersions}",
@ -151,6 +155,7 @@
"restartAppForChanges": "Restart the app to apply changes",
"deleteKeystoreLabel": "Delete keystore",
"deleteKeystoreHint": "Delete the keystore used to sign the app",
"deleteKeystoreDialogText": "Are you sure you want to delete the keystore used to sign patched applications?",
"deletedKeystore": "Keystore deleted",
"deleteTempDirLabel": "Delete temporary files",
"deleteTempDirHint": "Delete unused temporary files",
@ -168,7 +173,17 @@
"jsonSelectorErrorMessage": "Unable to use selected JSON file",
"deleteLogsLabel": "Delete logs",
"deleteLogsHint": "Delete collected manager logs",
"deletedLogs": "Logs deleted"
"deletedLogs": "Logs deleted",
"exportKeystoreLabel": "Export keystore",
"exportKeystoreHint": "Export keystore used to sign apps",
"exportedKeystore": "Keystore exported",
"noKeystoreExportFileFound": "No keystore to export",
"importKeystoreLabel": "Import keystore",
"importKeystoreHint": "Import keystore used to sign apps",
"importedKeystore": "Keystore imported",
"keystoreSelectorErrorMessage": "Unable to use selected KEYSTORE file",
"selectKeystorePassword": "Keystore Password",
"selectKeystorePasswordHint": "Select keystore password used to sign the apk"
},
"appInfoView": {
"widgetTitle": "App info",

View File

@ -3,20 +3,23 @@ import 'dart:io';
import 'package:collection/collection.dart';
import 'package:dio/dio.dart';
import 'package:dio_http_cache_lts/dio_http_cache_lts.dart';
import 'package:dio_cache_interceptor/dio_cache_interceptor.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter_cache_manager/flutter_cache_manager.dart';
import 'package:injectable/injectable.dart';
import 'package:native_dio_adapter/native_dio_adapter.dart';
import 'package:revanced_manager/models/patch.dart';
@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',
@ -30,13 +33,29 @@ class GithubAPI {
Future<void> initialize(String repoUrl) async {
try {
if (Platform.isIOS || Platform.isMacOS || Platform.isAndroid) {
final CronetEngine androidCronetEngine = await CronetEngine.build(
userAgent: 'ReVanced Manager',
enableBrotli: true,
enableQuic: true,
);
_dio.httpClientAdapter =
NativeAdapter(androidCronetEngine: androidCronetEngine);
_dio = Dio(
BaseOptions(
baseUrl: repoUrl,
),
);
}
_dio = Dio(
BaseOptions(
baseUrl: repoUrl,
),
);
_dio.interceptors.add(_dioCacheManager.interceptor);
_dio.interceptors.add(DioCacheInterceptor(options: _cacheOptions));
} on Exception catch (e) {
if (kDebugMode) {
print(e);
@ -46,7 +65,7 @@ class GithubAPI {
Future<void> clearAllCache() async {
try {
await _dioCacheManager.clearAll();
await _cacheOptions.store!.clean();
} on Exception catch (e) {
if (kDebugMode) {
print(e);
@ -54,11 +73,12 @@ class GithubAPI {
}
}
Future<Map<String, dynamic>?> getLatestRelease(String repoName) async {
Future<Map<String, dynamic>?> getLatestRelease(
String repoName,
) async {
try {
final response = await _dio.get(
'/repos/$repoName/releases',
options: _cacheOptions,
);
return response.data[0];
} on Exception catch (e) {
@ -83,7 +103,6 @@ class GithubAPI {
'path': path,
'since': since.toIso8601String(),
},
options: _cacheOptions,
);
final List<dynamic> commits = response.data;
return commits
@ -102,9 +121,13 @@ class GithubAPI {
return [];
}
Future<File?> getLatestReleaseFile(String extension, String repoName) async {
Future<File?> getLatestReleaseFile(
String extension,
String repoName,
) async {
try {
final Map<String, dynamic>? release = await getLatestRelease(repoName);
final Map<String, dynamic>? release =
await getLatestRelease(repoName);
if (release != null) {
final Map<String, dynamic>? asset =
(release['assets'] as List<dynamic>).firstWhereOrNull(
@ -143,7 +166,8 @@ class GithubAPI {
Future<String> getLastestReleaseVersion(String repoName) async {
try {
final Map<String, dynamic>? release = await getLatestRelease(repoName);
final Map<String, dynamic>? release =
await getLatestRelease(repoName);
if (release != null) {
return release['tag_name'];
} else {

View File

@ -11,6 +11,7 @@ import 'package:revanced_manager/models/patched_application.dart';
import 'package:revanced_manager/services/github_api.dart';
import 'package:revanced_manager/services/revanced_api.dart';
import 'package:revanced_manager/services/root_api.dart';
import 'package:revanced_manager/utils/check_for_supported_patch.dart';
import 'package:shared_preferences/shared_preferences.dart';
@lazySingleton
@ -21,7 +22,11 @@ class ManagerAPI {
final String patcherRepo = 'revanced-patcher';
final String cliRepo = 'revanced-cli';
late SharedPreferences _prefs;
bool isRooted = false;
String storedPatchesFile = '/selected-patches.json';
String keystoreFile =
'/sdcard/Android/data/app.revanced.manager.flutter/files/revanced-manager.keystore';
String defaultKeystorePassword = 's3cur3p@ssw0rd';
String defaultApiUrl = 'https://releases.revanced.app/';
String defaultRepoUrl = 'https://api.github.com';
String defaultPatcherRepo = 'revanced/revanced-patcher';
@ -29,9 +34,14 @@ class ManagerAPI {
String defaultIntegrationsRepo = 'revanced/revanced-integrations';
String defaultCliRepo = 'revanced/revanced-cli';
String defaultManagerRepo = 'revanced/revanced-manager';
String? patchesVersion = '';
bool isDefaultPatchesRepo() {
return getPatchesRepo() == 'revanced/revanced-patches';
}
Future<void> initialize() async {
_prefs = await SharedPreferences.getInstance();
isRooted = await _rootAPI.isRooted();
storedPatchesFile =
(await getApplicationDocumentsDirectory()).path + storedPatchesFile;
}
@ -114,6 +124,14 @@ class ManagerAPI {
await _prefs.setBool('experimentalPatchesEnabled', value);
}
Future<void> setKeystorePassword(String password) async {
await _prefs.setString('keystorePassword', password);
}
String getKeystorePassword() {
return _prefs.getString('keystorePassword') ?? defaultKeystorePassword;
}
Future<void> deleteTempFolder() async {
final Directory dir = Directory('/data/local/tmp/revanced-manager');
if (await dir.exists()) {
@ -123,7 +141,7 @@ class ManagerAPI {
Future<void> deleteKeystore() async {
final File keystore = File(
'/sdcard/Android/data/app.revanced.manager.flutter/files/revanced-manager.keystore',
keystoreFile,
);
if (await keystore.exists()) {
await keystore.delete();
@ -135,7 +153,9 @@ class ManagerAPI {
return apps.map((a) => PatchedApplication.fromJson(jsonDecode(a))).toList();
}
Future<void> setPatchedApps(List<PatchedApplication> patchedApps) async {
Future<void> setPatchedApps(
List<PatchedApplication> patchedApps,
) async {
if (patchedApps.length > 1) {
patchedApps.sort((a, b) => a.name.compareTo(b.name));
}
@ -237,15 +257,24 @@ class ManagerAPI {
}
Future<File?> downloadManager() async {
return await _revancedAPI.getLatestReleaseFile('.apk', defaultManagerRepo);
return await _revancedAPI.getLatestReleaseFile(
'.apk',
defaultManagerRepo,
);
}
Future<String?> getLatestPatcherReleaseTime() async {
return await _revancedAPI.getLatestReleaseTime('.gz', defaultPatcherRepo);
return await _revancedAPI.getLatestReleaseTime(
'.gz',
defaultPatcherRepo,
);
}
Future<String?> getLatestManagerReleaseTime() async {
return await _revancedAPI.getLatestReleaseTime('.apk', defaultManagerRepo);
return await _revancedAPI.getLatestReleaseTime(
'.apk',
defaultManagerRepo,
);
}
Future<String?> getLatestManagerVersion() async {
@ -267,6 +296,19 @@ class ManagerAPI {
return packageInfo.version;
}
Future<String?> getCurrentPatchesVersion() async {
if (isDefaultPatchesRepo()) {
patchesVersion = await getLatestPatchesVersion();
// print('Patches version: $patchesVersion');
return patchesVersion ?? '0.0.0';
} else {
// fetch from github
patchesVersion =
await _githubAPI.getLastestReleaseVersion(getPatchesRepo());
}
return null;
}
Future<List<PatchedApplication>> getAppsToRemove(
List<PatchedApplication> patchedApps,
) async {
@ -359,8 +401,9 @@ class ManagerAPI {
final int currentInstalledVersionInt = int.parse(
currentInstalledVersion.replaceAll(RegExp('[^0-9]'), ''),
);
final int currentSavedVersionInt =
int.parse(currentSavedVersion.replaceAll(RegExp('[^0-9]'), ''));
final int currentSavedVersionInt = int.parse(
currentSavedVersion.replaceAll(RegExp('[^0-9]'), ''),
);
if (currentInstalledVersionInt > currentSavedVersionInt) {
app.hasUpdates = true;
}
@ -383,7 +426,10 @@ class ManagerAPI {
return !existsNonRoot;
}
Future<bool> hasAppUpdates(String packageName, DateTime patchDate) async {
Future<bool> hasAppUpdates(
String packageName,
DateTime patchDate,
) async {
final List<String> commits = await _githubAPI.getCommits(
packageName,
getPatchesRepo(),
@ -421,7 +467,10 @@ class ManagerAPI {
return app != null && app.isSplit;
}
Future<void> setSelectedPatches(String app, List<String> patches) async {
Future<void> setSelectedPatches(
String app,
List<String> patches,
) async {
final File selectedPatchesFile = File(storedPatchesFile);
final Map<String, dynamic> patchesMap = await readSelectedPatchesFile();
if (patches.isEmpty) {
@ -432,9 +481,33 @@ class ManagerAPI {
selectedPatchesFile.writeAsString(jsonEncode(patchesMap));
}
// get default patches for app
Future<List<String>> getDefaultPatches() async {
final List<Patch> patches = await getPatches();
final List<String> defaultPatches = [];
if (areExperimentalPatchesEnabled() == false) {
defaultPatches.addAll(
patches
.where(
(element) =>
element.excluded == false && isPatchSupported(element),
)
.map((p) => p.name),
);
} else {
defaultPatches.addAll(
patches
.where((element) => isPatchSupported(element))
.map((p) => p.name),
);
}
return defaultPatches;
}
Future<List<String>> getSelectedPatches(String app) async {
final Map<String, dynamic> patchesMap = await readSelectedPatchesFile();
return List.from(patchesMap.putIfAbsent(app, () => List.empty()));
final List<String> defaultPatches = await getDefaultPatches();
return List.from(patchesMap.putIfAbsent(app, () => defaultPatches));
}
Future<Map<String, dynamic>> readSelectedPatchesFile() async {

View File

@ -118,13 +118,17 @@ class PatcherAPI {
return filteredPatches[packageName];
}
Future<List<Patch>> getAppliedPatches(List<String> appliedPatches) async {
Future<List<Patch>> getAppliedPatches(
List<String> appliedPatches,
) async {
return _patches
.where((patch) => appliedPatches.contains(patch.name))
.toList();
}
Future<bool> needsResourcePatching(List<Patch> selectedPatches) async {
Future<bool> needsResourcePatching(
List<Patch> selectedPatches,
) async {
return selectedPatches.any(
(patch) => patch.dependencies.any(
(dep) => dep.contains('resource-'),
@ -210,6 +214,7 @@ class PatcherAPI {
'selectedPatches': selectedPatches.map((p) => p.name).toList(),
'cacheDirPath': cacheDir.path,
'keyStoreFilePath': _keyStoreFile.path,
'keystorePassword': _managerAPI.getKeystorePassword(),
},
);
} on Exception catch (e) {
@ -234,7 +239,9 @@ class PatcherAPI {
}
} else {
await AppInstaller.installApk(_outFile!.path);
return await DeviceApps.isAppInstalled(patchedApp.packageName);
return await DeviceApps.isAppInstalled(
patchedApp.packageName,
);
}
} on Exception catch (e) {
if (kDebugMode) {
@ -303,7 +310,7 @@ class PatcherAPI {
ShareExtend.share(log.path, 'file');
}
String getRecommendedVersion(String packageName) {
String getSuggestedVersion(String packageName) {
final Map<String, int> versions = {};
for (final Patch patch in _patches) {
final Package? package = patch.compatiblePackages.firstWhereOrNull(

View File

@ -1,13 +1,14 @@
import 'dart:async';
import 'dart:developer';
import 'dart:io';
import 'package:collection/collection.dart';
import 'package:dio/dio.dart';
import 'package:dio_http_cache_lts/dio_http_cache_lts.dart';
import 'package:dio_cache_interceptor/dio_cache_interceptor.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter_cache_manager/flutter_cache_manager.dart';
import 'package:injectable/injectable.dart';
import 'package:native_dio_client/native_dio_client.dart';
import 'package:native_dio_adapter/native_dio_adapter.dart';
import 'package:revanced_manager/models/patch.dart';
import 'package:revanced_manager/utils/check_for_gms.dart';
import 'package:timeago/timeago.dart';
@ -15,10 +16,11 @@ import 'package:timeago/timeago.dart';
@lazySingleton
class RevancedAPI {
late Dio _dio = Dio();
final DioCacheManager _dioCacheManager = DioCacheManager(CacheConfig());
final Options _cacheOptions = buildCacheOptions(
const Duration(hours: 6),
final _cacheOptions = CacheOptions(
store: MemCacheStore(),
maxStale: const Duration(days: 1),
priority: CachePriority.high,
);
Future<void> initialize(String apiUrl) async {
@ -33,14 +35,25 @@ class RevancedAPI {
);
log('ReVanced API: Using default engine + $isGMSInstalled');
} else {
_dio = Dio(
BaseOptions(
baseUrl: apiUrl,
),
)..httpClientAdapter = NativeAdapter();
if (Platform.isIOS || Platform.isMacOS || Platform.isAndroid) {
final CronetEngine androidCronetEngine = await CronetEngine.build(
userAgent: 'ReVanced Manager',
enableBrotli: true,
enableQuic: true,
);
_dio.httpClientAdapter =
NativeAdapter(androidCronetEngine: androidCronetEngine);
_dio = Dio(
BaseOptions(
baseUrl: apiUrl,
),
);
}
log('ReVanced API: Using CronetEngine + $isGMSInstalled');
}
_dio.interceptors.add(_dioCacheManager.interceptor);
_dio.interceptors.add(DioCacheInterceptor(options: _cacheOptions));
} on Exception catch (e) {
if (kDebugMode) {
print(e);
@ -50,7 +63,7 @@ class RevancedAPI {
Future<void> clearAllCache() async {
try {
await _dioCacheManager.clearAll();
await _cacheOptions.store!.clean();
} on Exception catch (e) {
if (kDebugMode) {
print(e);
@ -61,7 +74,7 @@ class RevancedAPI {
Future<Map<String, List<dynamic>>> getContributors() async {
final Map<String, List<dynamic>> contributors = {};
try {
final response = await _dio.get('/contributors', options: _cacheOptions);
final response = await _dio.get('/contributors');
final List<dynamic> repositories = response.data['repositories'];
for (final Map<String, dynamic> repo in repositories) {
final String name = repo['name'];
@ -78,7 +91,7 @@ class RevancedAPI {
Future<List<Patch>> getPatches() async {
try {
final response = await _dio.get('/patches', options: _cacheOptions);
final response = await _dio.get('/patches');
final List<dynamic> patches = response.data;
return patches.map((patch) => Patch.fromJson(patch)).toList();
} on Exception catch (e) {
@ -94,7 +107,7 @@ class RevancedAPI {
String repoName,
) async {
try {
final response = await _dio.get('/tools', options: _cacheOptions);
final response = await _dio.get('/tools');
final List<dynamic> tools = response.data['tools'];
return tools.firstWhereOrNull(
(t) =>
@ -130,7 +143,10 @@ class RevancedAPI {
return null;
}
Future<File?> getLatestReleaseFile(String extension, String repoName) async {
Future<File?> getLatestReleaseFile(
String extension,
String repoName,
) async {
try {
final Map<String, dynamic>? release = await _getLatestRelease(
extension,
@ -149,6 +165,43 @@ class RevancedAPI {
return null;
}
StreamController<double> managerUpdateProgress = StreamController<double>();
void updateManagerDownloadProgress(int progress) {
managerUpdateProgress.add(progress.toDouble());
}
Stream<double> getManagerUpdateProgress() {
return managerUpdateProgress.stream;
}
void disposeManagerUpdateProgress() {
managerUpdateProgress.close();
}
Future<File?> downloadManager() async {
final Map<String, dynamic>? release = await _getLatestRelease(
'.apk',
'revanced/revanced-manager',
);
File? outputFile;
await for (final result in DefaultCacheManager().getFileStream(
release!['browser_download_url'] as String,
withProgress: true,
)) {
if (result is DownloadProgress) {
final totalSize = result.totalSize ?? 10000000;
final progress = (result.downloaded / totalSize * 100).round();
updateManagerDownloadProgress(progress);
} else if (result is FileInfo) {
// The download is complete; convert the FileInfo object to a File object
outputFile = File(result.file.path);
}
}
return outputFile;
}
Future<String?> getLatestReleaseTime(
String extension,
String repoName,

View File

@ -3,6 +3,7 @@ import 'package:flutter_i18n/flutter_i18n.dart';
import 'package:revanced_manager/ui/views/app_selector/app_selector_viewmodel.dart';
import 'package:revanced_manager/ui/widgets/appSelectorView/app_skeleton_loader.dart';
import 'package:revanced_manager/ui/widgets/appSelectorView/installed_app_item.dart';
import 'package:revanced_manager/ui/widgets/appSelectorView/not_installed_app_item.dart';
import 'package:revanced_manager/ui/widgets/shared/search_bar.dart';
import 'package:stacked/stacked.dart' hide SkeletonLoader;
@ -76,7 +77,16 @@ class _AppSelectorViewState extends State<AppSelectorView> {
SliverToBoxAdapter(
child: model.noApps
? Center(
child: I18nText('appSelectorCard.noAppsLabel'),
child: I18nText(
'appSelectorView.noApps',
child: Text(
'',
style: TextStyle(
color:
Theme.of(context).textTheme.titleLarge!.color,
),
),
),
)
: model.apps.isEmpty
? const AppSkeletonLoader()
@ -84,22 +94,48 @@ class _AppSelectorViewState extends State<AppSelectorView> {
padding: const EdgeInsets.symmetric(horizontal: 12.0)
.copyWith(bottom: 80),
child: Column(
children: model
.getFilteredApps(_query)
.map(
(app) => InstalledAppItem(
name: app.appName,
pkgName: app.packageName,
icon: app.icon,
patchesCount:
model.patchesCount(app.packageName),
onTap: () {
model.selectApp(app);
Navigator.of(context).pop();
},
),
)
.toList(),
children: [
...model
.getFilteredApps(_query)
.map(
(app) => InstalledAppItem(
name: app.appName,
pkgName: app.packageName,
icon: app.icon,
patchesCount:
model.patchesCount(app.packageName),
suggestedVersion:
model.getSuggestedVersion(
app.packageName,
),
onTap: () {
model.isRooted
? model.selectApp(app).then(
(_) => Navigator.of(context)
.pop(),
)
: model.showSelectFromStorageDialog(
context,
);
},
),
)
.toList(),
...model
.getFilteredAppsNames(_query)
.map(
(app) => NotInstalledAppItem(
name: app,
patchesCount: model.patchesCount(app),
suggestedVersion:
model.getSuggestedVersion(app),
onTap: () {
model.showDownloadToast();
},
),
)
.toList(),
],
),
),
),

View File

@ -4,25 +4,37 @@ import 'package:device_apps/device_apps.dart';
import 'package:file_picker/file_picker.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_i18n/flutter_i18n.dart';
import 'package:revanced_manager/app/app.locator.dart';
import 'package:revanced_manager/models/patch.dart';
import 'package:revanced_manager/models/patched_application.dart';
import 'package:revanced_manager/services/manager_api.dart';
import 'package:revanced_manager/services/patcher_api.dart';
import 'package:revanced_manager/services/revanced_api.dart';
import 'package:revanced_manager/services/toast.dart';
import 'package:revanced_manager/ui/views/patcher/patcher_viewmodel.dart';
import 'package:revanced_manager/ui/widgets/shared/custom_material_button.dart';
import 'package:stacked/stacked.dart';
class AppSelectorViewModel extends BaseViewModel {
final PatcherAPI _patcherAPI = locator<PatcherAPI>();
final ManagerAPI _managerAPI = locator<ManagerAPI>();
final RevancedAPI _revancedAPI = locator<RevancedAPI>();
final Toast _toast = locator<Toast>();
final List<ApplicationWithIcon> apps = [];
List<String> allApps = [];
bool noApps = false;
bool isRooted = false;
int patchesCount(String packageName) {
return _patcherAPI.getFilteredPatches(packageName).length;
}
List<Patch> patches = [];
Future<void> initialize() async {
patches = await _revancedAPI.getPatches();
isRooted = _managerAPI.isRooted;
apps.addAll(
await _patcherAPI
.getFilteredInstalledApps(_managerAPI.areUniversalPatchesEnabled()),
@ -34,9 +46,25 @@ class AppSelectorViewModel extends BaseViewModel {
.compareTo(_patcherAPI.getFilteredPatches(a.packageName).length),
);
noApps = apps.isEmpty;
getAllApps();
notifyListeners();
}
List<String> getAllApps() {
allApps = patches
.expand((e) => e.compatiblePackages.map((p) => p.name))
.toSet()
.where((name) => !apps.any((app) => app.packageName == name))
.toList();
return allApps;
}
String getSuggestedVersion(String packageName) {
return _patcherAPI.getSuggestedVersion(packageName);
}
Future<void> selectApp(ApplicationWithIcon application) async {
locator<PatcherViewModel>().selectedApp = PatchedApplication(
name: application.appName,
@ -50,6 +78,80 @@ class AppSelectorViewModel extends BaseViewModel {
locator<PatcherViewModel>().loadLastSelectedPatches();
}
Future showSelectFromStorageDialog(BuildContext context) async {
return showDialog(
context: context,
builder: (context) => SimpleDialog(
alignment: Alignment.center,
contentPadding:
const EdgeInsets.symmetric(horizontal: 20, vertical: 20),
children: [
const SizedBox(height: 10),
Icon(
Icons.block,
size: 28,
color: Theme.of(context).colorScheme.primary,
),
const SizedBox(height: 20),
I18nText(
'appSelectorView.featureNotAvailable',
child: const Text(
'',
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.w600,
wordSpacing: 1.5,
),
),
),
const SizedBox(height: 20),
I18nText(
'appSelectorView.featureNotAvailableText',
child: const Text(
'',
style: TextStyle(
fontSize: 14,
),
),
),
const SizedBox(height: 30),
CustomMaterialButton(
onPressed: () => selectAppFromStorage(context).then(
(_) {
Navigator.pop(context);
Navigator.pop(context);
},
),
label: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Icon(Icons.sd_card),
const SizedBox(width: 10),
I18nText('appSelectorView.selectFromStorageButton'),
],
),
),
const SizedBox(height: 10),
CustomMaterialButton(
isFilled: false,
onPressed: () {
Navigator.pop(context);
Navigator.pop(context);
},
label: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const SizedBox(width: 10),
I18nText('cancelButton'),
],
),
),
],
),
);
}
Future<void> selectAppFromStorage(BuildContext context) async {
try {
final FilePickerResult? result = await FilePicker.platform.pickFiles(
@ -105,4 +207,18 @@ class AppSelectorViewModel extends BaseViewModel {
)
.toList();
}
List<String> getFilteredAppsNames(String query) {
return allApps
.where(
(app) =>
query.isEmpty ||
query.length < 2 ||
app.toLowerCase().contains(query.toLowerCase()),
)
.toList();
}
void showDownloadToast() =>
_toast.showBottom('appSelectorView.downloadToast');
}

View File

@ -17,6 +17,7 @@ class HomeView extends StatelessWidget {
Widget build(BuildContext context) {
return ViewModelBuilder<HomeViewModel>.reactive(
disposeViewModel: false,
fireOnViewModelReadyOnce: true,
onViewModelReady: (model) => model.initialize(context),
viewModelBuilder: () => locator<HomeViewModel>(),
builder: (context, model, child) => Scaffold(
@ -50,8 +51,9 @@ class HomeView extends StatelessWidget {
),
const SizedBox(height: 10),
LatestCommitCard(
onPressed: () =>
onPressedManager: () =>
model.showUpdateConfirmationDialog(context),
onPressedPatches: () => model.forceRefresh(context),
),
const SizedBox(height: 23),
I18nText(

View File

@ -1,27 +1,30 @@
// ignore_for_file: use_build_context_synchronously
import 'dart:async';
import 'dart:io';
import 'package:app_installer/app_installer.dart';
import 'package:cross_connectivity/cross_connectivity.dart';
import 'package:device_apps/device_apps.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_i18n/flutter_i18n.dart';
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
import 'package:injectable/injectable.dart';
import 'package:path_provider/path_provider.dart';
import 'package:revanced_manager/app/app.locator.dart';
import 'package:revanced_manager/app/app.router.dart';
import 'package:revanced_manager/models/patched_application.dart';
import 'package:revanced_manager/services/github_api.dart';
import 'package:revanced_manager/services/manager_api.dart';
import 'package:revanced_manager/services/patcher_api.dart';
import 'package:revanced_manager/services/revanced_api.dart';
import 'package:revanced_manager/services/toast.dart';
import 'package:revanced_manager/ui/views/navigation/navigation_viewmodel.dart';
import 'package:revanced_manager/ui/views/patcher/patcher_viewmodel.dart';
import 'package:revanced_manager/ui/widgets/homeView/update_confirmation_dialog.dart';
import 'package:revanced_manager/ui/widgets/shared/custom_material_button.dart';
import 'package:revanced_manager/utils/about_info.dart';
import 'package:stacked/stacked.dart';
import 'package:stacked_services/stacked_services.dart';
import 'package:timezone/timezone.dart' as tz;
@lazySingleton
class HomeViewModel extends BaseViewModel {
@ -29,20 +32,35 @@ class HomeViewModel extends BaseViewModel {
final ManagerAPI _managerAPI = locator<ManagerAPI>();
final PatcherAPI _patcherAPI = locator<PatcherAPI>();
final GithubAPI _githubAPI = locator<GithubAPI>();
final RevancedAPI _revancedAPI = locator<RevancedAPI>();
final Toast _toast = locator<Toast>();
final flutterLocalNotificationsPlugin = FlutterLocalNotificationsPlugin();
DateTime? _lastUpdate;
bool showUpdatableApps = false;
List<PatchedApplication> patchedInstalledApps = [];
List<PatchedApplication> patchedUpdatableApps = [];
String _managerVersion = '';
Future<void> initialize(BuildContext context) async {
_managerVersion = await AboutInfo.getInfo().then(
(value) => value.keys.contains('version') ? value['version']! : '',
);
_managerVersion = await _managerAPI.getCurrentManagerVersion();
await flutterLocalNotificationsPlugin.initialize(
const InitializationSettings(
android: AndroidInitializationSettings('ic_notification'),
),
onSelectNotification: (p) =>
DeviceApps.openApp('app.revanced.manager.flutter'),
onDidReceiveNotificationResponse: (response) async {
if (response.id == 0) {
_toast.showBottom('homeView.installingMessage');
final File? managerApk = await _managerAPI.downloadManager();
if (managerApk != null) {
await AppInstaller.installApk(managerApk.path);
} else {
_toast.showBottom('homeView.errorDownloadMessage');
}
}
},
);
flutterLocalNotificationsPlugin
.resolvePlatformSpecificImplementation<
@ -52,6 +70,17 @@ class HomeViewModel extends BaseViewModel {
if (!isConnected) {
_toast.showBottom('homeView.noConnection');
}
final NotificationAppLaunchDetails? notificationAppLaunchDetails =
await flutterLocalNotificationsPlugin.getNotificationAppLaunchDetails();
if (notificationAppLaunchDetails?.didNotificationLaunchApp ?? false) {
_toast.showBottom('homeView.installingMessage');
final File? managerApk = await _managerAPI.downloadManager();
if (managerApk != null) {
await AppInstaller.installApk(managerApk.path);
} else {
_toast.showBottom('homeView.errorDownloadMessage');
}
}
_getPatchedApps();
_managerAPI.reAssessSavedApps().then((_) => _getPatchedApps());
}
@ -87,13 +116,28 @@ class HomeViewModel extends BaseViewModel {
Future<bool> hasManagerUpdates() async {
final String? latestVersion = await _managerAPI.getLatestManagerVersion();
final String currentVersion = await _managerAPI.getCurrentManagerVersion();
String currentVersion = await _managerAPI.getCurrentManagerVersion();
// add v to current version
if (!currentVersion.startsWith('v')) {
currentVersion = 'v$currentVersion';
}
if (latestVersion != currentVersion) {
return true;
}
return false;
}
Future<bool> hasPatchesUpdates() async {
final String? latestVersion = await _managerAPI.getLatestPatchesVersion();
final String? currentVersion = await _managerAPI.getCurrentPatchesVersion();
if (latestVersion != null) {
try {
final int latestVersionInt =
int.parse(latestVersion.replaceAll(RegExp('[^0-9]'), ''));
final int currentVersionInt =
int.parse(currentVersion.replaceAll(RegExp('[^0-9]'), ''));
int.parse(currentVersion!.replaceAll(RegExp('[^0-9]'), ''));
return latestVersionInt > currentVersionInt;
} on Exception catch (e) {
if (kDebugMode) {
@ -105,35 +149,115 @@ class HomeViewModel extends BaseViewModel {
return false;
}
Future<File?> downloadManager() async {
try {
final response = await _revancedAPI.downloadManager();
final bytes = await response!.readAsBytes();
final tempDir = await getTemporaryDirectory();
final tempFile = File('${tempDir.path}/revanced-manager.apk');
await tempFile.writeAsBytes(bytes);
return tempFile;
} on Exception catch (e) {
if (kDebugMode) {
print(e);
}
return null;
}
}
Future<void> updateManager(BuildContext context) async {
try {
_toast.showBottom('homeView.downloadingMessage');
final File? managerApk = await _managerAPI.downloadManager();
if (managerApk != null) {
await flutterLocalNotificationsPlugin.zonedSchedule(
0,
FlutterI18n.translate(
context,
'homeView.notificationTitle',
),
FlutterI18n.translate(
context,
'homeView.notificationText',
),
tz.TZDateTime.now(tz.local).add(const Duration(seconds: 5)),
const NotificationDetails(
android: AndroidNotificationDetails(
'revanced_manager_channel',
'ReVanced Manager Channel',
importance: Importance.max,
priority: Priority.high,
ticker: 'ticker',
showDialog(
context: context,
builder: (context) => SimpleDialog(
contentPadding: const EdgeInsets.all(16.0),
title: I18nText(
'homeView.downloadingMessage',
child: Text(
'',
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.w500,
color: Theme.of(context).colorScheme.secondary,
),
),
),
androidAllowWhileIdle: true,
uiLocalNotificationDateInterpretation:
UILocalNotificationDateInterpretation.absoluteTime,
);
children: [
Column(
children: [
Row(
children: [
Icon(
Icons.new_releases_outlined,
color: Theme.of(context).colorScheme.secondary,
),
const SizedBox(width: 8.0),
Text(
'v$_managerVersion',
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.w500,
color: Theme.of(context).colorScheme.secondary,
),
),
],
),
const SizedBox(height: 16.0),
StreamBuilder<double>(
initialData: 0.0,
stream: _revancedAPI.managerUpdateProgress.stream,
builder: (context, snapshot) {
return LinearProgressIndicator(
value: snapshot.data! * 0.01,
valueColor: AlwaysStoppedAnimation<Color>(
Theme.of(context).colorScheme.secondary,
),
);
},
),
const SizedBox(height: 16.0),
Align(
alignment: Alignment.centerRight,
child: CustomMaterialButton(
label: I18nText('cancelButton'),
onPressed: () {
_revancedAPI.disposeManagerUpdateProgress();
Navigator.of(context).pop();
},
),
),
],
),
],
),
);
final File? managerApk = await downloadManager();
if (managerApk != null) {
// await flutterLocalNotificationsPlugin.zonedSchedule(
// 0,
// FlutterI18n.translate(
// context,
// 'homeView.notificationTitle',
// ),
// FlutterI18n.translate(
// context,
// 'homeView.notificationText',
// ),
// tz.TZDateTime.now(tz.local).add(const Duration(seconds: 5)),
// const NotificationDetails(
// android: AndroidNotificationDetails(
// 'revanced_manager_channel',
// 'ReVanced Manager Channel',
// importance: Importance.max,
// priority: Priority.high,
// ticker: 'ticker',
// ),
// ),
// androidAllowWhileIdle: true,
// uiLocalNotificationDateInterpretation:
// UILocalNotificationDateInterpretation.absoluteTime,
// );
_toast.showBottom('homeView.installingMessage');
await AppInstaller.installApk(managerApk.path);
} else {
@ -151,7 +275,9 @@ class HomeViewModel extends BaseViewModel {
_toast.showBottom('homeView.updatesDisabled');
}
Future<void> showUpdateConfirmationDialog(BuildContext parentContext) {
Future<void> showUpdateConfirmationDialog(
BuildContext parentContext,
) {
return showModalBottomSheet(
context: parentContext,
isScrollControlled: true,
@ -180,6 +306,7 @@ class HomeViewModel extends BaseViewModel {
_lastUpdate!.difference(DateTime.now()).inSeconds > 2) {
_managerAPI.clearAllData();
}
_toast.showBottom('homeView.refreshSuccess');
initialize(context);
}
}

View File

@ -17,12 +17,14 @@ import 'package:stacked/stacked.dart';
class NavigationViewModel extends IndexTrackingViewModel {
Future<void> initialize(BuildContext context) async {
locator<Toast>().initialize(context);
final SharedPreferences prefs = await SharedPreferences.getInstance();
final SharedPreferences prefs =
await SharedPreferences.getInstance();
if (prefs.getBool('permissionsRequested') == null) {
await prefs.setBool('permissionsRequested', true);
RootAPI().hasRootPermissions().then(
(value) => Permission.requestInstallPackages.request().then(
(value) => Permission.ignoreBatteryOptimizations.request(),
(value) =>
Permission.ignoreBatteryOptimizations.request(),
),
);
}
@ -37,7 +39,8 @@ class NavigationViewModel extends IndexTrackingViewModel {
SystemUiOverlayStyle(
systemNavigationBarColor: Colors.transparent,
systemNavigationBarIconBrightness:
DynamicTheme.of(context)!.theme.brightness == Brightness.light
DynamicTheme.of(context)!.theme.brightness ==
Brightness.light
? Brightness.dark
: Brightness.light,
),

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';
@ -54,25 +57,25 @@ class PatcherViewModel extends BaseViewModel {
final bool isValid = await isValidPatchConfig();
if (context.mounted) {
if (isValid) {
navigateToInstaller();
showArmv7WarningDialog(context);
} else {
return showDialog(
context: context,
builder: (context) => AlertDialog(
title: I18nText('warning'),
backgroundColor: Theme.of(context).colorScheme.secondaryContainer,
content: I18nText('patcherView.patchDialogText'),
content: I18nText('patcherView.armv7WarningDialogText'),
actions: <Widget>[
CustomMaterialButton(
isFilled: false,
label: I18nText('noButton'),
onPressed: () => Navigator.of(context).pop(),
),
CustomMaterialButton(
label: I18nText('yesButton'),
isFilled: false,
onPressed: () {
Navigator.of(context).pop();
navigateToInstaller();
showArmv7WarningDialog(context);
},
)
],
@ -82,6 +85,42 @@ class PatcherViewModel extends BaseViewModel {
}
}
Future<void> showArmv7WarningDialog(BuildContext context) async {
final bool armv7 = await AboutInfo.getInfo().then(
(info) =>
info['arch'] != null &&
info['arch']!.contains('armeabi-v7a') &&
!info['arch']!.contains('arm64-v8a'),
);
if (context.mounted && armv7) {
return showDialog(
context: context,
builder: (context) => AlertDialog(
title: I18nText('warning'),
backgroundColor: Theme.of(context).colorScheme.secondaryContainer,
content: I18nText('patcherView.armv7WarningDialogText'),
actions: <Widget>[
CustomMaterialButton(
label: I18nText('noButton'),
onPressed: () => Navigator.of(context).pop(),
),
CustomMaterialButton(
label: I18nText('yesButton'),
isFilled: false,
onPressed: () {
Navigator.of(context).pop();
navigateToInstaller();
},
)
],
),
);
} else {
navigateToInstaller();
}
}
String getAppSelectionString() {
String text = '${selectedApp!.name} (${selectedApp!.packageName})';
if (text.length > 32) {
@ -90,24 +129,24 @@ class PatcherViewModel extends BaseViewModel {
return text;
}
String getRecommendedVersionString(BuildContext context) {
String recommendedVersion =
_patcherAPI.getRecommendedVersion(selectedApp!.packageName);
if (recommendedVersion.isEmpty) {
recommendedVersion = FlutterI18n.translate(
String getSuggestedVersionString(BuildContext context) {
String suggestedVersion =
_patcherAPI.getSuggestedVersion(selectedApp!.packageName);
if (suggestedVersion.isEmpty) {
suggestedVersion = FlutterI18n.translate(
context,
'appSelectorCard.anyVersion',
);
} else {
recommendedVersion = 'v$recommendedVersion';
suggestedVersion = 'v$suggestedVersion';
}
return '${FlutterI18n.translate(
context,
'appSelectorCard.currentVersion',
)}: v${selectedApp!.version}\n${FlutterI18n.translate(
context,
'appSelectorCard.recommendedVersion',
)}: $recommendedVersion';
'appSelectorCard.suggestedVersion',
)}: $suggestedVersion';
}
Future<void> loadLastSelectedPatches() async {

View File

@ -4,6 +4,7 @@ import 'package:revanced_manager/ui/views/patches_selector/patches_selector_view
import 'package:revanced_manager/ui/widgets/patchesSelectorView/patch_item.dart';
import 'package:revanced_manager/ui/widgets/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 {
@ -135,25 +136,13 @@ class _PatchesSelectorViewState extends State<PatchesSelectorView> {
Row(
children: [
ActionChip(
label: I18nText('patchesSelectorView.recommended'),
label: I18nText('patchesSelectorView.default'),
tooltip: FlutterI18n.translate(
context,
'patchesSelectorView.recommendedTooltip',
'patchesSelectorView.defaultTooltip',
),
onPressed: () {
model.selectRecommendedPatches();
},
),
const SizedBox(width: 8),
ActionChip(
label: I18nText('patchesSelectorView.all'),
tooltip: FlutterI18n.translate(
context,
'patchesSelectorView.allTooltip',
),
onPressed: () {
model.selectAllPatcherWarning(context);
model.selectAllPatches(true);
model.selectDefaultPatches();
},
),
const SizedBox(width: 8),
@ -180,7 +169,7 @@ class _PatchesSelectorViewState extends State<PatchesSelectorView> {
packageVersion: model.getAppVersion(),
supportedPackageVersions:
model.getSupportedVersions(patch),
isUnsupported: !model.isPatchSupported(patch),
isUnsupported: !isPatchSupported(patch),
isSelected: model.isSelected(patch),
onChanged: (value) =>
model.selectPatch(patch, value),

View File

@ -1,6 +1,4 @@
import 'package:collection/collection.dart';
import 'package:flutter/material.dart';
import 'package:flutter_i18n/widgets/I18nText.dart';
import 'package:revanced_manager/app/app.locator.dart';
import 'package:revanced_manager/models/patch.dart';
import 'package:revanced_manager/models/patched_application.dart';
@ -9,7 +7,7 @@ import 'package:revanced_manager/services/manager_api.dart';
import 'package:revanced_manager/services/patcher_api.dart';
import 'package:revanced_manager/services/toast.dart';
import 'package:revanced_manager/ui/views/patcher/patcher_viewmodel.dart';
import 'package:revanced_manager/ui/widgets/shared/custom_material_button.dart';
import 'package:revanced_manager/utils/check_for_supported_patch.dart';
import 'package:stacked/stacked.dart';
class PatchesSelectorViewModel extends BaseViewModel {
@ -50,39 +48,7 @@ class PatchesSelectorViewModel extends BaseViewModel {
notifyListeners();
}
Future<void> selectAllPatcherWarning(BuildContext context) {
return showDialog(
context: context,
builder: (context) => AlertDialog(
title: I18nText('warning'),
backgroundColor: Theme.of(context).colorScheme.secondaryContainer,
content: I18nText('patchesSelectorView.selectAllPatchesWarningContent'),
actions: <Widget>[
CustomMaterialButton(
label: I18nText('okButton'),
onPressed: () => Navigator.of(context).pop(),
)
],
),
);
}
void selectAllPatches(bool isSelected) {
selectedPatches.clear();
if (isSelected && _managerAPI.areExperimentalPatchesEnabled() == false) {
selectedPatches
.addAll(patches.where((element) => isPatchSupported(element)));
}
if (isSelected && _managerAPI.areExperimentalPatchesEnabled()) {
selectedPatches.addAll(patches);
}
notifyListeners();
}
void selectRecommendedPatches() {
void selectDefaultPatches() {
selectedPatches.clear();
if (_managerAPI.areExperimentalPatchesEnabled() == false) {
@ -153,16 +119,6 @@ class PatchesSelectorViewModel extends BaseViewModel {
}
}
bool isPatchSupported(Patch patch) {
final PatchedApplication app = locator<PatcherViewModel>().selectedApp!;
return patch.compatiblePackages.isEmpty ||
patch.compatiblePackages.any(
(pack) =>
pack.name == app.packageName &&
(pack.versions.isEmpty || pack.versions.contains(app.version)),
);
}
void onMenuSelection(value) {
switch (value) {
case 0:

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 SManageApiUrl extends BaseViewModel {
final ManagerAPI _managerAPI = locator<ManagerAPI>();
final Toast _toast = locator<Toast>();
final TextEditingController _apiUrlController = TextEditingController();
@ -90,7 +92,7 @@ class SManageApiUrl extends BaseViewModel {
label: I18nText('yesButton'),
onPressed: () {
_managerAPI.setApiUrl('');
Navigator.of(context).pop();
_toast.showBottom('settingsView.restartAppForChanges');
Navigator.of(context).pop();
},
)

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();
@ -117,13 +119,14 @@ class SManageSources extends BaseViewModel {
CustomMaterialButton(
label: I18nText('okButton'),
onPressed: () {
_managerAPI.setRepoUrl(_hostSourceController.text);
_managerAPI.setRepoUrl(_hostSourceController.text.trim());
_managerAPI.setPatchesRepo(
'${_orgPatSourceController.text}/${_patSourceController.text}',
'${_orgPatSourceController.text.trim()}/${_patSourceController.text.trim()}',
);
_managerAPI.setIntegrationsRepo(
'${_orgIntSourceController.text}/${_intSourceController.text}',
'${_orgIntSourceController.text.trim()}/${_intSourceController.text.trim()}',
);
_toast.showBottom('settingsView.restartAppForChanges');
Navigator.of(context).pop();
},
)
@ -151,7 +154,10 @@ class SManageSources extends BaseViewModel {
_managerAPI.setRepoUrl('');
_managerAPI.setPatchesRepo('');
_managerAPI.setIntegrationsRepo('');
Navigator.of(context).pop();
_toast.showBottom('settingsView.restartAppForChanges');
Navigator.of(context)
..pop()
..pop();
},
)
],

View File

@ -17,7 +17,8 @@ import 'package:stacked/stacked.dart';
import 'package:stacked_services/stacked_services.dart';
class SettingsViewModel extends BaseViewModel {
final NavigationService _navigationService = locator<NavigationService>();
final NavigationService _navigationService =
locator<NavigationService>();
final ManagerAPI _managerAPI = locator<ManagerAPI>();
final Toast _toast = locator<Toast>();
@ -62,8 +63,11 @@ class SettingsViewModel extends BaseViewModel {
try {
final File outFile = File(_managerAPI.storedPatchesFile);
if (outFile.existsSync()) {
final String dateTime =
DateTime.now().toString().replaceAll(' ', '_').split('.').first;
final String dateTime = DateTime.now()
.toString()
.replaceAll(' ', '_')
.split('.')
.first;
await CRFileSaver.saveFileWithDialog(
SaveFileDialogParams(
sourceFilePath: outFile.path,
@ -83,7 +87,8 @@ class SettingsViewModel extends BaseViewModel {
Future<void> importPatches() async {
try {
final FilePickerResult? result = await FilePicker.platform.pickFiles(
final FilePickerResult? result =
await FilePicker.platform.pickFiles(
type: FileType.custom,
allowedExtensions: ['json'],
);
@ -104,6 +109,46 @@ class SettingsViewModel extends BaseViewModel {
}
}
Future<void> exportKeystore() async {
try {
final File outFile = File(_managerAPI.keystoreFile);
if (outFile.existsSync()) {
final String dateTime =
DateTime.now().toString().replaceAll(' ', '_').split('.').first;
await CRFileSaver.saveFileWithDialog(
SaveFileDialogParams(
sourceFilePath: outFile.path,
destinationFileName: 'keystore_$dateTime.keystore',
),
);
_toast.showBottom('settingsView.exportedKeystore');
} else {
_toast.showBottom('settingsView.noKeystoreExportFileFound');
}
} on Exception catch (e) {
if (kDebugMode) {
print(e);
}
}
}
Future<void> importKeystore() async {
try {
final FilePickerResult? result = await FilePicker.platform.pickFiles();
if (result != null && result.files.single.path != null) {
final File inFile = File(result.files.single.path!);
inFile.copySync(_managerAPI.keystoreFile);
_toast.showBottom('settingsView.importedKeystore');
}
} on Exception catch (e) {
if (kDebugMode) {
print(e);
}
_toast.showBottom('settingsView.keystoreSelectorErrorMessage');
}
}
void resetSelectedPatches() {
_managerAPI.resetLastSelectedPatches();
_toast.showBottom('settingsView.resetStoredPatches');
@ -111,7 +156,7 @@ class SettingsViewModel extends BaseViewModel {
Future<int> getSdkVersion() async {
final AndroidDeviceInfo info = await DeviceInfoPlugin().androidInfo;
return info.version.sdkInt ?? -1;
return info.version.sdkInt;
}
Future<void> deleteLogs() async {

View File

@ -9,12 +9,14 @@ class InstalledAppItem extends StatefulWidget {
required this.pkgName,
required this.icon,
required this.patchesCount,
required this.suggestedVersion,
this.onTap,
}) : super(key: key);
final String name;
final String pkgName;
final Uint8List icon;
final int patchesCount;
final String suggestedVersion;
final Function()? onTap;
@override
@ -46,31 +48,35 @@ class _InstalledAppItemState extends State<InstalledAppItem> {
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text(
widget.name,
maxLines: 2,
overflow: TextOverflow.visible,
style: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.w500,
),
),
const SizedBox(height: 4),
Text(widget.pkgName),
Row(
children: <Widget>[
children: [
Text(
widget.name,
maxLines: 2,
overflow: TextOverflow.visible,
style: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.w500,
),
widget.suggestedVersion.isEmpty
? 'All versions'
: widget.suggestedVersion,
),
const SizedBox(width: 6),
const SizedBox(width: 4),
Text(
widget.patchesCount == 1
? '${widget.patchesCount} patch'
: '${widget.patchesCount} patches',
? '${widget.patchesCount} patch'
: '${widget.patchesCount} patches',
style: TextStyle(
fontSize: 8,
color: Theme.of(context).colorScheme.secondary,
),
),
],
),
const SizedBox(height: 4),
Text(widget.pkgName),
],
),
),

View File

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

View File

@ -55,6 +55,7 @@ class _ContributorsCardState extends State<ContributorsCard> {
Uri.parse(
widget.contributors[index]['html_url'],
),
mode: LaunchMode.externalApplication,
),
child: FutureBuilder<File?>(
future: DefaultCacheManager().getSingleFile(

View File

@ -8,9 +8,11 @@ import 'package:revanced_manager/ui/widgets/shared/custom_material_button.dart';
class LatestCommitCard extends StatefulWidget {
const LatestCommitCard({
Key? key,
required this.onPressed,
required this.onPressedManager,
required this.onPressedPatches,
}) : super(key: key);
final Function() onPressed;
final Function() onPressedManager;
final Function() onPressedPatches;
@override
State<LatestCommitCard> createState() => _LatestCommitCardState();
@ -21,66 +23,109 @@ class _LatestCommitCardState extends State<LatestCommitCard> {
@override
Widget build(BuildContext context) {
return CustomCard(
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Column(
crossAxisAlignment: CrossAxisAlignment.start,
return Column(
children: [
// ReVanced Manager
CustomCard(
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Row(
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
I18nText('latestCommitCard.patcherLabel'),
FutureBuilder<String?>(
future: model.getLatestPatcherReleaseTime(),
builder: (context, snapshot) => Text(
snapshot.hasData && snapshot.data!.isNotEmpty
? FlutterI18n.translate(
context,
'latestCommitCard.timeagoLabel',
translationParams: {'time': snapshot.data!},
)
: FlutterI18n.translate(
context,
'latestCommitCard.loadingLabel',
),
),
Row(
children: const <Widget>[
Text('ReVanced Manager'),
],
),
const SizedBox(height: 4),
Row(
children: <Widget>[
FutureBuilder<String?>(
future: model.getLatestManagerReleaseTime(),
builder: (context, snapshot) =>
snapshot.hasData && snapshot.data!.isNotEmpty
? I18nText(
'latestCommitCard.timeagoLabel',
translationParams: {'time': snapshot.data!},
)
: I18nText('latestCommitCard.loadingLabel'),
),
],
),
],
),
const SizedBox(height: 4),
Row(
children: <Widget>[
I18nText('latestCommitCard.managerLabel'),
FutureBuilder<String?>(
future: model.getLatestManagerReleaseTime(),
builder: (context, snapshot) =>
snapshot.hasData && snapshot.data!.isNotEmpty
? I18nText(
'latestCommitCard.timeagoLabel',
translationParams: {'time': snapshot.data!},
)
: I18nText('latestCommitCard.loadingLabel'),
FutureBuilder<bool>(
future: model.hasManagerUpdates(),
initialData: false,
builder: (context, snapshot) => Opacity(
opacity: snapshot.hasData && snapshot.data! ? 1.0 : 0.25,
child: CustomMaterialButton(
label: I18nText('updateButton'),
onPressed: snapshot.hasData && snapshot.data!
? widget.onPressedManager
: () => {},
),
],
),
),
],
),
FutureBuilder<bool>(
future: locator<HomeViewModel>().hasManagerUpdates(),
initialData: false,
builder: (context, snapshot) => Opacity(
opacity: snapshot.hasData && snapshot.data! ? 1.0 : 0.25,
child: CustomMaterialButton(
label: I18nText('latestCommitCard.updateButton'),
onPressed: snapshot.hasData && snapshot.data!
? widget.onPressed
: () => {},
),
const SizedBox(height: 16),
// ReVanced Patches
CustomCard(
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Row(
children: const <Widget>[
Text('ReVanced Patches'),
],
),
const SizedBox(height: 4),
Row(
children: <Widget>[
FutureBuilder<String?>(
future: model.getLatestPatcherReleaseTime(),
builder: (context, snapshot) => Text(
snapshot.hasData && snapshot.data!.isNotEmpty
? FlutterI18n.translate(
context,
'latestCommitCard.timeagoLabel',
translationParams: {'time': snapshot.data!},
)
: FlutterI18n.translate(
context,
'latestCommitCard.loadingLabel',
),
),
),
],
),
],
),
),
FutureBuilder<bool>(
future: locator<HomeViewModel>().hasPatchesUpdates(),
initialData: false,
builder: (context, snapshot) => Opacity(
opacity: snapshot.hasData && snapshot.data! ? 1.0 : 0.25,
child: CustomMaterialButton(
label: I18nText('updateButton'),
onPressed: snapshot.hasData && snapshot.data!
? widget.onPressedPatches
: () => {},
),
),
),
],
),
],
),
),
],
);
}
}

View File

@ -16,12 +16,12 @@ class UpdateConfirmationDialog extends StatelessWidget {
expand: false,
snap: true,
snapSizes: const [0.5],
builder: (context, scrollController) => SingleChildScrollView(
builder: (_, scrollController) => SingleChildScrollView(
controller: scrollController,
child: SafeArea(
child: FutureBuilder<Map<String, dynamic>?>(
future: model.getLatestManagerRelease(),
builder: (context, snapshot) {
builder: (_, snapshot) {
if (!snapshot.hasData) {
return const SizedBox(
height: 300,
@ -45,7 +45,8 @@ class UpdateConfirmationDialog extends StatelessWidget {
children: [
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
crossAxisAlignment:
CrossAxisAlignment.start,
children: [
I18nText(
'homeView.updateDialogTitle',
@ -62,12 +63,14 @@ class UpdateConfirmationDialog extends StatelessWidget {
children: [
Icon(
Icons.new_releases_outlined,
color:
Theme.of(context).colorScheme.secondary,
color: Theme.of(context)
.colorScheme
.secondary,
),
const SizedBox(width: 8.0),
Text(
snapshot.data!['tag_name'] ?? 'Unknown',
snapshot.data!['tag_name'] ??
'Unknown',
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.w500,
@ -93,7 +96,8 @@ class UpdateConfirmationDialog extends StatelessWidget {
),
),
Padding(
padding: const EdgeInsets.only(left: 24.0, bottom: 12.0),
padding:
const EdgeInsets.only(left: 24.0, bottom: 12.0),
child: I18nText(
'homeView.updateChangelogTitle',
child: Text(
@ -109,9 +113,12 @@ class UpdateConfirmationDialog extends StatelessWidget {
),
),
Container(
margin: const EdgeInsets.symmetric(horizontal: 24.0),
margin:
const EdgeInsets.symmetric(horizontal: 24.0),
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.secondaryContainer,
color: Theme.of(context)
.colorScheme
.secondaryContainer,
borderRadius: BorderRadius.circular(12.0),
),
child: Markdown(

View File

@ -63,7 +63,7 @@ class AppSelectorCard extends StatelessWidget {
const SizedBox(height: 4),
Text(
locator<PatcherViewModel>()
.getRecommendedVersionString(context),
.getSuggestedVersionString(context),
),
],
),

View File

@ -8,6 +8,7 @@ import 'package:revanced_manager/ui/views/settings/settings_viewmodel.dart';
import 'package:revanced_manager/ui/widgets/settingsView/settings_experimental_patches.dart';
import 'package:revanced_manager/ui/widgets/settingsView/settings_experimental_universal_patches.dart';
import 'package:revanced_manager/ui/widgets/settingsView/settings_section.dart';
import 'package:revanced_manager/ui/widgets/shared/custom_material_button.dart';
final _settingsViewModel = SettingsViewModel();
@ -21,6 +22,7 @@ class SAdvancedSection extends StatelessWidget {
children: <Widget>[
SManageApiUrlUI(),
SManageSourcesUI(),
// SManageKeystorePasswordUI(),
SExperimentalUniversalPatches(),
SExperimentalPatches(),
ListTile(
@ -36,7 +38,7 @@ class SAdvancedSection extends StatelessWidget {
),
),
subtitle: I18nText('settingsView.deleteKeystoreHint'),
onTap: () => _settingsViewModel.deleteKeystore,
onTap: () => _showDeleteKeystoreDialog(context),
),
ListTile(
contentPadding: const EdgeInsets.symmetric(horizontal: 20.0),
@ -71,4 +73,31 @@ class SAdvancedSection extends StatelessWidget {
],
);
}
Future<void> _showDeleteKeystoreDialog(context) {
return showDialog(
context: context,
builder: (context) => AlertDialog(
title: I18nText('warning'),
backgroundColor: Theme.of(context).colorScheme.secondaryContainer,
content: I18nText(
'settingsView.deleteKeystoreDialogText',
),
actions: <Widget>[
CustomMaterialButton(
isFilled: false,
label: I18nText('noButton'),
onPressed: () => Navigator.of(context).pop(),
),
CustomMaterialButton(
label: I18nText('yesButton'),
onPressed: () => {
Navigator.of(context).pop(),
_settingsViewModel.deleteKeystore()
},
)
],
),
);
}
}

View File

@ -1,5 +1,6 @@
import 'package:flutter/material.dart';
import 'package:flutter_i18n/widgets/I18nText.dart';
import 'package:revanced_manager/ui/views/settings/settingsFragment/settings_manage_keystore_password.dart';
import 'package:revanced_manager/ui/views/settings/settings_viewmodel.dart';
import 'package:revanced_manager/ui/widgets/settingsView/settings_section.dart';
@ -43,6 +44,40 @@ class SExportSection extends StatelessWidget {
subtitle: I18nText('settingsView.importPatchesHint'),
onTap: () => _settingsViewModel.importPatches(),
),
ListTile(
contentPadding: const EdgeInsets.symmetric(horizontal: 20.0),
title: I18nText(
'settingsView.exportKeystoreLabel',
child: const Text(
'',
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.w500,
),
),
),
subtitle: I18nText('settingsView.exportKeystoreHint'),
onTap: () => _settingsViewModel.exportKeystore(),
),
ListTile(
contentPadding: const EdgeInsets.symmetric(horizontal: 20.0),
title: I18nText(
'settingsView.importKeystoreLabel',
child: const Text(
'',
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.w500,
),
),
),
subtitle: I18nText('settingsView.importKeystoreHint'),
onTap: () {
_settingsViewModel.importKeystore();
final sManageKeystorePassword = SManageKeystorePassword();
sManageKeystorePassword.showKeystoreDialog(context);
},
),
ListTile(
contentPadding: const EdgeInsets.symmetric(horizontal: 20.0),
title: I18nText(

View File

@ -11,7 +11,7 @@ class AboutInfo {
'flavor': kReleaseMode ? 'release' : 'debug',
'model': info.model,
'androidVersion': info.version.release,
'arch': info.supportedAbis.first
'arch': info.supportedAbis
};
}
}

View File

@ -0,0 +1,14 @@
import 'package:revanced_manager/app/app.locator.dart';
import 'package:revanced_manager/models/patch.dart';
import 'package:revanced_manager/models/patched_application.dart';
import 'package:revanced_manager/ui/views/patcher/patcher_viewmodel.dart';
bool isPatchSupported(Patch patch) {
final PatchedApplication app = locator<PatcherViewModel>().selectedApp!;
return patch.compatiblePackages.isEmpty ||
patch.compatiblePackages.any(
(pack) =>
pack.name == app.packageName &&
(pack.versions.isEmpty || pack.versions.contains(app.version)),
);
}

7179
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

10
package.json Normal file
View File

@ -0,0 +1,10 @@
{
"devDependencies": {
"@saithodev/semantic-release-backmerge": "^3.1.0",
"@semantic-release/changelog": "^6.0.3",
"@semantic-release/git": "^10.0.1",
"semantic-release": "^21.0.1",
"semantic-release-export-data": "^1.0.1",
"semantic-release-flutter-plugin": "^1.1.2"
}
}

View File

@ -4,86 +4,88 @@ homepage: https://github.com/revanced/revanced-manager
publish_to: 'none'
version: 0.0.57+57
version: 1.0.0+100000000
environment:
sdk: ">=2.17.5 <3.0.0"
dependencies:
animations: ^2.0.4
animations: ^2.0.7
app_installer: ^1.1.0
collection: ^1.16.0
collection: ^1.17.0
cross_connectivity: ^3.0.5
cr_file_saver: ^0.0.2
cr_file_saver:
git:
url: https://github.com/dhruvanbhalara/cr_file_saver.git
ref: a08326ecb48f581b4b09e2e2665d31ed1704c7af
device_apps:
git:
url: https://github.com/ponces/flutter_plugin_device_apps
ref: revanced-manager
device_info_plus: ^4.1.2
dio: ^4.0.6
dio_brotli_transformer: ^1.0.1
dio_http_cache_lts: ^0.4.1
dynamic_color: ^1.5.4
device_info_plus: ^8.1.0
dynamic_color: ^1.6.3
dio: ^5.0.0
dynamic_themes: ^1.1.0
expandable: ^5.0.1
file_picker:
git:
url: https://github.com/alexmercerind/flutter_file_picker
ref: master
flex_color_scheme: ^6.0.0
flex_color_scheme: ^7.0.1
flutter:
sdk: flutter
flutter_background: ^1.1.0
flutter_background: ^1.2.0
flutter_cache_manager: ^3.3.0
flutter_i18n: ^0.32.4
flutter_local_notifications: ^9.8.0+1
flutter_local_notifications: ^13.0.0
flutter_localizations:
sdk: flutter
flutter_svg: ^1.1.1+1
fluttertoast: ^8.0.9
font_awesome_flutter: ^10.1.0
get_it: ^7.2.0
google_fonts: ^3.0.1
http: ^0.13.4
injectable: ^1.5.3
flutter_svg: ^2.0.4
fluttertoast: ^8.2.1
font_awesome_flutter: ^10.4.0
get_it: 7.2.0
google_fonts: ^4.0.3
http: ^0.13.5
injectable: ^2.1.1
intl: ^0.17.0
json_annotation: ^4.6.0
json_annotation: ^4.8.0
logcat:
git:
url: https://github.com/SuaMusica/logcat
ref: feature/nullSafe
native_dio_client: ^0.0.1-dev+1
package_info_plus: ^1.4.3+1
path_provider: ^2.0.11
permission_handler: ^10.0.0
package_info_plus: ^3.0.3
path_provider: ^2.0.14
permission_handler: ^10.2.0
native_dio_adapter: ^0.1.0
pull_to_refresh: ^2.0.0
root:
git:
url: https://github.com/gokul1630/root
ref: main
share_extend: ^2.0.0
shared_preferences: ^2.0.15
shared_preferences: ^2.1.0
skeletons: ^0.0.3
stacked: ^3.2.0
stacked_generator: ^1.0.0
stacked_generator: ^1.1.0
stacked_services: ^1.0.0
stacked_themes: ^0.3.9
timeago: ^3.2.2
timezone: ^0.8.0
url_launcher: ^6.1.5
stacked_themes: ^0.3.10
timeago: ^3.3.0
timezone: ^0.9.0
url_launcher: ^6.1.10
wakelock: ^0.6.2
flutter_dotenv: ^5.0.2
flutter_markdown: ^0.6.14
pub_release: ^8.0.3
flutter_markdown: ^0.6.13
dio_cache_interceptor: ^3.4.0
dev_dependencies:
json_serializable: ^6.3.1
json_serializable: ^6.6.1
build_runner: any
flutter_launcher_icons: ^0.10.0
flutter_launcher_icons: ^0.13.0
flutter_lints: ^2.0.1
flutter_test:
sdk: flutter
injectable_generator: ^1.5.4
injectable_generator: ^2.1.5