Compare commits

..

2 Commits

Author SHA1 Message Date
d3d8f89d53 chore: bump version to 0.0.55 2023-02-10 05:34:10 +01:00
3d8aee09c3 build: bump patcher version 2023-02-10 05:33:50 +01:00
86 changed files with 868 additions and 8820 deletions

3
.env Normal file
View File

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

2
.github/config.yaml vendored
View File

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

View File

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

55
.github/workflows/commit-build.yml vendored Normal file
View File

@ -0,0 +1,55 @@
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@v3
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:
- "dev"
- "flutter"
paths:
- "assets/i18n/en_US.json"
- ".github/workflows/crowdin.yml"

View File

@ -1,41 +1,41 @@
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
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@v3
with:
name: revanced-manager-${{ env.RELEASE_VERSION }}
path: revanced-manager-${{ env.RELEASE_VERSION }}.apk

View File

@ -15,11 +15,15 @@ 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 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
@ -47,4 +51,4 @@ jobs:
with:
repo_token: "${{ secrets.GITHUB_TOKEN }}"
prerelease: false
files: revanced-manager-${{ env.RELEASE_VERSION }}.apk
files: revanced-manager-${{ env.RELEASE_VERSION }}.apk

View File

@ -1,68 +0,0 @@
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,8 +134,5 @@ app.*.map.json
!/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages
!/dev/ci/**/Gemfile.lock
# Firebase related
.firebase
# Dependency directories
node_modules/
Firebase related
.firebase

View File

@ -1,75 +0,0 @@
{
"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
}
]
]
}

View File

@ -1 +0,0 @@

View File

@ -2,12 +2,18 @@
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 latest Manager, go [here](https://github.com/revanced/revanced-manager/releases/latest) and install the provided APK file.
To download the Alpha version of 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,10 +11,7 @@ include: package:flutter_lints/flutter.yaml
analyzer:
exclude:
- lib/app/app.locator.dart
- lib/app/app.router.dart
- lib/models/patch.g.dart
- lib/models/patched_application.g.dart
- lib/utils/env_class.g.dart
linter:
rules:

View File

@ -30,7 +30,6 @@ android {
ndkVersion flutter.ndkVersion
compileOptions {
coreLibraryDesugaringEnabled true
sourceCompatibility JavaVersion.VERSION_11
targetCompatibility JavaVersion.VERSION_11
}
@ -49,7 +48,6 @@ android {
targetSdkVersion 33
versionCode flutterVersionCode.toInteger()
versionName flutterVersionName
multiDexEnabled true
}
buildTypes {
@ -73,17 +71,14 @@ dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
// ReVanced
implementation "app.revanced:revanced-patcher:7.1.1"
implementation "app.revanced:revanced-patcher:6.4.3"
// Signing & aligning
implementation("org.bouncycastle:bcpkix-jdk15on:1.70")
implementation("com.android.tools.build:apksig:7.2.2")
// 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'
// MicroG cronet
implementation("org.microg:cronet-common:$cronetVersion")
implementation("org.microg:cronet-native:$cronetVersion")
}

View File

@ -43,9 +43,8 @@ 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 &&
@ -54,8 +53,8 @@ class MainActivity : FlutterActivity() {
integrationsPath != null &&
selectedPatches != null &&
cacheDirPath != null &&
keyStoreFilePath != null &&
keystorePassword != null
mergeIntegrations != null &&
keyStoreFilePath != null
) {
runPatcher(
result,
@ -67,8 +66,8 @@ class MainActivity : FlutterActivity() {
integrationsPath,
selectedPatches,
cacheDirPath,
keyStoreFilePath,
keystorePassword
mergeIntegrations,
keyStoreFilePath
)
} else {
result.notImplemented()
@ -89,8 +88,8 @@ class MainActivity : FlutterActivity() {
integrationsPath: String,
selectedPatches: List<String>,
cacheDirPath: String,
keyStoreFilePath: String,
keystorePassword: String
mergeIntegrations: Boolean,
keyStoreFilePath: String
) {
val originalFile = File(originalFilePath)
val inputFile = File(inputFilePath)
@ -140,17 +139,19 @@ class MainActivity : FlutterActivity() {
mapOf("progress" to 0.3, "header" to "", "log" to "")
)
}
handler.post {
installerChannel.invokeMethod(
"update",
mapOf(
"progress" to 0.4,
"header" to "Merging integrations...",
"log" to "Merging integrations"
if (mergeIntegrations) {
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(
@ -247,7 +248,7 @@ class MainActivity : FlutterActivity() {
// Signer("ReVanced", "s3cur3p@ssw0rd").signApk(patchedFile, outFile, keyStoreFile)
try {
Signer("ReVanced", keystorePassword).signApk(patchedFile, outFile, keyStoreFile)
Signer("ReVanced", "s3cur3p@ssw0rd").signApk(patchedFile, outFile, keyStoreFile)
} catch (e: Exception) {
//log to console
print("Error signing apk: ${e.message}")

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 814 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 KiB

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.7 KiB

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.9 KiB

After

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.1 KiB

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/ic_launcher_background"/>
<background android:drawable="@color/ic_launcher_background"/>
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
<monochrome android:drawable="@drawable/ic_launcher_monochrome"/>
<monochrome android:drawable="@drawable/ic_launcher_foreground"/>
</adaptive-icon>

View File

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

View File

@ -1,5 +1,6 @@
buildscript {
ext.kotlin_version = '1.7.10'
ext.cronetVersion = '102.5005.125'
ext.kotlin_version = '1.8.0'
repositories {
google()
mavenCentral()

View File

@ -1,7 +1,6 @@
#Mon May 09 12:07:41 MSK 2022
distributionBase=GRADLE_USER_HOME
distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-rc-1-bin.zip
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-7.6.1-bin.zip
distributionSha256Sum=6147605a23b4eff6c334927a86ff3508cb5d6722cd624c97ded4c2e8640f1f87
networkTimeout=10000
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME

View File

@ -13,7 +13,6 @@
"settingsTab": "Settings"
},
"homeView": {
"refreshSuccess": "Refreshed successfully",
"widgetTitle": "Dashboard",
"updatesSubtitle": "Updates",
"patchedSubtitle": "Patched applications",
@ -48,8 +47,7 @@
"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?",
"armv7WarningDialogText": "Patching on ARMv7 devices is not yet supported and might fail. Proceed anyways?"
"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?"
},
"appSelectorCard": {
"widgetTitle": "Select an application",
@ -57,7 +55,7 @@
"widgetSubtitle": "No application selected",
"noAppsLabel": "No applications found",
"currentVersion": "Current",
"suggestedVersion": "Suggested",
"recommendedVersion": "Recommended",
"anyVersion": "any"
},
"patchSelectorCard": {
@ -73,25 +71,20 @@
"appSelectorView": {
"viewTitle": "Select an application",
"searchBarHint": "Search applications",
"selectFromStorageButton": "Select from storage",
"storageButton": "Storage",
"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."
"errorMessage": "Unable to use selected application"
},
"patchesSelectorView": {
"viewTitle": "Select patches",
"searchBarHint": "Search patches",
"doneButton": "Done",
"default": "Default",
"defaultTooltip": "Select all default patches",
"recommended": "Recommended",
"all": "All",
"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 non-suggested patches and can cause unwanted behavior."
"selectAllPatchesWarningContent": "You are about to select all patches, that includes unrecommended patches and can cause unwanted behavior."
},
"patchItem": {
"unsupportedDialogText": "Selecting this patch may result in patching errors.\n\nApp version: {packageVersion}\nSupported versions:\n{supportedVersions}",
@ -152,10 +145,11 @@
"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",
@ -173,17 +167,7 @@
"jsonSelectorErrorMessage": "Unable to use selected JSON file",
"deleteLogsLabel": "Delete logs",
"deleteLogsHint": "Delete collected manager logs",
"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"
"deletedLogs": "Logs deleted"
},
"appInfoView": {
"widgetTitle": "App info",

View File

@ -1,3 +1,4 @@
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';
@ -37,6 +38,7 @@ import 'package:stacked_services/stacked_services.dart';
LazySingleton(classType: PatcherAPI),
LazySingleton(classType: RevancedAPI),
LazySingleton(classType: GithubAPI),
LazySingleton(classType: CrowdinAPI),
LazySingleton(classType: Toast),
],
)

View File

@ -4,12 +4,15 @@ 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:sentry_flutter/sentry_flutter.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:stacked_themes/stacked_themes.dart';
import 'package:timezone/data/latest.dart' as tz;
@ -22,12 +25,36 @@ Future main() async {
await locator<ManagerAPI>().initialize();
final String apiUrl = locator<ManagerAPI>().getApiUrl();
await locator<RevancedAPI>().initialize(apiUrl);
await locator<CrowdinAPI>().initialize();
final bool isSentryEnabled = locator<ManagerAPI>().isSentryEnabled();
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());
}

View File

@ -0,0 +1,63 @@
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 {
final 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',
),
),
);
final 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

@ -3,22 +3,21 @@ import 'dart:io';
import 'package:collection/collection.dart';
import 'package:dio/dio.dart';
import 'package:dio_cache_interceptor/dio_cache_interceptor.dart';
import 'package:flutter/foundation.dart';
import 'package:dio_http_cache_lts/dio_http_cache_lts.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_dio/sentry_dio.dart';
import 'package:sentry_flutter/sentry_flutter.dart';
@lazySingleton
class GithubAPI {
late Dio _dio = Dio();
final _cacheOptions = CacheOptions(
store: MemCacheStore(),
final DioCacheManager _dioCacheManager = DioCacheManager(CacheConfig());
final Options _cacheOptions = buildCacheOptions(
const Duration(hours: 6),
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',
@ -38,36 +37,32 @@ class GithubAPI {
),
);
_dio.interceptors.add(DioCacheInterceptor(options: _cacheOptions));
} on Exception catch (e) {
if (kDebugMode) {
print(e);
}
_dio.interceptors.add(_dioCacheManager.interceptor);
_dio.addSentry(
captureFailedRequests: true,
);
} on Exception catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
}
}
Future<void> clearAllCache() async {
try {
await _cacheOptions.store!.clean();
} on Exception catch (e) {
if (kDebugMode) {
print(e);
}
await _dioCacheManager.clearAll();
} on Exception catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
}
}
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) {
if (kDebugMode) {
print(e);
}
} on Exception catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
return null;
}
}
@ -86,6 +81,7 @@ class GithubAPI {
'path': path,
'since': since.toIso8601String(),
},
options: _cacheOptions,
);
final List<dynamic> commits = response.data;
return commits
@ -96,18 +92,13 @@ class GithubAPI {
'\n' as String,
)
.toList();
} on Exception catch (e) {
if (kDebugMode) {
print(e);
}
} on Exception catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
return List.empty();
}
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);
if (release != null) {
@ -121,10 +112,9 @@ class GithubAPI {
);
}
}
} on Exception catch (e) {
if (kDebugMode) {
print(e);
}
} on Exception catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
return null;
}
return null;
}
@ -137,12 +127,10 @@ class GithubAPI {
final List<dynamic> list = jsonDecode(f.readAsStringSync());
patches = list.map((patch) => Patch.fromJson(patch)).toList();
}
} on Exception catch (e) {
if (kDebugMode) {
print(e);
}
} on Exception catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
return List.empty();
}
return patches;
}
@ -154,12 +142,9 @@ class GithubAPI {
} else {
return 'Unknown';
}
} on Exception catch (e) {
if (kDebugMode) {
print(e);
}
return 'Unknown';
} on Exception catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
return '';
}
}
}

