Compare commits
38 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7063ffa013 | ||
|
|
bf4dc3c095 | ||
|
|
c10e5848bf | ||
|
|
92a3b0d6e0 | ||
|
|
b475bd25c8 | ||
|
|
d318224a6f | ||
|
|
0074fee865 | ||
|
|
5617535a63 | ||
|
|
68ccefc59f | ||
|
|
6d60541626 | ||
|
|
a635e5b8d0 | ||
|
|
48a10440fe | ||
|
|
8e3ba88318 | ||
|
|
ab8fccc544 | ||
|
|
8319dc9164 | ||
|
|
6829d3cdea | ||
|
|
3ae4d69110 | ||
|
|
dc665f227e | ||
|
|
a83496568f | ||
|
|
12d25570af | ||
|
|
378c947654 | ||
|
|
bd39a3140e | ||
|
|
7d3ca3dec1 | ||
|
|
1cb556c8f8 | ||
|
|
8c8f96de1c | ||
|
|
318cd87a9a | ||
|
|
5d63d5c2d3 | ||
|
|
7d347fccc6 | ||
|
|
a54ca799b9 | ||
|
|
f5bc1a996f | ||
|
|
8591bc4d01 | ||
|
|
40888c07f3 | ||
|
|
1c965c3788 | ||
|
|
4df690c2a2 | ||
|
|
d6abb61e2b | ||
|
|
3434c862e9 | ||
|
|
ea8af926fa | ||
|
|
c3df48174c |
118
.github/workflows/commit-build.yml
vendored
@@ -1,35 +1,104 @@
|
|||||||
name: "Android CI Actions"
|
name: Android Release
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- "**"
|
- "**"
|
||||||
tags-ignore:
|
tags-ignore:
|
||||||
- "v*" # Ignore tags that start with "v" (e.g. v1.0.0) because they are handled by release-build.yml
|
- "v*"
|
||||||
|
|
||||||
|
pull_request:
|
||||||
|
branches:
|
||||||
|
- "**"
|
||||||
|
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
release:
|
version:
|
||||||
|
name: Create version number
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- 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:
|
with:
|
||||||
java-version: '12'
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- name: Fetch all history for all tags and branches
|
||||||
|
run: |
|
||||||
|
git config remote.origin.url https://x-access-token:${{ secrets.GITHUB_TOKEN }}@github.com/${{ github.repository }}
|
||||||
|
git fetch --prune --depth=10000
|
||||||
|
|
||||||
|
- name: Install GitVersion
|
||||||
|
uses: gittools/actions/gitversion/setup@v0.9.7
|
||||||
|
with:
|
||||||
|
versionSpec: "5.x"
|
||||||
|
|
||||||
|
- name: Use GitVersion
|
||||||
|
id: gitversion
|
||||||
|
uses: gittools/actions/gitversion/execute@v0.9.7
|
||||||
|
|
||||||
|
- name: Create version.txt with nuGetVersion
|
||||||
|
run: echo ${{ steps.gitversion.outputs.nuGetVersion }} > version.txt
|
||||||
|
|
||||||
|
- name: Upload version.txt
|
||||||
|
uses: actions/upload-artifact@v2
|
||||||
|
with:
|
||||||
|
name: gitversion
|
||||||
|
path: version.txt
|
||||||
|
|
||||||
|
build:
|
||||||
|
name: Create Android Build
|
||||||
|
needs: version
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
|
||||||
|
- name: Get version.txt
|
||||||
|
uses: actions/download-artifact@v2
|
||||||
|
with:
|
||||||
|
name: gitversion
|
||||||
|
|
||||||
|
- name: Create new file without newline char from version.txt
|
||||||
|
run: tr -d '\n' < version.txt > version1.txt
|
||||||
|
|
||||||
|
- name: Read version
|
||||||
|
id: version
|
||||||
|
uses: juliangruber/read-file-action@v1
|
||||||
|
with:
|
||||||
|
path: version1.txt
|
||||||
|
|
||||||
|
- name: Update version in YAML
|
||||||
|
run: sed -i 's/99.99.99+99/${{ steps.version.outputs.content }}+${{ github.run_number }}/g' pubspec.yaml
|
||||||
|
|
||||||
|
- name: Download Android keystore
|
||||||
|
id: android_keystore
|
||||||
|
uses: timheuer/base64-to-file@v1.0.3
|
||||||
|
with:
|
||||||
|
fileName: upload-keystore.jks
|
||||||
|
encodedString: ${{ secrets.SIGNING_KEYSTORE }}
|
||||||
|
|
||||||
|
- name: Create key.properties
|
||||||
|
run: |
|
||||||
|
echo "storeFile=${{ steps.android_keystore.outputs.filePath }}" > android/key.properties
|
||||||
|
echo "storePassword=${{ secrets.SIGNING_KEYSTORE_PASSWORD }}" >> android/key.properties
|
||||||
|
echo "keyPassword=${{ secrets.SIGNING_KEY_PASSWORD }}" >> android/key.properties
|
||||||
|
echo "keyAlias=${{ secrets.SIGNING_KEY_ALIAS }}" >> android/key.properties
|
||||||
|
|
||||||
|
- uses: actions/setup-java@v3
|
||||||
|
with:
|
||||||
distribution: 'zulu'
|
distribution: 'zulu'
|
||||||
|
java-version: "12.x"
|
||||||
|
cache: gradle
|
||||||
|
|
||||||
- uses: subosito/flutter-action@v2
|
- uses: subosito/flutter-action@v2
|
||||||
with:
|
with:
|
||||||
channel: 'stable'
|
channel: 'stable'
|
||||||
- name: Set environment variables
|
cache: true
|
||||||
run: echo $SECRETS | base64 -d > lib/utils/environment.dart
|
|
||||||
env:
|
- name: Get dependencies
|
||||||
SECRETS: ${{ secrets.SECRETS }}
|
|
||||||
- name: Set up Flutter
|
|
||||||
run: flutter pub get
|
run: flutter pub get
|
||||||
|
|
||||||
- name: Generate files with Builder
|
- name: Generate files with Builder
|
||||||
run: flutter packages pub run build_runner build --delete-conflicting-outputs
|
run: flutter packages pub run build_runner build --delete-conflicting-outputs
|
||||||
|
|
||||||
- name: Build with Flutter
|
- name: Build with Flutter
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
@@ -37,6 +106,7 @@ jobs:
|
|||||||
SIGNING_KEY_PASSWORD: ${{ secrets.SIGNING_KEY_PASSWORD }}
|
SIGNING_KEY_PASSWORD: ${{ secrets.SIGNING_KEY_PASSWORD }}
|
||||||
SIGNING_STORE_PASSWORD: ${{ secrets.SIGNING_KEYSTORE_PASSWORD }}
|
SIGNING_STORE_PASSWORD: ${{ secrets.SIGNING_KEYSTORE_PASSWORD }}
|
||||||
run: flutter build apk
|
run: flutter build apk
|
||||||
|
|
||||||
- name: Sign APK
|
- name: Sign APK
|
||||||
id: sign_apk
|
id: sign_apk
|
||||||
uses: ilharp/sign-android-release@v1
|
uses: ilharp/sign-android-release@v1
|
||||||
@@ -46,10 +116,26 @@ jobs:
|
|||||||
keyStorePassword: ${{ secrets.SIGNING_KEYSTORE_PASSWORD }}
|
keyStorePassword: ${{ secrets.SIGNING_KEYSTORE_PASSWORD }}
|
||||||
keyAlias: ${{ secrets.SIGNING_KEY_ALIAS }}
|
keyAlias: ${{ secrets.SIGNING_KEY_ALIAS }}
|
||||||
keyPassword: ${{ secrets.SIGNING_KEY_PASSWORD }}
|
keyPassword: ${{ secrets.SIGNING_KEY_PASSWORD }}
|
||||||
|
|
||||||
- name: Add version to APK
|
- name: Add version to APK
|
||||||
run: mv ${{ steps.sign_apk.outputs.signedFile }} revanced-manager-${{ env.RELEASE_VERSION }}.apk
|
run: mv ${{ steps.sign_apk.outputs.signedFile }} revanced-manager-${{ env.RELEASE_VERSION }}.apk
|
||||||
|
|
||||||
- name: Upload APK
|
- name: Upload APK
|
||||||
uses: actions/upload-artifact@v2
|
uses: actions/upload-artifact@v3
|
||||||
with:
|
with:
|
||||||
name: revanced-manager-${{ env.RELEASE_VERSION }}
|
name: revanced-manager-${{ env.RELEASE_VERSION }}
|
||||||
path: revanced-manager-${{ env.RELEASE_VERSION }}.apk
|
path: revanced-manager-${{ env.RELEASE_VERSION }}.apk
|
||||||
|
|
||||||
|
# deploy:
|
||||||
|
# name: Deploy Android Build
|
||||||
|
# needs: build
|
||||||
|
# runs-on: ubuntu-latest
|
||||||
|
# steps:
|
||||||
|
# - uses: actions/checkout@v1
|
||||||
|
# - name: Get Android Build from artifacts
|
||||||
|
# uses: actions/download-artifact@v2
|
||||||
|
# with:
|
||||||
|
# name: android-release
|
||||||
|
|
||||||
|
env:
|
||||||
|
ACTIONS_ALLOW_UNSECURE_COMMANDS: true
|
||||||
|
|||||||
2
.github/workflows/crowdin.yml
vendored
@@ -19,7 +19,7 @@ jobs:
|
|||||||
uses: actions/checkout@v3
|
uses: actions/checkout@v3
|
||||||
|
|
||||||
- name: Crowdin
|
- name: Crowdin
|
||||||
uses: crowdin/github-action@1.5.0
|
uses: crowdin/github-action@v1
|
||||||
with:
|
with:
|
||||||
config: crowdin.yml
|
config: crowdin.yml
|
||||||
upload_sources: true
|
upload_sources: true
|
||||||
|
|||||||
8
.github/workflows/pull-request-build.yml
vendored
@@ -20,10 +20,6 @@ jobs:
|
|||||||
- uses: subosito/flutter-action@v2
|
- uses: subosito/flutter-action@v2
|
||||||
with:
|
with:
|
||||||
channel: 'stable'
|
channel: 'stable'
|
||||||
- name: Set environment variables
|
|
||||||
run: echo $SECRETS | base64 -d > lib/utils/environment.dart
|
|
||||||
env:
|
|
||||||
SECRETS: ${{ secrets.SECRETS }}
|
|
||||||
- name: Set up Flutter
|
- name: Set up Flutter
|
||||||
run: flutter pub get
|
run: flutter pub get
|
||||||
- name: Generate files with Builder
|
- name: Generate files with Builder
|
||||||
@@ -35,7 +31,7 @@ jobs:
|
|||||||
- name: Add version to APK
|
- name: Add version to APK
|
||||||
run: mv build/app/outputs/flutter-apk/app-release.apk revanced-manager-${{ env.RELEASE_VERSION }}.apk
|
run: mv build/app/outputs/flutter-apk/app-release.apk revanced-manager-${{ env.RELEASE_VERSION }}.apk
|
||||||
- name: Upload APK
|
- name: Upload APK
|
||||||
uses: actions/upload-artifact@v2
|
uses: actions/upload-artifact@v3
|
||||||
with:
|
with:
|
||||||
name: revanced-manager-${{ env.RELEASE_VERSION }}
|
name: revanced-manager-${{ env.RELEASE_VERSION }}
|
||||||
path: revanced-manager-${{ env.RELEASE_VERSION }}.apk
|
path: revanced-manager-${{ env.RELEASE_VERSION }}.apk
|
||||||
|
|||||||
4
.github/workflows/release-build.yml
vendored
@@ -20,10 +20,6 @@ jobs:
|
|||||||
- uses: subosito/flutter-action@v2
|
- uses: subosito/flutter-action@v2
|
||||||
with:
|
with:
|
||||||
channel: 'stable'
|
channel: 'stable'
|
||||||
- name: Set environment variables
|
|
||||||
run: echo $SECRETS | base64 -d > lib/utils/environment.dart
|
|
||||||
env:
|
|
||||||
SECRETS: ${{ secrets.SECRETS }}
|
|
||||||
- name: Set up Flutter
|
- name: Set up Flutter
|
||||||
run: flutter pub get
|
run: flutter pub get
|
||||||
- name: Generate files with Builder
|
- name: Generate files with Builder
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
The official ReVanced Manager based on Flutter.
|
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).
|
> **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
|
## 🔽 Download
|
||||||
To download the Alpha version of Manager, go [here](https://github.com/revanced/revanced-manager/releases/latest) and install the provided APK file.
|
To download the Alpha version of Manager, go [here](https://github.com/revanced/revanced-manager/releases/latest) and install the provided APK file.
|
||||||
@@ -30,7 +30,7 @@ If you wish to translate ReVanced Manager, we're accepting translations on [Crow
|
|||||||
## 🛠️ Building Manager from source
|
## 🛠️ Building Manager from source
|
||||||
1. Setup flutter environment for your [platform](https://docs.flutter.dev/get-started/install)
|
1. Setup flutter environment for your [platform](https://docs.flutter.dev/get-started/install)
|
||||||
2. Clone the repository locally
|
2. Clone the repository locally
|
||||||
3. Add your github token in gradle.properties like [this](https://github.com/revanced/revanced-documentation/blob/main/docs/revanced-development/2_building_from_source.md)
|
3. Add your github token in gradle.properties like [this](https://github.com/revanced/revanced-manager/blob/docs/docs/5_building-from-source.md)
|
||||||
4. Open the project in terminal
|
4. Open the project in terminal
|
||||||
5. Run `flutter pub get` in terminal
|
5. Run `flutter pub get` in terminal
|
||||||
6. Then `flutter packages pub run build_runner build --delete-conflicting-outputs` (Must be done on each git pull)
|
6. Then `flutter packages pub run build_runner build --delete-conflicting-outputs` (Must be done on each git pull)
|
||||||
|
|||||||
@@ -14,20 +14,149 @@ analyzer:
|
|||||||
- lib/utils/env_class.g.dart
|
- lib/utils/env_class.g.dart
|
||||||
|
|
||||||
linter:
|
linter:
|
||||||
# The lint rules applied to this project can be customized in the
|
|
||||||
# section below to disable rules from the `package:flutter_lints/flutter.yaml`
|
|
||||||
# included above or to enable additional rules. A list of all available lints
|
|
||||||
# and their documentation is published at
|
|
||||||
# https://dart-lang.github.io/linter/lints/index.html.
|
|
||||||
#
|
|
||||||
# Instead of disabling a lint rule for the entire project in the
|
|
||||||
# section below, it can also be suppressed for a single line of code
|
|
||||||
# or a specific dart file by using the `// ignore: name_of_lint` and
|
|
||||||
# `// ignore_for_file: name_of_lint` syntax on the line or in the file
|
|
||||||
# producing the lint.
|
|
||||||
rules:
|
rules:
|
||||||
# avoid_print: false # Uncomment to disable the `avoid_print` rule
|
- always_declare_return_types
|
||||||
# prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule
|
- require_trailing_commas
|
||||||
|
- always_put_control_body_on_new_line
|
||||||
# Additional information about this file can be found at
|
- always_require_non_null_named_parameters
|
||||||
# https://dart.dev/guides/language/analysis-options
|
- always_use_package_imports # we do this commonly
|
||||||
|
- annotate_overrides
|
||||||
|
- avoid_bool_literals_in_conditional_expressions
|
||||||
|
- avoid_double_and_int_checks
|
||||||
|
- avoid_empty_else
|
||||||
|
- avoid_equals_and_hash_code_on_mutable_classes
|
||||||
|
- avoid_escaping_inner_quotes
|
||||||
|
- avoid_field_initializers_in_const_classes
|
||||||
|
- avoid_function_literals_in_foreach_calls
|
||||||
|
- avoid_implementing_value_types
|
||||||
|
- avoid_init_to_null
|
||||||
|
- avoid_js_rounded_ints
|
||||||
|
- avoid_null_checks_in_equality_operators
|
||||||
|
- avoid_print
|
||||||
|
- avoid_redundant_argument_values
|
||||||
|
- avoid_relative_lib_imports
|
||||||
|
- avoid_renaming_method_parameters
|
||||||
|
- avoid_return_types_on_setters
|
||||||
|
- avoid_returning_null
|
||||||
|
- avoid_returning_null_for_future
|
||||||
|
- avoid_returning_null_for_void
|
||||||
|
- avoid_setters_without_getters
|
||||||
|
- avoid_shadowing_type_parameters
|
||||||
|
- avoid_single_cascade_in_expression_statements
|
||||||
|
- avoid_type_to_string
|
||||||
|
- avoid_types_as_parameter_names
|
||||||
|
- avoid_unnecessary_containers
|
||||||
|
- avoid_void_async
|
||||||
|
- avoid_web_libraries_in_flutter # we use web libraries in web-specific code, and our tests prevent us from using them elsewhere
|
||||||
|
- await_only_futures
|
||||||
|
- camel_case_extensions
|
||||||
|
- camel_case_types
|
||||||
|
- cancel_subscriptions
|
||||||
|
- cast_nullable_to_non_nullable
|
||||||
|
- close_sinks # not reliable enough
|
||||||
|
- control_flow_in_finally
|
||||||
|
- curly_braces_in_flow_control_structures
|
||||||
|
- depend_on_referenced_packages
|
||||||
|
- deprecated_consistency
|
||||||
|
- directives_ordering
|
||||||
|
- empty_catches
|
||||||
|
- empty_constructor_bodies
|
||||||
|
- empty_statements
|
||||||
|
- eol_at_end_of_file
|
||||||
|
- exhaustive_cases
|
||||||
|
- file_names
|
||||||
|
- flutter_style_todos
|
||||||
|
- hash_and_equals
|
||||||
|
- implementation_imports
|
||||||
|
- iterable_contains_unrelated_type
|
||||||
|
- leading_newlines_in_multiline_strings
|
||||||
|
- library_names
|
||||||
|
- library_prefixes
|
||||||
|
- library_private_types_in_public_api
|
||||||
|
- list_remove_unrelated_type
|
||||||
|
- missing_whitespace_between_adjacent_strings
|
||||||
|
- no_adjacent_strings_in_list
|
||||||
|
- no_duplicate_case_values
|
||||||
|
- no_logic_in_create_state
|
||||||
|
- non_constant_identifier_names
|
||||||
|
- noop_primitive_operations
|
||||||
|
- null_check_on_nullable_type_parameter
|
||||||
|
- null_closures
|
||||||
|
- overridden_fields
|
||||||
|
- package_api_docs
|
||||||
|
- package_names
|
||||||
|
- package_prefixed_library_names
|
||||||
|
- prefer_adjacent_string_concatenation
|
||||||
|
- prefer_asserts_in_initializer_lists
|
||||||
|
- prefer_collection_literals
|
||||||
|
- prefer_conditional_assignment
|
||||||
|
- prefer_const_constructors
|
||||||
|
- prefer_const_constructors_in_immutables
|
||||||
|
- prefer_const_declarations
|
||||||
|
- prefer_const_literals_to_create_immutables
|
||||||
|
- prefer_contains
|
||||||
|
- prefer_equal_for_default_values
|
||||||
|
- prefer_final_fields
|
||||||
|
- prefer_final_in_for_each
|
||||||
|
- prefer_final_locals
|
||||||
|
- prefer_for_elements_to_map_fromIterable
|
||||||
|
- prefer_foreach
|
||||||
|
- prefer_function_declarations_over_variables
|
||||||
|
- prefer_generic_function_type_aliases
|
||||||
|
- prefer_if_elements_to_conditional_expressions
|
||||||
|
- prefer_if_null_operators
|
||||||
|
- prefer_initializing_formals
|
||||||
|
- prefer_inlined_adds
|
||||||
|
- prefer_interpolation_to_compose_strings
|
||||||
|
- prefer_is_empty
|
||||||
|
- prefer_is_not_empty
|
||||||
|
- prefer_is_not_operator
|
||||||
|
- prefer_iterable_whereType
|
||||||
|
- prefer_mixin # Has false positives, see https://github.com/dart-lang/linter/issues/3018
|
||||||
|
- prefer_null_aware_method_calls # "call()" is confusing to people new to the language since it's not documented anywhere
|
||||||
|
- prefer_null_aware_operators
|
||||||
|
- prefer_single_quotes
|
||||||
|
- prefer_spread_collections
|
||||||
|
- prefer_typing_uninitialized_variables
|
||||||
|
- prefer_void_to_null
|
||||||
|
- provide_deprecation_message
|
||||||
|
- recursive_getters
|
||||||
|
- sized_box_for_whitespace
|
||||||
|
- slash_for_doc_comments
|
||||||
|
- sort_child_properties_last
|
||||||
|
- sort_constructors_first
|
||||||
|
- sort_unnamed_constructors_first
|
||||||
|
- test_types_in_equals
|
||||||
|
- throw_in_finally
|
||||||
|
- tighten_type_of_initializing_formals
|
||||||
|
- type_init_formals
|
||||||
|
- unnecessary_brace_in_string_interps
|
||||||
|
- unnecessary_const
|
||||||
|
- unnecessary_getters_setters
|
||||||
|
- unnecessary_new
|
||||||
|
- unnecessary_null_aware_assignments
|
||||||
|
- unnecessary_null_checks
|
||||||
|
- unnecessary_null_in_if_null_operators
|
||||||
|
- unnecessary_nullable_for_final_variable_declarations
|
||||||
|
- unnecessary_overrides
|
||||||
|
- unnecessary_parenthesis
|
||||||
|
- unnecessary_statements
|
||||||
|
- unnecessary_string_escapes
|
||||||
|
- unnecessary_string_interpolations
|
||||||
|
- unnecessary_this
|
||||||
|
- unrelated_type_equality_checks
|
||||||
|
- unsafe_html
|
||||||
|
- use_build_context_synchronously
|
||||||
|
- use_full_hex_values_for_flutter_colors
|
||||||
|
- use_function_type_syntax_for_parameters
|
||||||
|
- use_if_null_to_convert_nulls_to_bools
|
||||||
|
- use_is_even_rather_than_modulo
|
||||||
|
- use_key_in_widget_constructors
|
||||||
|
- use_late_for_private_fields_and_variables
|
||||||
|
- use_named_constants
|
||||||
|
- use_raw_strings
|
||||||
|
- use_rethrow_when_possible
|
||||||
|
- use_setters_to_change_properties
|
||||||
|
- use_test_throws_matchers
|
||||||
|
- valid_regexps
|
||||||
|
- void_checks
|
||||||
|
|||||||
@@ -71,7 +71,7 @@ dependencies {
|
|||||||
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
|
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
|
||||||
|
|
||||||
// ReVanced
|
// ReVanced
|
||||||
implementation "app.revanced:revanced-patcher:6.4.2"
|
implementation "app.revanced:revanced-patcher:7.0.0"
|
||||||
|
|
||||||
// Signing & aligning
|
// Signing & aligning
|
||||||
implementation("org.bouncycastle:bcpkix-jdk15on:1.70")
|
implementation("org.bouncycastle:bcpkix-jdk15on:1.70")
|
||||||
|
|||||||
@@ -17,7 +17,8 @@
|
|||||||
android:name="${applicationName}"
|
android:name="${applicationName}"
|
||||||
android:icon="@mipmap/ic_launcher"
|
android:icon="@mipmap/ic_launcher"
|
||||||
android:largeHeap="true"
|
android:largeHeap="true"
|
||||||
android:extractNativeLibs="true">
|
android:extractNativeLibs="true"
|
||||||
|
android:enableOnBackInvokedCallback="true">
|
||||||
<activity
|
<activity
|
||||||
android:name=".MainActivity"
|
android:name=".MainActivity"
|
||||||
android:exported="true"
|
android:exported="true"
|
||||||
@@ -28,8 +29,7 @@
|
|||||||
android:windowSoftInputMode="adjustResize">
|
android:windowSoftInputMode="adjustResize">
|
||||||
<meta-data
|
<meta-data
|
||||||
android:name="io.flutter.embedding.android.NormalTheme"
|
android:name="io.flutter.embedding.android.NormalTheme"
|
||||||
android:resource="@style/NormalTheme"
|
android:resource="@style/NormalTheme"/>
|
||||||
/>
|
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.MAIN"/>
|
<action android:name="android.intent.action.MAIN"/>
|
||||||
<category android:name="android.intent.category.LAUNCHER"/>
|
<category android:name="android.intent.category.LAUNCHER"/>
|
||||||
|
|||||||
@@ -43,7 +43,6 @@ class MainActivity : FlutterActivity() {
|
|||||||
val integrationsPath = call.argument<String>("integrationsPath")
|
val integrationsPath = call.argument<String>("integrationsPath")
|
||||||
val selectedPatches = call.argument<List<String>>("selectedPatches")
|
val selectedPatches = call.argument<List<String>>("selectedPatches")
|
||||||
val cacheDirPath = call.argument<String>("cacheDirPath")
|
val cacheDirPath = call.argument<String>("cacheDirPath")
|
||||||
val mergeIntegrations = call.argument<Boolean>("mergeIntegrations")
|
|
||||||
val keyStoreFilePath = call.argument<String>("keyStoreFilePath")
|
val keyStoreFilePath = call.argument<String>("keyStoreFilePath")
|
||||||
if (patchBundleFilePath != null &&
|
if (patchBundleFilePath != null &&
|
||||||
originalFilePath != null &&
|
originalFilePath != null &&
|
||||||
@@ -53,7 +52,6 @@ class MainActivity : FlutterActivity() {
|
|||||||
integrationsPath != null &&
|
integrationsPath != null &&
|
||||||
selectedPatches != null &&
|
selectedPatches != null &&
|
||||||
cacheDirPath != null &&
|
cacheDirPath != null &&
|
||||||
mergeIntegrations != null &&
|
|
||||||
keyStoreFilePath != null
|
keyStoreFilePath != null
|
||||||
) {
|
) {
|
||||||
runPatcher(
|
runPatcher(
|
||||||
@@ -66,7 +64,6 @@ class MainActivity : FlutterActivity() {
|
|||||||
integrationsPath,
|
integrationsPath,
|
||||||
selectedPatches,
|
selectedPatches,
|
||||||
cacheDirPath,
|
cacheDirPath,
|
||||||
mergeIntegrations,
|
|
||||||
keyStoreFilePath
|
keyStoreFilePath
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
@@ -88,7 +85,6 @@ class MainActivity : FlutterActivity() {
|
|||||||
integrationsPath: String,
|
integrationsPath: String,
|
||||||
selectedPatches: List<String>,
|
selectedPatches: List<String>,
|
||||||
cacheDirPath: String,
|
cacheDirPath: String,
|
||||||
mergeIntegrations: Boolean,
|
|
||||||
keyStoreFilePath: String
|
keyStoreFilePath: String
|
||||||
) {
|
) {
|
||||||
val originalFile = File(originalFilePath)
|
val originalFile = File(originalFilePath)
|
||||||
@@ -139,19 +135,17 @@ class MainActivity : FlutterActivity() {
|
|||||||
mapOf("progress" to 0.3, "header" to "", "log" to "")
|
mapOf("progress" to 0.3, "header" to "", "log" to "")
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
if (mergeIntegrations) {
|
handler.post {
|
||||||
handler.post {
|
installerChannel.invokeMethod(
|
||||||
installerChannel.invokeMethod(
|
"update",
|
||||||
"update",
|
mapOf(
|
||||||
mapOf(
|
"progress" to 0.4,
|
||||||
"progress" to 0.4,
|
"header" to "Merging integrations...",
|
||||||
"header" to "Merging integrations...",
|
"log" to "Merging integrations"
|
||||||
"log" to "Merging integrations"
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
}
|
)
|
||||||
patcher.addFiles(listOf(integrations)) {}
|
|
||||||
}
|
}
|
||||||
|
patcher.addIntegrations(listOf(integrations)) {}
|
||||||
|
|
||||||
handler.post {
|
handler.post {
|
||||||
installerChannel.invokeMethod(
|
installerChannel.invokeMethod(
|
||||||
|
|||||||
|
After Width: | Height: | Size: 2.1 KiB |
|
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.7 KiB |
|
After Width: | Height: | Size: 1.5 KiB |
|
Before Width: | Height: | Size: 814 B After Width: | Height: | Size: 1.1 KiB |
|
After Width: | Height: | Size: 2.7 KiB |
|
Before Width: | Height: | Size: 2.0 KiB After Width: | Height: | Size: 2.4 KiB |
|
After Width: | Height: | Size: 4.1 KiB |
|
Before Width: | Height: | Size: 3.4 KiB After Width: | Height: | Size: 3.7 KiB |
|
After Width: | Height: | Size: 5.5 KiB |
|
Before Width: | Height: | Size: 5.4 KiB After Width: | Height: | Size: 4.9 KiB |
|
After Width: | Height: | Size: 9.2 KiB |
@@ -1,5 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
<background android:drawable="@color/ic_launcher_background"/>
|
<background android:drawable="@drawable/ic_launcher_background"/>
|
||||||
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
|
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
|
||||||
|
<monochrome android:drawable="@drawable/ic_launcher_monochrome"/>
|
||||||
</adaptive-icon>
|
</adaptive-icon>
|
||||||
|
|||||||
@@ -1,4 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<resources>
|
|
||||||
<color name="ic_launcher_background">#1B1B1B</color>
|
|
||||||
</resources>
|
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
buildscript {
|
buildscript {
|
||||||
ext.cronetVersion = '102.5005.125'
|
ext.cronetVersion = '102.5005.125'
|
||||||
ext.kotlin_version = '1.8.0'
|
ext.kotlin_version = '1.7.10'
|
||||||
repositories {
|
repositories {
|
||||||
google()
|
google()
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
#Mon May 09 12:07:41 MSK 2022
|
|
||||||
distributionBase=GRADLE_USER_HOME
|
distributionBase=GRADLE_USER_HOME
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-rc-1-bin.zip
|
|
||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
zipStorePath=wrapper/dists
|
distributionUrl=https\://services.gradle.org/distributions/gradle-7.6.1-bin.zip
|
||||||
|
networkTimeout=10000
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
|
zipStorePath=wrapper/dists
|
||||||
|
|||||||
@@ -79,8 +79,11 @@
|
|||||||
"searchBarHint": "Search patches",
|
"searchBarHint": "Search patches",
|
||||||
"doneButton": "Done",
|
"doneButton": "Done",
|
||||||
"recommended": "Recommended",
|
"recommended": "Recommended",
|
||||||
|
"recommendedTooltip": "Select all recommended patches",
|
||||||
"all": "All",
|
"all": "All",
|
||||||
|
"allTooltip": "Select all patches",
|
||||||
"none": "None",
|
"none": "None",
|
||||||
|
"noneTooltip": "Deselect all patches",
|
||||||
"loadPatchesSelection": "Load patches selection",
|
"loadPatchesSelection": "Load patches selection",
|
||||||
"noSavedPatches": "No saved patches for the selected app.\nPress Done to save current selection.",
|
"noSavedPatches": "No saved patches for the selected app.\nPress Done to save current selection.",
|
||||||
"noPatchesFound": "No patches found for the selected app",
|
"noPatchesFound": "No patches found for the selected app",
|
||||||
@@ -145,8 +148,6 @@
|
|||||||
"exportSectionTitle": "Import & export",
|
"exportSectionTitle": "Import & export",
|
||||||
"aboutLabel": "About",
|
"aboutLabel": "About",
|
||||||
"snackbarMessage": "Copied to clipboard",
|
"snackbarMessage": "Copied to clipboard",
|
||||||
"sentryLabel": "Sentry logging",
|
|
||||||
"sentryHint": "Send anonymous logs to help us improve ReVanced Manager",
|
|
||||||
"restartAppForChanges": "Restart the app to apply changes",
|
"restartAppForChanges": "Restart the app to apply changes",
|
||||||
"deleteKeystoreLabel": "Delete keystore",
|
"deleteKeystoreLabel": "Delete keystore",
|
||||||
"deleteKeystoreHint": "Delete the keystore used to sign the app",
|
"deleteKeystoreHint": "Delete the keystore used to sign the app",
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import 'package:revanced_manager/services/crowdin_api.dart';
|
|
||||||
import 'package:revanced_manager/services/github_api.dart';
|
import 'package:revanced_manager/services/github_api.dart';
|
||||||
import 'package:revanced_manager/services/manager_api.dart';
|
import 'package:revanced_manager/services/manager_api.dart';
|
||||||
import 'package:revanced_manager/services/patcher_api.dart';
|
import 'package:revanced_manager/services/patcher_api.dart';
|
||||||
@@ -38,7 +37,6 @@ import 'package:stacked_services/stacked_services.dart';
|
|||||||
LazySingleton(classType: PatcherAPI),
|
LazySingleton(classType: PatcherAPI),
|
||||||
LazySingleton(classType: RevancedAPI),
|
LazySingleton(classType: RevancedAPI),
|
||||||
LazySingleton(classType: GithubAPI),
|
LazySingleton(classType: GithubAPI),
|
||||||
LazySingleton(classType: CrowdinAPI),
|
|
||||||
LazySingleton(classType: Toast),
|
LazySingleton(classType: Toast),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,18 +1,17 @@
|
|||||||
|
import 'dart:developer';
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_i18n/flutter_i18n.dart';
|
import 'package:flutter_i18n/flutter_i18n.dart';
|
||||||
import 'package:flutter_localizations/flutter_localizations.dart';
|
import 'package:flutter_localizations/flutter_localizations.dart';
|
||||||
import 'package:revanced_manager/app/app.locator.dart';
|
import 'package:revanced_manager/app/app.locator.dart';
|
||||||
import 'package:revanced_manager/services/crowdin_api.dart';
|
|
||||||
import 'package:revanced_manager/services/github_api.dart';
|
import 'package:revanced_manager/services/github_api.dart';
|
||||||
import 'package:revanced_manager/services/manager_api.dart';
|
import 'package:revanced_manager/services/manager_api.dart';
|
||||||
import 'package:revanced_manager/services/patcher_api.dart';
|
import 'package:revanced_manager/services/patcher_api.dart';
|
||||||
import 'package:revanced_manager/services/revanced_api.dart';
|
import 'package:revanced_manager/services/revanced_api.dart';
|
||||||
import 'package:revanced_manager/ui/theme/dynamic_theme_builder.dart';
|
import 'package:revanced_manager/ui/theme/dynamic_theme_builder.dart';
|
||||||
import 'package:revanced_manager/ui/views/navigation/navigation_view.dart';
|
import 'package:revanced_manager/ui/views/navigation/navigation_view.dart';
|
||||||
import 'package:revanced_manager/utils/environment.dart';
|
|
||||||
import 'package:shared_preferences/shared_preferences.dart';
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
import 'package:stacked_themes/stacked_themes.dart';
|
import 'package:stacked_themes/stacked_themes.dart';
|
||||||
import 'package:sentry_flutter/sentry_flutter.dart';
|
|
||||||
import 'package:timezone/data/latest.dart' as tz;
|
import 'package:timezone/data/latest.dart' as tz;
|
||||||
|
|
||||||
late SharedPreferences prefs;
|
late SharedPreferences prefs;
|
||||||
@@ -21,38 +20,14 @@ Future main() async {
|
|||||||
await setupLocator();
|
await setupLocator();
|
||||||
WidgetsFlutterBinding.ensureInitialized();
|
WidgetsFlutterBinding.ensureInitialized();
|
||||||
await locator<ManagerAPI>().initialize();
|
await locator<ManagerAPI>().initialize();
|
||||||
String apiUrl = locator<ManagerAPI>().getApiUrl();
|
final String apiUrl = locator<ManagerAPI>().getApiUrl();
|
||||||
await locator<RevancedAPI>().initialize(apiUrl);
|
await locator<RevancedAPI>().initialize(apiUrl);
|
||||||
await locator<CrowdinAPI>().initialize();
|
final String repoUrl = locator<ManagerAPI>().getRepoUrl();
|
||||||
bool isSentryEnabled = locator<ManagerAPI>().isSentryEnabled();
|
|
||||||
String repoUrl = locator<ManagerAPI>().getRepoUrl();
|
|
||||||
locator<GithubAPI>().initialize(repoUrl);
|
locator<GithubAPI>().initialize(repoUrl);
|
||||||
await locator<PatcherAPI>().initialize();
|
await locator<PatcherAPI>().initialize();
|
||||||
tz.initializeTimeZones();
|
tz.initializeTimeZones();
|
||||||
prefs = await SharedPreferences.getInstance();
|
prefs = await SharedPreferences.getInstance();
|
||||||
|
|
||||||
await SentryFlutter.init(
|
|
||||||
(options) {
|
|
||||||
options
|
|
||||||
..dsn = isSentryEnabled ? Environment.sentryDSN : ''
|
|
||||||
..environment = 'alpha'
|
|
||||||
..release = '0.1'
|
|
||||||
..tracesSampleRate = 1.0
|
|
||||||
..anrEnabled = true
|
|
||||||
..enableOutOfMemoryTracking = true
|
|
||||||
..sampleRate = isSentryEnabled ? 1.0 : 0.0
|
|
||||||
..beforeSend = (event, hint) {
|
|
||||||
if (isSentryEnabled) {
|
|
||||||
return event;
|
|
||||||
} else {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
} as BeforeSendCallback?;
|
|
||||||
},
|
|
||||||
appRunner: () {
|
|
||||||
runApp(const MyApp());
|
|
||||||
},
|
|
||||||
);
|
|
||||||
runApp(const MyApp());
|
runApp(const MyApp());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -65,7 +40,7 @@ class MyApp extends StatelessWidget {
|
|||||||
// String replaceLocale = rawLocale.replaceAll('_', '-');
|
// String replaceLocale = rawLocale.replaceAll('_', '-');
|
||||||
// List<String> localeList = replaceLocale.split('-');
|
// List<String> localeList = replaceLocale.split('-');
|
||||||
// Locale locale = Locale(localeList[0], localeList[1]);
|
// Locale locale = Locale(localeList[0], localeList[1]);
|
||||||
Locale locale = const Locale('en', 'US');
|
const Locale locale = Locale('en', 'US');
|
||||||
|
|
||||||
return DynamicThemeBuilder(
|
return DynamicThemeBuilder(
|
||||||
title: 'ReVanced Manager',
|
title: 'ReVanced Manager',
|
||||||
@@ -79,8 +54,9 @@ class MyApp extends StatelessWidget {
|
|||||||
useCountryCode: true,
|
useCountryCode: true,
|
||||||
),
|
),
|
||||||
missingTranslationHandler: (key, locale) {
|
missingTranslationHandler: (key, locale) {
|
||||||
print(
|
log(
|
||||||
'--> Missing translation: key: $key, languageCode: ${locale?.languageCode}');
|
'--> Missing translation: key: $key, languageCode: ${locale?.languageCode}',
|
||||||
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
GlobalMaterialLocalizations.delegate,
|
GlobalMaterialLocalizations.delegate,
|
||||||
|
|||||||
@@ -5,13 +5,6 @@ part 'patch.g.dart';
|
|||||||
|
|
||||||
@JsonSerializable()
|
@JsonSerializable()
|
||||||
class Patch {
|
class Patch {
|
||||||
final String name;
|
|
||||||
final String description;
|
|
||||||
final String version;
|
|
||||||
final bool excluded;
|
|
||||||
final List<String> dependencies;
|
|
||||||
final List<Package> compatiblePackages;
|
|
||||||
|
|
||||||
Patch({
|
Patch({
|
||||||
required this.name,
|
required this.name,
|
||||||
required this.description,
|
required this.description,
|
||||||
@@ -22,6 +15,12 @@ class Patch {
|
|||||||
});
|
});
|
||||||
|
|
||||||
factory Patch.fromJson(Map<String, dynamic> json) => _$PatchFromJson(json);
|
factory Patch.fromJson(Map<String, dynamic> json) => _$PatchFromJson(json);
|
||||||
|
final String name;
|
||||||
|
final String description;
|
||||||
|
final String version;
|
||||||
|
final bool excluded;
|
||||||
|
final List<String> dependencies;
|
||||||
|
final List<Package> compatiblePackages;
|
||||||
|
|
||||||
Map<String, dynamic> toJson() => _$PatchToJson(this);
|
Map<String, dynamic> toJson() => _$PatchToJson(this);
|
||||||
|
|
||||||
@@ -37,9 +36,6 @@ class Patch {
|
|||||||
|
|
||||||
@JsonSerializable()
|
@JsonSerializable()
|
||||||
class Package {
|
class Package {
|
||||||
final String name;
|
|
||||||
final List<String> versions;
|
|
||||||
|
|
||||||
Package({
|
Package({
|
||||||
required this.name,
|
required this.name,
|
||||||
required this.versions,
|
required this.versions,
|
||||||
@@ -47,6 +43,8 @@ class Package {
|
|||||||
|
|
||||||
factory Package.fromJson(Map<String, dynamic> json) =>
|
factory Package.fromJson(Map<String, dynamic> json) =>
|
||||||
_$PackageFromJson(json);
|
_$PackageFromJson(json);
|
||||||
|
final String name;
|
||||||
|
final List<String> versions;
|
||||||
|
|
||||||
Map toJson() => _$PackageToJson(this);
|
Map toJson() => _$PackageToJson(this);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,23 +6,6 @@ part 'patched_application.g.dart';
|
|||||||
|
|
||||||
@JsonSerializable()
|
@JsonSerializable()
|
||||||
class PatchedApplication {
|
class PatchedApplication {
|
||||||
String name;
|
|
||||||
String packageName;
|
|
||||||
String originalPackageName;
|
|
||||||
String version;
|
|
||||||
final String apkFilePath;
|
|
||||||
@JsonKey(
|
|
||||||
fromJson: decodeBase64,
|
|
||||||
toJson: encodeBase64,
|
|
||||||
)
|
|
||||||
Uint8List icon;
|
|
||||||
DateTime patchDate;
|
|
||||||
bool isRooted;
|
|
||||||
bool isFromStorage;
|
|
||||||
bool hasUpdates;
|
|
||||||
List<String> appliedPatches;
|
|
||||||
List<String> changelog;
|
|
||||||
|
|
||||||
PatchedApplication({
|
PatchedApplication({
|
||||||
required this.name,
|
required this.name,
|
||||||
required this.packageName,
|
required this.packageName,
|
||||||
@@ -40,6 +23,22 @@ class PatchedApplication {
|
|||||||
|
|
||||||
factory PatchedApplication.fromJson(Map<String, dynamic> json) =>
|
factory PatchedApplication.fromJson(Map<String, dynamic> json) =>
|
||||||
_$PatchedApplicationFromJson(json);
|
_$PatchedApplicationFromJson(json);
|
||||||
|
String name;
|
||||||
|
String packageName;
|
||||||
|
String originalPackageName;
|
||||||
|
String version;
|
||||||
|
final String apkFilePath;
|
||||||
|
@JsonKey(
|
||||||
|
fromJson: decodeBase64,
|
||||||
|
toJson: encodeBase64,
|
||||||
|
)
|
||||||
|
Uint8List icon;
|
||||||
|
DateTime patchDate;
|
||||||
|
bool isRooted;
|
||||||
|
bool isFromStorage;
|
||||||
|
bool hasUpdates;
|
||||||
|
List<String> appliedPatches;
|
||||||
|
List<String> changelog;
|
||||||
|
|
||||||
Map<String, dynamic> toJson() => _$PatchedApplicationToJson(this);
|
Map<String, dynamic> toJson() => _$PatchedApplicationToJson(this);
|
||||||
|
|
||||||
|
|||||||
@@ -1,61 +0,0 @@
|
|||||||
import 'package:dio/dio.dart';
|
|
||||||
import 'package:dio_http_cache_lts/dio_http_cache_lts.dart';
|
|
||||||
import 'package:injectable/injectable.dart' hide Environment;
|
|
||||||
import 'package:revanced_manager/utils/environment.dart';
|
|
||||||
import 'package:sentry_dio/sentry_dio.dart';
|
|
||||||
import 'package:sentry_flutter/sentry_flutter.dart';
|
|
||||||
|
|
||||||
@lazySingleton
|
|
||||||
class CrowdinAPI {
|
|
||||||
late Dio _dio = Dio();
|
|
||||||
final DioCacheManager _dioCacheManager = DioCacheManager(CacheConfig());
|
|
||||||
final apiKey = Environment.crowdinKEY;
|
|
||||||
|
|
||||||
Future<void> initialize() async {
|
|
||||||
try {
|
|
||||||
_dio = Dio(BaseOptions(
|
|
||||||
baseUrl: 'https://api.crowdin.com/api/v2',
|
|
||||||
));
|
|
||||||
|
|
||||||
_dio.interceptors.add(_dioCacheManager.interceptor);
|
|
||||||
_dio.addSentry(
|
|
||||||
captureFailedRequests: true,
|
|
||||||
);
|
|
||||||
} on Exception catch (e, s) {
|
|
||||||
await Sentry.captureException(e, stackTrace: s);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> clearAllCache() async {
|
|
||||||
try {
|
|
||||||
await _dioCacheManager.clearAll();
|
|
||||||
} on Exception catch (e, s) {
|
|
||||||
await Sentry.captureException(e, stackTrace: s);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<List> getLanguages() async {
|
|
||||||
try {
|
|
||||||
var response = await _dio.get(
|
|
||||||
'/projects',
|
|
||||||
options: buildCacheOptions(
|
|
||||||
const Duration(hours: 6),
|
|
||||||
maxStale: const Duration(days: 1),
|
|
||||||
options: Options(
|
|
||||||
headers: {
|
|
||||||
'Authorization': 'Bearer $apiKey',
|
|
||||||
},
|
|
||||||
contentType: 'application/json',
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
List targetLanguages =
|
|
||||||
await response.data['data'][0]['data']['targetLanguages'];
|
|
||||||
|
|
||||||
return targetLanguages;
|
|
||||||
} on Exception catch (e, s) {
|
|
||||||
await Sentry.captureException(e, stackTrace: s);
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,13 +1,13 @@
|
|||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:collection/collection.dart';
|
import 'package:collection/collection.dart';
|
||||||
import 'package:dio/dio.dart';
|
import 'package:dio/dio.dart';
|
||||||
import 'package:dio_http_cache_lts/dio_http_cache_lts.dart';
|
import 'package:dio_http_cache_lts/dio_http_cache_lts.dart';
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter_cache_manager/flutter_cache_manager.dart';
|
import 'package:flutter_cache_manager/flutter_cache_manager.dart';
|
||||||
import 'package:injectable/injectable.dart';
|
import 'package:injectable/injectable.dart';
|
||||||
import 'package:revanced_manager/models/patch.dart';
|
import 'package:revanced_manager/models/patch.dart';
|
||||||
import 'package:sentry_flutter/sentry_flutter.dart';
|
|
||||||
import 'package:sentry_dio/sentry_dio.dart';
|
|
||||||
|
|
||||||
@lazySingleton
|
@lazySingleton
|
||||||
class GithubAPI {
|
class GithubAPI {
|
||||||
@@ -28,38 +28,43 @@ class GithubAPI {
|
|||||||
'com.spotify.music': 'spotify',
|
'com.spotify.music': 'spotify',
|
||||||
};
|
};
|
||||||
|
|
||||||
void initialize(String repoUrl) async {
|
Future<void> initialize(String repoUrl) async {
|
||||||
try {
|
try {
|
||||||
_dio = Dio(BaseOptions(
|
_dio = Dio(
|
||||||
baseUrl: repoUrl,
|
BaseOptions(
|
||||||
));
|
baseUrl: repoUrl,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
_dio.interceptors.add(_dioCacheManager.interceptor);
|
_dio.interceptors.add(_dioCacheManager.interceptor);
|
||||||
_dio.addSentry(
|
} on Exception catch (e) {
|
||||||
captureFailedRequests: true,
|
if (kDebugMode) {
|
||||||
);
|
print(e);
|
||||||
} on Exception catch (e, s) {
|
}
|
||||||
await Sentry.captureException(e, stackTrace: s);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> clearAllCache() async {
|
Future<void> clearAllCache() async {
|
||||||
try {
|
try {
|
||||||
await _dioCacheManager.clearAll();
|
await _dioCacheManager.clearAll();
|
||||||
} on Exception catch (e, s) {
|
} on Exception catch (e) {
|
||||||
await Sentry.captureException(e, stackTrace: s);
|
if (kDebugMode) {
|
||||||
|
print(e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<Map<String, dynamic>?> getLatestRelease(String repoName) async {
|
Future<Map<String, dynamic>?> getLatestRelease(String repoName) async {
|
||||||
try {
|
try {
|
||||||
var response = await _dio.get(
|
final response = await _dio.get(
|
||||||
'/repos/$repoName/releases',
|
'/repos/$repoName/releases',
|
||||||
options: _cacheOptions,
|
options: _cacheOptions,
|
||||||
);
|
);
|
||||||
return response.data[0];
|
return response.data[0];
|
||||||
} on Exception catch (e, s) {
|
} on Exception catch (e) {
|
||||||
await Sentry.captureException(e, stackTrace: s);
|
if (kDebugMode) {
|
||||||
|
print(e);
|
||||||
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -69,10 +74,10 @@ class GithubAPI {
|
|||||||
String repoName,
|
String repoName,
|
||||||
DateTime since,
|
DateTime since,
|
||||||
) async {
|
) async {
|
||||||
String path =
|
final String path =
|
||||||
'src/main/kotlin/app/revanced/patches/${repoAppPath[packageName]}';
|
'src/main/kotlin/app/revanced/patches/${repoAppPath[packageName]}';
|
||||||
try {
|
try {
|
||||||
var response = await _dio.get(
|
final response = await _dio.get(
|
||||||
'/repos/$repoName/commits',
|
'/repos/$repoName/commits',
|
||||||
queryParameters: {
|
queryParameters: {
|
||||||
'path': path,
|
'path': path,
|
||||||
@@ -80,7 +85,7 @@ class GithubAPI {
|
|||||||
},
|
},
|
||||||
options: _cacheOptions,
|
options: _cacheOptions,
|
||||||
);
|
);
|
||||||
List<dynamic> commits = response.data;
|
final List<dynamic> commits = response.data;
|
||||||
return commits
|
return commits
|
||||||
.map(
|
.map(
|
||||||
(commit) => (commit['commit']['message']).split('\n')[0] +
|
(commit) => (commit['commit']['message']).split('\n')[0] +
|
||||||
@@ -89,17 +94,19 @@ class GithubAPI {
|
|||||||
'\n' as String,
|
'\n' as String,
|
||||||
)
|
)
|
||||||
.toList();
|
.toList();
|
||||||
} on Exception catch (e, s) {
|
} on Exception catch (e) {
|
||||||
await Sentry.captureException(e, stackTrace: s);
|
if (kDebugMode) {
|
||||||
return List.empty();
|
print(e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<File?> getLatestReleaseFile(String extension, String repoName) async {
|
Future<File?> getLatestReleaseFile(String extension, String repoName) async {
|
||||||
try {
|
try {
|
||||||
Map<String, dynamic>? release = await getLatestRelease(repoName);
|
final Map<String, dynamic>? release = await getLatestRelease(repoName);
|
||||||
if (release != null) {
|
if (release != null) {
|
||||||
Map<String, dynamic>? asset =
|
final Map<String, dynamic>? asset =
|
||||||
(release['assets'] as List<dynamic>).firstWhereOrNull(
|
(release['assets'] as List<dynamic>).firstWhereOrNull(
|
||||||
(asset) => (asset['name'] as String).endsWith(extension),
|
(asset) => (asset['name'] as String).endsWith(extension),
|
||||||
);
|
);
|
||||||
@@ -109,9 +116,10 @@ class GithubAPI {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} on Exception catch (e, s) {
|
} on Exception catch (e) {
|
||||||
await Sentry.captureException(e, stackTrace: s);
|
if (kDebugMode) {
|
||||||
return null;
|
print(e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -119,29 +127,34 @@ class GithubAPI {
|
|||||||
Future<List<Patch>> getPatches(String repoName) async {
|
Future<List<Patch>> getPatches(String repoName) async {
|
||||||
List<Patch> patches = [];
|
List<Patch> patches = [];
|
||||||
try {
|
try {
|
||||||
File? f = await getLatestReleaseFile('.json', repoName);
|
final File? f = await getLatestReleaseFile('.json', repoName);
|
||||||
if (f != null) {
|
if (f != null) {
|
||||||
List<dynamic> list = jsonDecode(f.readAsStringSync());
|
final List<dynamic> list = jsonDecode(f.readAsStringSync());
|
||||||
patches = list.map((patch) => Patch.fromJson(patch)).toList();
|
patches = list.map((patch) => Patch.fromJson(patch)).toList();
|
||||||
}
|
}
|
||||||
} on Exception catch (e, s) {
|
} on Exception catch (e) {
|
||||||
await Sentry.captureException(e, stackTrace: s);
|
if (kDebugMode) {
|
||||||
return List.empty();
|
print(e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return patches;
|
return patches;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<String> getLastestReleaseVersion(String repoName) async {
|
Future<String> getLastestReleaseVersion(String repoName) async {
|
||||||
try {
|
try {
|
||||||
Map<String, dynamic>? release = await getLatestRelease(repoName);
|
final Map<String, dynamic>? release = await getLatestRelease(repoName);
|
||||||
if (release != null) {
|
if (release != null) {
|
||||||
return release['tag_name'];
|
return release['tag_name'];
|
||||||
} else {
|
} else {
|
||||||
return 'Unknown';
|
return 'Unknown';
|
||||||
}
|
}
|
||||||
} on Exception catch (e, s) {
|
} on Exception catch (e) {
|
||||||
await Sentry.captureException(e, stackTrace: s);
|
if (kDebugMode) {
|
||||||
return '';
|
print(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
return 'Unknown';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
import 'package:device_apps/device_apps.dart';
|
import 'package:device_apps/device_apps.dart';
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:injectable/injectable.dart';
|
import 'package:injectable/injectable.dart';
|
||||||
import 'package:package_info_plus/package_info_plus.dart';
|
import 'package:package_info_plus/package_info_plus.dart';
|
||||||
import 'package:path_provider/path_provider.dart';
|
import 'package:path_provider/path_provider.dart';
|
||||||
@@ -10,7 +11,6 @@ import 'package:revanced_manager/models/patched_application.dart';
|
|||||||
import 'package:revanced_manager/services/github_api.dart';
|
import 'package:revanced_manager/services/github_api.dart';
|
||||||
import 'package:revanced_manager/services/revanced_api.dart';
|
import 'package:revanced_manager/services/revanced_api.dart';
|
||||||
import 'package:revanced_manager/services/root_api.dart';
|
import 'package:revanced_manager/services/root_api.dart';
|
||||||
import 'package:sentry_flutter/sentry_flutter.dart';
|
|
||||||
import 'package:shared_preferences/shared_preferences.dart';
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
|
|
||||||
@lazySingleton
|
@lazySingleton
|
||||||
@@ -20,8 +20,8 @@ class ManagerAPI {
|
|||||||
final RootAPI _rootAPI = RootAPI();
|
final RootAPI _rootAPI = RootAPI();
|
||||||
final String patcherRepo = 'revanced-patcher';
|
final String patcherRepo = 'revanced-patcher';
|
||||||
final String cliRepo = 'revanced-cli';
|
final String cliRepo = 'revanced-cli';
|
||||||
late String storedPatchesFile = '/selected-patches.json';
|
|
||||||
late SharedPreferences _prefs;
|
late SharedPreferences _prefs;
|
||||||
|
String storedPatchesFile = '/selected-patches.json';
|
||||||
String defaultApiUrl = 'https://releases.revanced.app/';
|
String defaultApiUrl = 'https://releases.revanced.app/';
|
||||||
String defaultRepoUrl = 'https://api.github.com';
|
String defaultRepoUrl = 'https://api.github.com';
|
||||||
String defaultPatcherRepo = 'revanced/revanced-patcher';
|
String defaultPatcherRepo = 'revanced/revanced-patcher';
|
||||||
@@ -98,14 +98,6 @@ class ManagerAPI {
|
|||||||
await _prefs.setBool('useDarkTheme', value);
|
await _prefs.setBool('useDarkTheme', value);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool isSentryEnabled() {
|
|
||||||
return _prefs.getBool('sentryEnabled') ?? true;
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> setSentryStatus(bool value) async {
|
|
||||||
await _prefs.setBool('sentryEnabled', value);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool areUniversalPatchesEnabled() {
|
bool areUniversalPatchesEnabled() {
|
||||||
return _prefs.getBool('universalPatchesEnabled') ?? false;
|
return _prefs.getBool('universalPatchesEnabled') ?? false;
|
||||||
}
|
}
|
||||||
@@ -131,14 +123,15 @@ class ManagerAPI {
|
|||||||
|
|
||||||
Future<void> deleteKeystore() async {
|
Future<void> deleteKeystore() async {
|
||||||
final File keystore = File(
|
final File keystore = File(
|
||||||
'/sdcard/Android/data/app.revanced.manager.flutter/files/revanced-manager.keystore');
|
'/sdcard/Android/data/app.revanced.manager.flutter/files/revanced-manager.keystore',
|
||||||
|
);
|
||||||
if (await keystore.exists()) {
|
if (await keystore.exists()) {
|
||||||
await keystore.delete();
|
await keystore.delete();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
List<PatchedApplication> getPatchedApps() {
|
List<PatchedApplication> getPatchedApps() {
|
||||||
List<String> apps = _prefs.getStringList('patchedApps') ?? [];
|
final List<String> apps = _prefs.getStringList('patchedApps') ?? [];
|
||||||
return apps.map((a) => PatchedApplication.fromJson(jsonDecode(a))).toList();
|
return apps.map((a) => PatchedApplication.fromJson(jsonDecode(a))).toList();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -146,14 +139,16 @@ class ManagerAPI {
|
|||||||
if (patchedApps.length > 1) {
|
if (patchedApps.length > 1) {
|
||||||
patchedApps.sort((a, b) => a.name.compareTo(b.name));
|
patchedApps.sort((a, b) => a.name.compareTo(b.name));
|
||||||
}
|
}
|
||||||
await _prefs.setStringList('patchedApps',
|
await _prefs.setStringList(
|
||||||
patchedApps.map((a) => json.encode(a.toJson())).toList());
|
'patchedApps',
|
||||||
|
patchedApps.map((a) => json.encode(a.toJson())).toList(),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> savePatchedApp(PatchedApplication app) async {
|
Future<void> savePatchedApp(PatchedApplication app) async {
|
||||||
List<PatchedApplication> patchedApps = getPatchedApps();
|
final List<PatchedApplication> patchedApps = getPatchedApps();
|
||||||
patchedApps.removeWhere((a) => a.packageName == app.packageName);
|
patchedApps.removeWhere((a) => a.packageName == app.packageName);
|
||||||
ApplicationWithIcon? installed = await DeviceApps.getApp(
|
final ApplicationWithIcon? installed = await DeviceApps.getApp(
|
||||||
app.packageName,
|
app.packageName,
|
||||||
true,
|
true,
|
||||||
) as ApplicationWithIcon?;
|
) as ApplicationWithIcon?;
|
||||||
@@ -167,17 +162,19 @@ class ManagerAPI {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<void> deletePatchedApp(PatchedApplication app) async {
|
Future<void> deletePatchedApp(PatchedApplication app) async {
|
||||||
List<PatchedApplication> patchedApps = getPatchedApps();
|
final List<PatchedApplication> patchedApps = getPatchedApps();
|
||||||
patchedApps.removeWhere((a) => a.packageName == app.packageName);
|
patchedApps.removeWhere((a) => a.packageName == app.packageName);
|
||||||
await setPatchedApps(patchedApps);
|
await setPatchedApps(patchedApps);
|
||||||
}
|
}
|
||||||
|
|
||||||
void clearAllData() async {
|
Future<void> clearAllData() async {
|
||||||
try {
|
try {
|
||||||
_revancedAPI.clearAllCache();
|
_revancedAPI.clearAllCache();
|
||||||
_githubAPI.clearAllCache();
|
_githubAPI.clearAllCache();
|
||||||
} on Exception catch (e, s) {
|
} on Exception catch (e) {
|
||||||
await Sentry.captureException(e, stackTrace: s);
|
if (kDebugMode) {
|
||||||
|
print(e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -187,21 +184,23 @@ class ManagerAPI {
|
|||||||
|
|
||||||
Future<List<Patch>> getPatches() async {
|
Future<List<Patch>> getPatches() async {
|
||||||
try {
|
try {
|
||||||
String repoName = getPatchesRepo();
|
final String repoName = getPatchesRepo();
|
||||||
if (repoName == defaultPatchesRepo) {
|
if (repoName == defaultPatchesRepo) {
|
||||||
return await _revancedAPI.getPatches();
|
return await _revancedAPI.getPatches();
|
||||||
} else {
|
} else {
|
||||||
return await _githubAPI.getPatches(repoName);
|
return await _githubAPI.getPatches(repoName);
|
||||||
}
|
}
|
||||||
} on Exception catch (e, s) {
|
} on Exception catch (e) {
|
||||||
await Sentry.captureException(e, stackTrace: s);
|
if (kDebugMode) {
|
||||||
|
print(e);
|
||||||
|
}
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<File?> downloadPatches() async {
|
Future<File?> downloadPatches() async {
|
||||||
try {
|
try {
|
||||||
String repoName = getPatchesRepo();
|
final String repoName = getPatchesRepo();
|
||||||
if (repoName == defaultPatchesRepo) {
|
if (repoName == defaultPatchesRepo) {
|
||||||
return await _revancedAPI.getLatestReleaseFile(
|
return await _revancedAPI.getLatestReleaseFile(
|
||||||
'.jar',
|
'.jar',
|
||||||
@@ -210,15 +209,17 @@ class ManagerAPI {
|
|||||||
} else {
|
} else {
|
||||||
return await _githubAPI.getLatestReleaseFile('.jar', repoName);
|
return await _githubAPI.getLatestReleaseFile('.jar', repoName);
|
||||||
}
|
}
|
||||||
} on Exception catch (e, s) {
|
} on Exception catch (e) {
|
||||||
await Sentry.captureException(e, stackTrace: s);
|
if (kDebugMode) {
|
||||||
|
print(e);
|
||||||
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<File?> downloadIntegrations() async {
|
Future<File?> downloadIntegrations() async {
|
||||||
try {
|
try {
|
||||||
String repoName = getIntegrationsRepo();
|
final String repoName = getIntegrationsRepo();
|
||||||
if (repoName == defaultIntegrationsRepo) {
|
if (repoName == defaultIntegrationsRepo) {
|
||||||
return await _revancedAPI.getLatestReleaseFile(
|
return await _revancedAPI.getLatestReleaseFile(
|
||||||
'.apk',
|
'.apk',
|
||||||
@@ -227,8 +228,10 @@ class ManagerAPI {
|
|||||||
} else {
|
} else {
|
||||||
return await _githubAPI.getLatestReleaseFile('.apk', repoName);
|
return await _githubAPI.getLatestReleaseFile('.apk', repoName);
|
||||||
}
|
}
|
||||||
} on Exception catch (e, s) {
|
} on Exception catch (e) {
|
||||||
await Sentry.captureException(e, stackTrace: s);
|
if (kDebugMode) {
|
||||||
|
print(e);
|
||||||
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -260,16 +263,16 @@ class ManagerAPI {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<String> getCurrentManagerVersion() async {
|
Future<String> getCurrentManagerVersion() async {
|
||||||
PackageInfo packageInfo = await PackageInfo.fromPlatform();
|
final PackageInfo packageInfo = await PackageInfo.fromPlatform();
|
||||||
return packageInfo.version;
|
return packageInfo.version;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<List<PatchedApplication>> getAppsToRemove(
|
Future<List<PatchedApplication>> getAppsToRemove(
|
||||||
List<PatchedApplication> patchedApps,
|
List<PatchedApplication> patchedApps,
|
||||||
) async {
|
) async {
|
||||||
List<PatchedApplication> toRemove = [];
|
final List<PatchedApplication> toRemove = [];
|
||||||
for (PatchedApplication app in patchedApps) {
|
for (final PatchedApplication app in patchedApps) {
|
||||||
bool isRemove = await isAppUninstalled(app);
|
final bool isRemove = await isAppUninstalled(app);
|
||||||
if (isRemove) {
|
if (isRemove) {
|
||||||
toRemove.add(app);
|
toRemove.add(app);
|
||||||
}
|
}
|
||||||
@@ -280,13 +283,13 @@ class ManagerAPI {
|
|||||||
Future<List<PatchedApplication>> getUnsavedApps(
|
Future<List<PatchedApplication>> getUnsavedApps(
|
||||||
List<PatchedApplication> patchedApps,
|
List<PatchedApplication> patchedApps,
|
||||||
) async {
|
) async {
|
||||||
List<PatchedApplication> unsavedApps = [];
|
final List<PatchedApplication> unsavedApps = [];
|
||||||
bool hasRootPermissions = await _rootAPI.hasRootPermissions();
|
final bool hasRootPermissions = await _rootAPI.hasRootPermissions();
|
||||||
if (hasRootPermissions) {
|
if (hasRootPermissions) {
|
||||||
List<String> installedApps = await _rootAPI.getInstalledApps();
|
final List<String> installedApps = await _rootAPI.getInstalledApps();
|
||||||
for (String packageName in installedApps) {
|
for (final String packageName in installedApps) {
|
||||||
if (!patchedApps.any((app) => app.packageName == packageName)) {
|
if (!patchedApps.any((app) => app.packageName == packageName)) {
|
||||||
ApplicationWithIcon? application = await DeviceApps.getApp(
|
final ApplicationWithIcon? application = await DeviceApps.getApp(
|
||||||
packageName,
|
packageName,
|
||||||
true,
|
true,
|
||||||
) as ApplicationWithIcon?;
|
) as ApplicationWithIcon?;
|
||||||
@@ -307,15 +310,13 @@ class ManagerAPI {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
List<Application> userApps = await DeviceApps.getInstalledApplications(
|
final List<Application> userApps =
|
||||||
includeSystemApps: false,
|
await DeviceApps.getInstalledApplications();
|
||||||
includeAppIcons: false,
|
for (final Application app in userApps) {
|
||||||
);
|
|
||||||
for (Application app in userApps) {
|
|
||||||
if (app.packageName.startsWith('app.revanced') &&
|
if (app.packageName.startsWith('app.revanced') &&
|
||||||
!app.packageName.startsWith('app.revanced.manager.') &&
|
!app.packageName.startsWith('app.revanced.manager.') &&
|
||||||
!patchedApps.any((uapp) => uapp.packageName == app.packageName)) {
|
!patchedApps.any((uapp) => uapp.packageName == app.packageName)) {
|
||||||
ApplicationWithIcon? application = await DeviceApps.getApp(
|
final ApplicationWithIcon? application = await DeviceApps.getApp(
|
||||||
app.packageName,
|
app.packageName,
|
||||||
true,
|
true,
|
||||||
) as ApplicationWithIcon?;
|
) as ApplicationWithIcon?;
|
||||||
@@ -329,7 +330,6 @@ class ManagerAPI {
|
|||||||
apkFilePath: application.apkFilePath,
|
apkFilePath: application.apkFilePath,
|
||||||
icon: application.icon,
|
icon: application.icon,
|
||||||
patchDate: DateTime.now(),
|
patchDate: DateTime.now(),
|
||||||
isRooted: false,
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -339,24 +339,27 @@ class ManagerAPI {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<void> reAssessSavedApps() async {
|
Future<void> reAssessSavedApps() async {
|
||||||
List<PatchedApplication> patchedApps = getPatchedApps();
|
final List<PatchedApplication> patchedApps = getPatchedApps();
|
||||||
List<PatchedApplication> unsavedApps = await getUnsavedApps(patchedApps);
|
final List<PatchedApplication> unsavedApps =
|
||||||
|
await getUnsavedApps(patchedApps);
|
||||||
patchedApps.addAll(unsavedApps);
|
patchedApps.addAll(unsavedApps);
|
||||||
List<PatchedApplication> toRemove = await getAppsToRemove(patchedApps);
|
final List<PatchedApplication> toRemove =
|
||||||
|
await getAppsToRemove(patchedApps);
|
||||||
patchedApps.removeWhere((a) => toRemove.contains(a));
|
patchedApps.removeWhere((a) => toRemove.contains(a));
|
||||||
for (PatchedApplication app in patchedApps) {
|
for (final PatchedApplication app in patchedApps) {
|
||||||
app.hasUpdates =
|
app.hasUpdates =
|
||||||
await hasAppUpdates(app.originalPackageName, app.patchDate);
|
await hasAppUpdates(app.originalPackageName, app.patchDate);
|
||||||
app.changelog =
|
app.changelog =
|
||||||
await getAppChangelog(app.originalPackageName, app.patchDate);
|
await getAppChangelog(app.originalPackageName, app.patchDate);
|
||||||
if (!app.hasUpdates) {
|
if (!app.hasUpdates) {
|
||||||
String? currentInstalledVersion =
|
final String? currentInstalledVersion =
|
||||||
(await DeviceApps.getApp(app.packageName))?.versionName;
|
(await DeviceApps.getApp(app.packageName))?.versionName;
|
||||||
if (currentInstalledVersion != null) {
|
if (currentInstalledVersion != null) {
|
||||||
String currentSavedVersion = app.version;
|
final String currentSavedVersion = app.version;
|
||||||
int currentInstalledVersionInt = int.parse(
|
final int currentInstalledVersionInt = int.parse(
|
||||||
currentInstalledVersion.replaceAll(RegExp('[^0-9]'), ''));
|
currentInstalledVersion.replaceAll(RegExp('[^0-9]'), ''),
|
||||||
int currentSavedVersionInt =
|
);
|
||||||
|
final int currentSavedVersionInt =
|
||||||
int.parse(currentSavedVersion.replaceAll(RegExp('[^0-9]'), ''));
|
int.parse(currentSavedVersion.replaceAll(RegExp('[^0-9]'), ''));
|
||||||
if (currentInstalledVersionInt > currentSavedVersionInt) {
|
if (currentInstalledVersionInt > currentSavedVersionInt) {
|
||||||
app.hasUpdates = true;
|
app.hasUpdates = true;
|
||||||
@@ -369,9 +372,9 @@ class ManagerAPI {
|
|||||||
|
|
||||||
Future<bool> isAppUninstalled(PatchedApplication app) async {
|
Future<bool> isAppUninstalled(PatchedApplication app) async {
|
||||||
bool existsRoot = false;
|
bool existsRoot = false;
|
||||||
bool existsNonRoot = await DeviceApps.isAppInstalled(app.packageName);
|
final bool existsNonRoot = await DeviceApps.isAppInstalled(app.packageName);
|
||||||
if (app.isRooted) {
|
if (app.isRooted) {
|
||||||
bool hasRootPermissions = await _rootAPI.hasRootPermissions();
|
final bool hasRootPermissions = await _rootAPI.hasRootPermissions();
|
||||||
if (hasRootPermissions) {
|
if (hasRootPermissions) {
|
||||||
existsRoot = await _rootAPI.isAppInstalled(app.packageName);
|
existsRoot = await _rootAPI.isAppInstalled(app.packageName);
|
||||||
}
|
}
|
||||||
@@ -381,7 +384,7 @@ class ManagerAPI {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<bool> hasAppUpdates(String packageName, DateTime patchDate) async {
|
Future<bool> hasAppUpdates(String packageName, DateTime patchDate) async {
|
||||||
List<String> commits = await _githubAPI.getCommits(
|
final List<String> commits = await _githubAPI.getCommits(
|
||||||
packageName,
|
packageName,
|
||||||
getPatchesRepo(),
|
getPatchesRepo(),
|
||||||
patchDate,
|
patchDate,
|
||||||
@@ -390,7 +393,9 @@ class ManagerAPI {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<List<String>> getAppChangelog(
|
Future<List<String>> getAppChangelog(
|
||||||
String packageName, DateTime patchDate) async {
|
String packageName,
|
||||||
|
DateTime patchDate,
|
||||||
|
) async {
|
||||||
List<String> newCommits = await _githubAPI.getCommits(
|
List<String> newCommits = await _githubAPI.getCommits(
|
||||||
packageName,
|
packageName,
|
||||||
getPatchesRepo(),
|
getPatchesRepo(),
|
||||||
@@ -418,36 +423,30 @@ class ManagerAPI {
|
|||||||
|
|
||||||
Future<void> setSelectedPatches(String app, List<String> patches) async {
|
Future<void> setSelectedPatches(String app, List<String> patches) async {
|
||||||
final File selectedPatchesFile = File(storedPatchesFile);
|
final File selectedPatchesFile = File(storedPatchesFile);
|
||||||
Map<String, dynamic> patchesMap = await readSelectedPatchesFile();
|
final Map<String, dynamic> patchesMap = await readSelectedPatchesFile();
|
||||||
if (patches.isEmpty) {
|
if (patches.isEmpty) {
|
||||||
patchesMap.remove(app);
|
patchesMap.remove(app);
|
||||||
} else {
|
} else {
|
||||||
patchesMap[app] = patches;
|
patchesMap[app] = patches;
|
||||||
}
|
}
|
||||||
if (selectedPatchesFile.existsSync()) {
|
|
||||||
selectedPatchesFile.createSync(recursive: true);
|
|
||||||
}
|
|
||||||
selectedPatchesFile.writeAsString(jsonEncode(patchesMap));
|
selectedPatchesFile.writeAsString(jsonEncode(patchesMap));
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<List<String>> getSelectedPatches(String app) async {
|
Future<List<String>> getSelectedPatches(String app) async {
|
||||||
Map<String, dynamic> patchesMap = await readSelectedPatchesFile();
|
final Map<String, dynamic> patchesMap = await readSelectedPatchesFile();
|
||||||
if (patchesMap.isNotEmpty) {
|
return List.from(patchesMap.putIfAbsent(app, () => List.empty()));
|
||||||
final List<String> patches =
|
|
||||||
List.from(patchesMap.putIfAbsent(app, () => List.empty()));
|
|
||||||
return patches;
|
|
||||||
}
|
|
||||||
return List.empty();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<Map<String, dynamic>> readSelectedPatchesFile() async {
|
Future<Map<String, dynamic>> readSelectedPatchesFile() async {
|
||||||
final File selectedPatchesFile = File(storedPatchesFile);
|
final File selectedPatchesFile = File(storedPatchesFile);
|
||||||
if (selectedPatchesFile.existsSync()) {
|
if (!selectedPatchesFile.existsSync()) {
|
||||||
String string = selectedPatchesFile.readAsStringSync();
|
return {};
|
||||||
if (string.trim().isEmpty) return {};
|
|
||||||
return json.decode(string);
|
|
||||||
}
|
}
|
||||||
return {};
|
final String string = selectedPatchesFile.readAsStringSync();
|
||||||
|
if (string.trim().isEmpty) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
return jsonDecode(string);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> resetLastSelectedPatches() async {
|
Future<void> resetLastSelectedPatches() async {
|
||||||
|
|||||||
@@ -1,7 +1,10 @@
|
|||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:app_installer/app_installer.dart';
|
import 'package:app_installer/app_installer.dart';
|
||||||
import 'package:collection/collection.dart';
|
import 'package:collection/collection.dart';
|
||||||
|
import 'package:cr_file_saver/file_saver.dart';
|
||||||
import 'package:device_apps/device_apps.dart';
|
import 'package:device_apps/device_apps.dart';
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:injectable/injectable.dart';
|
import 'package:injectable/injectable.dart';
|
||||||
import 'package:path_provider/path_provider.dart';
|
import 'package:path_provider/path_provider.dart';
|
||||||
@@ -10,9 +13,7 @@ import 'package:revanced_manager/models/patch.dart';
|
|||||||
import 'package:revanced_manager/models/patched_application.dart';
|
import 'package:revanced_manager/models/patched_application.dart';
|
||||||
import 'package:revanced_manager/services/manager_api.dart';
|
import 'package:revanced_manager/services/manager_api.dart';
|
||||||
import 'package:revanced_manager/services/root_api.dart';
|
import 'package:revanced_manager/services/root_api.dart';
|
||||||
import 'package:sentry_flutter/sentry_flutter.dart';
|
|
||||||
import 'package:share_extend/share_extend.dart';
|
import 'package:share_extend/share_extend.dart';
|
||||||
import 'package:cr_file_saver/file_saver.dart';
|
|
||||||
|
|
||||||
@lazySingleton
|
@lazySingleton
|
||||||
class PatcherAPI {
|
class PatcherAPI {
|
||||||
@@ -29,7 +30,7 @@ class PatcherAPI {
|
|||||||
|
|
||||||
Future<void> initialize() async {
|
Future<void> initialize() async {
|
||||||
await _loadPatches();
|
await _loadPatches();
|
||||||
Directory appCache = await getTemporaryDirectory();
|
final Directory appCache = await getTemporaryDirectory();
|
||||||
_dataDir = await getExternalStorageDirectory() ?? appCache;
|
_dataDir = await getExternalStorageDirectory() ?? appCache;
|
||||||
_tmpDir = Directory('${appCache.path}/patcher');
|
_tmpDir = Directory('${appCache.path}/patcher');
|
||||||
_keyStoreFile = File('${_dataDir.path}/revanced-manager.keystore');
|
_keyStoreFile = File('${_dataDir.path}/revanced-manager.keystore');
|
||||||
@@ -47,24 +48,29 @@ class PatcherAPI {
|
|||||||
if (_patches.isEmpty) {
|
if (_patches.isEmpty) {
|
||||||
_patches = await _managerAPI.getPatches();
|
_patches = await _managerAPI.getPatches();
|
||||||
}
|
}
|
||||||
} on Exception catch (e, s) {
|
} on Exception catch (e) {
|
||||||
await Sentry.captureException(e, stackTrace: s);
|
if (kDebugMode) {
|
||||||
|
print(e);
|
||||||
|
}
|
||||||
_patches = List.empty();
|
_patches = List.empty();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<List<ApplicationWithIcon>> getFilteredInstalledApps(bool showUniversalPatches) async {
|
Future<List<ApplicationWithIcon>> getFilteredInstalledApps(
|
||||||
List<ApplicationWithIcon> filteredApps = [];
|
bool showUniversalPatches,
|
||||||
bool? allAppsIncluded =
|
) async {
|
||||||
_patches.any((patch) => patch.compatiblePackages.isEmpty) && showUniversalPatches;
|
final List<ApplicationWithIcon> filteredApps = [];
|
||||||
|
final bool allAppsIncluded =
|
||||||
|
_patches.any((patch) => patch.compatiblePackages.isEmpty) &&
|
||||||
|
showUniversalPatches;
|
||||||
if (allAppsIncluded) {
|
if (allAppsIncluded) {
|
||||||
var allPackages = await DeviceApps.getInstalledApplications(
|
final allPackages = await DeviceApps.getInstalledApplications(
|
||||||
includeAppIcons: true,
|
includeAppIcons: true,
|
||||||
onlyAppsWithLaunchIntent: true,
|
onlyAppsWithLaunchIntent: true,
|
||||||
);
|
);
|
||||||
allPackages.forEach((pkg) async {
|
for (final pkg in allPackages) {
|
||||||
if (!filteredApps.any((app) => app.packageName == pkg.packageName)) {
|
if (!filteredApps.any((app) => app.packageName == pkg.packageName)) {
|
||||||
var appInfo = await DeviceApps.getApp(
|
final appInfo = await DeviceApps.getApp(
|
||||||
pkg.packageName,
|
pkg.packageName,
|
||||||
true,
|
true,
|
||||||
) as ApplicationWithIcon?;
|
) as ApplicationWithIcon?;
|
||||||
@@ -72,13 +78,13 @@ class PatcherAPI {
|
|||||||
filteredApps.add(appInfo);
|
filteredApps.add(appInfo);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
for (Patch patch in _patches) {
|
for (final Patch patch in _patches) {
|
||||||
for (Package package in patch.compatiblePackages) {
|
for (final Package package in patch.compatiblePackages) {
|
||||||
try {
|
try {
|
||||||
if (!filteredApps.any((app) => app.packageName == package.name)) {
|
if (!filteredApps.any((app) => app.packageName == package.name)) {
|
||||||
ApplicationWithIcon? app = await DeviceApps.getApp(
|
final ApplicationWithIcon? app = await DeviceApps.getApp(
|
||||||
package.name,
|
package.name,
|
||||||
true,
|
true,
|
||||||
) as ApplicationWithIcon?;
|
) as ApplicationWithIcon?;
|
||||||
@@ -86,9 +92,10 @@ class PatcherAPI {
|
|||||||
filteredApps.add(app);
|
filteredApps.add(app);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} on Exception catch (e, s) {
|
} on Exception catch (e) {
|
||||||
await Sentry.captureException(e, stackTrace: s);
|
if (kDebugMode) {
|
||||||
continue;
|
print(e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -97,12 +104,14 @@ class PatcherAPI {
|
|||||||
|
|
||||||
List<Patch> getFilteredPatches(String packageName) {
|
List<Patch> getFilteredPatches(String packageName) {
|
||||||
if (!filteredPatches.keys.contains(packageName)) {
|
if (!filteredPatches.keys.contains(packageName)) {
|
||||||
List<Patch> patches = _patches
|
final List<Patch> patches = _patches
|
||||||
.where((patch) =>
|
.where(
|
||||||
patch.compatiblePackages.isEmpty ||
|
(patch) =>
|
||||||
!patch.name.contains('settings') &&
|
patch.compatiblePackages.isEmpty ||
|
||||||
patch.compatiblePackages
|
!patch.name.contains('settings') &&
|
||||||
.any((pack) => pack.name == packageName))
|
patch.compatiblePackages
|
||||||
|
.any((pack) => pack.name == packageName),
|
||||||
|
)
|
||||||
.toList();
|
.toList();
|
||||||
filteredPatches[packageName] = patches;
|
filteredPatches[packageName] = patches;
|
||||||
}
|
}
|
||||||
@@ -115,25 +124,6 @@ class PatcherAPI {
|
|||||||
.toList();
|
.toList();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool dependencyNeedsIntegrations(String name) {
|
|
||||||
return name.contains('integrations') ||
|
|
||||||
_patches.any(
|
|
||||||
(patch) =>
|
|
||||||
patch.name == name &&
|
|
||||||
(patch.dependencies.any(
|
|
||||||
(dep) => dependencyNeedsIntegrations(dep),
|
|
||||||
)),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<bool> needsIntegrations(List<Patch> selectedPatches) async {
|
|
||||||
return selectedPatches.any(
|
|
||||||
(patch) => patch.dependencies.any(
|
|
||||||
(dep) => dependencyNeedsIntegrations(dep),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<bool> needsResourcePatching(List<Patch> selectedPatches) async {
|
Future<bool> needsResourcePatching(List<Patch> selectedPatches) async {
|
||||||
return selectedPatches.any(
|
return selectedPatches.any(
|
||||||
(patch) => patch.dependencies.any(
|
(patch) => patch.dependencies.any(
|
||||||
@@ -155,7 +145,7 @@ class PatcherAPI {
|
|||||||
String originalFilePath,
|
String originalFilePath,
|
||||||
) async {
|
) async {
|
||||||
try {
|
try {
|
||||||
bool hasRootPermissions = await _rootAPI.hasRootPermissions();
|
final bool hasRootPermissions = await _rootAPI.hasRootPermissions();
|
||||||
if (hasRootPermissions) {
|
if (hasRootPermissions) {
|
||||||
originalFilePath = await _rootAPI.getOriginalFilePath(
|
originalFilePath = await _rootAPI.getOriginalFilePath(
|
||||||
packageName,
|
packageName,
|
||||||
@@ -163,8 +153,10 @@ class PatcherAPI {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
return originalFilePath;
|
return originalFilePath;
|
||||||
} on Exception catch (e, s) {
|
} on Exception catch (e) {
|
||||||
await Sentry.captureException(e, stackTrace: s);
|
if (kDebugMode) {
|
||||||
|
print(e);
|
||||||
|
}
|
||||||
return originalFilePath;
|
return originalFilePath;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -174,11 +166,10 @@ class PatcherAPI {
|
|||||||
String originalFilePath,
|
String originalFilePath,
|
||||||
List<Patch> selectedPatches,
|
List<Patch> selectedPatches,
|
||||||
) async {
|
) async {
|
||||||
bool mergeIntegrations = await needsIntegrations(selectedPatches);
|
final bool includeSettings = await needsSettingsPatch(selectedPatches);
|
||||||
bool includeSettings = await needsSettingsPatch(selectedPatches);
|
|
||||||
if (includeSettings) {
|
if (includeSettings) {
|
||||||
try {
|
try {
|
||||||
Patch? settingsPatch = _patches.firstWhereOrNull(
|
final Patch? settingsPatch = _patches.firstWhereOrNull(
|
||||||
(patch) =>
|
(patch) =>
|
||||||
patch.name.contains('settings') &&
|
patch.name.contains('settings') &&
|
||||||
patch.compatiblePackages.any((pack) => pack.name == packageName),
|
patch.compatiblePackages.any((pack) => pack.name == packageName),
|
||||||
@@ -186,24 +177,22 @@ class PatcherAPI {
|
|||||||
if (settingsPatch != null) {
|
if (settingsPatch != null) {
|
||||||
selectedPatches.add(settingsPatch);
|
selectedPatches.add(settingsPatch);
|
||||||
}
|
}
|
||||||
} on Exception catch (e, s) {
|
} on Exception catch (e) {
|
||||||
await Sentry.captureException(e, stackTrace: s);
|
if (kDebugMode) {
|
||||||
// ignore
|
print(e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
File? patchBundleFile = await _managerAPI.downloadPatches();
|
final File? patchBundleFile = await _managerAPI.downloadPatches();
|
||||||
File? integrationsFile;
|
final File? integrationsFile = await _managerAPI.downloadIntegrations();
|
||||||
if (mergeIntegrations) {
|
|
||||||
integrationsFile = await _managerAPI.downloadIntegrations();
|
|
||||||
}
|
|
||||||
if (patchBundleFile != null) {
|
if (patchBundleFile != null) {
|
||||||
_dataDir.createSync();
|
_dataDir.createSync();
|
||||||
_tmpDir.createSync();
|
_tmpDir.createSync();
|
||||||
Directory workDir = _tmpDir.createTempSync('tmp-');
|
final Directory workDir = _tmpDir.createTempSync('tmp-');
|
||||||
File inputFile = File('${workDir.path}/base.apk');
|
final File inputFile = File('${workDir.path}/base.apk');
|
||||||
File patchedFile = File('${workDir.path}/patched.apk');
|
final File patchedFile = File('${workDir.path}/patched.apk');
|
||||||
_outFile = File('${workDir.path}/out.apk');
|
_outFile = File('${workDir.path}/out.apk');
|
||||||
Directory cacheDir = Directory('${workDir.path}/cache');
|
final Directory cacheDir = Directory('${workDir.path}/cache');
|
||||||
cacheDir.createSync();
|
cacheDir.createSync();
|
||||||
try {
|
try {
|
||||||
await patcherChannel.invokeMethod(
|
await patcherChannel.invokeMethod(
|
||||||
@@ -217,16 +206,16 @@ class PatcherAPI {
|
|||||||
'inputFilePath': inputFile.path,
|
'inputFilePath': inputFile.path,
|
||||||
'patchedFilePath': patchedFile.path,
|
'patchedFilePath': patchedFile.path,
|
||||||
'outFilePath': _outFile!.path,
|
'outFilePath': _outFile!.path,
|
||||||
'integrationsPath': mergeIntegrations ? integrationsFile!.path : '',
|
'integrationsPath': integrationsFile!.path,
|
||||||
'selectedPatches': selectedPatches.map((p) => p.name).toList(),
|
'selectedPatches': selectedPatches.map((p) => p.name).toList(),
|
||||||
'cacheDirPath': cacheDir.path,
|
'cacheDirPath': cacheDir.path,
|
||||||
'mergeIntegrations': mergeIntegrations,
|
|
||||||
'keyStoreFilePath': _keyStoreFile.path,
|
'keyStoreFilePath': _keyStoreFile.path,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
} on Exception catch (e, s) {
|
} on Exception catch (e) {
|
||||||
print(e);
|
if (kDebugMode) {
|
||||||
throw await Sentry.captureException(e, stackTrace: s);
|
print(e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -235,7 +224,7 @@ class PatcherAPI {
|
|||||||
if (_outFile != null) {
|
if (_outFile != null) {
|
||||||
try {
|
try {
|
||||||
if (patchedApp.isRooted) {
|
if (patchedApp.isRooted) {
|
||||||
bool hasRootPermissions = await _rootAPI.hasRootPermissions();
|
final bool hasRootPermissions = await _rootAPI.hasRootPermissions();
|
||||||
if (hasRootPermissions) {
|
if (hasRootPermissions) {
|
||||||
return _rootAPI.installApp(
|
return _rootAPI.installApp(
|
||||||
patchedApp.packageName,
|
patchedApp.packageName,
|
||||||
@@ -247,8 +236,10 @@ class PatcherAPI {
|
|||||||
await AppInstaller.installApk(_outFile!.path);
|
await AppInstaller.installApk(_outFile!.path);
|
||||||
return await DeviceApps.isAppInstalled(patchedApp.packageName);
|
return await DeviceApps.isAppInstalled(patchedApp.packageName);
|
||||||
}
|
}
|
||||||
} on Exception catch (e, s) {
|
} on Exception catch (e) {
|
||||||
await Sentry.captureException(e, stackTrace: s);
|
if (kDebugMode) {
|
||||||
|
print(e);
|
||||||
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -258,67 +249,68 @@ class PatcherAPI {
|
|||||||
void exportPatchedFile(String appName, String version) {
|
void exportPatchedFile(String appName, String version) {
|
||||||
try {
|
try {
|
||||||
if (_outFile != null) {
|
if (_outFile != null) {
|
||||||
String newName = _getFileName(appName, version);
|
final String newName = _getFileName(appName, version);
|
||||||
|
CRFileSaver.saveFileWithDialog(
|
||||||
// This is temporary workaround to populate initial file name
|
SaveFileDialogParams(
|
||||||
// ref: https://github.com/Cleveroad/cr_file_saver/issues/7
|
sourceFilePath: _outFile!.path,
|
||||||
int lastSeparator = _outFile!.path.lastIndexOf('/');
|
destinationFileName: newName,
|
||||||
String newSourcePath =
|
),
|
||||||
_outFile!.path.substring(0, lastSeparator + 1) + newName;
|
);
|
||||||
_outFile!.copySync(newSourcePath);
|
}
|
||||||
|
} on Exception catch (e) {
|
||||||
CRFileSaver.saveFileWithDialog(SaveFileDialogParams(
|
if (kDebugMode) {
|
||||||
sourceFilePath: newSourcePath, destinationFileName: newName));
|
print(e);
|
||||||
}
|
}
|
||||||
} on Exception catch (e, s) {
|
|
||||||
Sentry.captureException(e, stackTrace: s);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void sharePatchedFile(String appName, String version) {
|
void sharePatchedFile(String appName, String version) {
|
||||||
try {
|
try {
|
||||||
if (_outFile != null) {
|
if (_outFile != null) {
|
||||||
String newName = _getFileName(appName, version);
|
final String newName = _getFileName(appName, version);
|
||||||
int lastSeparator = _outFile!.path.lastIndexOf('/');
|
final int lastSeparator = _outFile!.path.lastIndexOf('/');
|
||||||
String newPath =
|
final String newPath =
|
||||||
_outFile!.path.substring(0, lastSeparator + 1) + newName;
|
_outFile!.path.substring(0, lastSeparator + 1) + newName;
|
||||||
File shareFile = _outFile!.copySync(newPath);
|
final File shareFile = _outFile!.copySync(newPath);
|
||||||
ShareExtend.share(shareFile.path, 'file');
|
ShareExtend.share(shareFile.path, 'file');
|
||||||
}
|
}
|
||||||
} on Exception catch (e, s) {
|
} on Exception catch (e) {
|
||||||
Sentry.captureException(e, stackTrace: s);
|
if (kDebugMode) {
|
||||||
|
print(e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
String _getFileName(String appName, String version) {
|
String _getFileName(String appName, String version) {
|
||||||
String prefix = appName.toLowerCase().replaceAll(' ', '-');
|
final String prefix = appName.toLowerCase().replaceAll(' ', '-');
|
||||||
String newName = '$prefix-revanced_v$version.apk';
|
final String newName = '$prefix-revanced_v$version.apk';
|
||||||
return newName;
|
return newName;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> sharePatcherLog(String logs) async {
|
Future<void> sharePatcherLog(String logs) async {
|
||||||
Directory appCache = await getTemporaryDirectory();
|
final Directory appCache = await getTemporaryDirectory();
|
||||||
Directory logDir = Directory('${appCache.path}/logs');
|
final Directory logDir = Directory('${appCache.path}/logs');
|
||||||
logDir.createSync();
|
logDir.createSync();
|
||||||
String dateTime = DateTime.now()
|
final String dateTime = DateTime.now()
|
||||||
.toIso8601String()
|
.toIso8601String()
|
||||||
.replaceAll('-', '')
|
.replaceAll('-', '')
|
||||||
.replaceAll(':', '')
|
.replaceAll(':', '')
|
||||||
.replaceAll('T', '')
|
.replaceAll('T', '')
|
||||||
.replaceAll('.', '');
|
.replaceAll('.', '');
|
||||||
File log = File('${logDir.path}/revanced-manager_patcher_$dateTime.log');
|
final File log =
|
||||||
|
File('${logDir.path}/revanced-manager_patcher_$dateTime.log');
|
||||||
log.writeAsStringSync(logs);
|
log.writeAsStringSync(logs);
|
||||||
ShareExtend.share(log.path, 'file');
|
ShareExtend.share(log.path, 'file');
|
||||||
}
|
}
|
||||||
|
|
||||||
String getRecommendedVersion(String packageName) {
|
String getRecommendedVersion(String packageName) {
|
||||||
Map<String, int> versions = {};
|
final Map<String, int> versions = {};
|
||||||
for (Patch patch in _patches) {
|
for (final Patch patch in _patches) {
|
||||||
Package? package = patch.compatiblePackages.firstWhereOrNull(
|
final Package? package = patch.compatiblePackages.firstWhereOrNull(
|
||||||
(pack) => pack.name == packageName,
|
(pack) => pack.name == packageName,
|
||||||
);
|
);
|
||||||
if (package != null) {
|
if (package != null) {
|
||||||
for (String version in package.versions) {
|
for (final String version in package.versions) {
|
||||||
versions.update(
|
versions.update(
|
||||||
version,
|
version,
|
||||||
(value) => versions[version]! + 1,
|
(value) => versions[version]! + 1,
|
||||||
@@ -328,7 +320,7 @@ class PatcherAPI {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (versions.isNotEmpty) {
|
if (versions.isNotEmpty) {
|
||||||
var entries = versions.entries.toList()
|
final entries = versions.entries.toList()
|
||||||
..sort((a, b) => a.value.compareTo(b.value));
|
..sort((a, b) => a.value.compareTo(b.value));
|
||||||
versions
|
versions
|
||||||
..clear()
|
..clear()
|
||||||
|
|||||||
@@ -1,15 +1,16 @@
|
|||||||
|
import 'dart:developer';
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:collection/collection.dart';
|
import 'package:collection/collection.dart';
|
||||||
import 'package:native_dio_client/native_dio_client.dart';
|
|
||||||
import 'package:dio/dio.dart';
|
import 'package:dio/dio.dart';
|
||||||
import 'package:dio_http_cache_lts/dio_http_cache_lts.dart';
|
import 'package:dio_http_cache_lts/dio_http_cache_lts.dart';
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter_cache_manager/flutter_cache_manager.dart';
|
import 'package:flutter_cache_manager/flutter_cache_manager.dart';
|
||||||
import 'package:injectable/injectable.dart';
|
import 'package:injectable/injectable.dart';
|
||||||
|
import 'package:native_dio_client/native_dio_client.dart';
|
||||||
import 'package:revanced_manager/models/patch.dart';
|
import 'package:revanced_manager/models/patch.dart';
|
||||||
import 'package:revanced_manager/utils/check_for_gms.dart';
|
import 'package:revanced_manager/utils/check_for_gms.dart';
|
||||||
import 'package:timeago/timeago.dart';
|
import 'package:timeago/timeago.dart';
|
||||||
import 'package:sentry_flutter/sentry_flutter.dart';
|
|
||||||
import 'package:sentry_dio/sentry_dio.dart';
|
|
||||||
|
|
||||||
@lazySingleton
|
@lazySingleton
|
||||||
class RevancedAPI {
|
class RevancedAPI {
|
||||||
@@ -22,48 +23,54 @@ class RevancedAPI {
|
|||||||
|
|
||||||
Future<void> initialize(String apiUrl) async {
|
Future<void> initialize(String apiUrl) async {
|
||||||
try {
|
try {
|
||||||
bool isGMSInstalled = await checkForGMS();
|
final bool isGMSInstalled = await checkForGMS();
|
||||||
|
|
||||||
if (!isGMSInstalled) {
|
if (!isGMSInstalled) {
|
||||||
_dio = Dio(BaseOptions(
|
_dio = Dio(
|
||||||
baseUrl: apiUrl,
|
BaseOptions(
|
||||||
));
|
baseUrl: apiUrl,
|
||||||
print('ReVanced API: Using default engine + $isGMSInstalled');
|
),
|
||||||
|
);
|
||||||
|
log('ReVanced API: Using default engine + $isGMSInstalled');
|
||||||
} else {
|
} else {
|
||||||
_dio = Dio(BaseOptions(
|
_dio = Dio(
|
||||||
baseUrl: apiUrl,
|
BaseOptions(
|
||||||
))
|
baseUrl: apiUrl,
|
||||||
..httpClientAdapter = NativeAdapter();
|
),
|
||||||
print('ReVanced API: Using CronetEngine + $isGMSInstalled');
|
)..httpClientAdapter = NativeAdapter();
|
||||||
|
log('ReVanced API: Using CronetEngine + $isGMSInstalled');
|
||||||
}
|
}
|
||||||
_dio.interceptors.add(_dioCacheManager.interceptor);
|
_dio.interceptors.add(_dioCacheManager.interceptor);
|
||||||
_dio.addSentry(
|
} on Exception catch (e) {
|
||||||
captureFailedRequests: true,
|
if (kDebugMode) {
|
||||||
);
|
print(e);
|
||||||
} on Exception catch (e, s) {
|
}
|
||||||
await Sentry.captureException(e, stackTrace: s);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> clearAllCache() async {
|
Future<void> clearAllCache() async {
|
||||||
try {
|
try {
|
||||||
await _dioCacheManager.clearAll();
|
await _dioCacheManager.clearAll();
|
||||||
} on Exception catch (e, s) {
|
} on Exception catch (e) {
|
||||||
await Sentry.captureException(e, stackTrace: s);
|
if (kDebugMode) {
|
||||||
|
print(e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<Map<String, List<dynamic>>> getContributors() async {
|
Future<Map<String, List<dynamic>>> getContributors() async {
|
||||||
Map<String, List<dynamic>> contributors = {};
|
final Map<String, List<dynamic>> contributors = {};
|
||||||
try {
|
try {
|
||||||
var response = await _dio.get('/contributors', options: _cacheOptions);
|
final response = await _dio.get('/contributors', options: _cacheOptions);
|
||||||
List<dynamic> repositories = response.data['repositories'];
|
final List<dynamic> repositories = response.data['repositories'];
|
||||||
for (Map<String, dynamic> repo in repositories) {
|
for (final Map<String, dynamic> repo in repositories) {
|
||||||
String name = repo['name'];
|
final String name = repo['name'];
|
||||||
contributors[name] = repo['contributors'];
|
contributors[name] = repo['contributors'];
|
||||||
}
|
}
|
||||||
} on Exception catch (e, s) {
|
} on Exception catch (e) {
|
||||||
await Sentry.captureException(e, stackTrace: s);
|
if (kDebugMode) {
|
||||||
|
print(e);
|
||||||
|
}
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
return contributors;
|
return contributors;
|
||||||
@@ -71,11 +78,13 @@ class RevancedAPI {
|
|||||||
|
|
||||||
Future<List<Patch>> getPatches() async {
|
Future<List<Patch>> getPatches() async {
|
||||||
try {
|
try {
|
||||||
var response = await _dio.get('/patches', options: _cacheOptions);
|
final response = await _dio.get('/patches', options: _cacheOptions);
|
||||||
List<dynamic> patches = response.data;
|
final List<dynamic> patches = response.data;
|
||||||
return patches.map((patch) => Patch.fromJson(patch)).toList();
|
return patches.map((patch) => Patch.fromJson(patch)).toList();
|
||||||
} on Exception catch (e, s) {
|
} on Exception catch (e) {
|
||||||
await Sentry.captureException(e, stackTrace: s);
|
if (kDebugMode) {
|
||||||
|
print(e);
|
||||||
|
}
|
||||||
return List.empty();
|
return List.empty();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -85,15 +94,17 @@ class RevancedAPI {
|
|||||||
String repoName,
|
String repoName,
|
||||||
) async {
|
) async {
|
||||||
try {
|
try {
|
||||||
var response = await _dio.get('/tools', options: _cacheOptions);
|
final response = await _dio.get('/tools', options: _cacheOptions);
|
||||||
List<dynamic> tools = response.data['tools'];
|
final List<dynamic> tools = response.data['tools'];
|
||||||
return tools.firstWhereOrNull(
|
return tools.firstWhereOrNull(
|
||||||
(t) =>
|
(t) =>
|
||||||
t['repository'] == repoName &&
|
t['repository'] == repoName &&
|
||||||
(t['name'] as String).endsWith(extension),
|
(t['name'] as String).endsWith(extension),
|
||||||
);
|
);
|
||||||
} on Exception catch (e, s) {
|
} on Exception catch (e) {
|
||||||
await Sentry.captureException(e, stackTrace: s);
|
if (kDebugMode) {
|
||||||
|
print(e);
|
||||||
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -103,15 +114,17 @@ class RevancedAPI {
|
|||||||
String repoName,
|
String repoName,
|
||||||
) async {
|
) async {
|
||||||
try {
|
try {
|
||||||
Map<String, dynamic>? release = await _getLatestRelease(
|
final Map<String, dynamic>? release = await _getLatestRelease(
|
||||||
extension,
|
extension,
|
||||||
repoName,
|
repoName,
|
||||||
);
|
);
|
||||||
if (release != null) {
|
if (release != null) {
|
||||||
return release['version'];
|
return release['version'];
|
||||||
}
|
}
|
||||||
} on Exception catch (e, s) {
|
} on Exception catch (e) {
|
||||||
await Sentry.captureException(e, stackTrace: s);
|
if (kDebugMode) {
|
||||||
|
print(e);
|
||||||
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
@@ -119,16 +132,18 @@ class RevancedAPI {
|
|||||||
|
|
||||||
Future<File?> getLatestReleaseFile(String extension, String repoName) async {
|
Future<File?> getLatestReleaseFile(String extension, String repoName) async {
|
||||||
try {
|
try {
|
||||||
Map<String, dynamic>? release = await _getLatestRelease(
|
final Map<String, dynamic>? release = await _getLatestRelease(
|
||||||
extension,
|
extension,
|
||||||
repoName,
|
repoName,
|
||||||
);
|
);
|
||||||
if (release != null) {
|
if (release != null) {
|
||||||
String url = release['browser_download_url'];
|
final String url = release['browser_download_url'];
|
||||||
return await DefaultCacheManager().getSingleFile(url);
|
return await DefaultCacheManager().getSingleFile(url);
|
||||||
}
|
}
|
||||||
} on Exception catch (e, s) {
|
} on Exception catch (e) {
|
||||||
await Sentry.captureException(e, stackTrace: s);
|
if (kDebugMode) {
|
||||||
|
print(e);
|
||||||
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
@@ -139,16 +154,19 @@ class RevancedAPI {
|
|||||||
String repoName,
|
String repoName,
|
||||||
) async {
|
) async {
|
||||||
try {
|
try {
|
||||||
Map<String, dynamic>? release = await _getLatestRelease(
|
final Map<String, dynamic>? release = await _getLatestRelease(
|
||||||
extension,
|
extension,
|
||||||
repoName,
|
repoName,
|
||||||
);
|
);
|
||||||
if (release != null) {
|
if (release != null) {
|
||||||
DateTime timestamp = DateTime.parse(release['timestamp'] as String);
|
final DateTime timestamp =
|
||||||
|
DateTime.parse(release['timestamp'] as String);
|
||||||
return format(timestamp, locale: 'en_short');
|
return format(timestamp, locale: 'en_short');
|
||||||
}
|
}
|
||||||
} on Exception catch (e, s) {
|
} on Exception catch (e) {
|
||||||
await Sentry.captureException(e, stackTrace: s);
|
if (kDebugMode) {
|
||||||
|
print(e);
|
||||||
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:root/root.dart';
|
import 'package:root/root.dart';
|
||||||
import 'package:sentry_flutter/sentry_flutter.dart';
|
|
||||||
|
|
||||||
class RootAPI {
|
class RootAPI {
|
||||||
final String _managerDirPath = '/data/local/tmp/revanced-manager';
|
final String _managerDirPath = '/data/local/tmp/revanced-manager';
|
||||||
@@ -8,10 +8,12 @@ class RootAPI {
|
|||||||
|
|
||||||
Future<bool> isRooted() async {
|
Future<bool> isRooted() async {
|
||||||
try {
|
try {
|
||||||
bool? isRooted = await Root.isRootAvailable();
|
final bool? isRooted = await Root.isRootAvailable();
|
||||||
return isRooted != null && isRooted;
|
return isRooted != null && isRooted;
|
||||||
} on Exception catch (e, s) {
|
} on Exception catch (e) {
|
||||||
await Sentry.captureException(e, stackTrace: s);
|
if (kDebugMode) {
|
||||||
|
print(e);
|
||||||
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -24,8 +26,10 @@ class RootAPI {
|
|||||||
return isRooted != null && isRooted;
|
return isRooted != null && isRooted;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
} on Exception catch (e, s) {
|
} on Exception catch (e) {
|
||||||
await Sentry.captureException(e, stackTrace: s);
|
if (kDebugMode) {
|
||||||
|
print(e);
|
||||||
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -70,16 +74,18 @@ class RootAPI {
|
|||||||
|
|
||||||
Future<List<String>> getInstalledApps() async {
|
Future<List<String>> getInstalledApps() async {
|
||||||
try {
|
try {
|
||||||
String? res = await Root.exec(
|
final String? res = await Root.exec(
|
||||||
cmd: 'ls "$_managerDirPath"',
|
cmd: 'ls "$_managerDirPath"',
|
||||||
);
|
);
|
||||||
if (res != null) {
|
if (res != null) {
|
||||||
List<String> apps = res.split('\n');
|
final List<String> apps = res.split('\n');
|
||||||
apps.removeWhere((pack) => pack.isEmpty);
|
apps.removeWhere((pack) => pack.isEmpty);
|
||||||
return apps.map((pack) => pack.trim()).toList();
|
return apps.map((pack) => pack.trim()).toList();
|
||||||
}
|
}
|
||||||
} on Exception catch (e, s) {
|
} on Exception catch (e) {
|
||||||
await Sentry.captureException(e, stackTrace: s);
|
if (kDebugMode) {
|
||||||
|
print(e);
|
||||||
|
}
|
||||||
return List.empty();
|
return List.empty();
|
||||||
}
|
}
|
||||||
return List.empty();
|
return List.empty();
|
||||||
@@ -125,19 +131,21 @@ class RootAPI {
|
|||||||
await installApk(packageName, patchedFilePath);
|
await installApk(packageName, patchedFilePath);
|
||||||
await mountApk(packageName, originalFilePath);
|
await mountApk(packageName, originalFilePath);
|
||||||
return true;
|
return true;
|
||||||
} on Exception catch (e, s) {
|
} on Exception catch (e) {
|
||||||
await Sentry.captureException(e, stackTrace: s);
|
if (kDebugMode) {
|
||||||
|
print(e);
|
||||||
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> installServiceDScript(String packageName) async {
|
Future<void> installServiceDScript(String packageName) async {
|
||||||
String content = '#!/system/bin/sh\n'
|
final String content = '#!/system/bin/sh\n'
|
||||||
'while [ "\$(getprop sys.boot_completed | tr -d \'"\'"\'\\\\r\'"\'"\')" != "1" ]; do sleep 3; done\n'
|
'while [ "\$(getprop sys.boot_completed | tr -d \'"\'"\'\\\\r\'"\'"\')" != "1" ]; do sleep 3; done\n'
|
||||||
'base_path=$_managerDirPath/$packageName/base.apk\n'
|
'base_path=$_managerDirPath/$packageName/base.apk\n'
|
||||||
'stock_path=\$(pm path $packageName | grep base | sed \'"\'"\'s/package://g\'"\'"\')\n'
|
'stock_path=\$(pm path $packageName | grep base | sed \'"\'"\'s/package://g\'"\'"\')\n'
|
||||||
'[ ! -z \$stock_path ] && mount -o bind \$base_path \$stock_path';
|
r'[ ! -z $stock_path ] && mount -o bind $base_path $stock_path';
|
||||||
String scriptFilePath = '$_serviceDDirPath/$packageName.sh';
|
final String scriptFilePath = '$_serviceDDirPath/$packageName.sh';
|
||||||
await Root.exec(
|
await Root.exec(
|
||||||
cmd: 'echo \'$content\' > "$scriptFilePath"',
|
cmd: 'echo \'$content\' > "$scriptFilePath"',
|
||||||
);
|
);
|
||||||
@@ -145,10 +153,10 @@ class RootAPI {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<void> installPostFsDataScript(String packageName) async {
|
Future<void> installPostFsDataScript(String packageName) async {
|
||||||
String content = '#!/system/bin/sh\n'
|
final String content = '#!/system/bin/sh\n'
|
||||||
'stock_path=\$(pm path $packageName | grep base | sed \'"\'"\'s/package://g\'"\'"\')\n'
|
'stock_path=\$(pm path $packageName | grep base | sed \'"\'"\'s/package://g\'"\'"\')\n'
|
||||||
'[ ! -z \$stock_path ] && umount -l \$stock_path';
|
r'[ ! -z $stock_path ] && umount -l $stock_path';
|
||||||
String scriptFilePath = '$_postFsDataDirPath/$packageName.sh';
|
final String scriptFilePath = '$_postFsDataDirPath/$packageName.sh';
|
||||||
await Root.exec(
|
await Root.exec(
|
||||||
cmd: 'echo \'$content\' > "$scriptFilePath"',
|
cmd: 'echo \'$content\' > "$scriptFilePath"',
|
||||||
);
|
);
|
||||||
@@ -156,7 +164,7 @@ class RootAPI {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<void> installApk(String packageName, String patchedFilePath) async {
|
Future<void> installApk(String packageName, String patchedFilePath) async {
|
||||||
String newPatchedFilePath = '$_managerDirPath/$packageName/base.apk';
|
final String newPatchedFilePath = '$_managerDirPath/$packageName/base.apk';
|
||||||
await Root.exec(
|
await Root.exec(
|
||||||
cmd: 'cp "$patchedFilePath" "$newPatchedFilePath"',
|
cmd: 'cp "$patchedFilePath" "$newPatchedFilePath"',
|
||||||
);
|
);
|
||||||
@@ -169,7 +177,7 @@ class RootAPI {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<void> mountApk(String packageName, String originalFilePath) async {
|
Future<void> mountApk(String packageName, String originalFilePath) async {
|
||||||
String newPatchedFilePath = '$_managerDirPath/$packageName/base.apk';
|
final String newPatchedFilePath = '$_managerDirPath/$packageName/base.apk';
|
||||||
await Root.exec(
|
await Root.exec(
|
||||||
cmd: 'am force-stop "$packageName"',
|
cmd: 'am force-stop "$packageName"',
|
||||||
);
|
);
|
||||||
@@ -182,7 +190,7 @@ class RootAPI {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<bool> isMounted(String packageName) async {
|
Future<bool> isMounted(String packageName) async {
|
||||||
String? res = await Root.exec(
|
final String? res = await Root.exec(
|
||||||
cmd: 'cat /proc/mounts | grep $packageName',
|
cmd: 'cat /proc/mounts | grep $packageName',
|
||||||
);
|
);
|
||||||
return res != null && res.isNotEmpty;
|
return res != null && res.isNotEmpty;
|
||||||
@@ -192,7 +200,7 @@ class RootAPI {
|
|||||||
String packageName,
|
String packageName,
|
||||||
String originalFilePath,
|
String originalFilePath,
|
||||||
) async {
|
) async {
|
||||||
bool isInstalled = await isAppInstalled(packageName);
|
final bool isInstalled = await isAppInstalled(packageName);
|
||||||
if (isInstalled && await isMounted(packageName)) {
|
if (isInstalled && await isMounted(packageName)) {
|
||||||
originalFilePath = '$_managerDirPath/$packageName/original.apk';
|
originalFilePath = '$_managerDirPath/$packageName/original.apk';
|
||||||
await setPermissions(
|
await setPermissions(
|
||||||
@@ -209,7 +217,8 @@ class RootAPI {
|
|||||||
String packageName,
|
String packageName,
|
||||||
String originalFilePath,
|
String originalFilePath,
|
||||||
) async {
|
) async {
|
||||||
String originalRootPath = '$_managerDirPath/$packageName/original.apk';
|
final String originalRootPath =
|
||||||
|
'$_managerDirPath/$packageName/original.apk';
|
||||||
await Root.exec(
|
await Root.exec(
|
||||||
cmd: 'mkdir -p "$_managerDirPath/$packageName"',
|
cmd: 'mkdir -p "$_managerDirPath/$packageName"',
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ import 'package:google_fonts/google_fonts.dart';
|
|||||||
|
|
||||||
var lightCustomColorScheme = ColorScheme.fromSeed(
|
var lightCustomColorScheme = ColorScheme.fromSeed(
|
||||||
seedColor: Colors.blue,
|
seedColor: Colors.blue,
|
||||||
brightness: Brightness.light,
|
|
||||||
primary: const Color(0xff1B73E8),
|
primary: const Color(0xff1B73E8),
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -13,7 +12,7 @@ var lightCustomTheme = ThemeData(
|
|||||||
navigationBarTheme: NavigationBarThemeData(
|
navigationBarTheme: NavigationBarThemeData(
|
||||||
labelTextStyle: MaterialStateProperty.all(
|
labelTextStyle: MaterialStateProperty.all(
|
||||||
TextStyle(
|
TextStyle(
|
||||||
color: lightCustomColorScheme.secondary,
|
color: lightCustomColorScheme.onSurface,
|
||||||
fontWeight: FontWeight.w500,
|
fontWeight: FontWeight.w500,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -34,13 +33,12 @@ var darkCustomTheme = ThemeData(
|
|||||||
navigationBarTheme: NavigationBarThemeData(
|
navigationBarTheme: NavigationBarThemeData(
|
||||||
labelTextStyle: MaterialStateProperty.all(
|
labelTextStyle: MaterialStateProperty.all(
|
||||||
TextStyle(
|
TextStyle(
|
||||||
color: darkCustomColorScheme.secondary,
|
color: darkCustomColorScheme.onSurface,
|
||||||
fontWeight: FontWeight.w500,
|
fontWeight: FontWeight.w500,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
canvasColor: const Color(0xff1B1A1D),
|
canvasColor: const Color(0xff1B1A1D),
|
||||||
scaffoldBackgroundColor: const Color(0xff1B1A1D),
|
scaffoldBackgroundColor: const Color(0xff1B1A1D),
|
||||||
toggleableActiveColor: const Color(0xffA5CAFF),
|
|
||||||
textTheme: GoogleFonts.robotoTextTheme(ThemeData.dark().textTheme),
|
textTheme: GoogleFonts.robotoTextTheme(ThemeData.dark().textTheme),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -7,65 +7,44 @@ import 'package:revanced_manager/theme.dart';
|
|||||||
import 'package:stacked_services/stacked_services.dart';
|
import 'package:stacked_services/stacked_services.dart';
|
||||||
|
|
||||||
class DynamicThemeBuilder extends StatelessWidget {
|
class DynamicThemeBuilder extends StatelessWidget {
|
||||||
final String title;
|
|
||||||
final Widget home;
|
|
||||||
final Iterable<LocalizationsDelegate> localizationsDelegates;
|
|
||||||
|
|
||||||
const DynamicThemeBuilder({
|
const DynamicThemeBuilder({
|
||||||
Key? key,
|
Key? key,
|
||||||
required this.title,
|
required this.title,
|
||||||
required this.home,
|
required this.home,
|
||||||
required this.localizationsDelegates,
|
required this.localizationsDelegates,
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
final String title;
|
||||||
|
final Widget home;
|
||||||
|
final Iterable<LocalizationsDelegate> localizationsDelegates;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return DynamicColorBuilder(
|
return DynamicColorBuilder(
|
||||||
builder: (lightColorScheme, darkColorScheme) {
|
builder: (lightColorScheme, darkColorScheme) {
|
||||||
ThemeData lightDynamicTheme = ThemeData(
|
final ThemeData lightDynamicTheme = ThemeData(
|
||||||
useMaterial3: true,
|
useMaterial3: true,
|
||||||
canvasColor: lightColorScheme?.background,
|
|
||||||
navigationBarTheme: NavigationBarThemeData(
|
navigationBarTheme: NavigationBarThemeData(
|
||||||
backgroundColor: lightColorScheme?.background,
|
|
||||||
indicatorColor: lightColorScheme?.primary.withAlpha(150),
|
|
||||||
labelTextStyle: MaterialStateProperty.all(
|
labelTextStyle: MaterialStateProperty.all(
|
||||||
GoogleFonts.roboto(
|
GoogleFonts.roboto(
|
||||||
color: lightColorScheme?.secondary,
|
color: lightColorScheme?.onSurface,
|
||||||
fontWeight: FontWeight.w500,
|
fontWeight: FontWeight.w500,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
iconTheme: MaterialStateProperty.all(
|
|
||||||
IconThemeData(
|
|
||||||
color: lightColorScheme?.secondary,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
scaffoldBackgroundColor: lightColorScheme?.background,
|
|
||||||
colorScheme: lightColorScheme?.harmonized(),
|
colorScheme: lightColorScheme?.harmonized(),
|
||||||
toggleableActiveColor: lightColorScheme?.primary,
|
|
||||||
textTheme: GoogleFonts.robotoTextTheme(ThemeData.light().textTheme),
|
textTheme: GoogleFonts.robotoTextTheme(ThemeData.light().textTheme),
|
||||||
);
|
);
|
||||||
ThemeData darkDynamicTheme = ThemeData(
|
final ThemeData darkDynamicTheme = ThemeData(
|
||||||
useMaterial3: true,
|
useMaterial3: true,
|
||||||
canvasColor: darkColorScheme?.background,
|
|
||||||
navigationBarTheme: NavigationBarThemeData(
|
navigationBarTheme: NavigationBarThemeData(
|
||||||
backgroundColor: darkColorScheme?.background,
|
|
||||||
indicatorColor: darkColorScheme?.primary.withOpacity(0.4),
|
|
||||||
labelTextStyle: MaterialStateProperty.all(
|
labelTextStyle: MaterialStateProperty.all(
|
||||||
GoogleFonts.roboto(
|
GoogleFonts.roboto(
|
||||||
color: darkColorScheme?.secondary,
|
color: darkColorScheme?.onSurface,
|
||||||
fontWeight: FontWeight.w500,
|
fontWeight: FontWeight.w500,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
iconTheme: MaterialStateProperty.all(
|
|
||||||
IconThemeData(
|
|
||||||
color: darkColorScheme?.secondary,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
scaffoldBackgroundColor: darkColorScheme?.background,
|
|
||||||
colorScheme: darkColorScheme?.harmonized(),
|
colorScheme: darkColorScheme?.harmonized(),
|
||||||
toggleableActiveColor: darkColorScheme?.primary,
|
|
||||||
textTheme: GoogleFonts.robotoTextTheme(ThemeData.dark().textTheme),
|
textTheme: GoogleFonts.robotoTextTheme(ThemeData.dark().textTheme),
|
||||||
);
|
);
|
||||||
return DynamicTheme(
|
return DynamicTheme(
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_i18n/flutter_i18n.dart';
|
import 'package:flutter_i18n/flutter_i18n.dart';
|
||||||
|
import 'package:revanced_manager/ui/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/installed_app_item.dart';
|
||||||
import 'package:revanced_manager/ui/widgets/shared/search_bar.dart';
|
import 'package:revanced_manager/ui/widgets/shared/search_bar.dart';
|
||||||
import 'package:revanced_manager/ui/widgets/appSelectorView/app_skeleton_loader.dart';
|
|
||||||
import 'package:stacked/stacked.dart' hide SkeletonLoader;
|
import 'package:stacked/stacked.dart' hide SkeletonLoader;
|
||||||
import 'package:revanced_manager/ui/views/app_selector/app_selector_viewmodel.dart';
|
|
||||||
|
|
||||||
class AppSelectorView extends StatefulWidget {
|
class AppSelectorView extends StatefulWidget {
|
||||||
const AppSelectorView({Key? key}) : super(key: key);
|
const AppSelectorView({Key? key}) : super(key: key);
|
||||||
@@ -19,7 +19,7 @@ class _AppSelectorViewState extends State<AppSelectorView> {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return ViewModelBuilder<AppSelectorViewModel>.reactive(
|
return ViewModelBuilder<AppSelectorViewModel>.reactive(
|
||||||
onModelReady: (model) => model.initialize(),
|
onViewModelReady: (model) => model.initialize(),
|
||||||
viewModelBuilder: () => AppSelectorViewModel(),
|
viewModelBuilder: () => AppSelectorViewModel(),
|
||||||
builder: (context, model, child) => Scaffold(
|
builder: (context, model, child) => Scaffold(
|
||||||
resizeToAvoidBottomInset: false,
|
resizeToAvoidBottomInset: false,
|
||||||
@@ -36,20 +36,19 @@ class _AppSelectorViewState extends State<AppSelectorView> {
|
|||||||
SliverAppBar(
|
SliverAppBar(
|
||||||
pinned: true,
|
pinned: true,
|
||||||
floating: true,
|
floating: true,
|
||||||
snap: false,
|
|
||||||
title: I18nText(
|
title: I18nText(
|
||||||
'appSelectorView.viewTitle',
|
'appSelectorView.viewTitle',
|
||||||
child: Text(
|
child: Text(
|
||||||
'',
|
'',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: Theme.of(context).textTheme.headline6!.color,
|
color: Theme.of(context).textTheme.titleLarge!.color,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
leading: IconButton(
|
leading: IconButton(
|
||||||
icon: Icon(
|
icon: Icon(
|
||||||
Icons.arrow_back,
|
Icons.arrow_back,
|
||||||
color: Theme.of(context).textTheme.headline6!.color,
|
color: Theme.of(context).textTheme.titleLarge!.color,
|
||||||
),
|
),
|
||||||
onPressed: () => Navigator.of(context).pop(),
|
onPressed: () => Navigator.of(context).pop(),
|
||||||
),
|
),
|
||||||
@@ -61,7 +60,6 @@ class _AppSelectorViewState extends State<AppSelectorView> {
|
|||||||
horizontal: 12.0,
|
horizontal: 12.0,
|
||||||
),
|
),
|
||||||
child: SearchBar(
|
child: SearchBar(
|
||||||
showSelectIcon: false,
|
|
||||||
hintText: FlutterI18n.translate(
|
hintText: FlutterI18n.translate(
|
||||||
context,
|
context,
|
||||||
'appSelectorView.searchBarHint',
|
'appSelectorView.searchBarHint',
|
||||||
@@ -88,17 +86,19 @@ class _AppSelectorViewState extends State<AppSelectorView> {
|
|||||||
child: Column(
|
child: Column(
|
||||||
children: model
|
children: model
|
||||||
.getFilteredApps(_query)
|
.getFilteredApps(_query)
|
||||||
.map((app) => InstalledAppItem(
|
.map(
|
||||||
name: app.appName,
|
(app) => InstalledAppItem(
|
||||||
pkgName: app.packageName,
|
name: app.appName,
|
||||||
icon: app.icon,
|
pkgName: app.packageName,
|
||||||
patchesCount:
|
icon: app.icon,
|
||||||
model.patchesCount(app.packageName),
|
patchesCount:
|
||||||
onTap: () {
|
model.patchesCount(app.packageName),
|
||||||
model.selectApp(app);
|
onTap: () {
|
||||||
Navigator.of(context).pop();
|
model.selectApp(app);
|
||||||
},
|
Navigator.of(context).pop();
|
||||||
))
|
},
|
||||||
|
),
|
||||||
|
)
|
||||||
.toList(),
|
.toList(),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -1,15 +1,16 @@
|
|||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:device_apps/device_apps.dart';
|
import 'package:device_apps/device_apps.dart';
|
||||||
import 'package:file_picker/file_picker.dart';
|
import 'package:file_picker/file_picker.dart';
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:revanced_manager/app/app.locator.dart';
|
import 'package:revanced_manager/app/app.locator.dart';
|
||||||
import 'package:revanced_manager/models/patched_application.dart';
|
import 'package:revanced_manager/models/patched_application.dart';
|
||||||
|
import 'package:revanced_manager/services/manager_api.dart';
|
||||||
import 'package:revanced_manager/services/patcher_api.dart';
|
import 'package:revanced_manager/services/patcher_api.dart';
|
||||||
import 'package:revanced_manager/services/toast.dart';
|
import 'package:revanced_manager/services/toast.dart';
|
||||||
import 'package:revanced_manager/ui/views/patcher/patcher_viewmodel.dart';
|
import 'package:revanced_manager/ui/views/patcher/patcher_viewmodel.dart';
|
||||||
import 'package:sentry_flutter/sentry_flutter.dart';
|
|
||||||
import 'package:stacked/stacked.dart';
|
import 'package:stacked/stacked.dart';
|
||||||
import '../../../services/manager_api.dart';
|
|
||||||
|
|
||||||
class AppSelectorViewModel extends BaseViewModel {
|
class AppSelectorViewModel extends BaseViewModel {
|
||||||
final PatcherAPI _patcherAPI = locator<PatcherAPI>();
|
final PatcherAPI _patcherAPI = locator<PatcherAPI>();
|
||||||
@@ -22,16 +23,21 @@ class AppSelectorViewModel extends BaseViewModel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<void> initialize() async {
|
Future<void> initialize() async {
|
||||||
apps.addAll(await _patcherAPI.getFilteredInstalledApps(_managerAPI.areUniversalPatchesEnabled()));
|
apps.addAll(
|
||||||
apps.sort(((a, b) => _patcherAPI
|
await _patcherAPI
|
||||||
.getFilteredPatches(b.packageName)
|
.getFilteredInstalledApps(_managerAPI.areUniversalPatchesEnabled()),
|
||||||
.length
|
);
|
||||||
.compareTo(_patcherAPI.getFilteredPatches(a.packageName).length)));
|
apps.sort(
|
||||||
|
(a, b) => _patcherAPI
|
||||||
|
.getFilteredPatches(b.packageName)
|
||||||
|
.length
|
||||||
|
.compareTo(_patcherAPI.getFilteredPatches(a.packageName).length),
|
||||||
|
);
|
||||||
noApps = apps.isEmpty;
|
noApps = apps.isEmpty;
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
void selectApp(ApplicationWithIcon application) async {
|
Future<void> selectApp(ApplicationWithIcon application) async {
|
||||||
locator<PatcherViewModel>().selectedApp = PatchedApplication(
|
locator<PatcherViewModel>().selectedApp = PatchedApplication(
|
||||||
name: application.appName,
|
name: application.appName,
|
||||||
packageName: application.packageName,
|
packageName: application.packageName,
|
||||||
@@ -42,27 +48,28 @@ class AppSelectorViewModel extends BaseViewModel {
|
|||||||
patchDate: DateTime.now(),
|
patchDate: DateTime.now(),
|
||||||
);
|
);
|
||||||
locator<PatcherViewModel>().loadLastSelectedPatches();
|
locator<PatcherViewModel>().loadLastSelectedPatches();
|
||||||
locator<PatcherViewModel>().notifyListeners();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> selectAppFromStorage(BuildContext context) async {
|
Future<void> selectAppFromStorage(BuildContext context) async {
|
||||||
try {
|
try {
|
||||||
FilePickerResult? result = await FilePicker.platform.pickFiles(
|
final FilePickerResult? result = await FilePicker.platform.pickFiles(
|
||||||
type: FileType.custom,
|
type: FileType.custom,
|
||||||
allowedExtensions: ['apk'],
|
allowedExtensions: ['apk'],
|
||||||
);
|
);
|
||||||
if (result != null && result.files.single.path != null) {
|
if (result != null && result.files.single.path != null) {
|
||||||
File apkFile = File(result.files.single.path!);
|
final File apkFile = File(result.files.single.path!);
|
||||||
List<String> pathSplit = result.files.single.path!.split("/");
|
final List<String> pathSplit = result.files.single.path!.split('/');
|
||||||
pathSplit.removeLast();
|
pathSplit.removeLast();
|
||||||
Directory filePickerCacheDir = Directory(pathSplit.join("/"));
|
final Directory filePickerCacheDir = Directory(pathSplit.join('/'));
|
||||||
Iterable<File> deletableFiles =
|
final Iterable<File> deletableFiles =
|
||||||
(await filePickerCacheDir.list().toList()).whereType<File>();
|
(await filePickerCacheDir.list().toList()).whereType<File>();
|
||||||
for (var file in deletableFiles) {
|
for (final file in deletableFiles) {
|
||||||
if (file.path != apkFile.path && file.path.endsWith(".apk"))
|
if (file.path != apkFile.path && file.path.endsWith('.apk')) {
|
||||||
file.delete();
|
file.delete();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
ApplicationWithIcon? application = await DeviceApps.getAppFromStorage(
|
final ApplicationWithIcon? application =
|
||||||
|
await DeviceApps.getAppFromStorage(
|
||||||
apkFile.path,
|
apkFile.path,
|
||||||
true,
|
true,
|
||||||
) as ApplicationWithIcon?;
|
) as ApplicationWithIcon?;
|
||||||
@@ -78,21 +85,24 @@ class AppSelectorViewModel extends BaseViewModel {
|
|||||||
isFromStorage: true,
|
isFromStorage: true,
|
||||||
);
|
);
|
||||||
locator<PatcherViewModel>().loadLastSelectedPatches();
|
locator<PatcherViewModel>().loadLastSelectedPatches();
|
||||||
locator<PatcherViewModel>().notifyListeners();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} on Exception catch (e, s) {
|
} on Exception catch (e) {
|
||||||
await Sentry.captureException(e, stackTrace: s);
|
if (kDebugMode) {
|
||||||
|
print(e);
|
||||||
|
}
|
||||||
_toast.showBottom('appSelectorView.errorMessage');
|
_toast.showBottom('appSelectorView.errorMessage');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
List<ApplicationWithIcon> getFilteredApps(String query) {
|
List<ApplicationWithIcon> getFilteredApps(String query) {
|
||||||
return apps
|
return apps
|
||||||
.where((app) =>
|
.where(
|
||||||
query.isEmpty ||
|
(app) =>
|
||||||
query.length < 2 ||
|
query.isEmpty ||
|
||||||
app.appName.toLowerCase().contains(query.toLowerCase()))
|
query.length < 2 ||
|
||||||
|
app.appName.toLowerCase().contains(query.toLowerCase()),
|
||||||
|
)
|
||||||
.toList();
|
.toList();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ class ContributorsView extends StatelessWidget {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return ViewModelBuilder<ContributorsViewModel>.reactive(
|
return ViewModelBuilder<ContributorsViewModel>.reactive(
|
||||||
viewModelBuilder: () => ContributorsViewModel(),
|
viewModelBuilder: () => ContributorsViewModel(),
|
||||||
onModelReady: (model) => model.getContributors(),
|
onViewModelReady: (model) => model.getContributors(),
|
||||||
builder: (context, model, child) => Scaffold(
|
builder: (context, model, child) => Scaffold(
|
||||||
body: CustomScrollView(
|
body: CustomScrollView(
|
||||||
slivers: <Widget>[
|
slivers: <Widget>[
|
||||||
@@ -23,7 +23,7 @@ class ContributorsView extends StatelessWidget {
|
|||||||
child: Text(
|
child: Text(
|
||||||
'',
|
'',
|
||||||
style: GoogleFonts.inter(
|
style: GoogleFonts.inter(
|
||||||
color: Theme.of(context).textTheme.headline6!.color,
|
color: Theme.of(context).textTheme.titleLarge!.color,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ class ContributorsViewModel extends BaseViewModel {
|
|||||||
List<dynamic> managerContributors = [];
|
List<dynamic> managerContributors = [];
|
||||||
|
|
||||||
Future<void> getContributors() async {
|
Future<void> getContributors() async {
|
||||||
Map<String, List<dynamic>> contributors =
|
final Map<String, List<dynamic>> contributors =
|
||||||
await _managerAPI.getContributors();
|
await _managerAPI.getContributors();
|
||||||
patcherContributors = contributors[_managerAPI.defaultPatcherRepo] ?? [];
|
patcherContributors = contributors[_managerAPI.defaultPatcherRepo] ?? [];
|
||||||
patchesContributors = contributors[_managerAPI.getPatchesRepo()] ?? [];
|
patchesContributors = contributors[_managerAPI.getPatchesRepo()] ?? [];
|
||||||
|
|||||||
@@ -1,13 +1,12 @@
|
|||||||
|
import 'package:animations/animations.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_i18n/flutter_i18n.dart';
|
import 'package:flutter_i18n/flutter_i18n.dart';
|
||||||
import 'package:google_fonts/google_fonts.dart';
|
import 'package:google_fonts/google_fonts.dart';
|
||||||
import 'package:animations/animations.dart';
|
|
||||||
import 'package:revanced_manager/app/app.locator.dart';
|
import 'package:revanced_manager/app/app.locator.dart';
|
||||||
import 'package:revanced_manager/ui/views/home/home_viewmodel.dart';
|
import 'package:revanced_manager/ui/views/home/home_viewmodel.dart';
|
||||||
import 'package:revanced_manager/ui/widgets/homeView/available_updates_card.dart';
|
import 'package:revanced_manager/ui/widgets/homeView/available_updates_card.dart';
|
||||||
import 'package:revanced_manager/ui/widgets/homeView/installed_apps_card.dart';
|
import 'package:revanced_manager/ui/widgets/homeView/installed_apps_card.dart';
|
||||||
import 'package:revanced_manager/ui/widgets/homeView/latest_commit_card.dart';
|
import 'package:revanced_manager/ui/widgets/homeView/latest_commit_card.dart';
|
||||||
import 'package:revanced_manager/ui/widgets/shared/custom_chip.dart';
|
|
||||||
import 'package:revanced_manager/ui/widgets/shared/custom_sliver_app_bar.dart';
|
import 'package:revanced_manager/ui/widgets/shared/custom_sliver_app_bar.dart';
|
||||||
import 'package:stacked/stacked.dart';
|
import 'package:stacked/stacked.dart';
|
||||||
|
|
||||||
@@ -18,12 +17,10 @@ class HomeView extends StatelessWidget {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return ViewModelBuilder<HomeViewModel>.reactive(
|
return ViewModelBuilder<HomeViewModel>.reactive(
|
||||||
disposeViewModel: false,
|
disposeViewModel: false,
|
||||||
onModelReady: (model) => model.initialize(context),
|
onViewModelReady: (model) => model.initialize(context),
|
||||||
viewModelBuilder: () => locator<HomeViewModel>(),
|
viewModelBuilder: () => locator<HomeViewModel>(),
|
||||||
builder: (context, model, child) => Scaffold(
|
builder: (context, model, child) => Scaffold(
|
||||||
body: RefreshIndicator(
|
body: RefreshIndicator(
|
||||||
color: Theme.of(context).colorScheme.secondary,
|
|
||||||
backgroundColor: Theme.of(context).colorScheme.secondaryContainer,
|
|
||||||
onRefresh: () => model.forceRefresh(context),
|
onRefresh: () => model.forceRefresh(context),
|
||||||
child: CustomScrollView(
|
child: CustomScrollView(
|
||||||
slivers: <Widget>[
|
slivers: <Widget>[
|
||||||
@@ -34,7 +31,7 @@ class HomeView extends StatelessWidget {
|
|||||||
child: Text(
|
child: Text(
|
||||||
'',
|
'',
|
||||||
style: GoogleFonts.inter(
|
style: GoogleFonts.inter(
|
||||||
color: Theme.of(context).textTheme.headline6!.color,
|
color: Theme.of(context).textTheme.titleLarge!.color,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -48,7 +45,7 @@ class HomeView extends StatelessWidget {
|
|||||||
'homeView.updatesSubtitle',
|
'homeView.updatesSubtitle',
|
||||||
child: Text(
|
child: Text(
|
||||||
'',
|
'',
|
||||||
style: Theme.of(context).textTheme.headline6!,
|
style: Theme.of(context).textTheme.titleLarge,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 10),
|
const SizedBox(height: 10),
|
||||||
@@ -61,27 +58,53 @@ class HomeView extends StatelessWidget {
|
|||||||
'homeView.patchedSubtitle',
|
'homeView.patchedSubtitle',
|
||||||
child: Text(
|
child: Text(
|
||||||
'',
|
'',
|
||||||
style: Theme.of(context).textTheme.headline6!,
|
style: Theme.of(context).textTheme.titleLarge,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
Row(
|
Row(
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
CustomChip(
|
ActionChip(
|
||||||
|
avatar: const Icon(Icons.grid_view),
|
||||||
label: I18nText('homeView.installed'),
|
label: I18nText('homeView.installed'),
|
||||||
isSelected: !model.showUpdatableApps,
|
side: BorderSide(
|
||||||
onSelected: (value) {
|
color: model.showUpdatableApps
|
||||||
|
? Theme.of(context).colorScheme.outline
|
||||||
|
: Theme.of(context)
|
||||||
|
.colorScheme
|
||||||
|
.secondaryContainer,
|
||||||
|
width: model.showUpdatableApps ? 1 : 1,
|
||||||
|
),
|
||||||
|
backgroundColor: model.showUpdatableApps
|
||||||
|
? Theme.of(context).colorScheme.background
|
||||||
|
: Theme.of(context)
|
||||||
|
.colorScheme
|
||||||
|
.secondaryContainer,
|
||||||
|
onPressed: () {
|
||||||
model.toggleUpdatableApps(false);
|
model.toggleUpdatableApps(false);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
const SizedBox(width: 10),
|
const SizedBox(width: 10),
|
||||||
CustomChip(
|
ActionChip(
|
||||||
|
avatar: const Icon(Icons.update),
|
||||||
label: I18nText('homeView.updatesAvailable'),
|
label: I18nText('homeView.updatesAvailable'),
|
||||||
isSelected: model.showUpdatableApps,
|
side: BorderSide(
|
||||||
onSelected: (value) {
|
color: !model.showUpdatableApps
|
||||||
|
? Theme.of(context).colorScheme.outline
|
||||||
|
: Theme.of(context)
|
||||||
|
.colorScheme
|
||||||
|
.secondaryContainer,
|
||||||
|
width: !model.showUpdatableApps ? 1 : 1,
|
||||||
|
),
|
||||||
|
backgroundColor: !model.showUpdatableApps
|
||||||
|
? Theme.of(context).colorScheme.background
|
||||||
|
: Theme.of(context)
|
||||||
|
.colorScheme
|
||||||
|
.secondaryContainer,
|
||||||
|
onPressed: () {
|
||||||
model.toggleUpdatableApps(true);
|
model.toggleUpdatableApps(true);
|
||||||
},
|
},
|
||||||
)
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
const SizedBox(height: 14),
|
const SizedBox(height: 14),
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
// ignore_for_file: use_build_context_synchronously
|
// ignore_for_file: use_build_context_synchronously
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:app_installer/app_installer.dart';
|
import 'package:app_installer/app_installer.dart';
|
||||||
import 'package:cross_connectivity/cross_connectivity.dart';
|
import 'package:cross_connectivity/cross_connectivity.dart';
|
||||||
import 'package:device_apps/device_apps.dart';
|
import 'package:device_apps/device_apps.dart';
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_i18n/flutter_i18n.dart';
|
import 'package:flutter_i18n/flutter_i18n.dart';
|
||||||
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
|
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
|
||||||
@@ -10,14 +12,13 @@ import 'package:injectable/injectable.dart';
|
|||||||
import 'package:revanced_manager/app/app.locator.dart';
|
import 'package:revanced_manager/app/app.locator.dart';
|
||||||
import 'package:revanced_manager/app/app.router.dart';
|
import 'package:revanced_manager/app/app.router.dart';
|
||||||
import 'package:revanced_manager/models/patched_application.dart';
|
import 'package:revanced_manager/models/patched_application.dart';
|
||||||
|
import 'package:revanced_manager/services/github_api.dart';
|
||||||
import 'package:revanced_manager/services/manager_api.dart';
|
import 'package:revanced_manager/services/manager_api.dart';
|
||||||
import 'package:revanced_manager/services/patcher_api.dart';
|
import 'package:revanced_manager/services/patcher_api.dart';
|
||||||
import 'package:revanced_manager/services/github_api.dart';
|
|
||||||
import 'package:revanced_manager/services/toast.dart';
|
import 'package:revanced_manager/services/toast.dart';
|
||||||
import 'package:revanced_manager/ui/views/navigation/navigation_viewmodel.dart';
|
import 'package:revanced_manager/ui/views/navigation/navigation_viewmodel.dart';
|
||||||
import 'package:revanced_manager/ui/views/patcher/patcher_viewmodel.dart';
|
import 'package:revanced_manager/ui/views/patcher/patcher_viewmodel.dart';
|
||||||
import 'package:revanced_manager/ui/widgets/homeView/update_confirmation_dialog.dart';
|
import 'package:revanced_manager/ui/widgets/homeView/update_confirmation_dialog.dart';
|
||||||
import 'package:sentry_flutter/sentry_flutter.dart';
|
|
||||||
import 'package:stacked/stacked.dart';
|
import 'package:stacked/stacked.dart';
|
||||||
import 'package:stacked_services/stacked_services.dart';
|
import 'package:stacked_services/stacked_services.dart';
|
||||||
import 'package:timezone/timezone.dart' as tz;
|
import 'package:timezone/timezone.dart' as tz;
|
||||||
@@ -47,7 +48,7 @@ class HomeViewModel extends BaseViewModel {
|
|||||||
.resolvePlatformSpecificImplementation<
|
.resolvePlatformSpecificImplementation<
|
||||||
AndroidFlutterLocalNotificationsPlugin>()
|
AndroidFlutterLocalNotificationsPlugin>()
|
||||||
?.requestPermission();
|
?.requestPermission();
|
||||||
bool isConnected = await Connectivity().checkConnection();
|
final bool isConnected = await Connectivity().checkConnection();
|
||||||
if (!isConnected) {
|
if (!isConnected) {
|
||||||
_toast.showBottom('homeView.noConnection');
|
_toast.showBottom('homeView.noConnection');
|
||||||
}
|
}
|
||||||
@@ -67,7 +68,7 @@ class HomeViewModel extends BaseViewModel {
|
|||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
void navigateToPatcher(PatchedApplication app) async {
|
Future<void> navigateToPatcher(PatchedApplication app) async {
|
||||||
locator<PatcherViewModel>().selectedApp = app;
|
locator<PatcherViewModel>().selectedApp = app;
|
||||||
locator<PatcherViewModel>().selectedPatches =
|
locator<PatcherViewModel>().selectedPatches =
|
||||||
await _patcherAPI.getAppliedPatches(app.appliedPatches);
|
await _patcherAPI.getAppliedPatches(app.appliedPatches);
|
||||||
@@ -76,10 +77,7 @@ class HomeViewModel extends BaseViewModel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void _getPatchedApps() {
|
void _getPatchedApps() {
|
||||||
patchedInstalledApps = _managerAPI
|
patchedInstalledApps = _managerAPI.getPatchedApps().toList();
|
||||||
.getPatchedApps()
|
|
||||||
.where((app) => app.hasUpdates == false)
|
|
||||||
.toList();
|
|
||||||
patchedUpdatableApps = _managerAPI
|
patchedUpdatableApps = _managerAPI
|
||||||
.getPatchedApps()
|
.getPatchedApps()
|
||||||
.where((app) => app.hasUpdates == true)
|
.where((app) => app.hasUpdates == true)
|
||||||
@@ -88,17 +86,19 @@ class HomeViewModel extends BaseViewModel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<bool> hasManagerUpdates() async {
|
Future<bool> hasManagerUpdates() async {
|
||||||
String? latestVersion = await _managerAPI.getLatestManagerVersion();
|
final String? latestVersion = await _managerAPI.getLatestManagerVersion();
|
||||||
String currentVersion = await _managerAPI.getCurrentManagerVersion();
|
final String currentVersion = await _managerAPI.getCurrentManagerVersion();
|
||||||
if (latestVersion != null) {
|
if (latestVersion != null) {
|
||||||
try {
|
try {
|
||||||
int latestVersionInt =
|
final int latestVersionInt =
|
||||||
int.parse(latestVersion.replaceAll(RegExp('[^0-9]'), ''));
|
int.parse(latestVersion.replaceAll(RegExp('[^0-9]'), ''));
|
||||||
int currentVersionInt =
|
final int currentVersionInt =
|
||||||
int.parse(currentVersion.replaceAll(RegExp('[^0-9]'), ''));
|
int.parse(currentVersion.replaceAll(RegExp('[^0-9]'), ''));
|
||||||
return latestVersionInt > currentVersionInt;
|
return latestVersionInt > currentVersionInt;
|
||||||
} on Exception catch (e, s) {
|
} on Exception catch (e) {
|
||||||
await Sentry.captureException(e, stackTrace: s);
|
if (kDebugMode) {
|
||||||
|
print(e);
|
||||||
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -108,7 +108,7 @@ class HomeViewModel extends BaseViewModel {
|
|||||||
Future<void> updateManager(BuildContext context) async {
|
Future<void> updateManager(BuildContext context) async {
|
||||||
try {
|
try {
|
||||||
_toast.showBottom('homeView.downloadingMessage');
|
_toast.showBottom('homeView.downloadingMessage');
|
||||||
File? managerApk = await _managerAPI.downloadManager();
|
final File? managerApk = await _managerAPI.downloadManager();
|
||||||
if (managerApk != null) {
|
if (managerApk != null) {
|
||||||
await flutterLocalNotificationsPlugin.zonedSchedule(
|
await flutterLocalNotificationsPlugin.zonedSchedule(
|
||||||
0,
|
0,
|
||||||
@@ -139,8 +139,10 @@ class HomeViewModel extends BaseViewModel {
|
|||||||
} else {
|
} else {
|
||||||
_toast.showBottom('homeView.errorDownloadMessage');
|
_toast.showBottom('homeView.errorDownloadMessage');
|
||||||
}
|
}
|
||||||
} on Exception catch (e, s) {
|
} on Exception catch (e) {
|
||||||
await Sentry.captureException(e, stackTrace: s);
|
if (kDebugMode) {
|
||||||
|
print(e);
|
||||||
|
}
|
||||||
_toast.showBottom('homeView.errorInstallMessage');
|
_toast.showBottom('homeView.errorInstallMessage');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,9 +2,9 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:flutter_i18n/flutter_i18n.dart';
|
import 'package:flutter_i18n/flutter_i18n.dart';
|
||||||
import 'package:google_fonts/google_fonts.dart';
|
import 'package:google_fonts/google_fonts.dart';
|
||||||
import 'package:revanced_manager/ui/views/installer/installer_viewmodel.dart';
|
import 'package:revanced_manager/ui/views/installer/installer_viewmodel.dart';
|
||||||
import 'package:revanced_manager/ui/widgets/shared/custom_material_button.dart';
|
|
||||||
import 'package:revanced_manager/ui/widgets/installerView/gradient_progress_indicator.dart';
|
import 'package:revanced_manager/ui/widgets/installerView/gradient_progress_indicator.dart';
|
||||||
import 'package:revanced_manager/ui/widgets/shared/custom_card.dart';
|
import 'package:revanced_manager/ui/widgets/shared/custom_card.dart';
|
||||||
|
import 'package:revanced_manager/ui/widgets/shared/custom_material_button.dart';
|
||||||
import 'package:revanced_manager/ui/widgets/shared/custom_popup_menu.dart';
|
import 'package:revanced_manager/ui/widgets/shared/custom_popup_menu.dart';
|
||||||
import 'package:revanced_manager/ui/widgets/shared/custom_sliver_app_bar.dart';
|
import 'package:revanced_manager/ui/widgets/shared/custom_sliver_app_bar.dart';
|
||||||
import 'package:stacked/stacked.dart';
|
import 'package:stacked/stacked.dart';
|
||||||
@@ -15,7 +15,7 @@ class InstallerView extends StatelessWidget {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return ViewModelBuilder<InstallerViewModel>.reactive(
|
return ViewModelBuilder<InstallerViewModel>.reactive(
|
||||||
onModelReady: (model) => model.initialize(context),
|
onViewModelReady: (model) => model.initialize(context),
|
||||||
viewModelBuilder: () => InstallerViewModel(),
|
viewModelBuilder: () => InstallerViewModel(),
|
||||||
builder: (context, model, child) => WillPopScope(
|
builder: (context, model, child) => WillPopScope(
|
||||||
child: SafeArea(
|
child: SafeArea(
|
||||||
@@ -28,7 +28,7 @@ class InstallerView extends StatelessWidget {
|
|||||||
title: Text(
|
title: Text(
|
||||||
model.headerLogs,
|
model.headerLogs,
|
||||||
style: GoogleFonts.inter(
|
style: GoogleFonts.inter(
|
||||||
color: Theme.of(context).textTheme.headline6!.color,
|
color: Theme.of(context).textTheme.titleLarge!.color,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
onBackButtonPressed: () => model.onWillPop(context),
|
onBackButtonPressed: () => model.onWillPop(context),
|
||||||
@@ -48,15 +48,15 @@ class InstallerView extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
1: I18nText(
|
1: I18nText(
|
||||||
'installerView.exportApkMenuOption',
|
'installerView.exportApkMenuOption',
|
||||||
child: const Text(
|
child: const Text(
|
||||||
'',
|
'',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
),
|
||||||
2: I18nText(
|
2: I18nText(
|
||||||
'installerView.shareLogMenuOption',
|
'installerView.shareLogMenuOption',
|
||||||
child: const Text(
|
child: const Text(
|
||||||
@@ -71,9 +71,9 @@ class InstallerView extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
],
|
],
|
||||||
bottom: PreferredSize(
|
bottom: PreferredSize(
|
||||||
preferredSize: const Size(double.infinity, 1.0),
|
preferredSize: const Size(double.infinity, 1.0),
|
||||||
child:
|
child: GradientProgressIndicator(progress: model.progress),
|
||||||
GradientProgressIndicator(progress: model.progress!)),
|
),
|
||||||
),
|
),
|
||||||
SliverPadding(
|
SliverPadding(
|
||||||
padding: const EdgeInsets.all(20.0),
|
padding: const EdgeInsets.all(20.0),
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
// ignore_for_file: use_build_context_synchronously
|
// ignore_for_file: use_build_context_synchronously
|
||||||
import 'package:device_apps/device_apps.dart';
|
import 'package:device_apps/device_apps.dart';
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:flutter_background/flutter_background.dart';
|
import 'package:flutter_background/flutter_background.dart';
|
||||||
@@ -14,7 +15,6 @@ import 'package:revanced_manager/services/root_api.dart';
|
|||||||
import 'package:revanced_manager/services/toast.dart';
|
import 'package:revanced_manager/services/toast.dart';
|
||||||
import 'package:revanced_manager/ui/views/patcher/patcher_viewmodel.dart';
|
import 'package:revanced_manager/ui/views/patcher/patcher_viewmodel.dart';
|
||||||
import 'package:revanced_manager/ui/widgets/shared/custom_material_button.dart';
|
import 'package:revanced_manager/ui/widgets/shared/custom_material_button.dart';
|
||||||
import 'package:sentry_flutter/sentry_flutter.dart';
|
|
||||||
import 'package:stacked/stacked.dart';
|
import 'package:stacked/stacked.dart';
|
||||||
import 'package:wakelock/wakelock.dart';
|
import 'package:wakelock/wakelock.dart';
|
||||||
|
|
||||||
@@ -51,16 +51,15 @@ class InstallerViewModel extends BaseViewModel {
|
|||||||
context,
|
context,
|
||||||
'installerView.notificationText',
|
'installerView.notificationText',
|
||||||
),
|
),
|
||||||
notificationImportance: AndroidNotificationImportance.Default,
|
|
||||||
notificationIcon: const AndroidResource(
|
notificationIcon: const AndroidResource(
|
||||||
name: 'ic_notification',
|
name: 'ic_notification',
|
||||||
defType: 'drawable',
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
).then((value) => FlutterBackground.enableBackgroundExecution());
|
).then((value) => FlutterBackground.enableBackgroundExecution());
|
||||||
} on Exception catch (e, s) {
|
} on Exception catch (e) {
|
||||||
await Sentry.captureException(e, stackTrace: s);
|
if (kDebugMode) {
|
||||||
// ignore
|
print(e);
|
||||||
|
} // ignore
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
await Wakelock.enable();
|
await Wakelock.enable();
|
||||||
@@ -73,10 +72,10 @@ class InstallerViewModel extends BaseViewModel {
|
|||||||
switch (call.method) {
|
switch (call.method) {
|
||||||
case 'update':
|
case 'update':
|
||||||
if (call.arguments != null) {
|
if (call.arguments != null) {
|
||||||
Map<dynamic, dynamic> arguments = call.arguments;
|
final Map<dynamic, dynamic> arguments = call.arguments;
|
||||||
double progress = arguments['progress'];
|
final double progress = arguments['progress'];
|
||||||
String header = arguments['header'];
|
final String header = arguments['header'];
|
||||||
String log = arguments['log'];
|
final String log = arguments['log'];
|
||||||
update(progress, header, log);
|
update(progress, header, log);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
@@ -133,14 +132,15 @@ class InstallerViewModel extends BaseViewModel {
|
|||||||
_app.apkFilePath,
|
_app.apkFilePath,
|
||||||
_patches,
|
_patches,
|
||||||
);
|
);
|
||||||
} on Exception catch (e, s) {
|
} on Exception catch (e) {
|
||||||
update(
|
update(
|
||||||
-100.0,
|
-100.0,
|
||||||
'Aborting...',
|
'Aborting...',
|
||||||
'An error occurred! Aborting\nError:\n$e',
|
'An error occurred! Aborting\nError:\n$e',
|
||||||
);
|
);
|
||||||
await Sentry.captureException(e, stackTrace: s);
|
if (kDebugMode) {
|
||||||
throw await Sentry.captureException(e, stackTrace: s);
|
print(e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
update(-100.0, 'Aborting...', 'No app or patches selected! Aborting');
|
update(-100.0, 'Aborting...', 'No app or patches selected! Aborting');
|
||||||
@@ -148,24 +148,28 @@ class InstallerViewModel extends BaseViewModel {
|
|||||||
if (FlutterBackground.isBackgroundExecutionEnabled) {
|
if (FlutterBackground.isBackgroundExecutionEnabled) {
|
||||||
try {
|
try {
|
||||||
FlutterBackground.disableBackgroundExecution();
|
FlutterBackground.disableBackgroundExecution();
|
||||||
} on Exception catch (e, s) {
|
} on Exception catch (e) {
|
||||||
await Sentry.captureException(e, stackTrace: s);
|
if (kDebugMode) {
|
||||||
// ignore
|
print(e);
|
||||||
|
} // ignore
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
await Wakelock.disable();
|
await Wakelock.disable();
|
||||||
} on Exception catch (e, s) {
|
} on Exception catch (e) {
|
||||||
await Sentry.captureException(e, stackTrace: s);
|
if (kDebugMode) {
|
||||||
|
print(e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void installResult(BuildContext context, bool installAsRoot) async {
|
Future<void> installResult(BuildContext context, bool installAsRoot) async {
|
||||||
try {
|
try {
|
||||||
_app.isRooted = installAsRoot;
|
_app.isRooted = installAsRoot;
|
||||||
bool hasMicroG = _patches.any((p) => p.name.endsWith('microg-support'));
|
final bool hasMicroG =
|
||||||
bool rootMicroG = installAsRoot && hasMicroG;
|
_patches.any((p) => p.name.endsWith('microg-support'));
|
||||||
bool rootFromStorage = installAsRoot && _app.isFromStorage;
|
final bool rootMicroG = installAsRoot && hasMicroG;
|
||||||
bool ytWithoutRootMicroG =
|
final bool rootFromStorage = installAsRoot && _app.isFromStorage;
|
||||||
|
final bool ytWithoutRootMicroG =
|
||||||
!installAsRoot && !hasMicroG && _app.packageName.contains('youtube');
|
!installAsRoot && !hasMicroG && _app.packageName.contains('youtube');
|
||||||
if (rootMicroG || rootFromStorage || ytWithoutRootMicroG) {
|
if (rootMicroG || rootFromStorage || ytWithoutRootMicroG) {
|
||||||
return showDialog(
|
return showDialog(
|
||||||
@@ -212,24 +216,30 @@ class InstallerViewModel extends BaseViewModel {
|
|||||||
await _managerAPI.savePatchedApp(_app);
|
await _managerAPI.savePatchedApp(_app);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} on Exception catch (e, s) {
|
} on Exception catch (e) {
|
||||||
await Sentry.captureException(e, stackTrace: s);
|
if (kDebugMode) {
|
||||||
|
print(e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void exportResult() {
|
void exportResult() {
|
||||||
try {
|
try {
|
||||||
_patcherAPI.exportPatchedFile(_app.name, _app.version);
|
_patcherAPI.exportPatchedFile(_app.name, _app.version);
|
||||||
} on Exception catch (e, s) {
|
} on Exception catch (e) {
|
||||||
Sentry.captureException(e, stackTrace: s);
|
if (kDebugMode) {
|
||||||
|
print(e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void shareResult() {
|
void shareResult() {
|
||||||
try {
|
try {
|
||||||
_patcherAPI.sharePatchedFile(_app.name, _app.version);
|
_patcherAPI.sharePatchedFile(_app.name, _app.version);
|
||||||
} on Exception catch (e, s) {
|
} on Exception catch (e) {
|
||||||
Sentry.captureException(e, stackTrace: s);
|
if (kDebugMode) {
|
||||||
|
print(e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -243,8 +253,10 @@ class InstallerViewModel extends BaseViewModel {
|
|||||||
locator<PatcherViewModel>().selectedApp = null;
|
locator<PatcherViewModel>().selectedApp = null;
|
||||||
locator<PatcherViewModel>().selectedPatches.clear();
|
locator<PatcherViewModel>().selectedPatches.clear();
|
||||||
locator<PatcherViewModel>().notifyListeners();
|
locator<PatcherViewModel>().notifyListeners();
|
||||||
} on Exception catch (e, s) {
|
} on Exception catch (e) {
|
||||||
await Sentry.captureException(e, stackTrace: s);
|
if (kDebugMode) {
|
||||||
|
print(e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ class NavigationView extends StatelessWidget {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return ViewModelBuilder<NavigationViewModel>.reactive(
|
return ViewModelBuilder<NavigationViewModel>.reactive(
|
||||||
onModelReady: (model) => model.initialize(context),
|
onViewModelReady: (model) => model.initialize(context),
|
||||||
viewModelBuilder: () => locator<NavigationViewModel>(),
|
viewModelBuilder: () => locator<NavigationViewModel>(),
|
||||||
builder: (context, model, child) => Scaffold(
|
builder: (context, model, child) => Scaffold(
|
||||||
body: PageTransitionSwitcher(
|
body: PageTransitionSwitcher(
|
||||||
@@ -42,7 +42,6 @@ class NavigationView extends StatelessWidget {
|
|||||||
context,
|
context,
|
||||||
'navigationView.dashboardTab',
|
'navigationView.dashboardTab',
|
||||||
),
|
),
|
||||||
tooltip: '',
|
|
||||||
),
|
),
|
||||||
NavigationDestination(
|
NavigationDestination(
|
||||||
icon: model.isIndexSelected(1)
|
icon: model.isIndexSelected(1)
|
||||||
@@ -52,7 +51,6 @@ class NavigationView extends StatelessWidget {
|
|||||||
context,
|
context,
|
||||||
'navigationView.patcherTab',
|
'navigationView.patcherTab',
|
||||||
),
|
),
|
||||||
tooltip: '',
|
|
||||||
),
|
),
|
||||||
NavigationDestination(
|
NavigationDestination(
|
||||||
icon: model.isIndexSelected(2)
|
icon: model.isIndexSelected(2)
|
||||||
@@ -62,7 +60,6 @@ class NavigationView extends StatelessWidget {
|
|||||||
context,
|
context,
|
||||||
'navigationView.settingsTab',
|
'navigationView.settingsTab',
|
||||||
),
|
),
|
||||||
tooltip: '',
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -15,9 +15,9 @@ import 'package:stacked/stacked.dart';
|
|||||||
|
|
||||||
@lazySingleton
|
@lazySingleton
|
||||||
class NavigationViewModel extends IndexTrackingViewModel {
|
class NavigationViewModel extends IndexTrackingViewModel {
|
||||||
void initialize(BuildContext context) async {
|
Future<void> initialize(BuildContext context) async {
|
||||||
locator<Toast>().initialize(context);
|
locator<Toast>().initialize(context);
|
||||||
SharedPreferences prefs = await SharedPreferences.getInstance();
|
final SharedPreferences prefs = await SharedPreferences.getInstance();
|
||||||
if (prefs.getBool('permissionsRequested') == null) {
|
if (prefs.getBool('permissionsRequested') == null) {
|
||||||
await prefs.setBool('permissionsRequested', true);
|
await prefs.setBool('permissionsRequested', true);
|
||||||
RootAPI().hasRootPermissions().then(
|
RootAPI().hasRootPermissions().then(
|
||||||
@@ -27,7 +27,7 @@ class NavigationViewModel extends IndexTrackingViewModel {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (prefs.getBool('useDarkTheme') == null) {
|
if (prefs.getBool('useDarkTheme') == null) {
|
||||||
bool isDark =
|
final bool isDark =
|
||||||
MediaQuery.of(context).platformBrightness != Brightness.light;
|
MediaQuery.of(context).platformBrightness != Brightness.light;
|
||||||
await prefs.setBool('useDarkTheme', isDark);
|
await prefs.setBool('useDarkTheme', isDark);
|
||||||
await DynamicTheme.of(context)!.setTheme(isDark ? 1 : 0);
|
await DynamicTheme.of(context)!.setTheme(isDark ? 1 : 0);
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ class PatcherView extends StatelessWidget {
|
|||||||
child: Text(
|
child: Text(
|
||||||
'',
|
'',
|
||||||
style: GoogleFonts.inter(
|
style: GoogleFonts.inter(
|
||||||
color: Theme.of(context).textTheme.headline6!.color,
|
color: Theme.of(context).textTheme.titleLarge!.color,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -40,43 +40,45 @@ class PatcherViewModel extends BaseViewModel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<bool> isValidPatchConfig() async {
|
Future<bool> isValidPatchConfig() async {
|
||||||
bool needsResourcePatching = await _patcherAPI.needsResourcePatching(
|
final bool needsResourcePatching = await _patcherAPI.needsResourcePatching(
|
||||||
selectedPatches,
|
selectedPatches,
|
||||||
);
|
);
|
||||||
if (needsResourcePatching && selectedApp != null) {
|
if (needsResourcePatching && selectedApp != null) {
|
||||||
bool isSplit = await _managerAPI.isSplitApk(selectedApp!);
|
final bool isSplit = await _managerAPI.isSplitApk(selectedApp!);
|
||||||
return !isSplit;
|
return !isSplit;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> showPatchConfirmationDialog(BuildContext context) async {
|
Future<void> showPatchConfirmationDialog(BuildContext context) async {
|
||||||
bool isValid = await isValidPatchConfig();
|
final bool isValid = await isValidPatchConfig();
|
||||||
if (isValid) {
|
if (context.mounted) {
|
||||||
navigateToInstaller();
|
if (isValid) {
|
||||||
} else {
|
navigateToInstaller();
|
||||||
return showDialog(
|
} else {
|
||||||
context: context,
|
return showDialog(
|
||||||
builder: (context) => AlertDialog(
|
context: context,
|
||||||
title: I18nText('warning'),
|
builder: (context) => AlertDialog(
|
||||||
backgroundColor: Theme.of(context).colorScheme.secondaryContainer,
|
title: I18nText('warning'),
|
||||||
content: I18nText('patcherView.patchDialogText'),
|
backgroundColor: Theme.of(context).colorScheme.secondaryContainer,
|
||||||
actions: <Widget>[
|
content: I18nText('patcherView.patchDialogText'),
|
||||||
CustomMaterialButton(
|
actions: <Widget>[
|
||||||
isFilled: false,
|
CustomMaterialButton(
|
||||||
label: I18nText('noButton'),
|
isFilled: false,
|
||||||
onPressed: () => Navigator.of(context).pop(),
|
label: I18nText('noButton'),
|
||||||
),
|
onPressed: () => Navigator.of(context).pop(),
|
||||||
CustomMaterialButton(
|
),
|
||||||
label: I18nText('yesButton'),
|
CustomMaterialButton(
|
||||||
onPressed: () {
|
label: I18nText('yesButton'),
|
||||||
Navigator.of(context).pop();
|
onPressed: () {
|
||||||
navigateToInstaller();
|
Navigator.of(context).pop();
|
||||||
},
|
navigateToInstaller();
|
||||||
)
|
},
|
||||||
],
|
)
|
||||||
),
|
],
|
||||||
);
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -110,9 +112,9 @@ class PatcherViewModel extends BaseViewModel {
|
|||||||
|
|
||||||
Future<void> loadLastSelectedPatches() async {
|
Future<void> loadLastSelectedPatches() async {
|
||||||
this.selectedPatches.clear();
|
this.selectedPatches.clear();
|
||||||
List<String> selectedPatches =
|
final List<String> selectedPatches =
|
||||||
await _managerAPI.getSelectedPatches(selectedApp!.originalPackageName);
|
await _managerAPI.getSelectedPatches(selectedApp!.originalPackageName);
|
||||||
List<Patch> patches =
|
final List<Patch> patches =
|
||||||
_patcherAPI.getFilteredPatches(selectedApp!.originalPackageName);
|
_patcherAPI.getFilteredPatches(selectedApp!.originalPackageName);
|
||||||
this
|
this
|
||||||
.selectedPatches
|
.selectedPatches
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:flutter_i18n/flutter_i18n.dart';
|
import 'package:flutter_i18n/flutter_i18n.dart';
|
||||||
import 'package:revanced_manager/ui/views/patches_selector/patches_selector_viewmodel.dart';
|
import 'package:revanced_manager/ui/views/patches_selector/patches_selector_viewmodel.dart';
|
||||||
import 'package:revanced_manager/ui/widgets/patchesSelectorView/patch_item.dart';
|
import 'package:revanced_manager/ui/widgets/patchesSelectorView/patch_item.dart';
|
||||||
import 'package:revanced_manager/ui/widgets/shared/custom_chip.dart';
|
|
||||||
import 'package:revanced_manager/ui/widgets/shared/custom_popup_menu.dart';
|
import 'package:revanced_manager/ui/widgets/shared/custom_popup_menu.dart';
|
||||||
import 'package:revanced_manager/ui/widgets/shared/search_bar.dart';
|
import 'package:revanced_manager/ui/widgets/shared/search_bar.dart';
|
||||||
import 'package:stacked/stacked.dart';
|
import 'package:stacked/stacked.dart';
|
||||||
@@ -20,7 +19,7 @@ class _PatchesSelectorViewState extends State<PatchesSelectorView> {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return ViewModelBuilder<PatchesSelectorViewModel>.reactive(
|
return ViewModelBuilder<PatchesSelectorViewModel>.reactive(
|
||||||
onModelReady: (model) => model.initialize(),
|
onViewModelReady: (model) => model.initialize(),
|
||||||
viewModelBuilder: () => PatchesSelectorViewModel(),
|
viewModelBuilder: () => PatchesSelectorViewModel(),
|
||||||
builder: (context, model, child) => Scaffold(
|
builder: (context, model, child) => Scaffold(
|
||||||
resizeToAvoidBottomInset: false,
|
resizeToAvoidBottomInset: false,
|
||||||
@@ -45,26 +44,24 @@ class _PatchesSelectorViewState extends State<PatchesSelectorView> {
|
|||||||
SliverAppBar(
|
SliverAppBar(
|
||||||
pinned: true,
|
pinned: true,
|
||||||
floating: true,
|
floating: true,
|
||||||
snap: false,
|
|
||||||
title: I18nText(
|
title: I18nText(
|
||||||
'patchesSelectorView.viewTitle',
|
'patchesSelectorView.viewTitle',
|
||||||
child: Text(
|
child: Text(
|
||||||
'',
|
'',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: Theme.of(context).textTheme.headline6!.color,
|
color: Theme.of(context).textTheme.titleLarge!.color,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
leading: IconButton(
|
leading: IconButton(
|
||||||
icon: Icon(
|
icon: Icon(
|
||||||
Icons.arrow_back,
|
Icons.arrow_back,
|
||||||
color: Theme.of(context).textTheme.headline6!.color,
|
color: Theme.of(context).textTheme.titleLarge!.color,
|
||||||
),
|
),
|
||||||
onPressed: () => Navigator.of(context).pop(),
|
onPressed: () => Navigator.of(context).pop(),
|
||||||
),
|
),
|
||||||
actions: [
|
actions: [
|
||||||
Container(
|
Container(
|
||||||
height: 2,
|
|
||||||
margin: const EdgeInsets.only(top: 12, bottom: 12),
|
margin: const EdgeInsets.only(top: 12, bottom: 12),
|
||||||
padding:
|
padding:
|
||||||
const EdgeInsets.symmetric(horizontal: 6, vertical: 6),
|
const EdgeInsets.symmetric(horizontal: 6, vertical: 6),
|
||||||
@@ -76,7 +73,7 @@ class _PatchesSelectorViewState extends State<PatchesSelectorView> {
|
|||||||
child: Text(
|
child: Text(
|
||||||
model.patchesVersion!,
|
model.patchesVersion!,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: Theme.of(context).textTheme.headline6!.color,
|
color: Theme.of(context).textTheme.titleLarge!.color,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -103,7 +100,6 @@ class _PatchesSelectorViewState extends State<PatchesSelectorView> {
|
|||||||
horizontal: 12.0,
|
horizontal: 12.0,
|
||||||
),
|
),
|
||||||
child: SearchBar(
|
child: SearchBar(
|
||||||
showSelectIcon: true,
|
|
||||||
hintText: FlutterI18n.translate(
|
hintText: FlutterI18n.translate(
|
||||||
context,
|
context,
|
||||||
'patchesSelectorView.searchBarHint',
|
'patchesSelectorView.searchBarHint',
|
||||||
@@ -113,12 +109,6 @@ class _PatchesSelectorViewState extends State<PatchesSelectorView> {
|
|||||||
_query = searchQuery;
|
_query = searchQuery;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
onSelectAll: (value) {
|
|
||||||
if (value) {
|
|
||||||
model.selectAllPatcherWarning(context);
|
|
||||||
}
|
|
||||||
model.selectAllPatches(value);
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -144,24 +134,36 @@ class _PatchesSelectorViewState extends State<PatchesSelectorView> {
|
|||||||
children: [
|
children: [
|
||||||
Row(
|
Row(
|
||||||
children: [
|
children: [
|
||||||
CustomChip(
|
ActionChip(
|
||||||
label:
|
label: I18nText('patchesSelectorView.recommended'),
|
||||||
I18nText('patchesSelectorView.recommended'),
|
tooltip: FlutterI18n.translate(
|
||||||
onSelected: (value) {
|
context,
|
||||||
|
'patchesSelectorView.recommendedTooltip',
|
||||||
|
),
|
||||||
|
onPressed: () {
|
||||||
model.selectRecommendedPatches();
|
model.selectRecommendedPatches();
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
const SizedBox(width: 8),
|
const SizedBox(width: 8),
|
||||||
CustomChip(
|
ActionChip(
|
||||||
label: I18nText('patchesSelectorView.all'),
|
label: I18nText('patchesSelectorView.all'),
|
||||||
onSelected: (value) {
|
tooltip: FlutterI18n.translate(
|
||||||
|
context,
|
||||||
|
'patchesSelectorView.allTooltip',
|
||||||
|
),
|
||||||
|
onPressed: () {
|
||||||
|
model.selectAllPatcherWarning(context);
|
||||||
model.selectAllPatches(true);
|
model.selectAllPatches(true);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
const SizedBox(width: 8),
|
const SizedBox(width: 8),
|
||||||
CustomChip(
|
ActionChip(
|
||||||
label: I18nText('patchesSelectorView.none'),
|
label: I18nText('patchesSelectorView.none'),
|
||||||
onSelected: (value) {
|
tooltip: FlutterI18n.translate(
|
||||||
|
context,
|
||||||
|
'patchesSelectorView.noneTooltip',
|
||||||
|
),
|
||||||
|
onPressed: () {
|
||||||
model.clearPatches();
|
model.clearPatches();
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import 'package:collection/collection.dart';
|
import 'package:collection/collection.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_i18n/widgets/I18nText.dart';
|
import 'package:flutter_i18n/widgets/I18nText.dart';
|
||||||
import 'package:revanced_manager/app/app.locator.dart';
|
import 'package:revanced_manager/app/app.locator.dart';
|
||||||
import 'package:revanced_manager/models/patch.dart';
|
import 'package:revanced_manager/models/patch.dart';
|
||||||
@@ -10,7 +11,6 @@ import 'package:revanced_manager/services/toast.dart';
|
|||||||
import 'package:revanced_manager/ui/views/patcher/patcher_viewmodel.dart';
|
import 'package:revanced_manager/ui/views/patcher/patcher_viewmodel.dart';
|
||||||
import 'package:revanced_manager/ui/widgets/shared/custom_material_button.dart';
|
import 'package:revanced_manager/ui/widgets/shared/custom_material_button.dart';
|
||||||
import 'package:stacked/stacked.dart';
|
import 'package:stacked/stacked.dart';
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
|
|
||||||
class PatchesSelectorViewModel extends BaseViewModel {
|
class PatchesSelectorViewModel extends BaseViewModel {
|
||||||
final PatcherAPI _patcherAPI = locator<PatcherAPI>();
|
final PatcherAPI _patcherAPI = locator<PatcherAPI>();
|
||||||
@@ -25,10 +25,12 @@ class PatchesSelectorViewModel extends BaseViewModel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<void> initialize() async {
|
Future<void> initialize() async {
|
||||||
getPatchesVersion();
|
getPatchesVersion().whenComplete(() => notifyListeners());
|
||||||
patches.addAll(_patcherAPI.getFilteredPatches(
|
patches.addAll(
|
||||||
locator<PatcherViewModel>().selectedApp!.originalPackageName,
|
_patcherAPI.getFilteredPatches(
|
||||||
));
|
locator<PatcherViewModel>().selectedApp!.originalPackageName,
|
||||||
|
),
|
||||||
|
);
|
||||||
patches.sort((a, b) => a.name.compareTo(b.name));
|
patches.sort((a, b) => a.name.compareTo(b.name));
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
@@ -84,8 +86,11 @@ class PatchesSelectorViewModel extends BaseViewModel {
|
|||||||
selectedPatches.clear();
|
selectedPatches.clear();
|
||||||
|
|
||||||
if (_managerAPI.areExperimentalPatchesEnabled() == false) {
|
if (_managerAPI.areExperimentalPatchesEnabled() == false) {
|
||||||
selectedPatches.addAll(patches.where(
|
selectedPatches.addAll(
|
||||||
(element) => element.excluded == false && isPatchSupported(element)));
|
patches.where(
|
||||||
|
(element) => element.excluded == false && isPatchSupported(element),
|
||||||
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_managerAPI.areExperimentalPatchesEnabled()) {
|
if (_managerAPI.areExperimentalPatchesEnabled()) {
|
||||||
@@ -117,15 +122,18 @@ class PatchesSelectorViewModel extends BaseViewModel {
|
|||||||
patchesVersion = await _githubAPI
|
patchesVersion = await _githubAPI
|
||||||
.getLastestReleaseVersion(_managerAPI.getPatchesRepo());
|
.getLastestReleaseVersion(_managerAPI.getPatchesRepo());
|
||||||
}
|
}
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
List<Patch> getQueriedPatches(String query) {
|
List<Patch> getQueriedPatches(String query) {
|
||||||
return patches
|
return patches
|
||||||
.where((patch) =>
|
.where(
|
||||||
query.isEmpty ||
|
(patch) =>
|
||||||
query.length < 2 ||
|
query.isEmpty ||
|
||||||
patch.name.toLowerCase().contains(query.toLowerCase()) ||
|
query.length < 2 ||
|
||||||
patch.getSimpleName().toLowerCase().contains(query.toLowerCase()))
|
patch.name.toLowerCase().contains(query.toLowerCase()) ||
|
||||||
|
patch.getSimpleName().toLowerCase().contains(query.toLowerCase()),
|
||||||
|
)
|
||||||
.toList();
|
.toList();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -134,8 +142,8 @@ class PatchesSelectorViewModel extends BaseViewModel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
List<String> getSupportedVersions(Patch patch) {
|
List<String> getSupportedVersions(Patch patch) {
|
||||||
PatchedApplication app = locator<PatcherViewModel>().selectedApp!;
|
final PatchedApplication app = locator<PatcherViewModel>().selectedApp!;
|
||||||
Package? package = patch.compatiblePackages.firstWhereOrNull(
|
final Package? package = patch.compatiblePackages.firstWhereOrNull(
|
||||||
(pack) => pack.name == app.packageName,
|
(pack) => pack.name == app.packageName,
|
||||||
);
|
);
|
||||||
if (package != null) {
|
if (package != null) {
|
||||||
@@ -146,10 +154,13 @@ class PatchesSelectorViewModel extends BaseViewModel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool isPatchSupported(Patch patch) {
|
bool isPatchSupported(Patch patch) {
|
||||||
PatchedApplication app = locator<PatcherViewModel>().selectedApp!;
|
final PatchedApplication app = locator<PatcherViewModel>().selectedApp!;
|
||||||
return patch.compatiblePackages.isEmpty || patch.compatiblePackages.any((pack) =>
|
return patch.compatiblePackages.isEmpty ||
|
||||||
pack.name == app.packageName &&
|
patch.compatiblePackages.any(
|
||||||
(pack.versions.isEmpty || pack.versions.contains(app.version)));
|
(pack) =>
|
||||||
|
pack.name == app.packageName &&
|
||||||
|
(pack.versions.isEmpty || pack.versions.contains(app.version)),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
void onMenuSelection(value) {
|
void onMenuSelection(value) {
|
||||||
@@ -161,20 +172,23 @@ class PatchesSelectorViewModel extends BaseViewModel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<void> saveSelectedPatches() async {
|
Future<void> saveSelectedPatches() async {
|
||||||
List<String> selectedPatches =
|
final List<String> selectedPatches =
|
||||||
this.selectedPatches.map((patch) => patch.name).toList();
|
this.selectedPatches.map((patch) => patch.name).toList();
|
||||||
await _managerAPI.setSelectedPatches(
|
await _managerAPI.setSelectedPatches(
|
||||||
locator<PatcherViewModel>().selectedApp!.originalPackageName,
|
locator<PatcherViewModel>().selectedApp!.originalPackageName,
|
||||||
selectedPatches);
|
selectedPatches,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> loadSelectedPatches() async {
|
Future<void> loadSelectedPatches() async {
|
||||||
List<String> selectedPatches = await _managerAPI.getSelectedPatches(
|
final List<String> selectedPatches = await _managerAPI.getSelectedPatches(
|
||||||
locator<PatcherViewModel>().selectedApp!.originalPackageName);
|
locator<PatcherViewModel>().selectedApp!.originalPackageName,
|
||||||
|
);
|
||||||
if (selectedPatches.isNotEmpty) {
|
if (selectedPatches.isNotEmpty) {
|
||||||
this.selectedPatches.clear();
|
this.selectedPatches.clear();
|
||||||
this.selectedPatches.addAll(
|
this.selectedPatches.addAll(
|
||||||
patches.where((patch) => selectedPatches.contains(patch.name)));
|
patches.where((patch) => selectedPatches.contains(patch.name)),
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
locator<Toast>().showBottom('patchesSelectorView.noSavedPatches');
|
locator<Toast>().showBottom('patchesSelectorView.noSavedPatches');
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ class SManageApiUrl extends BaseViewModel {
|
|||||||
final TextEditingController _apiUrlController = TextEditingController();
|
final TextEditingController _apiUrlController = TextEditingController();
|
||||||
|
|
||||||
Future<void> showApiUrlDialog(BuildContext context) async {
|
Future<void> showApiUrlDialog(BuildContext context) async {
|
||||||
String apiUrl = _managerAPI.getApiUrl();
|
final String apiUrl = _managerAPI.getApiUrl();
|
||||||
_apiUrlController.text = apiUrl.replaceAll('https://', '');
|
_apiUrlController.text = apiUrl.replaceAll('https://', '');
|
||||||
return showDialog(
|
return showDialog(
|
||||||
context: context,
|
context: context,
|
||||||
@@ -19,9 +19,9 @@ class SManageSources extends BaseViewModel {
|
|||||||
final TextEditingController _intSourceController = TextEditingController();
|
final TextEditingController _intSourceController = TextEditingController();
|
||||||
|
|
||||||
Future<void> showSourcesDialog(BuildContext context) async {
|
Future<void> showSourcesDialog(BuildContext context) async {
|
||||||
String hostRepository = _managerAPI.getRepoUrl();
|
final String hostRepository = _managerAPI.getRepoUrl();
|
||||||
String patchesRepo = _managerAPI.getPatchesRepo();
|
final String patchesRepo = _managerAPI.getPatchesRepo();
|
||||||
String integrationsRepo = _managerAPI.getIntegrationsRepo();
|
final String integrationsRepo = _managerAPI.getIntegrationsRepo();
|
||||||
_hostSourceController.text = hostRepository;
|
_hostSourceController.text = hostRepository;
|
||||||
_orgPatSourceController.text = patchesRepo.split('/')[0];
|
_orgPatSourceController.text = patchesRepo.split('/')[0];
|
||||||
_patSourceController.text = patchesRepo.split('/')[1];
|
_patSourceController.text = patchesRepo.split('/')[1];
|
||||||
@@ -152,8 +152,6 @@ class SManageSources extends BaseViewModel {
|
|||||||
_managerAPI.setPatchesRepo('');
|
_managerAPI.setPatchesRepo('');
|
||||||
_managerAPI.setIntegrationsRepo('');
|
_managerAPI.setIntegrationsRepo('');
|
||||||
Navigator.of(context).pop();
|
Navigator.of(context).pop();
|
||||||
Navigator.of(context).pop();
|
|
||||||
Navigator.of(context).pop();
|
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
@@ -4,7 +4,6 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:flutter_i18n/flutter_i18n.dart';
|
import 'package:flutter_i18n/flutter_i18n.dart';
|
||||||
import 'package:revanced_manager/app/app.locator.dart';
|
import 'package:revanced_manager/app/app.locator.dart';
|
||||||
import 'package:revanced_manager/main.dart';
|
import 'package:revanced_manager/main.dart';
|
||||||
import 'package:revanced_manager/services/crowdin_api.dart';
|
|
||||||
import 'package:revanced_manager/services/toast.dart';
|
import 'package:revanced_manager/services/toast.dart';
|
||||||
import 'package:revanced_manager/ui/views/navigation/navigation_viewmodel.dart';
|
import 'package:revanced_manager/ui/views/navigation/navigation_viewmodel.dart';
|
||||||
import 'package:revanced_manager/ui/views/settings/settings_viewmodel.dart';
|
import 'package:revanced_manager/ui/views/settings/settings_viewmodel.dart';
|
||||||
@@ -16,7 +15,6 @@ import 'package:timeago/timeago.dart' as timeago;
|
|||||||
final _settingViewModel = SettingsViewModel();
|
final _settingViewModel = SettingsViewModel();
|
||||||
|
|
||||||
class SUpdateLanguage extends BaseViewModel {
|
class SUpdateLanguage extends BaseViewModel {
|
||||||
final CrowdinAPI _crowdinAPI = locator<CrowdinAPI>();
|
|
||||||
final Toast _toast = locator<Toast>();
|
final Toast _toast = locator<Toast>();
|
||||||
late SharedPreferences _prefs;
|
late SharedPreferences _prefs;
|
||||||
String selectedLanguage = 'English';
|
String selectedLanguage = 'English';
|
||||||
@@ -43,7 +41,6 @@ class SUpdateLanguage extends BaseViewModel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<void> initLang() async {
|
Future<void> initLang() async {
|
||||||
languages = await _crowdinAPI.getLanguages();
|
|
||||||
languages.sort((a, b) => a['name'].compareTo(b['name']));
|
languages.sort((a, b) => a['name'].compareTo(b['name']));
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
@@ -7,7 +7,6 @@ import 'package:flutter_i18n/widgets/I18nText.dart';
|
|||||||
import 'package:revanced_manager/app/app.locator.dart';
|
import 'package:revanced_manager/app/app.locator.dart';
|
||||||
import 'package:revanced_manager/services/manager_api.dart';
|
import 'package:revanced_manager/services/manager_api.dart';
|
||||||
import 'package:revanced_manager/ui/views/settings/settings_viewmodel.dart';
|
import 'package:revanced_manager/ui/views/settings/settings_viewmodel.dart';
|
||||||
import 'package:revanced_manager/ui/widgets/settingsView/custom_switch_tile.dart';
|
|
||||||
import 'package:revanced_manager/ui/widgets/settingsView/settings_section.dart';
|
import 'package:revanced_manager/ui/widgets/settingsView/settings_section.dart';
|
||||||
import 'package:stacked/stacked.dart';
|
import 'package:stacked/stacked.dart';
|
||||||
|
|
||||||
@@ -23,9 +22,9 @@ class SUpdateTheme extends BaseViewModel {
|
|||||||
return _managerAPI.getUseDynamicTheme();
|
return _managerAPI.getUseDynamicTheme();
|
||||||
}
|
}
|
||||||
|
|
||||||
void setUseDynamicTheme(BuildContext context, bool value) async {
|
Future<void> setUseDynamicTheme(BuildContext context, bool value) async {
|
||||||
await _managerAPI.setUseDynamicTheme(value);
|
await _managerAPI.setUseDynamicTheme(value);
|
||||||
int currentTheme = DynamicTheme.of(context)!.themeId;
|
final int currentTheme = DynamicTheme.of(context)!.themeId;
|
||||||
if (currentTheme.isEven) {
|
if (currentTheme.isEven) {
|
||||||
await DynamicTheme.of(context)!.setTheme(value ? 2 : 0);
|
await DynamicTheme.of(context)!.setTheme(value ? 2 : 0);
|
||||||
} else {
|
} else {
|
||||||
@@ -38,9 +37,9 @@ class SUpdateTheme extends BaseViewModel {
|
|||||||
return _managerAPI.getUseDarkTheme();
|
return _managerAPI.getUseDarkTheme();
|
||||||
}
|
}
|
||||||
|
|
||||||
void setUseDarkTheme(BuildContext context, bool value) async {
|
Future<void> setUseDarkTheme(BuildContext context, bool value) async {
|
||||||
await _managerAPI.setUseDarkTheme(value);
|
await _managerAPI.setUseDarkTheme(value);
|
||||||
int currentTheme = DynamicTheme.of(context)!.themeId;
|
final int currentTheme = DynamicTheme.of(context)!.themeId;
|
||||||
if (currentTheme < 2) {
|
if (currentTheme < 2) {
|
||||||
await DynamicTheme.of(context)!.setTheme(value ? 1 : 0);
|
await DynamicTheme.of(context)!.setTheme(value ? 1 : 0);
|
||||||
} else {
|
} else {
|
||||||
@@ -64,8 +63,8 @@ class SUpdateThemeUI extends StatelessWidget {
|
|||||||
return SettingsSection(
|
return SettingsSection(
|
||||||
title: 'settingsView.appearanceSectionTitle',
|
title: 'settingsView.appearanceSectionTitle',
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
CustomSwitchTile(
|
SwitchListTile(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 20.0),
|
contentPadding: const EdgeInsets.symmetric(horizontal: 20.0),
|
||||||
title: I18nText(
|
title: I18nText(
|
||||||
'settingsView.darkThemeLabel',
|
'settingsView.darkThemeLabel',
|
||||||
child: const Text(
|
child: const Text(
|
||||||
@@ -78,7 +77,7 @@ class SUpdateThemeUI extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
subtitle: I18nText('settingsView.darkThemeHint'),
|
subtitle: I18nText('settingsView.darkThemeHint'),
|
||||||
value: SUpdateTheme().getDarkThemeStatus(),
|
value: SUpdateTheme().getDarkThemeStatus(),
|
||||||
onTap: (value) => SUpdateTheme().setUseDarkTheme(
|
onChanged: (value) => SUpdateTheme().setUseDarkTheme(
|
||||||
context,
|
context,
|
||||||
value,
|
value,
|
||||||
),
|
),
|
||||||
@@ -88,8 +87,8 @@ class SUpdateThemeUI extends StatelessWidget {
|
|||||||
builder: (context, snapshot) => Visibility(
|
builder: (context, snapshot) => Visibility(
|
||||||
visible:
|
visible:
|
||||||
snapshot.hasData && snapshot.data! >= ANDROID_12_SDK_VERSION,
|
snapshot.hasData && snapshot.data! >= ANDROID_12_SDK_VERSION,
|
||||||
child: CustomSwitchTile(
|
child: SwitchListTile(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 20.0),
|
contentPadding: const EdgeInsets.symmetric(horizontal: 20.0),
|
||||||
title: I18nText(
|
title: I18nText(
|
||||||
'settingsView.dynamicThemeLabel',
|
'settingsView.dynamicThemeLabel',
|
||||||
child: const Text(
|
child: const Text(
|
||||||
@@ -102,11 +101,12 @@ class SUpdateThemeUI extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
subtitle: I18nText('settingsView.dynamicThemeHint'),
|
subtitle: I18nText('settingsView.dynamicThemeHint'),
|
||||||
value: _settingViewModel.sUpdateTheme.getDynamicThemeStatus(),
|
value: _settingViewModel.sUpdateTheme.getDynamicThemeStatus(),
|
||||||
onTap: (value) =>
|
onChanged: (value) => {
|
||||||
_settingViewModel.sUpdateTheme.setUseDynamicTheme(
|
_settingViewModel.sUpdateTheme.setUseDynamicTheme(
|
||||||
context,
|
context,
|
||||||
value,
|
value,
|
||||||
),
|
),
|
||||||
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -3,8 +3,7 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_i18n/flutter_i18n.dart';
|
import 'package:flutter_i18n/flutter_i18n.dart';
|
||||||
import 'package:google_fonts/google_fonts.dart';
|
import 'package:google_fonts/google_fonts.dart';
|
||||||
import 'package:revanced_manager/ui/views/settings/settingsFragement/settings_update_language.dart';
|
import 'package:revanced_manager/ui/views/settings/settingsFragment/settings_update_theme.dart';
|
||||||
import 'package:revanced_manager/ui/views/settings/settingsFragement/settings_update_theme.dart';
|
|
||||||
import 'package:revanced_manager/ui/views/settings/settings_viewmodel.dart';
|
import 'package:revanced_manager/ui/views/settings/settings_viewmodel.dart';
|
||||||
import 'package:revanced_manager/ui/widgets/settingsView/settings_advanced_section.dart';
|
import 'package:revanced_manager/ui/widgets/settingsView/settings_advanced_section.dart';
|
||||||
import 'package:revanced_manager/ui/widgets/settingsView/settings_export_section.dart';
|
import 'package:revanced_manager/ui/widgets/settingsView/settings_export_section.dart';
|
||||||
@@ -33,7 +32,7 @@ class SettingsView extends StatelessWidget {
|
|||||||
child: Text(
|
child: Text(
|
||||||
'',
|
'',
|
||||||
style: GoogleFonts.inter(
|
style: GoogleFonts.inter(
|
||||||
color: Theme.of(context).textTheme.headline6!.color,
|
color: Theme.of(context).textTheme.titleLarge!.color,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -50,8 +49,6 @@ class SettingsView extends StatelessWidget {
|
|||||||
_settingsDivider,
|
_settingsDivider,
|
||||||
SExportSection(),
|
SExportSection(),
|
||||||
_settingsDivider,
|
_settingsDivider,
|
||||||
// SLoggingSection(),
|
|
||||||
// _settingsDivider,
|
|
||||||
SInfoSection(),
|
SInfoSection(),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import 'dart:io';
|
|||||||
import 'package:cr_file_saver/file_saver.dart';
|
import 'package:cr_file_saver/file_saver.dart';
|
||||||
import 'package:device_info_plus/device_info_plus.dart';
|
import 'package:device_info_plus/device_info_plus.dart';
|
||||||
import 'package:file_picker/file_picker.dart';
|
import 'package:file_picker/file_picker.dart';
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:logcat/logcat.dart';
|
import 'package:logcat/logcat.dart';
|
||||||
import 'package:path_provider/path_provider.dart';
|
import 'package:path_provider/path_provider.dart';
|
||||||
import 'package:revanced_manager/app/app.locator.dart';
|
import 'package:revanced_manager/app/app.locator.dart';
|
||||||
@@ -9,9 +10,8 @@ import 'package:revanced_manager/app/app.router.dart';
|
|||||||
import 'package:revanced_manager/services/manager_api.dart';
|
import 'package:revanced_manager/services/manager_api.dart';
|
||||||
import 'package:revanced_manager/services/toast.dart';
|
import 'package:revanced_manager/services/toast.dart';
|
||||||
import 'package:revanced_manager/ui/views/patcher/patcher_viewmodel.dart';
|
import 'package:revanced_manager/ui/views/patcher/patcher_viewmodel.dart';
|
||||||
import 'package:revanced_manager/ui/views/settings/settingsFragement/settings_update_language.dart';
|
import 'package:revanced_manager/ui/views/settings/settingsFragment/settings_update_language.dart';
|
||||||
import 'package:revanced_manager/ui/views/settings/settingsFragement/settings_update_theme.dart';
|
import 'package:revanced_manager/ui/views/settings/settingsFragment/settings_update_theme.dart';
|
||||||
import 'package:sentry_flutter/sentry_flutter.dart';
|
|
||||||
import 'package:share_extend/share_extend.dart';
|
import 'package:share_extend/share_extend.dart';
|
||||||
import 'package:stacked/stacked.dart';
|
import 'package:stacked/stacked.dart';
|
||||||
import 'package:stacked_services/stacked_services.dart';
|
import 'package:stacked_services/stacked_services.dart';
|
||||||
@@ -28,16 +28,6 @@ class SettingsViewModel extends BaseViewModel {
|
|||||||
_navigationService.navigateTo(Routes.contributorsView);
|
_navigationService.navigateTo(Routes.contributorsView);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool isSentryEnabled() {
|
|
||||||
return _managerAPI.isSentryEnabled();
|
|
||||||
}
|
|
||||||
|
|
||||||
void useSentry(bool value) {
|
|
||||||
_managerAPI.setSentryStatus(value);
|
|
||||||
_toast.showBottom('settingsView.restartAppForChanges');
|
|
||||||
notifyListeners();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool areUniversalPatchesEnabled() {
|
bool areUniversalPatchesEnabled() {
|
||||||
return _managerAPI.areUniversalPatchesEnabled();
|
return _managerAPI.areUniversalPatchesEnabled();
|
||||||
}
|
}
|
||||||
@@ -70,47 +60,47 @@ class SettingsViewModel extends BaseViewModel {
|
|||||||
|
|
||||||
Future<void> exportPatches() async {
|
Future<void> exportPatches() async {
|
||||||
try {
|
try {
|
||||||
File outFile = File(_managerAPI.storedPatchesFile);
|
final File outFile = File(_managerAPI.storedPatchesFile);
|
||||||
if (outFile.existsSync()) {
|
if (outFile.existsSync()) {
|
||||||
String dateTime =
|
final String dateTime =
|
||||||
DateTime.now().toString().replaceAll(' ', '_').split('.').first;
|
DateTime.now().toString().replaceAll(' ', '_').split('.').first;
|
||||||
String tempFilePath =
|
await CRFileSaver.saveFileWithDialog(
|
||||||
'${outFile.path.substring(0, outFile.path.lastIndexOf('/') + 1)}selected_patches_$dateTime.json';
|
SaveFileDialogParams(
|
||||||
outFile.copySync(tempFilePath);
|
sourceFilePath: outFile.path,
|
||||||
await CRFileSaver.saveFileWithDialog(SaveFileDialogParams(
|
destinationFileName: 'selected_patches_$dateTime.json',
|
||||||
sourceFilePath: tempFilePath, destinationFileName: ''));
|
),
|
||||||
File(tempFilePath).delete();
|
);
|
||||||
locator<Toast>().showBottom('settingsView.exportedPatches');
|
_toast.showBottom('settingsView.exportedPatches');
|
||||||
} else {
|
} else {
|
||||||
locator<Toast>().showBottom('settingsView.noExportFileFound');
|
_toast.showBottom('settingsView.noExportFileFound');
|
||||||
|
}
|
||||||
|
} on Exception catch (e) {
|
||||||
|
if (kDebugMode) {
|
||||||
|
print(e);
|
||||||
}
|
}
|
||||||
} on Exception catch (e, s) {
|
|
||||||
Sentry.captureException(e, stackTrace: s);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> importPatches() async {
|
Future<void> importPatches() async {
|
||||||
try {
|
try {
|
||||||
FilePickerResult? result = await FilePicker.platform.pickFiles(
|
final FilePickerResult? result = await FilePicker.platform.pickFiles(
|
||||||
type: FileType.custom,
|
type: FileType.custom,
|
||||||
allowedExtensions: ['json'],
|
allowedExtensions: ['json'],
|
||||||
);
|
);
|
||||||
if (result != null && result.files.single.path != null) {
|
if (result != null && result.files.single.path != null) {
|
||||||
File inFile = File(result.files.single.path!);
|
final File inFile = File(result.files.single.path!);
|
||||||
final File storedPatchesFile = File(_managerAPI.storedPatchesFile);
|
inFile.copySync(_managerAPI.storedPatchesFile);
|
||||||
if (!storedPatchesFile.existsSync()) {
|
|
||||||
storedPatchesFile.createSync(recursive: true);
|
|
||||||
}
|
|
||||||
inFile.copySync(storedPatchesFile.path);
|
|
||||||
inFile.delete();
|
inFile.delete();
|
||||||
if (locator<PatcherViewModel>().selectedApp != null) {
|
if (locator<PatcherViewModel>().selectedApp != null) {
|
||||||
locator<PatcherViewModel>().loadLastSelectedPatches();
|
locator<PatcherViewModel>().loadLastSelectedPatches();
|
||||||
}
|
}
|
||||||
locator<Toast>().showBottom('settingsView.importedPatches');
|
_toast.showBottom('settingsView.importedPatches');
|
||||||
}
|
}
|
||||||
} on Exception catch (e, s) {
|
} on Exception catch (e) {
|
||||||
await Sentry.captureException(e, stackTrace: s);
|
if (kDebugMode) {
|
||||||
locator<Toast>().showBottom('settingsView.jsonSelectorErrorMessage');
|
print(e);
|
||||||
|
}
|
||||||
|
_toast.showBottom('settingsView.jsonSelectorErrorMessage');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -120,13 +110,13 @@ class SettingsViewModel extends BaseViewModel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<int> getSdkVersion() async {
|
Future<int> getSdkVersion() async {
|
||||||
AndroidDeviceInfo info = await DeviceInfoPlugin().androidInfo;
|
final AndroidDeviceInfo info = await DeviceInfoPlugin().androidInfo;
|
||||||
return info.version.sdkInt ?? -1;
|
return info.version.sdkInt ?? -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> deleteLogs() async {
|
Future<void> deleteLogs() async {
|
||||||
Directory appCacheDir = await getTemporaryDirectory();
|
final Directory appCacheDir = await getTemporaryDirectory();
|
||||||
Directory logsDir = Directory('${appCacheDir.path}/logs');
|
final Directory logsDir = Directory('${appCacheDir.path}/logs');
|
||||||
if (logsDir.existsSync()) {
|
if (logsDir.existsSync()) {
|
||||||
logsDir.deleteSync(recursive: true);
|
logsDir.deleteSync(recursive: true);
|
||||||
}
|
}
|
||||||
@@ -134,17 +124,18 @@ class SettingsViewModel extends BaseViewModel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<void> exportLogcatLogs() async {
|
Future<void> exportLogcatLogs() async {
|
||||||
Directory appCache = await getTemporaryDirectory();
|
final Directory appCache = await getTemporaryDirectory();
|
||||||
Directory logDir = Directory('${appCache.path}/logs');
|
final Directory logDir = Directory('${appCache.path}/logs');
|
||||||
logDir.createSync();
|
logDir.createSync();
|
||||||
String dateTime = DateTime.now()
|
final String dateTime = DateTime.now()
|
||||||
.toIso8601String()
|
.toIso8601String()
|
||||||
.replaceAll('-', '')
|
.replaceAll('-', '')
|
||||||
.replaceAll(':', '')
|
.replaceAll(':', '')
|
||||||
.replaceAll('T', '')
|
.replaceAll('T', '')
|
||||||
.replaceAll('.', '');
|
.replaceAll('.', '');
|
||||||
File logcat = File('${logDir.path}/revanced-manager_logcat_$dateTime.log');
|
final File logcat =
|
||||||
String logs = await Logcat.execute();
|
File('${logDir.path}/revanced-manager_logcat_$dateTime.log');
|
||||||
|
final String logs = await Logcat.execute();
|
||||||
logcat.writeAsStringSync(logs);
|
logcat.writeAsStringSync(logs);
|
||||||
ShareExtend.share(logcat.path, 'file');
|
ShareExtend.share(logcat.path, 'file');
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,12 +8,11 @@ import 'package:revanced_manager/ui/widgets/shared/custom_sliver_app_bar.dart';
|
|||||||
import 'package:stacked/stacked.dart';
|
import 'package:stacked/stacked.dart';
|
||||||
|
|
||||||
class AppInfoView extends StatelessWidget {
|
class AppInfoView extends StatelessWidget {
|
||||||
final PatchedApplication app;
|
|
||||||
|
|
||||||
const AppInfoView({
|
const AppInfoView({
|
||||||
Key? key,
|
Key? key,
|
||||||
required this.app,
|
required this.app,
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
final PatchedApplication app;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
@@ -28,7 +27,7 @@ class AppInfoView extends StatelessWidget {
|
|||||||
child: Text(
|
child: Text(
|
||||||
'',
|
'',
|
||||||
style: GoogleFonts.inter(
|
style: GoogleFonts.inter(
|
||||||
color: Theme.of(context).textTheme.headline6!.color,
|
color: Theme.of(context).textTheme.titleLarge!.color,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -52,13 +51,13 @@ class AppInfoView extends StatelessWidget {
|
|||||||
Text(
|
Text(
|
||||||
app.name,
|
app.name,
|
||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
style: Theme.of(context).textTheme.headline6,
|
style: Theme.of(context).textTheme.titleLarge,
|
||||||
),
|
),
|
||||||
const SizedBox(height: 4),
|
const SizedBox(height: 4),
|
||||||
Text(
|
Text(
|
||||||
app.version,
|
app.version,
|
||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
style: Theme.of(context).textTheme.subtitle1,
|
style: Theme.of(context).textTheme.titleLarge,
|
||||||
),
|
),
|
||||||
const SizedBox(height: 20),
|
const SizedBox(height: 20),
|
||||||
Padding(
|
Padding(
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ class AppInfoViewModel extends BaseViewModel {
|
|||||||
) async {
|
) async {
|
||||||
bool isUninstalled = true;
|
bool isUninstalled = true;
|
||||||
if (app.isRooted) {
|
if (app.isRooted) {
|
||||||
bool hasRootPermissions = await _rootAPI.hasRootPermissions();
|
final bool hasRootPermissions = await _rootAPI.hasRootPermissions();
|
||||||
if (hasRootPermissions) {
|
if (hasRootPermissions) {
|
||||||
await _rootAPI.deleteApp(app.packageName, app.apkFilePath);
|
await _rootAPI.deleteApp(app.packageName, app.apkFilePath);
|
||||||
if (!onlyUnpatch) {
|
if (!onlyUnpatch) {
|
||||||
@@ -45,7 +45,7 @@ class AppInfoViewModel extends BaseViewModel {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void navigateToPatcher(PatchedApplication app) async {
|
Future<void> navigateToPatcher(PatchedApplication app) async {
|
||||||
locator<PatcherViewModel>().selectedApp = app;
|
locator<PatcherViewModel>().selectedApp = app;
|
||||||
locator<PatcherViewModel>().selectedPatches =
|
locator<PatcherViewModel>().selectedPatches =
|
||||||
await _patcherAPI.getAppliedPatches(app.appliedPatches);
|
await _patcherAPI.getAppliedPatches(app.appliedPatches);
|
||||||
@@ -62,7 +62,7 @@ class AppInfoViewModel extends BaseViewModel {
|
|||||||
PatchedApplication app,
|
PatchedApplication app,
|
||||||
bool onlyUnpatch,
|
bool onlyUnpatch,
|
||||||
) async {
|
) async {
|
||||||
bool hasRootPermissions = await _rootAPI.hasRootPermissions();
|
final bool hasRootPermissions = await _rootAPI.hasRootPermissions();
|
||||||
if (app.isRooted && !hasRootPermissions) {
|
if (app.isRooted && !hasRootPermissions) {
|
||||||
return showDialog(
|
return showDialog(
|
||||||
context: context,
|
context: context,
|
||||||
@@ -134,7 +134,8 @@ class AppInfoViewModel extends BaseViewModel {
|
|||||||
title: I18nText('appInfoView.appliedPatchesLabel'),
|
title: I18nText('appInfoView.appliedPatchesLabel'),
|
||||||
backgroundColor: Theme.of(context).colorScheme.secondaryContainer,
|
backgroundColor: Theme.of(context).colorScheme.secondaryContainer,
|
||||||
content: SingleChildScrollView(
|
content: SingleChildScrollView(
|
||||||
child: Text(getAppliedPatchesString(app.appliedPatches))),
|
child: Text(getAppliedPatchesString(app.appliedPatches)),
|
||||||
|
),
|
||||||
actions: <Widget>[
|
actions: <Widget>[
|
||||||
CustomMaterialButton(
|
CustomMaterialButton(
|
||||||
label: I18nText('okButton'),
|
label: I18nText('okButton'),
|
||||||
@@ -146,13 +147,15 @@ class AppInfoViewModel extends BaseViewModel {
|
|||||||
}
|
}
|
||||||
|
|
||||||
String getAppliedPatchesString(List<String> appliedPatches) {
|
String getAppliedPatchesString(List<String> appliedPatches) {
|
||||||
List<String> names = appliedPatches
|
final List<String> names = appliedPatches
|
||||||
.map((p) => p
|
.map(
|
||||||
.replaceAll('-', ' ')
|
(p) => p
|
||||||
.split('-')
|
.replaceAll('-', ' ')
|
||||||
.join(' ')
|
.split('-')
|
||||||
.toTitleCase()
|
.join(' ')
|
||||||
.replaceFirst('Microg', 'MicroG'))
|
.toTitleCase()
|
||||||
|
.replaceFirst('Microg', 'MicroG'),
|
||||||
|
)
|
||||||
.toList();
|
.toList();
|
||||||
return '\u2022 ${names.join('\n\u2022 ')}';
|
return '\u2022 ${names.join('\n\u2022 ')}';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,7 +21,6 @@ class AppSkeletonLoader extends StatelessWidget {
|
|||||||
style: SkeletonAvatarStyle(
|
style: SkeletonAvatarStyle(
|
||||||
width: screenWidth * 0.15,
|
width: screenWidth * 0.15,
|
||||||
height: screenWidth * 0.15,
|
height: screenWidth * 0.15,
|
||||||
shape: BoxShape.rectangle,
|
|
||||||
borderRadius: const BorderRadius.all(Radius.circular(12)),
|
borderRadius: const BorderRadius.all(Radius.circular(12)),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -3,12 +3,6 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:revanced_manager/ui/widgets/shared/custom_card.dart';
|
import 'package:revanced_manager/ui/widgets/shared/custom_card.dart';
|
||||||
|
|
||||||
class InstalledAppItem extends StatefulWidget {
|
class InstalledAppItem extends StatefulWidget {
|
||||||
final String name;
|
|
||||||
final String pkgName;
|
|
||||||
final Uint8List icon;
|
|
||||||
final int patchesCount;
|
|
||||||
final Function()? onTap;
|
|
||||||
|
|
||||||
const InstalledAppItem({
|
const InstalledAppItem({
|
||||||
Key? key,
|
Key? key,
|
||||||
required this.name,
|
required this.name,
|
||||||
@@ -17,6 +11,11 @@ class InstalledAppItem extends StatefulWidget {
|
|||||||
required this.patchesCount,
|
required this.patchesCount,
|
||||||
this.onTap,
|
this.onTap,
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
final String name;
|
||||||
|
final String pkgName;
|
||||||
|
final Uint8List icon;
|
||||||
|
final int patchesCount;
|
||||||
|
final Function()? onTap;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<InstalledAppItem> createState() => _InstalledAppItemState();
|
State<InstalledAppItem> createState() => _InstalledAppItemState();
|
||||||
@@ -48,7 +47,6 @@ class _InstalledAppItemState extends State<InstalledAppItem> {
|
|||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
Row(
|
Row(
|
||||||
crossAxisAlignment: CrossAxisAlignment.center,
|
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
Text(
|
Text(
|
||||||
widget.name,
|
widget.name,
|
||||||
@@ -62,8 +60,8 @@ class _InstalledAppItemState extends State<InstalledAppItem> {
|
|||||||
const SizedBox(width: 6),
|
const SizedBox(width: 6),
|
||||||
Text(
|
Text(
|
||||||
widget.patchesCount == 1
|
widget.patchesCount == 1
|
||||||
? "${widget.patchesCount} patch"
|
? '${widget.patchesCount} patch'
|
||||||
: "${widget.patchesCount} patches",
|
: '${widget.patchesCount} patches',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 8,
|
fontSize: 8,
|
||||||
color: Theme.of(context).colorScheme.secondary,
|
color: Theme.of(context).colorScheme.secondary,
|
||||||
|
|||||||
@@ -6,14 +6,13 @@ import 'package:revanced_manager/ui/widgets/shared/custom_card.dart';
|
|||||||
import 'package:url_launcher/url_launcher.dart';
|
import 'package:url_launcher/url_launcher.dart';
|
||||||
|
|
||||||
class ContributorsCard extends StatefulWidget {
|
class ContributorsCard extends StatefulWidget {
|
||||||
final String title;
|
|
||||||
final List<dynamic> contributors;
|
|
||||||
|
|
||||||
const ContributorsCard({
|
const ContributorsCard({
|
||||||
Key? key,
|
Key? key,
|
||||||
required this.title,
|
required this.title,
|
||||||
required this.contributors,
|
required this.contributors,
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
final String title;
|
||||||
|
final List<dynamic> contributors;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<ContributorsCard> createState() => _ContributorsCardState();
|
State<ContributorsCard> createState() => _ContributorsCardState();
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ import 'package:flutter_i18n/flutter_i18n.dart';
|
|||||||
import 'package:revanced_manager/app/app.locator.dart';
|
import 'package:revanced_manager/app/app.locator.dart';
|
||||||
import 'package:revanced_manager/models/patched_application.dart';
|
import 'package:revanced_manager/models/patched_application.dart';
|
||||||
import 'package:revanced_manager/ui/views/home/home_viewmodel.dart';
|
import 'package:revanced_manager/ui/views/home/home_viewmodel.dart';
|
||||||
import 'package:revanced_manager/ui/widgets/shared/application_item.dart';
|
|
||||||
import 'package:revanced_manager/ui/widgets/shared/custom_card.dart';
|
import 'package:revanced_manager/ui/widgets/shared/custom_card.dart';
|
||||||
|
|
||||||
class AvailableUpdatesCard extends StatelessWidget {
|
class AvailableUpdatesCard extends StatelessWidget {
|
||||||
@@ -29,7 +28,7 @@ class AvailableUpdatesCard extends StatelessWidget {
|
|||||||
child: Text(
|
child: Text(
|
||||||
'',
|
'',
|
||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
style: Theme.of(context).textTheme.subtitle1!.copyWith(
|
style: Theme.of(context).textTheme.titleMedium!.copyWith(
|
||||||
color: Theme.of(context).colorScheme.secondary,
|
color: Theme.of(context).colorScheme.secondary,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -54,7 +53,7 @@ class AvailableUpdatesCard extends StatelessWidget {
|
|||||||
// child: Text(
|
// child: Text(
|
||||||
// '',
|
// '',
|
||||||
// textAlign: TextAlign.center,
|
// textAlign: TextAlign.center,
|
||||||
// style: Theme.of(context).textTheme.subtitle1!.copyWith(
|
// style: Theme.of(context).textTheme.titleMedium!.copyWith(
|
||||||
// color: Theme.of(context).colorScheme.secondary,
|
// color: Theme.of(context).colorScheme.secondary,
|
||||||
// ),
|
// ),
|
||||||
// ),
|
// ),
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ class InstalledAppsCard extends StatelessWidget {
|
|||||||
child: Text(
|
child: Text(
|
||||||
'',
|
'',
|
||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
style: Theme.of(context).textTheme.subtitle1!.copyWith(
|
style: Theme.of(context).textTheme.titleMedium!.copyWith(
|
||||||
color: Theme.of(context).colorScheme.secondary,
|
color: Theme.of(context).colorScheme.secondary,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -2,16 +2,15 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:flutter_i18n/flutter_i18n.dart';
|
import 'package:flutter_i18n/flutter_i18n.dart';
|
||||||
import 'package:revanced_manager/app/app.locator.dart';
|
import 'package:revanced_manager/app/app.locator.dart';
|
||||||
import 'package:revanced_manager/ui/views/home/home_viewmodel.dart';
|
import 'package:revanced_manager/ui/views/home/home_viewmodel.dart';
|
||||||
import 'package:revanced_manager/ui/widgets/shared/custom_material_button.dart';
|
|
||||||
import 'package:revanced_manager/ui/widgets/shared/custom_card.dart';
|
import 'package:revanced_manager/ui/widgets/shared/custom_card.dart';
|
||||||
|
import 'package:revanced_manager/ui/widgets/shared/custom_material_button.dart';
|
||||||
|
|
||||||
class LatestCommitCard extends StatefulWidget {
|
class LatestCommitCard extends StatefulWidget {
|
||||||
final Function() onPressed;
|
|
||||||
|
|
||||||
const LatestCommitCard({
|
const LatestCommitCard({
|
||||||
Key? key,
|
Key? key,
|
||||||
required this.onPressed,
|
required this.onPressed,
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
final Function() onPressed;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<LatestCommitCard> createState() => _LatestCommitCardState();
|
State<LatestCommitCard> createState() => _LatestCommitCardState();
|
||||||
@@ -73,7 +72,6 @@ class _LatestCommitCardState extends State<LatestCommitCard> {
|
|||||||
builder: (context, snapshot) => Opacity(
|
builder: (context, snapshot) => Opacity(
|
||||||
opacity: snapshot.hasData && snapshot.data! ? 1.0 : 0.25,
|
opacity: snapshot.hasData && snapshot.data! ? 1.0 : 0.25,
|
||||||
child: CustomMaterialButton(
|
child: CustomMaterialButton(
|
||||||
isExpanded: false,
|
|
||||||
label: I18nText('latestCommitCard.updateButton'),
|
label: I18nText('latestCommitCard.updateButton'),
|
||||||
onPressed: snapshot.hasData && snapshot.data!
|
onPressed: snapshot.hasData && snapshot.data!
|
||||||
? widget.onPressed
|
? widget.onPressed
|
||||||
|
|||||||
@@ -14,7 +14,6 @@ class UpdateConfirmationDialog extends StatelessWidget {
|
|||||||
|
|
||||||
return DraggableScrollableSheet(
|
return DraggableScrollableSheet(
|
||||||
expand: false,
|
expand: false,
|
||||||
initialChildSize: 0.5,
|
|
||||||
snap: true,
|
snap: true,
|
||||||
snapSizes: const [0.5],
|
snapSizes: const [0.5],
|
||||||
builder: (context, scrollController) => SingleChildScrollView(
|
builder: (context, scrollController) => SingleChildScrollView(
|
||||||
@@ -37,7 +36,11 @@ class UpdateConfirmationDialog extends StatelessWidget {
|
|||||||
children: [
|
children: [
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.only(
|
padding: const EdgeInsets.only(
|
||||||
top: 40.0, left: 24.0, right: 24.0, bottom: 32.0),
|
top: 40.0,
|
||||||
|
left: 24.0,
|
||||||
|
right: 24.0,
|
||||||
|
bottom: 32.0,
|
||||||
|
),
|
||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
Expanded(
|
Expanded(
|
||||||
@@ -47,10 +50,11 @@ class UpdateConfirmationDialog extends StatelessWidget {
|
|||||||
I18nText(
|
I18nText(
|
||||||
'homeView.updateDialogTitle',
|
'homeView.updateDialogTitle',
|
||||||
child: const Text(
|
child: const Text(
|
||||||
"",
|
'',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 24,
|
fontSize: 24,
|
||||||
fontWeight: FontWeight.bold),
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 4.0),
|
const SizedBox(height: 4.0),
|
||||||
@@ -63,7 +67,7 @@ class UpdateConfirmationDialog extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
const SizedBox(width: 8.0),
|
const SizedBox(width: 8.0),
|
||||||
Text(
|
Text(
|
||||||
snapshot.data!["tag_name"] ?? "Unknown",
|
snapshot.data!['tag_name'] ?? 'Unknown',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 20,
|
fontSize: 20,
|
||||||
fontWeight: FontWeight.w500,
|
fontWeight: FontWeight.w500,
|
||||||
@@ -93,13 +97,14 @@ class UpdateConfirmationDialog extends StatelessWidget {
|
|||||||
child: I18nText(
|
child: I18nText(
|
||||||
'homeView.updateChangelogTitle',
|
'homeView.updateChangelogTitle',
|
||||||
child: Text(
|
child: Text(
|
||||||
"",
|
'',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 20,
|
fontSize: 20,
|
||||||
fontWeight: FontWeight.w500,
|
fontWeight: FontWeight.w500,
|
||||||
color: Theme.of(context)
|
color: Theme.of(context)
|
||||||
.colorScheme
|
.colorScheme
|
||||||
.onSecondaryContainer),
|
.onSecondaryContainer,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -113,7 +118,7 @@ class UpdateConfirmationDialog extends StatelessWidget {
|
|||||||
shrinkWrap: true,
|
shrinkWrap: true,
|
||||||
physics: const NeverScrollableScrollPhysics(),
|
physics: const NeverScrollableScrollPhysics(),
|
||||||
padding: const EdgeInsets.all(20.0),
|
padding: const EdgeInsets.all(20.0),
|
||||||
data: snapshot.data!["body"] ?? "",
|
data: snapshot.data!['body'] ?? '',
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
class GradientProgressIndicator extends StatefulWidget {
|
class GradientProgressIndicator extends StatefulWidget {
|
||||||
final double? progress;
|
|
||||||
const GradientProgressIndicator({required this.progress, super.key});
|
const GradientProgressIndicator({required this.progress, super.key});
|
||||||
|
final double? progress;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<GradientProgressIndicator> createState() =>
|
State<GradientProgressIndicator> createState() =>
|
||||||
|
|||||||
@@ -6,12 +6,11 @@ import 'package:revanced_manager/ui/views/patcher/patcher_viewmodel.dart';
|
|||||||
import 'package:revanced_manager/ui/widgets/shared/custom_card.dart';
|
import 'package:revanced_manager/ui/widgets/shared/custom_card.dart';
|
||||||
|
|
||||||
class AppSelectorCard extends StatelessWidget {
|
class AppSelectorCard extends StatelessWidget {
|
||||||
final Function() onPressed;
|
|
||||||
|
|
||||||
const AppSelectorCard({
|
const AppSelectorCard({
|
||||||
Key? key,
|
Key? key,
|
||||||
required this.onPressed,
|
required this.onPressed,
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
final Function() onPressed;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
@@ -33,40 +32,41 @@ class AppSelectorCard extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
locator<PatcherViewModel>().selectedApp == null
|
if (locator<PatcherViewModel>().selectedApp == null)
|
||||||
? I18nText('appSelectorCard.widgetSubtitle')
|
I18nText('appSelectorCard.widgetSubtitle')
|
||||||
: Row(
|
else
|
||||||
children: <Widget>[
|
Row(
|
||||||
SizedBox(
|
children: <Widget>[
|
||||||
height: 18.0,
|
SizedBox(
|
||||||
child: ClipOval(
|
height: 18.0,
|
||||||
child: Image.memory(
|
child: ClipOval(
|
||||||
locator<PatcherViewModel>().selectedApp == null
|
child: Image.memory(
|
||||||
? Uint8List(0)
|
locator<PatcherViewModel>().selectedApp == null
|
||||||
: locator<PatcherViewModel>().selectedApp!.icon,
|
? Uint8List(0)
|
||||||
fit: BoxFit.cover,
|
: locator<PatcherViewModel>().selectedApp!.icon,
|
||||||
),
|
fit: BoxFit.cover,
|
||||||
),
|
|
||||||
),
|
),
|
||||||
const SizedBox(width: 6),
|
),
|
||||||
Text(
|
|
||||||
locator<PatcherViewModel>()
|
|
||||||
.getAppSelectionString(),
|
|
||||||
style: const TextStyle(fontWeight: FontWeight.w600),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
locator<PatcherViewModel>().selectedApp == null
|
const SizedBox(width: 6),
|
||||||
? Container()
|
Text(
|
||||||
: Column(
|
locator<PatcherViewModel>().getAppSelectionString(),
|
||||||
children: [
|
style: const TextStyle(fontWeight: FontWeight.w600),
|
||||||
const SizedBox(height: 4),
|
|
||||||
Text(
|
|
||||||
locator<PatcherViewModel>()
|
|
||||||
.getRecommendedVersionString(context),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
if (locator<PatcherViewModel>().selectedApp == null)
|
||||||
|
Container()
|
||||||
|
else
|
||||||
|
Column(
|
||||||
|
children: [
|
||||||
|
const SizedBox(height: 4),
|
||||||
|
Text(
|
||||||
|
locator<PatcherViewModel>()
|
||||||
|
.getRecommendedVersionString(context),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -6,12 +6,11 @@ import 'package:revanced_manager/ui/views/patcher/patcher_viewmodel.dart';
|
|||||||
import 'package:revanced_manager/ui/widgets/shared/custom_card.dart';
|
import 'package:revanced_manager/ui/widgets/shared/custom_card.dart';
|
||||||
|
|
||||||
class PatchSelectorCard extends StatelessWidget {
|
class PatchSelectorCard extends StatelessWidget {
|
||||||
final Function() onPressed;
|
|
||||||
|
|
||||||
const PatchSelectorCard({
|
const PatchSelectorCard({
|
||||||
Key? key,
|
Key? key,
|
||||||
required this.onPressed,
|
required this.onPressed,
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
final Function() onPressed;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
@@ -46,11 +45,12 @@ class PatchSelectorCard extends StatelessWidget {
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
const SizedBox(height: 4),
|
const SizedBox(height: 4),
|
||||||
locator<PatcherViewModel>().selectedApp == null
|
if (locator<PatcherViewModel>().selectedApp == null)
|
||||||
? I18nText('patchSelectorCard.widgetSubtitle')
|
I18nText('patchSelectorCard.widgetSubtitle')
|
||||||
: locator<PatcherViewModel>().selectedPatches.isEmpty
|
else
|
||||||
? I18nText('patchSelectorCard.widgetEmptySubtitle')
|
locator<PatcherViewModel>().selectedPatches.isEmpty
|
||||||
: Text(_getPatchesSelection()),
|
? I18nText('patchSelectorCard.widgetEmptySubtitle')
|
||||||
|
: Text(_getPatchesSelection()),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@@ -58,7 +58,7 @@ class PatchSelectorCard extends StatelessWidget {
|
|||||||
|
|
||||||
String _getPatchesSelection() {
|
String _getPatchesSelection() {
|
||||||
String text = '';
|
String text = '';
|
||||||
for (Patch p in locator<PatcherViewModel>().selectedPatches) {
|
for (final Patch p in locator<PatcherViewModel>().selectedPatches) {
|
||||||
text += '\u2022 ${p.getSimpleName()} (v${p.version})\n';
|
text += '\u2022 ${p.getSimpleName()} (v${p.version})\n';
|
||||||
}
|
}
|
||||||
return text.substring(0, text.length - 1);
|
return text.substring(0, text.length - 1);
|
||||||
|
|||||||
@@ -3,11 +3,24 @@ import 'package:flutter_i18n/flutter_i18n.dart';
|
|||||||
import 'package:revanced_manager/app/app.locator.dart';
|
import 'package:revanced_manager/app/app.locator.dart';
|
||||||
import 'package:revanced_manager/services/manager_api.dart';
|
import 'package:revanced_manager/services/manager_api.dart';
|
||||||
import 'package:revanced_manager/services/toast.dart';
|
import 'package:revanced_manager/services/toast.dart';
|
||||||
import 'package:revanced_manager/ui/widgets/shared/custom_material_button.dart';
|
|
||||||
import 'package:revanced_manager/ui/widgets/shared/custom_card.dart';
|
import 'package:revanced_manager/ui/widgets/shared/custom_card.dart';
|
||||||
|
import 'package:revanced_manager/ui/widgets/shared/custom_material_button.dart';
|
||||||
|
|
||||||
// ignore: must_be_immutable
|
// ignore: must_be_immutable
|
||||||
class PatchItem extends StatefulWidget {
|
class PatchItem extends StatefulWidget {
|
||||||
|
PatchItem({
|
||||||
|
Key? key,
|
||||||
|
required this.name,
|
||||||
|
required this.simpleName,
|
||||||
|
required this.description,
|
||||||
|
required this.version,
|
||||||
|
required this.packageVersion,
|
||||||
|
required this.supportedPackageVersions,
|
||||||
|
required this.isUnsupported,
|
||||||
|
required this.isSelected,
|
||||||
|
required this.onChanged,
|
||||||
|
this.child,
|
||||||
|
}) : super(key: key);
|
||||||
final String name;
|
final String name;
|
||||||
final String simpleName;
|
final String simpleName;
|
||||||
final String description;
|
final String description;
|
||||||
@@ -21,20 +34,6 @@ class PatchItem extends StatefulWidget {
|
|||||||
final toast = locator<Toast>();
|
final toast = locator<Toast>();
|
||||||
final _managerAPI = locator<ManagerAPI>();
|
final _managerAPI = locator<ManagerAPI>();
|
||||||
|
|
||||||
PatchItem(
|
|
||||||
{Key? key,
|
|
||||||
required this.name,
|
|
||||||
required this.simpleName,
|
|
||||||
required this.description,
|
|
||||||
required this.version,
|
|
||||||
required this.packageVersion,
|
|
||||||
required this.supportedPackageVersions,
|
|
||||||
required this.isUnsupported,
|
|
||||||
required this.isSelected,
|
|
||||||
required this.onChanged,
|
|
||||||
this.child})
|
|
||||||
: super(key: key);
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<PatchItem> createState() => _PatchItemState();
|
State<PatchItem> createState() => _PatchItemState();
|
||||||
}
|
}
|
||||||
@@ -75,30 +74,31 @@ class _PatchItemState extends State<PatchItem> {
|
|||||||
Row(
|
Row(
|
||||||
crossAxisAlignment: CrossAxisAlignment.end,
|
crossAxisAlignment: CrossAxisAlignment.end,
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
Text(
|
Expanded(
|
||||||
widget.simpleName,
|
child: Text(
|
||||||
maxLines: 2,
|
widget.simpleName,
|
||||||
overflow: TextOverflow.visible,
|
maxLines: 2,
|
||||||
style: const TextStyle(
|
overflow: TextOverflow.visible,
|
||||||
fontSize: 16,
|
style: const TextStyle(
|
||||||
fontWeight: FontWeight.w600,
|
fontSize: 16,
|
||||||
),
|
fontWeight: FontWeight.w600,
|
||||||
),
|
),
|
||||||
const SizedBox(width: 6),
|
|
||||||
Text(
|
|
||||||
widget.version,
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 16,
|
|
||||||
color: Theme.of(context).colorScheme.secondary,
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
const SizedBox(height: 1),
|
||||||
|
Text(
|
||||||
|
widget.version,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 14,
|
||||||
|
color: Theme.of(context).colorScheme.secondary,
|
||||||
|
),
|
||||||
|
),
|
||||||
const SizedBox(height: 4),
|
const SizedBox(height: 4),
|
||||||
Text(
|
Text(
|
||||||
widget.description,
|
widget.description,
|
||||||
softWrap: true,
|
softWrap: true,
|
||||||
maxLines: 3,
|
|
||||||
overflow: TextOverflow.visible,
|
overflow: TextOverflow.visible,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
@@ -139,38 +139,37 @@ class _PatchItemState extends State<PatchItem> {
|
|||||||
)
|
)
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
widget.isUnsupported
|
if (widget.isUnsupported)
|
||||||
? Row(
|
Row(
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.only(top: 8),
|
padding: const EdgeInsets.only(top: 8),
|
||||||
child: TextButton.icon(
|
child: TextButton.icon(
|
||||||
label: I18nText('warning'),
|
label: I18nText('warning'),
|
||||||
icon: const Icon(Icons.warning, size: 20.0),
|
icon: const Icon(Icons.warning, size: 20.0),
|
||||||
onPressed: () => _showUnsupportedWarningDialog(),
|
onPressed: () => _showUnsupportedWarningDialog(),
|
||||||
style: ButtonStyle(
|
style: ButtonStyle(
|
||||||
shape: MaterialStateProperty.all(
|
shape: MaterialStateProperty.all(
|
||||||
RoundedRectangleBorder(
|
RoundedRectangleBorder(
|
||||||
borderRadius: BorderRadius.circular(8),
|
borderRadius: BorderRadius.circular(8),
|
||||||
side: BorderSide(
|
side: BorderSide(
|
||||||
width: 1,
|
color: Theme.of(context).colorScheme.secondary,
|
||||||
color:
|
|
||||||
Theme.of(context).colorScheme.secondary,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
backgroundColor: MaterialStateProperty.all(
|
|
||||||
Colors.transparent,
|
|
||||||
),
|
|
||||||
foregroundColor: MaterialStateProperty.all(
|
|
||||||
Theme.of(context).colorScheme.secondary,
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
backgroundColor: MaterialStateProperty.all(
|
||||||
|
Colors.transparent,
|
||||||
|
),
|
||||||
|
foregroundColor: MaterialStateProperty.all(
|
||||||
|
Theme.of(context).colorScheme.secondary,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
],
|
),
|
||||||
)
|
),
|
||||||
: Container(),
|
],
|
||||||
|
)
|
||||||
|
else
|
||||||
|
Container(),
|
||||||
widget.child ?? const SizedBox(),
|
widget.child ?? const SizedBox(),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -3,8 +3,8 @@ import 'package:flutter_i18n/flutter_i18n.dart';
|
|||||||
import 'package:google_fonts/google_fonts.dart';
|
import 'package:google_fonts/google_fonts.dart';
|
||||||
|
|
||||||
class OptionsTextField extends StatelessWidget {
|
class OptionsTextField extends StatelessWidget {
|
||||||
final String hint;
|
|
||||||
const OptionsTextField({Key? key, required this.hint}) : super(key: key);
|
const OptionsTextField({Key? key, required this.hint}) : super(key: key);
|
||||||
|
final String hint;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
@@ -12,7 +12,7 @@ class OptionsTextField extends StatelessWidget {
|
|||||||
final sWidth = MediaQuery.of(context).size.width;
|
final sWidth = MediaQuery.of(context).size.width;
|
||||||
return Container(
|
return Container(
|
||||||
margin: const EdgeInsets.only(top: 12, bottom: 6),
|
margin: const EdgeInsets.only(top: 12, bottom: 6),
|
||||||
padding: const EdgeInsets.all(0),
|
padding: EdgeInsets.zero,
|
||||||
child: TextField(
|
child: TextField(
|
||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
constraints: BoxConstraints(
|
constraints: BoxConstraints(
|
||||||
@@ -28,9 +28,9 @@ class OptionsTextField extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class OptionsFilePicker extends StatelessWidget {
|
class OptionsFilePicker extends StatelessWidget {
|
||||||
final String optionName;
|
|
||||||
const OptionsFilePicker({Key? key, required this.optionName})
|
const OptionsFilePicker({Key? key, required this.optionName})
|
||||||
: super(key: key);
|
: super(key: key);
|
||||||
|
final String optionName;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
@@ -61,7 +61,7 @@ class OptionsFilePicker extends StatelessWidget {
|
|||||||
child: Text(
|
child: Text(
|
||||||
'Select File',
|
'Select File',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: Theme.of(context).textTheme.bodyText1?.color,
|
color: Theme.of(context).textTheme.bodyLarge?.color,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
import 'package:flutter_i18n/flutter_i18n.dart';
|
import 'package:flutter_i18n/flutter_i18n.dart';
|
||||||
import 'package:revanced_manager/utils/about_info.dart';
|
import 'package:revanced_manager/utils/about_info.dart';
|
||||||
import 'package:flutter/services.dart';
|
|
||||||
|
|
||||||
class AboutWidget extends StatefulWidget {
|
class AboutWidget extends StatefulWidget {
|
||||||
const AboutWidget({Key? key, this.padding}) : super(key: key);
|
const AboutWidget({Key? key, this.padding}) : super(key: key);
|
||||||
|
|||||||
@@ -1,14 +1,13 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
class CustomSwitch extends StatelessWidget {
|
class CustomSwitch extends StatelessWidget {
|
||||||
final ValueChanged<bool> onChanged;
|
|
||||||
final bool value;
|
|
||||||
|
|
||||||
const CustomSwitch({
|
const CustomSwitch({
|
||||||
Key? key,
|
Key? key,
|
||||||
required this.onChanged,
|
required this.onChanged,
|
||||||
required this.value,
|
required this.value,
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
final ValueChanged<bool> onChanged;
|
||||||
|
final bool value;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
@@ -45,7 +44,7 @@ class CustomSwitch extends StatelessWidget {
|
|||||||
shape: BoxShape.circle,
|
shape: BoxShape.circle,
|
||||||
color: value
|
color: value
|
||||||
? Theme.of(context).colorScheme.primaryContainer
|
? Theme.of(context).colorScheme.primaryContainer
|
||||||
: Colors.white,
|
: Theme.of(context).colorScheme.surface,
|
||||||
boxShadow: [
|
boxShadow: [
|
||||||
BoxShadow(
|
BoxShadow(
|
||||||
color: Colors.black12.withOpacity(0.1),
|
color: Colors.black12.withOpacity(0.1),
|
||||||
|
|||||||
@@ -2,12 +2,6 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:revanced_manager/ui/widgets/settingsView/custom_switch.dart';
|
import 'package:revanced_manager/ui/widgets/settingsView/custom_switch.dart';
|
||||||
|
|
||||||
class CustomSwitchTile extends StatelessWidget {
|
class CustomSwitchTile extends StatelessWidget {
|
||||||
final Widget title;
|
|
||||||
final Widget subtitle;
|
|
||||||
final bool value;
|
|
||||||
final Function(bool) onTap;
|
|
||||||
final EdgeInsetsGeometry? padding;
|
|
||||||
|
|
||||||
const CustomSwitchTile({
|
const CustomSwitchTile({
|
||||||
Key? key,
|
Key? key,
|
||||||
required this.title,
|
required this.title,
|
||||||
@@ -16,6 +10,11 @@ class CustomSwitchTile extends StatelessWidget {
|
|||||||
required this.onTap,
|
required this.onTap,
|
||||||
this.padding,
|
this.padding,
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
final Widget title;
|
||||||
|
final Widget subtitle;
|
||||||
|
final bool value;
|
||||||
|
final Function(bool) onTap;
|
||||||
|
final EdgeInsetsGeometry? padding;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
|||||||
@@ -1,12 +1,6 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
class CustomTextField extends StatelessWidget {
|
class CustomTextField extends StatelessWidget {
|
||||||
final TextEditingController inputController;
|
|
||||||
final Widget label;
|
|
||||||
final String hint;
|
|
||||||
final Widget? leadingIcon;
|
|
||||||
final Function(String)? onChanged;
|
|
||||||
|
|
||||||
const CustomTextField({
|
const CustomTextField({
|
||||||
Key? key,
|
Key? key,
|
||||||
required this.inputController,
|
required this.inputController,
|
||||||
@@ -15,6 +9,11 @@ class CustomTextField extends StatelessWidget {
|
|||||||
this.leadingIcon,
|
this.leadingIcon,
|
||||||
required this.onChanged,
|
required this.onChanged,
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
final TextEditingController inputController;
|
||||||
|
final Widget label;
|
||||||
|
final String hint;
|
||||||
|
final Widget? leadingIcon;
|
||||||
|
final Function(String)? onChanged;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
@@ -45,10 +44,8 @@ class CustomTextField extends StatelessWidget {
|
|||||||
border: OutlineInputBorder(
|
border: OutlineInputBorder(
|
||||||
borderSide: BorderSide(
|
borderSide: BorderSide(
|
||||||
color: Theme.of(context).colorScheme.primary,
|
color: Theme.of(context).colorScheme.primary,
|
||||||
width: 1.0,
|
|
||||||
),
|
),
|
||||||
borderRadius: BorderRadius.circular(10),
|
borderRadius: BorderRadius.circular(10),
|
||||||
gapPadding: 4.0,
|
|
||||||
),
|
),
|
||||||
focusedBorder: OutlineInputBorder(
|
focusedBorder: OutlineInputBorder(
|
||||||
borderSide: BorderSide(
|
borderSide: BorderSide(
|
||||||
@@ -60,14 +57,12 @@ class CustomTextField extends StatelessWidget {
|
|||||||
errorBorder: OutlineInputBorder(
|
errorBorder: OutlineInputBorder(
|
||||||
borderSide: BorderSide(
|
borderSide: BorderSide(
|
||||||
color: Theme.of(context).colorScheme.error,
|
color: Theme.of(context).colorScheme.error,
|
||||||
width: 1.0,
|
|
||||||
),
|
),
|
||||||
borderRadius: BorderRadius.circular(10),
|
borderRadius: BorderRadius.circular(10),
|
||||||
),
|
),
|
||||||
enabledBorder: OutlineInputBorder(
|
enabledBorder: OutlineInputBorder(
|
||||||
borderSide: BorderSide(
|
borderSide: BorderSide(
|
||||||
color: Theme.of(context).colorScheme.primary,
|
color: Theme.of(context).colorScheme.primary,
|
||||||
width: 1.0,
|
|
||||||
),
|
),
|
||||||
borderRadius: BorderRadius.circular(10),
|
borderRadius: BorderRadius.circular(10),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -2,8 +2,8 @@
|
|||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_i18n/widgets/I18nText.dart';
|
import 'package:flutter_i18n/widgets/I18nText.dart';
|
||||||
import 'package:revanced_manager/ui/views/settings/settingsFragement/settings_manage_api_url.dart';
|
import 'package:revanced_manager/ui/views/settings/settingsFragment/settings_manage_api_url.dart';
|
||||||
import 'package:revanced_manager/ui/views/settings/settingsFragement/settings_manage_sources.dart';
|
import 'package:revanced_manager/ui/views/settings/settingsFragment/settings_manage_sources.dart';
|
||||||
import 'package:revanced_manager/ui/views/settings/settings_viewmodel.dart';
|
import 'package:revanced_manager/ui/views/settings/settings_viewmodel.dart';
|
||||||
import 'package:revanced_manager/ui/widgets/settingsView/settings_experimental_patches.dart';
|
import 'package:revanced_manager/ui/widgets/settingsView/settings_experimental_patches.dart';
|
||||||
import 'package:revanced_manager/ui/widgets/settingsView/settings_experimental_universal_patches.dart';
|
import 'package:revanced_manager/ui/widgets/settingsView/settings_experimental_universal_patches.dart';
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_i18n/widgets/I18nText.dart';
|
import 'package:flutter_i18n/widgets/I18nText.dart';
|
||||||
import 'package:revanced_manager/ui/views/settings/settings_viewmodel.dart';
|
import 'package:revanced_manager/ui/views/settings/settings_viewmodel.dart';
|
||||||
import 'package:revanced_manager/ui/widgets/settingsView/custom_switch_tile.dart';
|
|
||||||
|
|
||||||
class SExperimentalPatches extends StatefulWidget {
|
class SExperimentalPatches extends StatefulWidget {
|
||||||
const SExperimentalPatches({super.key});
|
const SExperimentalPatches({super.key});
|
||||||
@@ -15,8 +14,8 @@ final _settingsViewModel = SettingsViewModel();
|
|||||||
class _SExperimentalPatchesState extends State<SExperimentalPatches> {
|
class _SExperimentalPatchesState extends State<SExperimentalPatches> {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return CustomSwitchTile(
|
return SwitchListTile(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 20.0),
|
contentPadding: const EdgeInsets.symmetric(horizontal: 20.0),
|
||||||
title: I18nText(
|
title: I18nText(
|
||||||
'settingsView.experimentalPatchesLabel',
|
'settingsView.experimentalPatchesLabel',
|
||||||
child: const Text(
|
child: const Text(
|
||||||
@@ -29,7 +28,7 @@ class _SExperimentalPatchesState extends State<SExperimentalPatches> {
|
|||||||
),
|
),
|
||||||
subtitle: I18nText('settingsView.experimentalPatchesHint'),
|
subtitle: I18nText('settingsView.experimentalPatchesHint'),
|
||||||
value: _settingsViewModel.areExperimentalPatchesEnabled(),
|
value: _settingsViewModel.areExperimentalPatchesEnabled(),
|
||||||
onTap: (value) {
|
onChanged: (value) {
|
||||||
setState(() {
|
setState(() {
|
||||||
_settingsViewModel.useExperimentalPatches(value);
|
_settingsViewModel.useExperimentalPatches(value);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,22 +1,23 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_i18n/widgets/I18nText.dart';
|
import 'package:flutter_i18n/widgets/I18nText.dart';
|
||||||
import 'package:revanced_manager/ui/views/settings/settings_viewmodel.dart';
|
import 'package:revanced_manager/ui/views/settings/settings_viewmodel.dart';
|
||||||
import 'package:revanced_manager/ui/widgets/settingsView/custom_switch_tile.dart';
|
|
||||||
|
|
||||||
class SExperimentalUniversalPatches extends StatefulWidget {
|
class SExperimentalUniversalPatches extends StatefulWidget {
|
||||||
const SExperimentalUniversalPatches({super.key});
|
const SExperimentalUniversalPatches({super.key});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<SExperimentalUniversalPatches> createState() => _SExperimentalUniversalPatchesState();
|
State<SExperimentalUniversalPatches> createState() =>
|
||||||
|
_SExperimentalUniversalPatchesState();
|
||||||
}
|
}
|
||||||
|
|
||||||
final _settingsViewModel = SettingsViewModel();
|
final _settingsViewModel = SettingsViewModel();
|
||||||
|
|
||||||
class _SExperimentalUniversalPatchesState extends State<SExperimentalUniversalPatches> {
|
class _SExperimentalUniversalPatchesState
|
||||||
|
extends State<SExperimentalUniversalPatches> {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return CustomSwitchTile(
|
return SwitchListTile(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 20.0),
|
contentPadding: const EdgeInsets.symmetric(horizontal: 20.0),
|
||||||
title: I18nText(
|
title: I18nText(
|
||||||
'settingsView.experimentalUniversalPatchesLabel',
|
'settingsView.experimentalUniversalPatchesLabel',
|
||||||
child: const Text(
|
child: const Text(
|
||||||
@@ -29,7 +30,7 @@ class _SExperimentalUniversalPatchesState extends State<SExperimentalUniversalPa
|
|||||||
),
|
),
|
||||||
subtitle: I18nText('settingsView.experimentalUniversalPatchesHint'),
|
subtitle: I18nText('settingsView.experimentalUniversalPatchesHint'),
|
||||||
value: _settingsViewModel.areUniversalPatchesEnabled(),
|
value: _settingsViewModel.areUniversalPatchesEnabled(),
|
||||||
onTap: (value) {
|
onChanged: (value) {
|
||||||
setState(() {
|
setState(() {
|
||||||
_settingsViewModel.showUniversalPatches(value);
|
_settingsViewModel.showUniversalPatches(value);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,36 +0,0 @@
|
|||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:flutter_i18n/widgets/I18nText.dart';
|
|
||||||
import 'package:revanced_manager/ui/views/settings/settings_viewmodel.dart';
|
|
||||||
import 'package:revanced_manager/ui/widgets/settingsView/custom_switch_tile.dart';
|
|
||||||
import 'package:revanced_manager/ui/widgets/settingsView/settings_section.dart';
|
|
||||||
|
|
||||||
final _settingsViewModel = SettingsViewModel();
|
|
||||||
|
|
||||||
class SLoggingSection extends StatelessWidget {
|
|
||||||
const SLoggingSection({super.key});
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return SettingsSection(
|
|
||||||
title: 'settingsView.logsSectionTitle',
|
|
||||||
children: <Widget>[
|
|
||||||
CustomSwitchTile(
|
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 20.0),
|
|
||||||
title: I18nText(
|
|
||||||
'settingsView.sentryLabel',
|
|
||||||
child: const Text(
|
|
||||||
'',
|
|
||||||
style: TextStyle(
|
|
||||||
fontSize: 20,
|
|
||||||
fontWeight: FontWeight.w500,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
subtitle: I18nText('settingsView.sentryHint'),
|
|
||||||
value: _settingsViewModel.isSentryEnabled(),
|
|
||||||
onTap: (value) => _settingsViewModel.useSentry(value),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -2,14 +2,13 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:flutter_i18n/flutter_i18n.dart';
|
import 'package:flutter_i18n/flutter_i18n.dart';
|
||||||
|
|
||||||
class SettingsSection extends StatelessWidget {
|
class SettingsSection extends StatelessWidget {
|
||||||
final String title;
|
|
||||||
final List<Widget> children;
|
|
||||||
|
|
||||||
const SettingsSection({
|
const SettingsSection({
|
||||||
Key? key,
|
Key? key,
|
||||||
required this.title,
|
required this.title,
|
||||||
required this.children,
|
required this.children,
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
final String title;
|
||||||
|
final List<Widget> children;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
|||||||
@@ -2,11 +2,6 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:flutter_i18n/flutter_i18n.dart';
|
import 'package:flutter_i18n/flutter_i18n.dart';
|
||||||
|
|
||||||
class SettingsTileDialog extends StatelessWidget {
|
class SettingsTileDialog extends StatelessWidget {
|
||||||
final String title;
|
|
||||||
final String subtitle;
|
|
||||||
final Function()? onTap;
|
|
||||||
final EdgeInsetsGeometry? padding;
|
|
||||||
|
|
||||||
const SettingsTileDialog({
|
const SettingsTileDialog({
|
||||||
Key? key,
|
Key? key,
|
||||||
required this.title,
|
required this.title,
|
||||||
@@ -14,6 +9,10 @@ class SettingsTileDialog extends StatelessWidget {
|
|||||||
required this.onTap,
|
required this.onTap,
|
||||||
this.padding,
|
this.padding,
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
final String title;
|
||||||
|
final String subtitle;
|
||||||
|
final Function()? onTap;
|
||||||
|
final EdgeInsetsGeometry? padding;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
|||||||
@@ -1,13 +1,7 @@
|
|||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:url_launcher/url_launcher.dart';
|
import 'package:url_launcher/url_launcher.dart';
|
||||||
|
|
||||||
class SocialMediaItem extends StatelessWidget {
|
class SocialMediaItem extends StatelessWidget {
|
||||||
final Widget? icon;
|
|
||||||
final Widget title;
|
|
||||||
final Widget? subtitle;
|
|
||||||
final String? url;
|
|
||||||
|
|
||||||
const SocialMediaItem({
|
const SocialMediaItem({
|
||||||
Key? key,
|
Key? key,
|
||||||
this.icon,
|
this.icon,
|
||||||
@@ -15,6 +9,10 @@ class SocialMediaItem extends StatelessWidget {
|
|||||||
this.subtitle,
|
this.subtitle,
|
||||||
this.url,
|
this.url,
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
final Widget? icon;
|
||||||
|
final Widget title;
|
||||||
|
final Widget? subtitle;
|
||||||
|
final String? url;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
|||||||
@@ -6,12 +6,11 @@ import 'package:revanced_manager/ui/widgets/settingsView/social_media_item.dart'
|
|||||||
import 'package:revanced_manager/ui/widgets/shared/custom_card.dart';
|
import 'package:revanced_manager/ui/widgets/shared/custom_card.dart';
|
||||||
|
|
||||||
class SocialMediaWidget extends StatelessWidget {
|
class SocialMediaWidget extends StatelessWidget {
|
||||||
final EdgeInsetsGeometry? padding;
|
|
||||||
|
|
||||||
const SocialMediaWidget({
|
const SocialMediaWidget({
|
||||||
Key? key,
|
Key? key,
|
||||||
this.padding,
|
this.padding,
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
final EdgeInsetsGeometry? padding;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
|||||||
@@ -1,19 +1,13 @@
|
|||||||
import 'dart:typed_data';
|
import 'dart:typed_data';
|
||||||
|
|
||||||
|
import 'package:expandable/expandable.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_i18n/flutter_i18n.dart';
|
import 'package:flutter_i18n/flutter_i18n.dart';
|
||||||
import 'package:revanced_manager/ui/widgets/shared/custom_material_button.dart';
|
|
||||||
import 'package:revanced_manager/ui/widgets/shared/custom_card.dart';
|
import 'package:revanced_manager/ui/widgets/shared/custom_card.dart';
|
||||||
import 'package:expandable/expandable.dart';
|
import 'package:revanced_manager/ui/widgets/shared/custom_material_button.dart';
|
||||||
import 'package:timeago/timeago.dart';
|
import 'package:timeago/timeago.dart';
|
||||||
|
|
||||||
class ApplicationItem extends StatefulWidget {
|
class ApplicationItem extends StatefulWidget {
|
||||||
final Uint8List icon;
|
|
||||||
final String name;
|
|
||||||
final DateTime patchDate;
|
|
||||||
final List<String> changelog;
|
|
||||||
final bool isUpdatableApp;
|
|
||||||
final Function() onPressed;
|
|
||||||
|
|
||||||
const ApplicationItem({
|
const ApplicationItem({
|
||||||
Key? key,
|
Key? key,
|
||||||
required this.icon,
|
required this.icon,
|
||||||
@@ -23,6 +17,12 @@ class ApplicationItem extends StatefulWidget {
|
|||||||
required this.isUpdatableApp,
|
required this.isUpdatableApp,
|
||||||
required this.onPressed,
|
required this.onPressed,
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
final Uint8List icon;
|
||||||
|
final String name;
|
||||||
|
final DateTime patchDate;
|
||||||
|
final List<String> changelog;
|
||||||
|
final bool isUpdatableApp;
|
||||||
|
final Function() onPressed;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<ApplicationItem> createState() => _ApplicationItemState();
|
State<ApplicationItem> createState() => _ApplicationItemState();
|
||||||
@@ -33,7 +33,7 @@ class _ApplicationItemState extends State<ApplicationItem>
|
|||||||
late AnimationController _animationController;
|
late AnimationController _animationController;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
_animationController = AnimationController(
|
_animationController = AnimationController(
|
||||||
vsync: this,
|
vsync: this,
|
||||||
@@ -49,95 +49,100 @@ class _ApplicationItemState extends State<ApplicationItem>
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
ExpandableController expController = ExpandableController();
|
final ExpandableController expController = ExpandableController();
|
||||||
return Container(
|
return Container(
|
||||||
margin: const EdgeInsets.only(bottom: 16.0),
|
margin: const EdgeInsets.only(bottom: 16.0),
|
||||||
child: CustomCard(
|
child: CustomCard(
|
||||||
onTap: () {
|
onTap: () {
|
||||||
expController.toggle();
|
expController.toggle();
|
||||||
_animationController.isCompleted
|
_animationController.isCompleted
|
||||||
? _animationController.reverse()
|
? _animationController.reverse()
|
||||||
: _animationController.forward();
|
: _animationController.forward();
|
||||||
},
|
},
|
||||||
child: ExpandablePanel(
|
child: ExpandablePanel(
|
||||||
controller: expController,
|
controller: expController,
|
||||||
theme: const ExpandableThemeData(
|
theme: const ExpandableThemeData(
|
||||||
inkWellBorderRadius: BorderRadius.all(Radius.circular(16)),
|
inkWellBorderRadius: BorderRadius.all(Radius.circular(16)),
|
||||||
tapBodyToCollapse: false,
|
tapBodyToCollapse: false,
|
||||||
tapBodyToExpand: false,
|
tapBodyToExpand: false,
|
||||||
tapHeaderToExpand: false,
|
tapHeaderToExpand: false,
|
||||||
hasIcon: false,
|
hasIcon: false,
|
||||||
animationDuration: Duration(milliseconds: 450),
|
animationDuration: Duration(milliseconds: 450),
|
||||||
),
|
),
|
||||||
header: Row(
|
header: Row(
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
SizedBox(
|
SizedBox(
|
||||||
width: 40,
|
width: 40,
|
||||||
child: Image.memory(widget.icon, height: 40, width: 40),
|
child: Image.memory(widget.icon, height: 40, width: 40),
|
||||||
),
|
),
|
||||||
const SizedBox(width: 4),
|
const SizedBox(width: 4),
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.only(left: 15.0),
|
padding: const EdgeInsets.only(left: 15.0),
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: <Widget>[
|
|
||||||
Text(
|
|
||||||
widget.name.length > 12
|
|
||||||
? '${widget.name.substring(0, 12)}...'
|
|
||||||
: widget.name,
|
|
||||||
style: const TextStyle(
|
|
||||||
fontSize: 16,
|
|
||||||
fontWeight: FontWeight.w500,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Text(format(widget.patchDate)),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const Spacer(),
|
|
||||||
RotationTransition(
|
|
||||||
turns: Tween(begin: 0.0, end: 0.50)
|
|
||||||
.animate(_animationController),
|
|
||||||
child: const Padding(
|
|
||||||
padding: EdgeInsets.all(8.0),
|
|
||||||
child: Icon(Icons.arrow_drop_down),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(width: 8),
|
|
||||||
Column(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.end,
|
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
CustomMaterialButton(
|
Text(
|
||||||
label: widget.isUpdatableApp
|
widget.name.length > 12
|
||||||
? I18nText('applicationItem.patchButton')
|
? '${widget.name.substring(0, 12)}...'
|
||||||
: I18nText('applicationItem.infoButton'),
|
: widget.name,
|
||||||
onPressed: widget.onPressed,
|
style: const TextStyle(
|
||||||
|
fontSize: 16,
|
||||||
|
fontWeight: FontWeight.w500,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
|
Text(format(widget.patchDate)),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
],
|
),
|
||||||
),
|
const Spacer(),
|
||||||
collapsed: const SizedBox(),
|
RotationTransition(
|
||||||
expanded: Padding(
|
turns:
|
||||||
padding: const EdgeInsets.only(
|
Tween(begin: 0.0, end: 0.50).animate(_animationController),
|
||||||
top: 16.0, left: 4.0, right: 4.0, bottom: 4.0),
|
child: const Padding(
|
||||||
child: Column(
|
padding: EdgeInsets.all(8.0),
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
child: Icon(Icons.arrow_drop_down),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 8),
|
||||||
|
Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.end,
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
I18nText(
|
CustomMaterialButton(
|
||||||
'applicationItem.changelogLabel',
|
label: widget.isUpdatableApp
|
||||||
child: const Text(
|
? I18nText('applicationItem.patchButton')
|
||||||
'',
|
: I18nText('applicationItem.infoButton'),
|
||||||
style: TextStyle(fontWeight: FontWeight.w700),
|
onPressed: widget.onPressed,
|
||||||
),
|
|
||||||
),
|
),
|
||||||
const SizedBox(height: 4),
|
|
||||||
Text('\u2022 ${widget.changelog.join('\n\u2022 ')}'),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
collapsed: const SizedBox(),
|
||||||
|
expanded: Padding(
|
||||||
|
padding: const EdgeInsets.only(
|
||||||
|
top: 16.0,
|
||||||
|
left: 4.0,
|
||||||
|
right: 4.0,
|
||||||
|
bottom: 4.0,
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: <Widget>[
|
||||||
|
I18nText(
|
||||||
|
'applicationItem.changelogLabel',
|
||||||
|
child: const Text(
|
||||||
|
'',
|
||||||
|
style: TextStyle(fontWeight: FontWeight.w700),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 4),
|
||||||
|
Text('\u2022 ${widget.changelog.join('\n\u2022 ')}'),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
));
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,21 +1,20 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
class CustomCard extends StatelessWidget {
|
class CustomCard extends StatelessWidget {
|
||||||
|
const CustomCard({
|
||||||
|
Key? key,
|
||||||
|
this.isFilled = true,
|
||||||
|
required this.child,
|
||||||
|
this.onTap,
|
||||||
|
this.padding,
|
||||||
|
this.backgroundColor,
|
||||||
|
}) : super(key: key);
|
||||||
final bool isFilled;
|
final bool isFilled;
|
||||||
final Widget child;
|
final Widget child;
|
||||||
final Function()? onTap;
|
final Function()? onTap;
|
||||||
final EdgeInsetsGeometry? padding;
|
final EdgeInsetsGeometry? padding;
|
||||||
final Color? backgroundColor;
|
final Color? backgroundColor;
|
||||||
|
|
||||||
const CustomCard(
|
|
||||||
{Key? key,
|
|
||||||
this.isFilled = true,
|
|
||||||
required this.child,
|
|
||||||
this.onTap,
|
|
||||||
this.padding,
|
|
||||||
this.backgroundColor})
|
|
||||||
: super(key: key);
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Material(
|
return Material(
|
||||||
|
|||||||
@@ -1,16 +1,15 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
class CustomChip extends StatelessWidget {
|
class CustomChip extends StatelessWidget {
|
||||||
final Widget label;
|
|
||||||
final bool isSelected;
|
|
||||||
final Function(bool)? onSelected;
|
|
||||||
|
|
||||||
const CustomChip({
|
const CustomChip({
|
||||||
Key? key,
|
Key? key,
|
||||||
required this.label,
|
required this.label,
|
||||||
this.isSelected = false,
|
this.isSelected = false,
|
||||||
this.onSelected,
|
this.onSelected,
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
final Widget label;
|
||||||
|
final bool isSelected;
|
||||||
|
final Function(bool)? onSelected;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
@@ -18,7 +17,7 @@ class CustomChip extends StatelessWidget {
|
|||||||
showCheckmark: false,
|
showCheckmark: false,
|
||||||
label: label,
|
label: label,
|
||||||
selected: isSelected,
|
selected: isSelected,
|
||||||
labelStyle: Theme.of(context).textTheme.subtitle2!.copyWith(
|
labelStyle: Theme.of(context).textTheme.titleSmall!.copyWith(
|
||||||
color: isSelected
|
color: isSelected
|
||||||
? Theme.of(context).colorScheme.primary
|
? Theme.of(context).colorScheme.primary
|
||||||
: Theme.of(context).colorScheme.secondary,
|
: Theme.of(context).colorScheme.secondary,
|
||||||
|
|||||||
@@ -1,11 +1,6 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
class CustomMaterialButton extends StatelessWidget {
|
class CustomMaterialButton extends StatelessWidget {
|
||||||
final Widget label;
|
|
||||||
final bool isFilled;
|
|
||||||
final bool isExpanded;
|
|
||||||
final Function()? onPressed;
|
|
||||||
|
|
||||||
const CustomMaterialButton({
|
const CustomMaterialButton({
|
||||||
Key? key,
|
Key? key,
|
||||||
required this.label,
|
required this.label,
|
||||||
@@ -13,6 +8,10 @@ class CustomMaterialButton extends StatelessWidget {
|
|||||||
this.isExpanded = false,
|
this.isExpanded = false,
|
||||||
required this.onPressed,
|
required this.onPressed,
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
final Widget label;
|
||||||
|
final bool isFilled;
|
||||||
|
final bool isExpanded;
|
||||||
|
final Function()? onPressed;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
@@ -28,7 +27,6 @@ class CustomMaterialButton extends StatelessWidget {
|
|||||||
side: isFilled
|
side: isFilled
|
||||||
? BorderSide.none
|
? BorderSide.none
|
||||||
: BorderSide(
|
: BorderSide(
|
||||||
width: 1,
|
|
||||||
color: Theme.of(context).colorScheme.primary,
|
color: Theme.of(context).colorScheme.primary,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -50,12 +48,6 @@ class CustomMaterialButton extends StatelessWidget {
|
|||||||
|
|
||||||
// ignore: must_be_immutable
|
// ignore: must_be_immutable
|
||||||
class TimerButton extends StatefulWidget {
|
class TimerButton extends StatefulWidget {
|
||||||
Widget label;
|
|
||||||
bool isFilled;
|
|
||||||
int seconds;
|
|
||||||
final bool isRunning;
|
|
||||||
final Function()? onTimerEnd;
|
|
||||||
|
|
||||||
TimerButton({
|
TimerButton({
|
||||||
Key? key,
|
Key? key,
|
||||||
required this.seconds,
|
required this.seconds,
|
||||||
@@ -64,6 +56,11 @@ class TimerButton extends StatefulWidget {
|
|||||||
this.label = const Text(''),
|
this.label = const Text(''),
|
||||||
this.isFilled = true,
|
this.isFilled = true,
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
Widget label;
|
||||||
|
bool isFilled;
|
||||||
|
int seconds;
|
||||||
|
final bool isRunning;
|
||||||
|
final Function()? onTimerEnd;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<TimerButton> createState() => _TimerButtonState();
|
State<TimerButton> createState() => _TimerButtonState();
|
||||||
@@ -101,7 +98,6 @@ class _TimerButtonState extends State<TimerButton> {
|
|||||||
side: widget.isFilled
|
side: widget.isFilled
|
||||||
? BorderSide.none
|
? BorderSide.none
|
||||||
: BorderSide(
|
: BorderSide(
|
||||||
width: 1,
|
|
||||||
color: Theme.of(context).colorScheme.primary,
|
color: Theme.of(context).colorScheme.primary,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -1,14 +1,13 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
class CustomPopupMenu extends StatelessWidget {
|
class CustomPopupMenu extends StatelessWidget {
|
||||||
final Function(dynamic) onSelected;
|
|
||||||
final Map<int, Widget> children;
|
|
||||||
|
|
||||||
const CustomPopupMenu({
|
const CustomPopupMenu({
|
||||||
Key? key,
|
Key? key,
|
||||||
required this.onSelected,
|
required this.onSelected,
|
||||||
required this.children,
|
required this.children,
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
final Function(dynamic) onSelected;
|
||||||
|
final Map<int, Widget> children;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
|||||||
@@ -1,12 +1,6 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
class CustomSliverAppBar extends StatelessWidget {
|
class CustomSliverAppBar extends StatelessWidget {
|
||||||
final Widget title;
|
|
||||||
final List<Widget>? actions;
|
|
||||||
final PreferredSizeWidget? bottom;
|
|
||||||
final bool isMainView;
|
|
||||||
final Function()? onBackButtonPressed;
|
|
||||||
|
|
||||||
const CustomSliverAppBar({
|
const CustomSliverAppBar({
|
||||||
Key? key,
|
Key? key,
|
||||||
required this.title,
|
required this.title,
|
||||||
@@ -15,13 +9,16 @@ class CustomSliverAppBar extends StatelessWidget {
|
|||||||
this.isMainView = false,
|
this.isMainView = false,
|
||||||
this.onBackButtonPressed,
|
this.onBackButtonPressed,
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
final Widget title;
|
||||||
|
final List<Widget>? actions;
|
||||||
|
final PreferredSizeWidget? bottom;
|
||||||
|
final bool isMainView;
|
||||||
|
final Function()? onBackButtonPressed;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return SliverAppBar(
|
return SliverAppBar(
|
||||||
pinned: true,
|
pinned: true,
|
||||||
snap: false,
|
|
||||||
floating: false,
|
|
||||||
expandedHeight: 100.0,
|
expandedHeight: 100.0,
|
||||||
automaticallyImplyLeading: !isMainView,
|
automaticallyImplyLeading: !isMainView,
|
||||||
flexibleSpace: FlexibleSpaceBar(
|
flexibleSpace: FlexibleSpaceBar(
|
||||||
@@ -36,7 +33,7 @@ class CustomSliverAppBar extends StatelessWidget {
|
|||||||
: IconButton(
|
: IconButton(
|
||||||
icon: Icon(
|
icon: Icon(
|
||||||
Icons.arrow_back,
|
Icons.arrow_back,
|
||||||
color: Theme.of(context).textTheme.headline6!.color,
|
color: Theme.of(context).textTheme.titleLarge!.color,
|
||||||
),
|
),
|
||||||
onPressed:
|
onPressed:
|
||||||
onBackButtonPressed ?? () => Navigator.of(context).pop(),
|
onBackButtonPressed ?? () => Navigator.of(context).pop(),
|
||||||
|
|||||||
@@ -2,21 +2,19 @@ import 'package:animations/animations.dart';
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
class OpenContainerWrapper extends StatelessWidget {
|
class OpenContainerWrapper extends StatelessWidget {
|
||||||
final OpenContainerBuilder openBuilder;
|
|
||||||
final CloseContainerBuilder closedBuilder;
|
|
||||||
|
|
||||||
const OpenContainerWrapper({
|
const OpenContainerWrapper({
|
||||||
Key? key,
|
Key? key,
|
||||||
required this.openBuilder,
|
required this.openBuilder,
|
||||||
required this.closedBuilder,
|
required this.closedBuilder,
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
final OpenContainerBuilder openBuilder;
|
||||||
|
final CloseContainerBuilder closedBuilder;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return OpenContainer(
|
return OpenContainer(
|
||||||
openBuilder: openBuilder,
|
openBuilder: openBuilder,
|
||||||
closedBuilder: closedBuilder,
|
closedBuilder: closedBuilder,
|
||||||
transitionType: ContainerTransitionType.fade,
|
|
||||||
transitionDuration: const Duration(milliseconds: 400),
|
transitionDuration: const Duration(milliseconds: 400),
|
||||||
openColor: Theme.of(context).colorScheme.primary,
|
openColor: Theme.of(context).colorScheme.primary,
|
||||||
closedColor: Colors.transparent,
|
closedColor: Colors.transparent,
|
||||||
|
|||||||
@@ -1,10 +1,6 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
class SearchBar extends StatefulWidget {
|
class SearchBar extends StatefulWidget {
|
||||||
final String? hintText;
|
|
||||||
final bool showSelectIcon;
|
|
||||||
final Function(bool)? onSelectAll;
|
|
||||||
|
|
||||||
const SearchBar({
|
const SearchBar({
|
||||||
Key? key,
|
Key? key,
|
||||||
required this.hintText,
|
required this.hintText,
|
||||||
@@ -12,6 +8,9 @@ class SearchBar extends StatefulWidget {
|
|||||||
this.onSelectAll,
|
this.onSelectAll,
|
||||||
required this.onQueryChanged,
|
required this.onQueryChanged,
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
final String? hintText;
|
||||||
|
final bool showSelectIcon;
|
||||||
|
final Function(bool)? onSelectAll;
|
||||||
|
|
||||||
final Function(String) onQueryChanged;
|
final Function(String) onQueryChanged;
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
|
import 'package:device_info_plus/device_info_plus.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:package_info_plus/package_info_plus.dart';
|
import 'package:package_info_plus/package_info_plus.dart';
|
||||||
import 'package:device_info_plus/device_info_plus.dart';
|
|
||||||
|
|
||||||
class AboutInfo {
|
class AboutInfo {
|
||||||
static Future<Map<String, dynamic>> getInfo() async {
|
static Future<Map<String, dynamic>> getInfo() async {
|
||||||
|
|||||||
@@ -1,5 +0,0 @@
|
|||||||
// 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 = '';
|
|
||||||
}
|
|
||||||
13
pubspec.yaml
@@ -4,18 +4,17 @@ homepage: https://github.com/revanced/revanced-manager
|
|||||||
|
|
||||||
publish_to: 'none'
|
publish_to: 'none'
|
||||||
|
|
||||||
version: 0.0.53+53
|
version: 0.0.57+57
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: ">=2.17.5 <3.0.0"
|
sdk: ">=2.17.5 <3.0.0"
|
||||||
|
|
||||||
dependencies:
|
dependencies:
|
||||||
sentry_flutter: ^6.12.2
|
|
||||||
animations: ^2.0.4
|
animations: ^2.0.4
|
||||||
app_installer: ^1.1.0
|
app_installer: ^1.1.0
|
||||||
collection: ^1.16.0
|
collection: ^1.16.0
|
||||||
cross_connectivity: ^3.0.5
|
cross_connectivity: ^3.0.5
|
||||||
cr_file_saver: ^0.0.1+2
|
cr_file_saver: ^0.0.2
|
||||||
device_apps:
|
device_apps:
|
||||||
git:
|
git:
|
||||||
url: https://github.com/ponces/flutter_plugin_device_apps
|
url: https://github.com/ponces/flutter_plugin_device_apps
|
||||||
@@ -65,15 +64,14 @@ dependencies:
|
|||||||
share_extend: ^2.0.0
|
share_extend: ^2.0.0
|
||||||
shared_preferences: ^2.0.15
|
shared_preferences: ^2.0.15
|
||||||
skeletons: ^0.0.3
|
skeletons: ^0.0.3
|
||||||
stacked: ^3.0.0
|
stacked: ^3.2.0
|
||||||
stacked_generator: ^0.8.0
|
stacked_generator: ^1.0.0
|
||||||
stacked_services: ^0.9.3
|
stacked_services: ^1.0.0
|
||||||
stacked_themes: ^0.3.9
|
stacked_themes: ^0.3.9
|
||||||
timeago: ^3.2.2
|
timeago: ^3.2.2
|
||||||
timezone: ^0.8.0
|
timezone: ^0.8.0
|
||||||
url_launcher: ^6.1.5
|
url_launcher: ^6.1.5
|
||||||
wakelock: ^0.6.2
|
wakelock: ^0.6.2
|
||||||
sentry_dio: ^6.12.2
|
|
||||||
flutter_dotenv: ^5.0.2
|
flutter_dotenv: ^5.0.2
|
||||||
pub_release: ^8.0.3
|
pub_release: ^8.0.3
|
||||||
flutter_markdown: ^0.6.13
|
flutter_markdown: ^0.6.13
|
||||||
@@ -93,4 +91,3 @@ flutter:
|
|||||||
uses-material-design: true
|
uses-material-design: true
|
||||||
assets:
|
assets:
|
||||||
- assets/i18n/
|
- assets/i18n/
|
||||||
- .env
|
|
||||||
|
|||||||