View File

@ -1,7 +1,6 @@
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';
@ -11,7 +10,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:sentry_flutter/sentry_flutter.dart';
import 'package:shared_preferences/shared_preferences.dart';
@lazySingleton
@ -22,11 +21,7 @@ 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';
@ -34,14 +29,9 @@ 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;
}
@ -108,6 +98,14 @@ 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;
}
@ -124,14 +122,6 @@ 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()) {
@ -141,7 +131,7 @@ class ManagerAPI {
Future<void> deleteKeystore() async {
final File keystore = File(
keystoreFile,
'/sdcard/Android/data/app.revanced.manager.flutter/files/revanced-manager.keystore',
);
if (await keystore.exists()) {
await keystore.delete();
@ -153,9 +143,7 @@ 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));
}
@ -191,10 +179,8 @@ class ManagerAPI {
try {
_revancedAPI.clearAllCache();
_githubAPI.clearAllCache();
} on Exception catch (e) {
if (kDebugMode) {
print(e);
}
} on Exception catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
}
}
@ -210,10 +196,8 @@ class ManagerAPI {
} else {
return await _githubAPI.getPatches(repoName);
}
} on Exception catch (e) {
if (kDebugMode) {
print(e);
}
} on Exception catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
return [];
}
}
@ -229,10 +213,8 @@ class ManagerAPI {
} else {
return await _githubAPI.getLatestReleaseFile('.jar', repoName);
}
} on Exception catch (e) {
if (kDebugMode) {
print(e);
}
} on Exception catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
return null;
}
}
@ -248,33 +230,22 @@ class ManagerAPI {
} else {
return await _githubAPI.getLatestReleaseFile('.apk', repoName);
}
} on Exception catch (e) {
if (kDebugMode) {
print(e);
}
} on Exception catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
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 {
@ -296,19 +267,6 @@ 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 {
@ -401,9 +359,8 @@ 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;
}
@ -426,10 +383,7 @@ 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(),
@ -467,10 +421,7 @@ 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) {
@ -481,33 +432,9 @@ 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();
final List<String> defaultPatches = await getDefaultPatches();
return List.from(patchesMap.putIfAbsent(app, () => defaultPatches));
return List.from(patchesMap.putIfAbsent(app, () => List.empty()));
}
Future<Map<String, dynamic>> readSelectedPatchesFile() async {

View File

@ -13,6 +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';
@lazySingleton
@ -48,10 +49,8 @@ class PatcherAPI {
if (_patches.isEmpty) {
_patches = await _managerAPI.getPatches();
}
} on Exception catch (e) {
if (kDebugMode) {
print(e);
}
} on Exception catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
_patches = List.empty();
}
}
@ -92,10 +91,9 @@ class PatcherAPI {
filteredApps.add(app);
}
}
} on Exception catch (e) {
if (kDebugMode) {
print(e);
}
} on Exception catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
continue;
}
}
}
@ -118,17 +116,32 @@ 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 {
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 {
return selectedPatches.any(
(patch) => patch.dependencies.any(
(dep) => dep.contains('resource-'),
@ -157,10 +170,8 @@ class PatcherAPI {
);
}
return originalFilePath;
} on Exception catch (e) {
if (kDebugMode) {
print(e);
}
} on Exception catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
return originalFilePath;
}
}
@ -170,6 +181,7 @@ class PatcherAPI {
String originalFilePath,
List<Patch> selectedPatches,
) async {
final bool mergeIntegrations = await needsIntegrations(selectedPatches);
final bool includeSettings = await needsSettingsPatch(selectedPatches);
if (includeSettings) {
try {
@ -181,14 +193,16 @@ class PatcherAPI {
if (settingsPatch != null) {
selectedPatches.add(settingsPatch);
}
} on Exception catch (e) {
if (kDebugMode) {
print(e);
}
} on Exception catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
// ignore
}
}
final File? patchBundleFile = await _managerAPI.downloadPatches();
final File? integrationsFile = await _managerAPI.downloadIntegrations();
File? integrationsFile;
if (mergeIntegrations) {
integrationsFile = await _managerAPI.downloadIntegrations();
}
if (patchBundleFile != null) {
_dataDir.createSync();
_tmpDir.createSync();
@ -210,17 +224,18 @@ class PatcherAPI {
'inputFilePath': inputFile.path,
'patchedFilePath': patchedFile.path,
'outFilePath': _outFile!.path,
'integrationsPath': integrationsFile!.path,
'integrationsPath': mergeIntegrations ? integrationsFile!.path : '',
'selectedPatches': selectedPatches.map((p) => p.name).toList(),
'cacheDirPath': cacheDir.path,
'mergeIntegrations': mergeIntegrations,
'keyStoreFilePath': _keyStoreFile.path,
'keystorePassword': _managerAPI.getKeystorePassword(),
},
);
} on Exception catch (e) {
} on Exception catch (e, s) {
if (kDebugMode) {
print(e);
}
throw await Sentry.captureException(e, stackTrace: s);
}
}
}
@ -239,14 +254,10 @@ class PatcherAPI {
}
} else {
await AppInstaller.installApk(_outFile!.path);
return await DeviceApps.isAppInstalled(
patchedApp.packageName,
);
}
} on Exception catch (e) {
if (kDebugMode) {
print(e);
return await DeviceApps.isAppInstalled(patchedApp.packageName);
}
} on Exception catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
return false;
}
}
@ -257,17 +268,11 @@ class PatcherAPI {
try {
if (_outFile != null) {
final String newName = _getFileName(appName, version);
CRFileSaver.saveFileWithDialog(
SaveFileDialogParams(
sourceFilePath: _outFile!.path,
destinationFileName: newName,
),
);
}
} on Exception catch (e) {
if (kDebugMode) {
print(e);
CRFileSaver.saveFileWithDialog(SaveFileDialogParams(
sourceFilePath: _outFile!.path, destinationFileName: newName,),);
}
} on Exception catch (e, s) {
Sentry.captureException(e, stackTrace: s);
}
}
@ -281,10 +286,8 @@ class PatcherAPI {
final File shareFile = _outFile!.copySync(newPath);
ShareExtend.share(shareFile.path, 'file');
}
} on Exception catch (e) {
if (kDebugMode) {
print(e);
}
} on Exception catch (e, s) {
Sentry.captureException(e, stackTrace: s);
}
}
@ -310,7 +313,7 @@ class PatcherAPI {
ShareExtend.share(log.path, 'file');
}
String getSuggestedVersion(String packageName) {
String getRecommendedVersion(String packageName) {
final Map<String, int> versions = {};
for (final Patch patch in _patches) {
final Package? package = patch.compatiblePackages.firstWhereOrNull(

View File

@ -1,65 +1,74 @@
import 'dart:async';
import 'dart:developer';
import 'dart:io';
import 'package:collection/collection.dart';
import 'package:dio/dio.dart';
import 'package:dio_cache_interceptor/dio_cache_interceptor.dart';
import 'package:flutter/foundation.dart';
import 'package:dio_http_cache_lts/dio_http_cache_lts.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:revanced_manager/models/patch.dart';
import 'package:revanced_manager/utils/check_for_gms.dart';
import 'package:sentry_dio/sentry_dio.dart';
import 'package:sentry_flutter/sentry_flutter.dart';
import 'package:timeago/timeago.dart';
@lazySingleton
class RevancedAPI {
late Dio _dio = Dio();
final _cacheOptions = CacheOptions(
store: MemCacheStore(),
final DioCacheManager _dioCacheManager = DioCacheManager(CacheConfig());
final Options _cacheOptions = buildCacheOptions(
const Duration(hours: 6),
maxStale: const Duration(days: 1),
priority: CachePriority.high,
);
Future<void> initialize(String apiUrl) async {
try {
_dio = Dio(
BaseOptions(
baseUrl: apiUrl,
),
);
final bool isGMSInstalled = await checkForGMS();
_dio.interceptors.add(DioCacheInterceptor(options: _cacheOptions));
} on Exception catch (e) {
if (kDebugMode) {
print(e);
if (!isGMSInstalled) {
_dio = Dio(
BaseOptions(
baseUrl: apiUrl,
),
);
log('ReVanced API: Using default engine + $isGMSInstalled');
} else {
_dio = Dio(
BaseOptions(
baseUrl: apiUrl,
),
)..httpClientAdapter = NativeAdapter();
log('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);
}
}
Future<void> clearAllCache() async {
try {
await _cacheOptions.store!.clean();
} on Exception catch (e) {
if (kDebugMode) {
print(e);
}
await _dioCacheManager.clearAll();
} on Exception catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
}
}
Future<Map<String, List<dynamic>>> getContributors() async {
final Map<String, List<dynamic>> contributors = {};
try {
final response = await _dio.get('/contributors');
final response = await _dio.get('/contributors', options: _cacheOptions);
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) {
if (kDebugMode) {
print(e);
}
} on Exception catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
return {};
}
return contributors;
@ -67,13 +76,11 @@ class RevancedAPI {
Future<List<Patch>> getPatches() async {
try {
final response = await _dio.get('/patches');
final response = await _dio.get('/patches', options: _cacheOptions);
final List<dynamic> patches = response.data;
return patches.map((patch) => Patch.fromJson(patch)).toList();
} on Exception catch (e) {
if (kDebugMode) {
print(e);
}
} on Exception catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
return List.empty();
}
}
@ -83,17 +90,15 @@ class RevancedAPI {
String repoName,
) async {
try {
final response = await _dio.get('/tools');
final response = await _dio.get('/tools', options: _cacheOptions);
final List<dynamic> tools = response.data['tools'];
return tools.firstWhereOrNull(
(t) =>
t['repository'] == repoName &&
(t['name'] as String).endsWith(extension),
);
} on Exception catch (e) {
if (kDebugMode) {
print(e);
}
} on Exception catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
return null;
}
}
@ -110,19 +115,14 @@ class RevancedAPI {
if (release != null) {
return release['version'];
}
} on Exception catch (e) {
if (kDebugMode) {
print(e);
}
} on Exception catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
return null;
}
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,
@ -132,52 +132,13 @@ class RevancedAPI {
final String url = release['browser_download_url'];
return await DefaultCacheManager().getSingleFile(url);
}
} on Exception catch (e) {
if (kDebugMode) {
print(e);
}
} on Exception catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
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,
@ -192,10 +153,8 @@ class RevancedAPI {
DateTime.parse(release['timestamp'] as String);
return format(timestamp, locale: 'en_short');
}
} on Exception catch (e) {
if (kDebugMode) {
print(e);
}
} on Exception catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
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';
@ -10,10 +10,8 @@ class RootAPI {
try {
final bool? isRooted = await Root.isRootAvailable();
return isRooted != null && isRooted;
} on Exception catch (e) {
if (kDebugMode) {
print(e);
}
} on Exception catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
return false;
}
}
@ -26,10 +24,8 @@ class RootAPI {
return isRooted != null && isRooted;
}
return false;
} on Exception catch (e) {
if (kDebugMode) {
print(e);
}
} on Exception catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
return false;
}
}
@ -82,10 +78,8 @@ class RootAPI {
apps.removeWhere((pack) => pack.isEmpty);
return apps.map((pack) => pack.trim()).toList();
}
} on Exception catch (e) {
if (kDebugMode) {
print(e);
}
} on Exception catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
return List.empty();
}
return List.empty();
@ -131,10 +125,8 @@ class RootAPI {
await installApk(packageName, patchedFilePath);
await mountApk(packageName, originalFilePath);
return true;
} on Exception catch (e) {
if (kDebugMode) {
print(e);
}
} on Exception catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
return false;
}
}

View File

@ -12,7 +12,7 @@ var lightCustomTheme = ThemeData(
navigationBarTheme: NavigationBarThemeData(
labelTextStyle: MaterialStateProperty.all(
TextStyle(
color: lightCustomColorScheme.onSurface,
color: lightCustomColorScheme.secondary,
fontWeight: FontWeight.w500,
),
),
@ -33,12 +33,13 @@ var darkCustomTheme = ThemeData(
navigationBarTheme: NavigationBarThemeData(
labelTextStyle: MaterialStateProperty.all(
TextStyle(
color: darkCustomColorScheme.onSurface,
color: darkCustomColorScheme.secondary,
fontWeight: FontWeight.w500,
),
),
),
canvasColor: const Color(0xff1B1A1D),
scaffoldBackgroundColor: const Color(0xff1B1A1D),
toggleableActiveColor: const Color(0xffA5CAFF),
textTheme: GoogleFonts.robotoTextTheme(ThemeData.dark().textTheme),
);

View File

@ -23,28 +23,48 @@ class DynamicThemeBuilder extends StatelessWidget {
builder: (lightColorScheme, darkColorScheme) {
final ThemeData lightDynamicTheme = ThemeData(
useMaterial3: true,
canvasColor: lightColorScheme?.surface,
navigationBarTheme: NavigationBarThemeData(
backgroundColor: lightColorScheme?.surface,
indicatorColor: lightColorScheme?.secondaryContainer,
labelTextStyle: MaterialStateProperty.all(
GoogleFonts.roboto(
color: lightColorScheme?.onSurface,
fontWeight: FontWeight.w500,
),
),
iconTheme: MaterialStateProperty.all(
IconThemeData(
color: lightColorScheme?.onSecondaryContainer,
),
),
),
scaffoldBackgroundColor: lightColorScheme?.surface,
colorScheme: lightColorScheme?.harmonized(),
toggleableActiveColor: lightColorScheme?.primary,
textTheme: GoogleFonts.robotoTextTheme(ThemeData.light().textTheme),
);
final ThemeData darkDynamicTheme = ThemeData(
useMaterial3: true,
canvasColor: darkColorScheme?.surface,
navigationBarTheme: NavigationBarThemeData(
backgroundColor: darkColorScheme?.surface,
indicatorColor: darkColorScheme?.secondaryContainer,
labelTextStyle: MaterialStateProperty.all(
GoogleFonts.roboto(
color: darkColorScheme?.onSurface,
fontWeight: FontWeight.w500,
),
),
iconTheme: MaterialStateProperty.all(
IconThemeData(
color: darkColorScheme?.onSecondaryContainer,
),
),
),
scaffoldBackgroundColor: darkColorScheme?.surface,
colorScheme: darkColorScheme?.harmonized(),
toggleableActiveColor: darkColorScheme?.primary,
textTheme: GoogleFonts.robotoTextTheme(ThemeData.dark().textTheme),
);
return DynamicTheme(
@ -70,4 +90,4 @@ class DynamicThemeBuilder extends StatelessWidget {
},
);
}
}
}

View File

@ -3,7 +3,6 @@ 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;
@ -42,14 +41,14 @@ class _AppSelectorViewState extends State<AppSelectorView> {
child: Text(
'',
style: TextStyle(
color: Theme.of(context).textTheme.titleLarge!.color,
color: Theme.of(context).textTheme.headline6!.color,
),
),
),
leading: IconButton(
icon: Icon(
Icons.arrow_back,
color: Theme.of(context).textTheme.titleLarge!.color,
color: Theme.of(context).textTheme.headline6!.color,
),
onPressed: () => Navigator.of(context).pop(),
),
@ -77,16 +76,7 @@ class _AppSelectorViewState extends State<AppSelectorView> {
SliverToBoxAdapter(
child: model.noApps
? Center(
child: I18nText(
'appSelectorView.noAppsLabel',
child: Text(
'',
style: TextStyle(
color:
Theme.of(context).textTheme.titleLarge!.color,
),
),
),
child: I18nText('appSelectorCard.noAppsLabel'),
)
: model.apps.isEmpty
? const AppSkeletonLoader()
@ -94,48 +84,22 @@ 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),
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(),
],
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(),
),
),
),

View File

@ -2,39 +2,27 @@ 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:revanced_manager/ui/widgets/shared/custom_material_button.dart';
import 'package:sentry_flutter/sentry_flutter.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()),
@ -46,25 +34,9 @@ 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,
@ -78,79 +50,6 @@ 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);
},
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(
@ -188,10 +87,8 @@ class AppSelectorViewModel extends BaseViewModel {
locator<PatcherViewModel>().loadLastSelectedPatches();
}
}
} on Exception catch (e) {
if (kDebugMode) {
print(e);
}
} on Exception catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
_toast.showBottom('appSelectorView.errorMessage');
}
}
@ -206,18 +103,4 @@ 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

@ -23,7 +23,7 @@ class ContributorsView extends StatelessWidget {
child: Text(
'',
style: GoogleFonts.inter(
color: Theme.of(context).textTheme.titleLarge!.color,
color: Theme.of(context).textTheme.headline6!.color,
),
),
),

View File

@ -7,6 +7,7 @@ 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';
@ -17,11 +18,12 @@ 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(
body: RefreshIndicator(
color: Theme.of(context).colorScheme.secondary,
backgroundColor: Theme.of(context).colorScheme.secondaryContainer,
onRefresh: () => model.forceRefresh(context),
child: CustomScrollView(
slivers: <Widget>[
@ -32,7 +34,7 @@ class HomeView extends StatelessWidget {
child: Text(
'',
style: GoogleFonts.inter(
color: Theme.of(context).textTheme.titleLarge!.color,
color: Theme.of(context).textTheme.headline6!.color,
),
),
),
@ -46,67 +48,40 @@ class HomeView extends StatelessWidget {
'homeView.updatesSubtitle',
child: Text(
'',
style: Theme.of(context).textTheme.titleLarge,
style: Theme.of(context).textTheme.headline6,
),
),
const SizedBox(height: 10),
LatestCommitCard(
onPressedManager: () =>
onPressed: () =>
model.showUpdateConfirmationDialog(context),
onPressedPatches: () => model.forceRefresh(context),
),
const SizedBox(height: 23),
I18nText(
'homeView.patchedSubtitle',
child: Text(
'',
style: Theme.of(context).textTheme.titleLarge,
style: Theme.of(context).textTheme.headline6,
),
),
const SizedBox(height: 8),
Row(
children: <Widget>[
ActionChip(
avatar: const Icon(Icons.grid_view),
CustomChip(
label: I18nText('homeView.installed'),
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: () {
isSelected: !model.showUpdatableApps,
onSelected: (value) {
model.toggleUpdatableApps(false);
},
),
const SizedBox(width: 10),
ActionChip(
avatar: const Icon(Icons.update),
CustomChip(
label: I18nText('homeView.updatesAvailable'),
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: () {
isSelected: model.showUpdatableApps,
onSelected: (value) {
model.toggleUpdatableApps(true);
},
),
)
],
),
const SizedBox(height: 14),

View File

@ -1,30 +1,27 @@
// 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:flutter/foundation.dart';
import 'package:device_apps/device_apps.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:sentry_flutter/sentry_flutter.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 {
@ -32,35 +29,20 @@ 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'),
),
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');
}
}
},
onSelectNotification: (p) =>
DeviceApps.openApp('app.revanced.manager.flutter'),
);
flutterLocalNotificationsPlugin
.resolvePlatformSpecificImplementation<
@ -70,17 +52,6 @@ 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());
}
@ -116,157 +87,58 @@ class HomeViewModel extends BaseViewModel {
Future<bool> hasManagerUpdates() async {
final String? latestVersion = await _managerAPI.getLatestManagerVersion();
String currentVersion = await _managerAPI.getCurrentManagerVersion();
// add v to current version
if (!currentVersion.startsWith('v')) {
currentVersion = 'v$currentVersion';
}
if (latestVersion != currentVersion) {
return true;
}
return false;
}
Future<bool> hasPatchesUpdates() async {
final String? latestVersion = await _managerAPI.getLatestPatchesVersion();
final String? currentVersion = await _managerAPI.getCurrentPatchesVersion();
final String currentVersion = await _managerAPI.getCurrentManagerVersion();
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) {
print(e);
}
} on Exception catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
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');
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,
),
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',
),
),
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,
// );
androidAllowWhileIdle: true,
uiLocalNotificationDateInterpretation:
UILocalNotificationDateInterpretation.absoluteTime,
);
_toast.showBottom('homeView.installingMessage');
await AppInstaller.installApk(managerApk.path);
} else {
_toast.showBottom('homeView.errorDownloadMessage');
}
} on Exception catch (e) {
if (kDebugMode) {
print(e);
}
} on Exception catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
_toast.showBottom('homeView.errorInstallMessage');
}
}
@ -275,9 +147,7 @@ class HomeViewModel extends BaseViewModel {
_toast.showBottom('homeView.updatesDisabled');
}
Future<void> showUpdateConfirmationDialog(
BuildContext parentContext,
) {
Future<void> showUpdateConfirmationDialog(BuildContext parentContext) {
return showModalBottomSheet(
context: parentContext,
isScrollControlled: true,
@ -306,7 +176,6 @@ class HomeViewModel extends BaseViewModel {
_lastUpdate!.difference(DateTime.now()).inSeconds > 2) {
_managerAPI.clearAllData();
}
_toast.showBottom('homeView.refreshSuccess');
initialize(context);
}
}

View File

@ -28,7 +28,7 @@ class InstallerView extends StatelessWidget {
title: Text(
model.headerLogs,
style: GoogleFonts.inter(
color: Theme.of(context).textTheme.titleLarge!.color,
color: Theme.of(context).textTheme.headline6!.color,
),
),
onBackButtonPressed: () => model.onWillPop(context),

View File

@ -1,6 +1,5 @@
// 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';
@ -15,6 +14,7 @@ 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';
@ -56,10 +56,9 @@ class InstallerViewModel extends BaseViewModel {
),
),
).then((value) => FlutterBackground.enableBackgroundExecution());
} on Exception catch (e) {
if (kDebugMode) {
print(e);
} // ignore
} on Exception catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
// ignore
}
}
await Wakelock.enable();
@ -132,15 +131,14 @@ class InstallerViewModel extends BaseViewModel {
_app.apkFilePath,
_patches,
);
} on Exception catch (e) {
} on Exception catch (e, s) {
update(
-100.0,
'Aborting...',
'An error occurred! Aborting\nError:\n$e',
);
if (kDebugMode) {
print(e);
}
await Sentry.captureException(e, stackTrace: s);
throw await Sentry.captureException(e, stackTrace: s);
}
} else {
update(-100.0, 'Aborting...', 'No app or patches selected! Aborting');
@ -148,17 +146,14 @@ class InstallerViewModel extends BaseViewModel {
if (FlutterBackground.isBackgroundExecutionEnabled) {
try {
FlutterBackground.disableBackgroundExecution();
} on Exception catch (e) {
if (kDebugMode) {
print(e);
} // ignore
} on Exception catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
// ignore
}
}
await Wakelock.disable();
} on Exception catch (e) {
if (kDebugMode) {
print(e);
}
} on Exception catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
}
}
@ -216,30 +211,24 @@ class InstallerViewModel extends BaseViewModel {
await _managerAPI.savePatchedApp(_app);
}
}
} on Exception catch (e) {
if (kDebugMode) {
print(e);
}
} on Exception catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
}
}
void exportResult() {
try {
_patcherAPI.exportPatchedFile(_app.name, _app.version);
} on Exception catch (e) {
if (kDebugMode) {
print(e);
}
} on Exception catch (e, s) {
Sentry.captureException(e, stackTrace: s);
}
}
void shareResult() {
try {
_patcherAPI.sharePatchedFile(_app.name, _app.version);
} on Exception catch (e) {
if (kDebugMode) {
print(e);
}
} on Exception catch (e, s) {
Sentry.captureException(e, stackTrace: s);
}
}
@ -253,10 +242,8 @@ class InstallerViewModel extends BaseViewModel {
locator<PatcherViewModel>().selectedApp = null;
locator<PatcherViewModel>().selectedPatches.clear();
locator<PatcherViewModel>().notifyListeners();
} on Exception catch (e) {
if (kDebugMode) {
print(e);
}
} on Exception catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
}
}

View File

@ -42,6 +42,7 @@ class NavigationView extends StatelessWidget {
context,
'navigationView.dashboardTab',
),
tooltip: '',
),
NavigationDestination(
icon: model.isIndexSelected(1)
@ -51,6 +52,7 @@ class NavigationView extends StatelessWidget {
context,
'navigationView.patcherTab',
),
tooltip: '',
),
NavigationDestination(
icon: model.isIndexSelected(2)
@ -60,6 +62,7 @@ class NavigationView extends StatelessWidget {
context,
'navigationView.settingsTab',
),
tooltip: '',
),
],
),

View File

@ -17,14 +17,12 @@ 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(),
),
);
}
@ -39,8 +37,7 @@ 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

@ -34,7 +34,7 @@ class PatcherView extends StatelessWidget {
child: Text(
'',
style: GoogleFonts.inter(
color: Theme.of(context).textTheme.titleLarge!.color,
color: Theme.of(context).textTheme.headline6!.color,
),
),
),

View File

@ -1,5 +1,3 @@
// ignore_for_file: use_build_context_synchronously
import 'package:flutter/material.dart';
import 'package:flutter_i18n/flutter_i18n.dart';
import 'package:injectable/injectable.dart';
@ -10,7 +8,6 @@ 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';
@ -55,57 +52,23 @@ class PatcherViewModel extends BaseViewModel {
Future<void> showPatchConfirmationDialog(BuildContext context) async {
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['arch'];
final supportedAbis = ['arm64-v8a', 'x86', 'x86_64'];
return !archs.any((arch) => supportedAbis.contains(arch));
});
if (context.mounted && armv7) {
if (isValid) {
navigateToInstaller();
} else {
return showDialog(
context: context,
builder: (context) => AlertDialog(
title: I18nText('warning'),
backgroundColor: Theme.of(context).colorScheme.secondaryContainer,
content: I18nText('patcherView.armv7WarningDialogText'),
content: I18nText('patcherView.patchDialogText'),
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();
@ -114,8 +77,6 @@ class PatcherViewModel extends BaseViewModel {
],
),
);
} else {
navigateToInstaller();
}
}
@ -127,24 +88,24 @@ class PatcherViewModel extends BaseViewModel {
return text;
}
String getSuggestedVersionString(BuildContext context) {
String suggestedVersion =
_patcherAPI.getSuggestedVersion(selectedApp!.packageName);
if (suggestedVersion.isEmpty) {
suggestedVersion = FlutterI18n.translate(
String getRecommendedVersionString(BuildContext context) {
String recommendedVersion =
_patcherAPI.getRecommendedVersion(selectedApp!.packageName);
if (recommendedVersion.isEmpty) {
recommendedVersion = FlutterI18n.translate(
context,
'appSelectorCard.anyVersion',
);
} else {
suggestedVersion = 'v$suggestedVersion';
recommendedVersion = 'v$recommendedVersion';
}
return '${FlutterI18n.translate(
context,
'appSelectorCard.currentVersion',
)}: v${selectedApp!.version}\n${FlutterI18n.translate(
context,
'appSelectorCard.suggestedVersion',
)}: $suggestedVersion';
'appSelectorCard.recommendedVersion',
)}: $recommendedVersion';
}
Future<void> loadLastSelectedPatches() async {
@ -153,8 +114,7 @@ class PatcherViewModel extends BaseViewModel {
await _managerAPI.getSelectedPatches(selectedApp!.originalPackageName);
final List<Patch> patches =
_patcherAPI.getFilteredPatches(selectedApp!.originalPackageName);
this
.selectedPatches
this.selectedPatches
.addAll(patches.where((patch) => selectedPatches.contains(patch.name)));
notifyListeners();
}

View File

@ -2,9 +2,9 @@ import 'package:flutter/material.dart';
import 'package:flutter_i18n/flutter_i18n.dart';
import 'package:revanced_manager/ui/views/patches_selector/patches_selector_viewmodel.dart';
import 'package:revanced_manager/ui/widgets/patchesSelectorView/patch_item.dart';
import 'package:revanced_manager/ui/widgets/shared/custom_chip.dart';
import 'package:revanced_manager/ui/widgets/shared/custom_popup_menu.dart';
import 'package:revanced_manager/ui/widgets/shared/search_bar.dart';
import 'package:revanced_manager/utils/check_for_supported_patch.dart';
import 'package:stacked/stacked.dart';
class PatchesSelectorView extends StatefulWidget {
@ -50,19 +50,20 @@ class _PatchesSelectorViewState extends State<PatchesSelectorView> {
child: Text(
'',
style: TextStyle(
color: Theme.of(context).textTheme.titleLarge!.color,
color: Theme.of(context).textTheme.headline6!.color,
),
),
),
leading: IconButton(
icon: Icon(
Icons.arrow_back,
color: Theme.of(context).textTheme.titleLarge!.color,
color: Theme.of(context).textTheme.headline6!.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),
@ -74,7 +75,7 @@ class _PatchesSelectorViewState extends State<PatchesSelectorView> {
child: Text(
model.patchesVersion!,
style: TextStyle(
color: Theme.of(context).textTheme.titleLarge!.color,
color: Theme.of(context).textTheme.headline6!.color,
),
),
),
@ -135,24 +136,27 @@ class _PatchesSelectorViewState extends State<PatchesSelectorView> {
children: [
Row(
children: [
ActionChip(
label: I18nText('patchesSelectorView.default'),
tooltip: FlutterI18n.translate(
context,
'patchesSelectorView.defaultTooltip',
),
onPressed: () {
model.selectDefaultPatches();
CustomChip(
label:
I18nText('patchesSelectorView.recommended'),
onSelected: (value) {
model.selectRecommendedPatches();
},
),
const SizedBox(width: 8),
ActionChip(
CustomChip(
label: I18nText('patchesSelectorView.all'),
onSelected: (value) {
if (value) {
model.selectAllPatcherWarning(context);
}
model.selectAllPatches(true);
},
),
const SizedBox(width: 8),
CustomChip(
label: I18nText('patchesSelectorView.none'),
tooltip: FlutterI18n.translate(
context,
'patchesSelectorView.noneTooltip',
),
onPressed: () {
onSelected: (value) {
model.clearPatches();
},
),
@ -169,7 +173,7 @@ class _PatchesSelectorViewState extends State<PatchesSelectorView> {
packageVersion: model.getAppVersion(),
supportedPackageVersions:
model.getSupportedVersions(patch),
isUnsupported: !isPatchSupported(patch),
isUnsupported: !model.isPatchSupported(patch),
isSelected: model.isSelected(patch),
onChanged: (value) =>
model.selectPatch(patch, value),

View File

@ -1,4 +1,6 @@
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';
@ -7,7 +9,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/utils/check_for_supported_patch.dart';
import 'package:revanced_manager/ui/widgets/shared/custom_material_button.dart';
import 'package:stacked/stacked.dart';
class PatchesSelectorViewModel extends BaseViewModel {
@ -23,7 +25,7 @@ class PatchesSelectorViewModel extends BaseViewModel {
}
Future<void> initialize() async {
getPatchesVersion().whenComplete(() => notifyListeners());
getPatchesVersion();
patches.addAll(
_patcherAPI.getFilteredPatches(
locator<PatcherViewModel>().selectedApp!.originalPackageName,
@ -48,7 +50,39 @@ class PatchesSelectorViewModel extends BaseViewModel {
notifyListeners();
}
void selectDefaultPatches() {
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() {
selectedPatches.clear();
if (_managerAPI.areExperimentalPatchesEnabled() == false) {
@ -119,6 +153,16 @@ 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,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/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';
@ -12,7 +11,6 @@ import 'package:stacked/stacked.dart';
class SManageApiUrl extends BaseViewModel {
final ManagerAPI _managerAPI = locator<ManagerAPI>();
final Toast _toast = locator<Toast>();
final TextEditingController _apiUrlController = TextEditingController();
@ -92,7 +90,7 @@ class SManageApiUrl extends BaseViewModel {
label: I18nText('yesButton'),
onPressed: () {
_managerAPI.setApiUrl('');
_toast.showBottom('settingsView.restartAppForChanges');
Navigator.of(context).pop();
Navigator.of(context).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/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';
@ -12,7 +11,6 @@ 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();
@ -119,14 +117,13 @@ class SManageSources extends BaseViewModel {
CustomMaterialButton(
label: I18nText('okButton'),
onPressed: () {
_managerAPI.setRepoUrl(_hostSourceController.text.trim());
_managerAPI.setRepoUrl(_hostSourceController.text);
_managerAPI.setPatchesRepo(
'${_orgPatSourceController.text.trim()}/${_patSourceController.text.trim()}',
'${_orgPatSourceController.text}/${_patSourceController.text}',
);
_managerAPI.setIntegrationsRepo(
'${_orgIntSourceController.text.trim()}/${_intSourceController.text.trim()}',
'${_orgIntSourceController.text}/${_intSourceController.text}',
);
_toast.showBottom('settingsView.restartAppForChanges');
Navigator.of(context).pop();
},
)
@ -154,10 +151,9 @@ class SManageSources extends BaseViewModel {
_managerAPI.setRepoUrl('');
_managerAPI.setPatchesRepo('');
_managerAPI.setIntegrationsRepo('');
_toast.showBottom('settingsView.restartAppForChanges');
Navigator.of(context)
..pop()
..pop();
Navigator.of(context).pop();
Navigator.of(context).pop();
Navigator.of(context).pop();
},
)
],

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/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';
@ -15,6 +16,7 @@ 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';
@ -41,6 +43,7 @@ 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,6 +7,7 @@ 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';
@ -63,8 +64,8 @@ class SUpdateThemeUI extends StatelessWidget {
return SettingsSection(
title: 'settingsView.appearanceSectionTitle',
children: <Widget>[
SwitchListTile(
contentPadding: const EdgeInsets.symmetric(horizontal: 20.0),
CustomSwitchTile(
padding: const EdgeInsets.symmetric(horizontal: 20.0),
title: I18nText(
'settingsView.darkThemeLabel',
child: const Text(
@ -77,7 +78,7 @@ class SUpdateThemeUI extends StatelessWidget {
),
subtitle: I18nText('settingsView.darkThemeHint'),
value: SUpdateTheme().getDarkThemeStatus(),
onChanged: (value) => SUpdateTheme().setUseDarkTheme(
onTap: (value) => SUpdateTheme().setUseDarkTheme(
context,
value,
),
@ -87,8 +88,8 @@ class SUpdateThemeUI extends StatelessWidget {
builder: (context, snapshot) => Visibility(
visible:
snapshot.hasData && snapshot.data! >= ANDROID_12_SDK_VERSION,
child: SwitchListTile(
contentPadding: const EdgeInsets.symmetric(horizontal: 20.0),
child: CustomSwitchTile(
padding: const EdgeInsets.symmetric(horizontal: 20.0),
title: I18nText(
'settingsView.dynamicThemeLabel',
child: const Text(
@ -101,12 +102,11 @@ class SUpdateThemeUI extends StatelessWidget {
),
subtitle: I18nText('settingsView.dynamicThemeHint'),
value: _settingViewModel.sUpdateTheme.getDynamicThemeStatus(),
onChanged: (value) => {
_settingViewModel.sUpdateTheme.setUseDynamicTheme(
context,
value,
),
},
onTap: (value) =>
_settingViewModel.sUpdateTheme.setUseDynamicTheme(
context,
value,
),
),
),
),

View File

@ -1,86 +0,0 @@
// 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

@ -3,7 +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/settingsFragment/settings_update_theme.dart';
import 'package:revanced_manager/ui/views/settings/settingsFragement/settings_update_theme.dart';
import 'package:revanced_manager/ui/views/settings/settings_viewmodel.dart';
import 'package:revanced_manager/ui/widgets/settingsView/settings_advanced_section.dart';
import 'package:revanced_manager/ui/widgets/settingsView/settings_export_section.dart';
@ -32,7 +32,7 @@ class SettingsView extends StatelessWidget {
child: Text(
'',
style: GoogleFonts.inter(
color: Theme.of(context).textTheme.titleLarge!.color,
color: Theme.of(context).textTheme.headline6!.color,
),
),
),
@ -49,6 +49,8 @@ class SettingsView extends StatelessWidget {
_settingsDivider,
SExportSection(),
_settingsDivider,
// SLoggingSection(),
// _settingsDivider,
SInfoSection(),
],
),

View File

@ -2,7 +2,6 @@ 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';
@ -10,15 +9,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/settingsFragment/settings_update_language.dart';
import 'package:revanced_manager/ui/views/settings/settingsFragment/settings_update_theme.dart';
import 'package:revanced_manager/ui/views/settings/settingsFragement/settings_update_language.dart';
import 'package:revanced_manager/ui/views/settings/settingsFragement/settings_update_theme.dart';
import 'package:sentry_flutter/sentry_flutter.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>();
@ -29,6 +28,16 @@ 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();
}
@ -63,32 +72,22 @@ class SettingsViewModel extends BaseViewModel {
try {
final File outFile = File(_managerAPI.storedPatchesFile);
if (outFile.existsSync()) {
final String dateTime = DateTime.now()
.toString()
.replaceAll(' ', '_')
.split('.')
.first;
await CRFileSaver.saveFileWithDialog(
SaveFileDialogParams(
sourceFilePath: outFile.path,
destinationFileName: 'selected_patches_$dateTime.json',
),
);
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 {
_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 {
final FilePickerResult? result =
await FilePicker.platform.pickFiles(
final FilePickerResult? result = await FilePicker.platform.pickFiles(
type: FileType.custom,
allowedExtensions: ['json'],
);
@ -101,54 +100,12 @@ class SettingsViewModel extends BaseViewModel {
}
_toast.showBottom('settingsView.importedPatches');
}
} on Exception catch (e) {
if (kDebugMode) {
print(e);
}
} on Exception catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
_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');
}
}
void resetSelectedPatches() {
_managerAPI.resetLastSelectedPatches();
_toast.showBottom('settingsView.resetStoredPatches');
@ -156,7 +113,7 @@ class SettingsViewModel extends BaseViewModel {
Future<int> getSdkVersion() async {
final AndroidDeviceInfo info = await DeviceInfoPlugin().androidInfo;
return info.version.sdkInt;
return info.version.sdkInt ?? -1;
}
Future<void> deleteLogs() async {

View File

@ -27,7 +27,7 @@ class AppInfoView extends StatelessWidget {
child: Text(
'',
style: GoogleFonts.inter(
color: Theme.of(context).textTheme.titleLarge!.color,
color: Theme.of(context).textTheme.headline6!.color,
),
),
),
@ -51,13 +51,13 @@ class AppInfoView extends StatelessWidget {
Text(
app.name,
textAlign: TextAlign.center,
style: Theme.of(context).textTheme.titleLarge,
style: Theme.of(context).textTheme.headline6,
),
const SizedBox(height: 4),
Text(
app.version,
textAlign: TextAlign.center,
style: Theme.of(context).textTheme.titleLarge,
style: Theme.of(context).textTheme.subtitle1,
),
const SizedBox(height: 20),
Padding(

View File

@ -9,14 +9,12 @@ 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
@ -48,35 +46,31 @@ 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: [
children: <Widget>[
Text(
widget.suggestedVersion.isEmpty
? 'All versions'
: widget.suggestedVersion,
widget.name,
maxLines: 2,
overflow: TextOverflow.visible,
style: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.w500,
),
),
const SizedBox(width: 4),
const SizedBox(width: 6),
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

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

View File

@ -28,7 +28,7 @@ class AvailableUpdatesCard extends StatelessWidget {
child: Text(
'',
textAlign: TextAlign.center,
style: Theme.of(context).textTheme.titleMedium!.copyWith(
style: Theme.of(context).textTheme.subtitle1!.copyWith(
color: Theme.of(context).colorScheme.secondary,
),
),
@ -53,7 +53,7 @@ class AvailableUpdatesCard extends StatelessWidget {
// child: Text(
// '',
// textAlign: TextAlign.center,
// style: Theme.of(context).textTheme.titleMedium!.copyWith(
// style: Theme.of(context).textTheme.subtitle1!.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.titleMedium!.copyWith(
style: Theme.of(context).textTheme.subtitle1!.copyWith(
color: Theme.of(context).colorScheme.secondary,
),
),

View File

@ -8,11 +8,9 @@ import 'package:revanced_manager/ui/widgets/shared/custom_material_button.dart';
class LatestCommitCard extends StatefulWidget {
const LatestCommitCard({
Key? key,
required this.onPressedManager,
required this.onPressedPatches,
required this.onPressed,
}) : super(key: key);
final Function() onPressedManager;
final Function() onPressedPatches;
final Function() onPressed;
@override
State<LatestCommitCard> createState() => _LatestCommitCardState();
@ -23,109 +21,66 @@ class _LatestCommitCardState extends State<LatestCommitCard> {
@override
Widget build(BuildContext context) {
return Column(
children: [
// ReVanced Manager
CustomCard(
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
return CustomCard(
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Column(
crossAxisAlignment: CrossAxisAlignment.start,
Row(
children: <Widget>[
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'),
),
],
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',
),
),
),
],
),
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
: () => {},
),
),
),
],
),
),
const SizedBox(height: 16),
// ReVanced Patches
CustomCard(
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Column(
crossAxisAlignment: CrossAxisAlignment.start,
const SizedBox(height: 4),
Row(
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',
),
),
),
],
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: 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
: () => {},
),
),
),
],
),
),
],
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
: () => {},
),
),
),
],
),
);
}
}

View File

@ -16,12 +16,12 @@ class UpdateConfirmationDialog extends StatelessWidget {
expand: false,
snap: true,
snapSizes: const [0.5],
builder: (_, scrollController) => SingleChildScrollView(
builder: (context, scrollController) => SingleChildScrollView(
controller: scrollController,
child: SafeArea(
child: FutureBuilder<Map<String, dynamic>?>(
future: model.getLatestManagerRelease(),
builder: (_, snapshot) {
builder: (context, snapshot) {
if (!snapshot.hasData) {
return const SizedBox(
height: 300,
@ -45,8 +45,7 @@ class UpdateConfirmationDialog extends StatelessWidget {
children: [
Expanded(
child: Column(
crossAxisAlignment:
CrossAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
I18nText(
'homeView.updateDialogTitle',
@ -63,14 +62,12 @@ 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,
@ -96,8 +93,7 @@ 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(
@ -113,12 +109,9 @@ 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>()
.getSuggestedVersionString(context),
.getRecommendedVersionString(context),
),
],
),

View File

@ -75,7 +75,7 @@ class _PatchItemState extends State<PatchItem> {
crossAxisAlignment: CrossAxisAlignment.end,
children: <Widget>[
Expanded(
child: Text(
child: Text(
widget.simpleName,
maxLines: 2,
overflow: TextOverflow.visible,
@ -99,6 +99,7 @@ class _PatchItemState extends State<PatchItem> {
Text(
widget.description,
softWrap: true,
maxLines: 3,
overflow: TextOverflow.visible,
style: TextStyle(
fontSize: 14,
@ -200,4 +201,4 @@ class _PatchItemState extends State<PatchItem> {
),
);
}
}
}

View File

@ -61,7 +61,7 @@ class OptionsFilePicker extends StatelessWidget {
child: Text(
'Select File',
style: TextStyle(
color: Theme.of(context).textTheme.bodyLarge?.color,
color: Theme.of(context).textTheme.bodyText1?.color,
),
),
),

View File

@ -2,13 +2,12 @@
import 'package:flutter/material.dart';
import 'package:flutter_i18n/widgets/I18nText.dart';
import 'package:revanced_manager/ui/views/settings/settingsFragment/settings_manage_api_url.dart';
import 'package:revanced_manager/ui/views/settings/settingsFragment/settings_manage_sources.dart';
import 'package:revanced_manager/ui/views/settings/settingsFragement/settings_manage_api_url.dart';
import 'package:revanced_manager/ui/views/settings/settingsFragement/settings_manage_sources.dart';
import 'package:revanced_manager/ui/views/settings/settings_viewmodel.dart';
import 'package:revanced_manager/ui/widgets/settingsView/settings_experimental_patches.dart';
import 'package:revanced_manager/ui/widgets/settingsView/settings_experimental_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();
@ -22,7 +21,6 @@ class SAdvancedSection extends StatelessWidget {
children: <Widget>[
SManageApiUrlUI(),
SManageSourcesUI(),
// SManageKeystorePasswordUI(),
SExperimentalUniversalPatches(),
SExperimentalPatches(),
ListTile(
@ -38,7 +36,7 @@ class SAdvancedSection extends StatelessWidget {
),
),
subtitle: I18nText('settingsView.deleteKeystoreHint'),
onTap: () => _showDeleteKeystoreDialog(context),
onTap: () => _settingsViewModel.deleteKeystore,
),
ListTile(
contentPadding: const EdgeInsets.symmetric(horizontal: 20.0),
@ -73,31 +71,4 @@ 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,6 +1,7 @@
import 'package:flutter/material.dart';
import 'package:flutter_i18n/widgets/I18nText.dart';
import 'package:revanced_manager/ui/views/settings/settings_viewmodel.dart';
import 'package:revanced_manager/ui/widgets/settingsView/custom_switch_tile.dart';
class SExperimentalPatches extends StatefulWidget {
const SExperimentalPatches({super.key});
@ -14,8 +15,8 @@ final _settingsViewModel = SettingsViewModel();
class _SExperimentalPatchesState extends State<SExperimentalPatches> {
@override
Widget build(BuildContext context) {
return SwitchListTile(
contentPadding: const EdgeInsets.symmetric(horizontal: 20.0),
return CustomSwitchTile(
padding: const EdgeInsets.symmetric(horizontal: 20.0),
title: I18nText(
'settingsView.experimentalPatchesLabel',
child: const Text(
@ -28,7 +29,7 @@ class _SExperimentalPatchesState extends State<SExperimentalPatches> {
),
subtitle: I18nText('settingsView.experimentalPatchesHint'),
value: _settingsViewModel.areExperimentalPatchesEnabled(),
onChanged: (value) {
onTap: (value) {
setState(() {
_settingsViewModel.useExperimentalPatches(value);
});

View File

@ -1,6 +1,7 @@
import 'package:flutter/material.dart';
import 'package:flutter_i18n/widgets/I18nText.dart';
import 'package:revanced_manager/ui/views/settings/settings_viewmodel.dart';
import 'package:revanced_manager/ui/widgets/settingsView/custom_switch_tile.dart';
class SExperimentalUniversalPatches extends StatefulWidget {
const SExperimentalUniversalPatches({super.key});
@ -16,8 +17,8 @@ class _SExperimentalUniversalPatchesState
extends State<SExperimentalUniversalPatches> {
@override
Widget build(BuildContext context) {
return SwitchListTile(
contentPadding: const EdgeInsets.symmetric(horizontal: 20.0),
return CustomSwitchTile(
padding: const EdgeInsets.symmetric(horizontal: 20.0),
title: I18nText(
'settingsView.experimentalUniversalPatchesLabel',
child: const Text(
@ -30,7 +31,7 @@ class _SExperimentalUniversalPatchesState
),
subtitle: I18nText('settingsView.experimentalUniversalPatchesHint'),
value: _settingsViewModel.areUniversalPatchesEnabled(),
onChanged: (value) {
onTap: (value) {
setState(() {
_settingsViewModel.showUniversalPatches(value);
});

View File

@ -1,6 +1,5 @@
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';
@ -44,40 +43,6 @@ 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

@ -0,0 +1,36 @@
import 'package:flutter/material.dart';
import 'package:flutter_i18n/widgets/I18nText.dart';
import 'package:revanced_manager/ui/views/settings/settings_viewmodel.dart';
import 'package:revanced_manager/ui/widgets/settingsView/custom_switch_tile.dart';
import 'package:revanced_manager/ui/widgets/settingsView/settings_section.dart';
final _settingsViewModel = SettingsViewModel();
class SLoggingSection extends StatelessWidget {
const SLoggingSection({super.key});
@override
Widget build(BuildContext context) {
return SettingsSection(
title: 'settingsView.logsSectionTitle',
children: <Widget>[
CustomSwitchTile(
padding: const EdgeInsets.symmetric(horizontal: 20.0),
title: I18nText(
'settingsView.sentryLabel',
child: const Text(
'',
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.w500,
),
),
),
subtitle: I18nText('settingsView.sentryHint'),
value: _settingsViewModel.isSentryEnabled(),
onTap: (value) => _settingsViewModel.useSentry(value),
),
],
);
}
}

View File

@ -17,7 +17,7 @@ class CustomChip extends StatelessWidget {
showCheckmark: false,
label: label,
selected: isSelected,
labelStyle: Theme.of(context).textTheme.titleSmall!.copyWith(
labelStyle: Theme.of(context).textTheme.subtitle2!.copyWith(
color: isSelected
? Theme.of(context).colorScheme.primary
: Theme.of(context).colorScheme.secondary,

View File

@ -33,7 +33,7 @@ class CustomSliverAppBar extends StatelessWidget {
: IconButton(
icon: Icon(
Icons.arrow_back,
color: Theme.of(context).textTheme.titleLarge!.color,
color: Theme.of(context).textTheme.headline6!.color,
),
onPressed:
onBackButtonPressed ?? () => Navigator.of(context).pop(),

View File

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

View File

@ -0,0 +1,10 @@
// Check for google mobile services on device
import 'package:device_apps/device_apps.dart';
Future<bool> checkForGMS() async {
bool isGMSInstalled = true;
isGMSInstalled = await DeviceApps.isAppInstalled('com.google.android.gms') ||
await DeviceApps.isAppInstalled('com.android.vending');
return isGMSInstalled;
}

View File

@ -1,14 +0,0 @@
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)),
);
}

View File

@ -0,0 +1,5 @@
// Dummy environment variables used for building the app locally. These automatically get set with correct values during workflow builds.
class Environment {
static const sentryDSN = '';
static const crowdinKEY = '';
}

7179
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,10 +0,0 @@
{
"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,87 +4,88 @@ homepage: https://github.com/revanced/revanced-manager
publish_to: 'none'
version: 1.1.0+100100000
version: 0.0.55+55
environment:
sdk: ">=2.17.5 <3.0.0"
dependencies:
animations: ^2.0.7
sentry_flutter: ^6.12.2
animations: ^2.0.4
app_installer: ^1.1.0
collection: ^1.17.0
collection: ^1.16.0
cross_connectivity: ^3.0.5
cr_file_saver:
git:
url: https://github.com/dhruvanbhalara/cr_file_saver.git
ref: a08326ecb48f581b4b09e2e2665d31ed1704c7af
cr_file_saver: ^0.0.2
device_apps:
git:
url: https://github.com/ponces/flutter_plugin_device_apps
ref: revanced-manager
device_info_plus: ^8.1.0
dynamic_color: ^1.6.3
dio: ^5.0.0
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
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: ^7.0.1
flex_color_scheme: ^6.0.0
flutter:
sdk: flutter
flutter_background: ^1.2.0
flutter_background: ^1.1.0
flutter_cache_manager: ^3.3.0
flutter_i18n: ^0.32.4
flutter_local_notifications: ^13.0.0
flutter_local_notifications: ^9.8.0+1
flutter_localizations:
sdk: flutter
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
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
intl: ^0.17.0
json_annotation: ^4.8.0
json_annotation: ^4.6.0
logcat:
git:
url: https://github.com/SuaMusica/logcat
ref: feature/nullSafe
package_info_plus: ^3.0.3
path_provider: ^2.0.14
permission_handler: ^10.2.0
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
pull_to_refresh: ^2.0.0
root:
git:
url: https://github.com/gokul1630/root
ref: main
share_extend: ^2.0.0
shared_preferences: ^2.1.0
shared_preferences: ^2.0.15
skeletons: ^0.0.3
stacked: ^3.2.0
stacked_generator: ^1.1.0
stacked_services: ^1.0.0
stacked_themes: ^0.3.10
timeago: ^3.3.0
timezone: ^0.9.0
url_launcher: ^6.1.10
stacked: ^3.0.0
stacked_generator: ^0.8.0
stacked_services: ^0.9.3
stacked_themes: ^0.3.9
timeago: ^3.2.2
timezone: ^0.8.0
url_launcher: ^6.1.5
wakelock: ^0.6.2
sentry_dio: ^6.12.2
flutter_dotenv: ^5.0.2
flutter_markdown: ^0.6.14
pub_release: ^8.0.3
dio_cache_interceptor: ^3.4.0
flutter_markdown: ^0.6.13
dev_dependencies:
json_serializable: ^6.6.1
json_serializable: ^6.3.1
build_runner: any
flutter_launcher_icons: ^0.13.0
flutter_launcher_icons: ^0.10.0
flutter_lints: ^2.0.1
flutter_test:
sdk: flutter
injectable_generator: ^2.1.5
injectable_generator: ^1.5.4
@ -92,3 +93,4 @@ flutter:
uses-material-design: true
assets:
- assets/i18n/
- .env