mirror of
https://github.com/ReVanced/revanced-manager.git
synced 2025-05-19 15:37:43 +08:00
Compare commits
40 Commits
v1.18.0
...
v1.19.0-de
Author | SHA1 | Date | |
---|---|---|---|
edc8ef4f44 | |||
9aeb156d92 | |||
8be07de373 | |||
93482b0041 | |||
5b2c55142e | |||
088a3b7c28 | |||
b115643034 | |||
07fc964f9c | |||
3e51caf111 | |||
deb1ba339f | |||
70a1086edf | |||
c7d975e612 | |||
7104d6d6dd | |||
d933997c89 | |||
bea99bb4c6 | |||
f38a593434 | |||
85950991ab | |||
6fd740f8c0 | |||
ec2c2d8ccb | |||
efb2d5ef32 | |||
acb1e2434b | |||
8e52abda9a | |||
9764326242 | |||
97c33d6c54 | |||
30d5f3ad3f | |||
079c0defaf | |||
cf1afddb9e | |||
b07b9351c4 | |||
7911459817 | |||
ef9b1d5c2d | |||
fd2780624a | |||
4f22e88e42 | |||
0d45fe4a97 | |||
28ae2766f0 | |||
edd86024b9 | |||
f3c78c2c24 | |||
3f96608398 | |||
9ed43efe5d | |||
975180b075 | |||
c94eb7a48e |
52
.github/workflows/release-build.yml
vendored
52
.github/workflows/release-build.yml
vendored
@ -1,50 +1,58 @@
|
||||
name: "Release Build"
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
tags:
|
||||
- "v*"
|
||||
branches:
|
||||
- main
|
||||
- dev
|
||||
paths:
|
||||
- ".github/workflows/release-build.yml"
|
||||
- "android/**"
|
||||
- "assets/**"
|
||||
- "lib/**"
|
||||
- ".releaserc.js"
|
||||
- "pubspec.yaml"
|
||||
|
||||
jobs:
|
||||
release:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Set env
|
||||
run: echo "RELEASE_VERSION=${GITHUB_REF#refs/*/}" >> $GITHUB_ENV
|
||||
|
||||
- name: Set up JDK
|
||||
uses: actions/setup-java@v4
|
||||
with:
|
||||
java-version: "17"
|
||||
distribution: "zulu"
|
||||
|
||||
- uses: subosito/flutter-action@v2
|
||||
with:
|
||||
channel: "stable"
|
||||
|
||||
- name: Cache Node modules
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
node_modules
|
||||
key: npm-${{ hashFiles('package-lock.json') }}
|
||||
|
||||
- name: Setup semantic-release
|
||||
run: npm install
|
||||
|
||||
- name: Set up Flutter
|
||||
run: flutter pub get
|
||||
|
||||
- name: Generate files with Builder
|
||||
run: dart run build_runner build --delete-conflicting-outputs
|
||||
|
||||
- name: Build with Flutter
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
SIGNING_KEY_ALIAS: ${{ secrets.SIGNING_KEY_ALIAS }}
|
||||
SIGNING_KEY_PASSWORD: ${{ secrets.SIGNING_KEY_PASSWORD }}
|
||||
SIGNING_STORE_PASSWORD: ${{ secrets.SIGNING_KEYSTORE_PASSWORD }}
|
||||
run: flutter build apk
|
||||
- name: Sign APK
|
||||
id: sign_apk
|
||||
uses: ilharp/sign-android-release@v1
|
||||
with:
|
||||
releaseDir: build/app/outputs/apk/release
|
||||
signingKey: ${{ secrets.SIGNING_KEYSTORE }}
|
||||
signingKey: "keystore.jks"
|
||||
keyStorePassword: ${{ secrets.SIGNING_KEYSTORE_PASSWORD }}
|
||||
keyAlias: ${{ secrets.SIGNING_KEY_ALIAS }}
|
||||
keyPassword: ${{ secrets.SIGNING_KEY_PASSWORD }}
|
||||
- name: Add version to APK
|
||||
run: mv ${{steps.sign_apk.outputs.signedFile}} revanced-manager-${{ env.RELEASE_VERSION }}.apk
|
||||
- name: Publish release APK
|
||||
uses: "marvinpinto/action-automatic-releases@latest"
|
||||
with:
|
||||
repo_token: "${{ secrets.GITHUB_TOKEN }}"
|
||||
prerelease: false
|
||||
files: revanced-manager-${{ env.RELEASE_VERSION }}.apk
|
||||
run: |
|
||||
echo "${{ secrets.SIGNING_KEYSTORE }}" | base64 --decode > android/app/keystore.jks
|
||||
npx semantic-release
|
||||
|
84
.releaserc.js
Normal file
84
.releaserc.js
Normal file
@ -0,0 +1,84 @@
|
||||
module.exports = {
|
||||
"branches": [
|
||||
"main",
|
||||
{
|
||||
"name": "dev",
|
||||
"prerelease": true
|
||||
}
|
||||
],
|
||||
"plugins": [
|
||||
[
|
||||
"@semantic-release/commit-analyzer", {
|
||||
"releaseRules": [
|
||||
{ "type": "build", "scope": "Needs bump", "release": "patch" }
|
||||
]
|
||||
}
|
||||
],
|
||||
"@semantic-release/changelog",
|
||||
[
|
||||
"@semantic-release/release-notes-generator",
|
||||
{
|
||||
"preset": "conventionalcommits",
|
||||
writerOpts: {
|
||||
commitPartial: "* {{subject}} ([{{author.name}}]({{~@root.host}}/{{~@root.owner}}/{{~@root.repository}}/commit/{{hash}}))\n",
|
||||
mainTemplate: `
|
||||
{{#each commitGroups}}
|
||||
{{#if title}}
|
||||
## {{title}}
|
||||
{{/if}}
|
||||
{{#each commits}}
|
||||
{{> commit root=@root}}
|
||||
{{/each}}
|
||||
{{/each}}
|
||||
`
|
||||
}
|
||||
}
|
||||
],
|
||||
[
|
||||
"@droidsolutions-oss/semantic-release-update-file",
|
||||
{
|
||||
"files": [
|
||||
{
|
||||
"path": ["pubspec.yaml"],
|
||||
"type": "flutter",
|
||||
"branches": ["main", "dev"]
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
[
|
||||
"@semantic-release/exec",
|
||||
{
|
||||
"prepareCmd": "flutter build apk"
|
||||
}
|
||||
],
|
||||
[
|
||||
"@semantic-release/git",
|
||||
{
|
||||
"assets": [
|
||||
"pubspec.yaml"
|
||||
]
|
||||
}
|
||||
],
|
||||
[
|
||||
"@semantic-release/github",
|
||||
{
|
||||
"assets": [
|
||||
{
|
||||
"path": "build/app/outputs/apk/release/revanced-manager*.apk"
|
||||
}
|
||||
],
|
||||
"successComment": false
|
||||
}
|
||||
],
|
||||
[
|
||||
"@saithodev/semantic-release-backmerge",
|
||||
{
|
||||
"backmergeBranches": [{"from": "main", "to": "dev"}],
|
||||
"clearWorkspace": true
|
||||
}
|
||||
]
|
||||
],
|
||||
|
||||
|
||||
};
|
77
SECURITY.md
Normal file
77
SECURITY.md
Normal file
@ -0,0 +1,77 @@
|
||||
<p align="center">
|
||||
<picture>
|
||||
<source
|
||||
width="256px"
|
||||
media="(prefers-color-scheme: dark)"
|
||||
srcset="assets/revanced-headline/revanced-headline-vertical-dark.svg"
|
||||
>
|
||||
<img
|
||||
width="256px"
|
||||
src="assets/revanced-headline/revanced-headline-vertical-light.svg"
|
||||
>
|
||||
</picture>
|
||||
<br>
|
||||
<a href="https://revanced.app/">
|
||||
<picture>
|
||||
<source height="24px" media="(prefers-color-scheme: dark)" srcset="assets/revanced-logo/revanced-logo-round.svg" />
|
||||
<img height="24px" src="assets/revanced-logo/revanced-logo-round.svg" />
|
||||
</picture>
|
||||
</a>
|
||||
<a href="https://github.com/ReVanced">
|
||||
<picture>
|
||||
<source height="24px" media="(prefers-color-scheme: dark)" srcset="https://i.ibb.co/dMMmCrW/Git-Hub-Mark.png" />
|
||||
<img height="24px" src="https://i.ibb.co/9wV3HGF/Git-Hub-Mark-Light.png" />
|
||||
</picture>
|
||||
</a>
|
||||
<a href="http://revanced.app/discord">
|
||||
<picture>
|
||||
<source height="24px" media="(prefers-color-scheme: dark)" srcset="https://user-images.githubusercontent.com/13122796/178032563-d4e084b7-244e-4358-af50-26bde6dd4996.png" />
|
||||
<img height="24px" src="https://user-images.githubusercontent.com/13122796/178032563-d4e084b7-244e-4358-af50-26bde6dd4996.png" />
|
||||
</picture>
|
||||
</a>
|
||||
<a href="https://reddit.com/r/revancedapp">
|
||||
<picture>
|
||||
<source height="24px" media="(prefers-color-scheme: dark)" srcset="https://user-images.githubusercontent.com/13122796/178032351-9d9d5619-8ef7-470a-9eec-2744ece54553.png" />
|
||||
<img height="24px" src="https://user-images.githubusercontent.com/13122796/178032351-9d9d5619-8ef7-470a-9eec-2744ece54553.png" />
|
||||
</picture>
|
||||
</a>
|
||||
<a href="https://t.me/app_revanced">
|
||||
<picture>
|
||||
<source height="24px" media="(prefers-color-scheme: dark)" srcset="https://user-images.githubusercontent.com/13122796/178032213-faf25ab8-0bc3-4a94-a730-b524c96df124.png" />
|
||||
<img height="24px" src="https://user-images.githubusercontent.com/13122796/178032213-faf25ab8-0bc3-4a94-a730-b524c96df124.png" />
|
||||
</picture>
|
||||
</a>
|
||||
<a href="https://x.com/revancedapp">
|
||||
<picture>
|
||||
<source media="(prefers-color-scheme: dark)" srcset="https://user-images.githubusercontent.com/93124920/270180600-7c1b38bf-889b-4d68-bd5e-b9d86f91421a.png">
|
||||
<img height="24px" src="https://user-images.githubusercontent.com/93124920/270108715-d80743fa-b330-4809-b1e6-79fbdc60d09c.png" />
|
||||
</picture>
|
||||
</a>
|
||||
<a href="https://www.youtube.com/@ReVanced">
|
||||
<picture>
|
||||
<source height="24px" media="(prefers-color-scheme: dark)" srcset="https://user-images.githubusercontent.com/13122796/178032714-c51c7492-0666-44ac-99c2-f003a695ab50.png" />
|
||||
<img height="24px" src="https://user-images.githubusercontent.com/13122796/178032714-c51c7492-0666-44ac-99c2-f003a695ab50.png" />
|
||||
</picture>
|
||||
</a>
|
||||
<br>
|
||||
<br>
|
||||
Continuing the legacy of Vanced
|
||||
</p>
|
||||
|
||||
# 🔒 Security Policy
|
||||
|
||||
This document describes how to report security vulnerabilities for ReVanced Manager.
|
||||
|
||||
## 🚨 Reporting a Vulnerability
|
||||
|
||||
Please open an issue in our [advisory tracker](https://github.com/ReVanced/revanced-manager/security/advisories/new) or reach out privately to us on [Discord](https://discord.gg/revanced).
|
||||
|
||||
If a vulnerability is confirmed and accepted, you can join our [Discord](https://discord.gg/revanced) server to receive a special contributor role.
|
||||
|
||||
### ⏳ Supported Versions
|
||||
|
||||
| Version | Branch | Supported |
|
||||
| ------- | ------------|------------------- |
|
||||
|  | main | :white_check_mark: |
|
||||
|  | dev | :white_check_mark: |
|
||||
|  | compose-dev | :white_check_mark: |
|
@ -21,7 +21,6 @@ linter:
|
||||
- always_declare_return_types
|
||||
- require_trailing_commas
|
||||
- always_put_control_body_on_new_line
|
||||
- always_require_non_null_named_parameters
|
||||
- always_use_package_imports # we do this commonly
|
||||
- annotate_overrides
|
||||
- avoid_bool_literals_in_conditional_expressions
|
||||
@ -40,8 +39,6 @@ linter:
|
||||
- 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
|
||||
@ -126,6 +123,7 @@ linter:
|
||||
- slash_for_doc_comments
|
||||
- sort_child_properties_last
|
||||
- sort_constructors_first
|
||||
- sort_pub_dependencies
|
||||
- sort_unnamed_constructors_first
|
||||
- test_types_in_equals
|
||||
- throw_in_finally
|
||||
|
@ -47,11 +47,30 @@ android {
|
||||
release {
|
||||
shrinkResources false
|
||||
minifyEnabled false
|
||||
resValue "string", "app_name", "ReVanced Manager"
|
||||
signingConfig signingConfigs.debug
|
||||
ndk {
|
||||
abiFilters 'arm64-v8a', 'armeabi-v7a', 'x86_64'
|
||||
}
|
||||
if (System.getenv("signingKey") != null) {
|
||||
signingConfigs {
|
||||
create("release") {
|
||||
storeFile = file(System.getenv("signingKey"))
|
||||
storePassword = System.getenv("keyStorePassword")
|
||||
keyAlias = System.getenv("keyAlias")
|
||||
keyPassword = System.getenv("keyPassword")
|
||||
}
|
||||
}
|
||||
signingConfig = signingConfigs.release
|
||||
resValue "string", "app_name", "ReVanced Manager"
|
||||
applicationVariants.all { variant ->
|
||||
variant.outputs.all {
|
||||
outputFileName = "revanced-manager-v${flutterVersionName}.apk"
|
||||
}
|
||||
}
|
||||
} else {
|
||||
signingConfig = signingConfigs.debug
|
||||
resValue "string", "app_name", "ReVanced Manager Debug"
|
||||
applicationIdSuffix ".debug"
|
||||
}
|
||||
}
|
||||
debug {
|
||||
shrinkResources false
|
||||
|
@ -1,6 +1,7 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
|
||||
<uses-permission android:name="android.permission.VIBRATE" />
|
||||
|
||||
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
|
||||
<uses-permission android:name="android.permission.READ_MEDIA_VIDEO" />
|
||||
<uses-permission android:name="android.permission.READ_MEDIA_AUDIO" />
|
||||
|
@ -401,13 +401,7 @@ class MainActivity : FlutterActivity() {
|
||||
|
||||
updateProgress(0.8, "Signing...", "Signing APK")
|
||||
|
||||
try {
|
||||
Signer("ReVanced", keystorePassword)
|
||||
.signApk(patchedFile, outFile, keyStoreFile)
|
||||
} catch (e: Exception) {
|
||||
print("Error signing APK: ${e.message}")
|
||||
e.printStackTrace()
|
||||
}
|
||||
Signer("ReVanced", keystorePassword).signApk(patchedFile, outFile, keyStoreFile)
|
||||
|
||||
updateProgress(.85, "Patched", "Patched APK")
|
||||
} catch (ex: Throwable) {
|
||||
|
@ -16,6 +16,8 @@
|
||||
"noShowAgain": "Don't show this again",
|
||||
"add": "Add",
|
||||
"remove": "Remove",
|
||||
"showChangelogButton": "Show changelog",
|
||||
"showUpdateButton": "Show update",
|
||||
"navigationView": {
|
||||
"dashboardTab": "Dashboard",
|
||||
"patcherTab": "Patcher",
|
||||
@ -27,6 +29,7 @@
|
||||
|
||||
"updatesSubtitle": "Updates",
|
||||
"patchedSubtitle": "Patched apps",
|
||||
"changeLaterSubtitle": "You can change this in the settings at a later time.",
|
||||
|
||||
"noUpdates": "No updates available",
|
||||
|
||||
@ -35,20 +38,25 @@
|
||||
"noInstallations": "No patched apps installed",
|
||||
"installUpdate": "Continue to install the update?",
|
||||
|
||||
"updateDialogTitle": "Update Manager",
|
||||
"updatePatchesDialogTitle": "Update ReVanced Patches",
|
||||
"updateSheetTitle": "Update ReVanced Manager",
|
||||
"updateDialogTitle": "New update available",
|
||||
"updatePatchesSheetTitle": "Update ReVanced Patches",
|
||||
"updateChangelogTitle": "Changelog",
|
||||
|
||||
"patchesConsentDialogText": "ReVanced Patches needs to be downloaded.",
|
||||
"patchesConsentDialogText2": "This will connect you to {url}.",
|
||||
"patchesConsentDialogText3": "Auto update?",
|
||||
"patchesConsentDialogText3Sub": "You can change this in settings at a later time.",
|
||||
"updateDialogText": "A new update is available for {file}.\n\nThe currently installed version is {version}.",
|
||||
|
||||
"downloadConsentDialogTitle": "Download necessary files?",
|
||||
"downloadConsentDialogText": "ReVanced Manager needs to download necessary files to work properly.",
|
||||
"downloadConsentDialogText2": "This will connect you to {url}.",
|
||||
|
||||
"checkUpdateDialogTitle": "Check for updates?",
|
||||
"checkUpdateDialogText": "Do you want ReVanced Manager to check for updates automatically?",
|
||||
|
||||
"notificationTitle": "Update downloaded",
|
||||
"notificationText": "Tap to install the update",
|
||||
|
||||
"downloadingMessage": "Downloading update...",
|
||||
"downloadedMessage": "Update downloaded!",
|
||||
"downloadedMessage": "Update downloaded",
|
||||
|
||||
"installingMessage": "Installing update...",
|
||||
|
||||
@ -72,22 +80,21 @@
|
||||
"widgetTitle": "Patcher",
|
||||
"patchButton": "Patch",
|
||||
|
||||
"armv7WarningDialogText": "Patching on ARMv7 devices is not yet supported and might fail. Proceed anyways?",
|
||||
"armv7WarningDialogText": "Patching on ARMv7 devices is not yet supported and might fail. Continue anyways?",
|
||||
|
||||
"removedPatchesWarningDialogText": "The following patches have been removed since the last time you used them.\n\n{patches}\n\nProceed anyways?",
|
||||
"removedPatchesWarningDialogText": "The following patches have been removed since the last time you used them.\n\n{patches}\n\nContinue anyways?",
|
||||
"requiredOptionDialogText" : "Some patch options have to be set."
|
||||
},
|
||||
"appSelectorCard": {
|
||||
"widgetTitle": "Select an application",
|
||||
"widgetTitleSelected": "Selected application",
|
||||
"widgetSubtitle": "No application selected",
|
||||
"widgetTitle": "Select an app",
|
||||
"widgetTitleSelected": "Selected app",
|
||||
"widgetSubtitle": "No app selected",
|
||||
|
||||
"noAppsLabel": "No applications found",
|
||||
"notInstalled":"App not installed",
|
||||
|
||||
"currentVersion": "Current",
|
||||
"suggestedVersion": "Suggested",
|
||||
"allVersions": "All versions"
|
||||
"anyVersion": "Any version"
|
||||
},
|
||||
"patchSelectorCard": {
|
||||
"widgetTitle": "Select patches",
|
||||
@ -101,20 +108,20 @@
|
||||
"widgetSubtitle": "We are online!"
|
||||
},
|
||||
"appSelectorView": {
|
||||
"viewTitle": "Select an application",
|
||||
"searchBarHint": "Search applications",
|
||||
"viewTitle": "Select an app",
|
||||
"searchBarHint": "Search app",
|
||||
|
||||
"storageButton": "Storage",
|
||||
"selectFromStorageButton": "Select from storage",
|
||||
|
||||
|
||||
"errorMessage": "Unable to use selected application",
|
||||
|
||||
"downloadToast": "Download function is not available yet",
|
||||
|
||||
"requireSuggestedAppVersionDialogText": "The version of the app you have selected does not match the suggested version. Please select the app that matches the suggested version.\n\nSelected version: v{selected}\nSuggested version: v{suggested}\n\nTo proceed anyway, disable \"Require suggested app version\" in the settings.",
|
||||
"requireSuggestedAppVersionDialogText": "The version of the app you have selected does not match the suggested version which can lead to unexpected issues. Please use the suggested version.\n\nSelected version: {selected}\nSuggested version: {suggested}\n\nTo continue anyway, disable \"Require suggested app version\" in the settings.",
|
||||
|
||||
"featureNotAvailable": "Feature not implemented",
|
||||
"featureNotAvailableText": "This application is a split APK and cannot be selected. Unfortunately, this feature is only available for rooted users at the moment. However, you can still install the application by selecting its APK files from your device's storage instead"
|
||||
"featureNotAvailableText": "This app is a split APK and can only be patched and installed reliably by mounting with root permissions. However, you can patch and install a full APK by selecting it from storage."
|
||||
},
|
||||
"patchesSelectorView": {
|
||||
"viewTitle": "Select patches",
|
||||
@ -158,13 +165,13 @@
|
||||
"unsupportedPatchVersion": "Patch is not supported for this app version.",
|
||||
"unsupportedRequiredOption": "This patch contains a required option that is not supported by this app",
|
||||
|
||||
"patchesChangeWarningDialogText": "It is recommended to use the default patch selection and options. Changing them may result in unexpected issues.\n\nYou'll need to turn on \"Allow changing patch selection\" in settings before changing any patch selection.",
|
||||
"patchesChangeWarningDialogText": "It is recommended to use the default patch selection and options. Changing them may result in unexpected issues.\n\nYou'll need to turn on \"Allow changing patch selection\" in the settings before changing any patch selection.",
|
||||
"patchesChangeWarningDialogButton": "Use default selection"
|
||||
},
|
||||
"installerView": {
|
||||
"widgetTitle": "Installer",
|
||||
"installType": "Select install type",
|
||||
"installTypeDescription": "Select the installation type to proceed with.",
|
||||
"installTypeDescription": "Select the installation type to continue with.",
|
||||
|
||||
"installButton": "Install",
|
||||
"installRootType": "Mount",
|
||||
@ -175,7 +182,7 @@
|
||||
"pressBackAgain": "Press back again to cancel",
|
||||
"openButton": "Open",
|
||||
"shareButton": "Share file",
|
||||
|
||||
|
||||
"notificationTitle": "ReVanced Manager is patching",
|
||||
"notificationText": "Tap to return to the installer",
|
||||
|
||||
@ -208,7 +215,7 @@
|
||||
"englishOption": "English",
|
||||
|
||||
"sourcesLabel": "Sources",
|
||||
"sourcesLabelHint": "Configure your sources",
|
||||
"sourcesLabelHint": "Configure the source of patches and integrations",
|
||||
"sourcesIntegrationsLabel": "Integrations source",
|
||||
"sourcesResetDialogTitle": "Reset",
|
||||
"sourcesResetDialogText": "Are you sure you want to reset your sources to their default values?",
|
||||
@ -216,13 +223,13 @@
|
||||
"sourcesUpdateNote": "Note: Patches will be updated to the latest version automatically.\n\nThis will reveal your IP address to the server.",
|
||||
|
||||
"apiURLLabel": "API URL",
|
||||
"apiURLHint": "Configure your API URL",
|
||||
"apiURLHint": "Configure the URL of the API to use",
|
||||
"selectApiURL": "API URL",
|
||||
"hostRepositoryLabel": "Repository API",
|
||||
"orgPatchesLabel": "Patches organization",
|
||||
"sourcesPatchesLabel": "Patches source",
|
||||
"orgIntegrationsLabel": "Integrations organization",
|
||||
|
||||
|
||||
"contributorsLabel": "Contributors",
|
||||
"contributorsHint": "A list of contributors of ReVanced",
|
||||
|
||||
@ -230,19 +237,22 @@
|
||||
"logsHint": "Share ReVanced Manager logs",
|
||||
|
||||
"enablePatchesSelectionLabel": "Allow changing patch selection",
|
||||
"enablePatchesSelectionHint": "Allow changing the selection of patches",
|
||||
"enablePatchesSelectionHint": "Do not prevent selecting or deselecting patches",
|
||||
"enablePatchesSelectionWarningText": "Changing the selection of patches may cause unexpected issues.\n\nEnable anyways?",
|
||||
"disablePatchesSelectionWarningText": "You are about to disable changing the selection of patches.\nThe default selection of patches will be restored.\n\nDisable anyways?",
|
||||
|
||||
"autoUpdatePatchesLabel": "Auto update patches",
|
||||
"autoUpdatePatchesHint": "Automatically update patches to the latest version",
|
||||
"showUpdateDialogLabel": "Show update dialog",
|
||||
"showUpdateDialogHint": "Show a dialog when a new update is available",
|
||||
"universalPatchesLabel": "Show universal patches",
|
||||
"universalPatchesHint": "Display all apps and universal patches (may slow down the app list)",
|
||||
|
||||
"versionCompatibilityCheckLabel": "Version compatibility check",
|
||||
"versionCompatibilityCheckHint": "Restricts patches to supported app versions",
|
||||
"versionCompatibilityCheckHint": "Prevent selecting patches that are not compatible with the selected app version",
|
||||
|
||||
"requireSuggestedAppVersionLabel": "Require suggested app version",
|
||||
"requireSuggestedAppVersionHint": "Enforce selection of suggested app version",
|
||||
"requireSuggestedAppVersionHint": "Prevent selecting an app with a version that is not the suggested",
|
||||
"requireSuggestedAppVersionDialogText": "Selecting an app that is not the suggested version may cause unexpected issues.\n\nDo you want to proceed anyways?",
|
||||
|
||||
"aboutLabel": "About",
|
||||
@ -320,7 +330,7 @@
|
||||
|
||||
"patchedDateHint": "{date} at {time}",
|
||||
"appliedPatchesHint": "{quantity} applied patches",
|
||||
|
||||
|
||||
"updateNotImplemented": "This feature has not been implemented yet"
|
||||
},
|
||||
"contributorsView": {
|
||||
|
@ -81,7 +81,7 @@ class GithubAPI {
|
||||
int updates = 0;
|
||||
final String currentVersion =
|
||||
await _managerAPI.getCurrentManagerVersion();
|
||||
while (response.data[updates]['tag_name'] != 'v$currentVersion') {
|
||||
while (response.data[updates]['tag_name'] != currentVersion) {
|
||||
updates++;
|
||||
}
|
||||
for (int i = 1; i < updates; i++) {
|
||||
|
@ -15,6 +15,8 @@ import 'package:revanced_manager/services/github_api.dart';
|
||||
import 'package:revanced_manager/services/patcher_api.dart';
|
||||
import 'package:revanced_manager/services/revanced_api.dart';
|
||||
import 'package:revanced_manager/services/root_api.dart';
|
||||
import 'package:revanced_manager/services/toast.dart';
|
||||
import 'package:revanced_manager/ui/widgets/shared/haptics/haptic_checkbox_list_tile.dart';
|
||||
import 'package:revanced_manager/utils/check_for_supported_patch.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
import 'package:timeago/timeago.dart';
|
||||
@ -23,6 +25,7 @@ import 'package:timeago/timeago.dart';
|
||||
class ManagerAPI {
|
||||
final RevancedAPI _revancedAPI = locator<RevancedAPI>();
|
||||
final GithubAPI _githubAPI = locator<GithubAPI>();
|
||||
final Toast _toast = locator<Toast>();
|
||||
final RootAPI _rootAPI = RootAPI();
|
||||
final String patcherRepo = 'revanced-patcher';
|
||||
final String cliRepo = 'revanced-cli';
|
||||
@ -50,12 +53,12 @@ class ManagerAPI {
|
||||
String? integrationsVersion = '';
|
||||
|
||||
bool isDefaultPatchesRepo() {
|
||||
return getPatchesRepo().toLowerCase() == 'revanced/revanced-patches';
|
||||
return getPatchesRepo().toLowerCase() == defaultPatchesRepo;
|
||||
}
|
||||
|
||||
bool isDefaultIntegrationsRepo() {
|
||||
return getIntegrationsRepo().toLowerCase() ==
|
||||
'revanced/revanced-integrations';
|
||||
defaultIntegrationsRepo;
|
||||
}
|
||||
|
||||
Future<void> initialize() async {
|
||||
@ -65,6 +68,16 @@ class ManagerAPI {
|
||||
(await getSdkVersion()) >= 31; // ANDROID_12_SDK_VERSION = 31
|
||||
storedPatchesFile =
|
||||
(await getApplicationDocumentsDirectory()).path + storedPatchesFile;
|
||||
|
||||
// Migrate to new API URL if not done yet as the old one is sunset.
|
||||
final bool hasMigrated = _prefs.getBool('migratedToNewApiUrl') ?? false;
|
||||
if (!hasMigrated) {
|
||||
final String apiUrl = getApiUrl().toLowerCase();
|
||||
if (apiUrl.contains('releases.revanced.app')) {
|
||||
await setApiUrl(''); // Reset to default.
|
||||
_prefs.setBool('migratedToNewApiUrl', true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Future<int> getSdkVersion() async {
|
||||
@ -82,6 +95,7 @@ class ManagerAPI {
|
||||
}
|
||||
await _revancedAPI.clearAllCache();
|
||||
await _prefs.setString('apiUrl', url);
|
||||
_toast.showBottom('settingsView.restartAppForChanges');
|
||||
}
|
||||
|
||||
String getRepoUrl() {
|
||||
@ -114,12 +128,12 @@ class ManagerAPI {
|
||||
await _prefs.setString('patchesRepo', value);
|
||||
}
|
||||
|
||||
bool getPatchesConsent() {
|
||||
return _prefs.getBool('patchesConsent') ?? false;
|
||||
bool getDownloadConsent() {
|
||||
return _prefs.getBool('downloadConsent') ?? false;
|
||||
}
|
||||
|
||||
Future<void> setPatchesConsent(bool consent) async {
|
||||
await _prefs.setBool('patchesConsent', consent);
|
||||
void setDownloadConsent(bool consent) {
|
||||
_prefs.setBool('downloadConsent', consent);
|
||||
}
|
||||
|
||||
bool isPatchesAutoUpdate() {
|
||||
@ -142,6 +156,14 @@ class ManagerAPI {
|
||||
_prefs.setBool('showPatchesChangeWarning', !value);
|
||||
}
|
||||
|
||||
bool showUpdateDialog() {
|
||||
return _prefs.getBool('showUpdateDialog') ?? true;
|
||||
}
|
||||
|
||||
void setShowUpdateDialog(bool value) {
|
||||
_prefs.setBool('showUpdateDialog', value);
|
||||
}
|
||||
|
||||
bool isChangingToggleModified() {
|
||||
return _prefs.getBool('isChangingToggleModified') ?? false;
|
||||
}
|
||||
@ -150,8 +172,8 @@ class ManagerAPI {
|
||||
_prefs.setBool('isChangingToggleModified', value);
|
||||
}
|
||||
|
||||
Future<void> setPatchesAutoUpdate(bool value) async {
|
||||
await _prefs.setBool('patchesAutoUpdate', value);
|
||||
void setPatchesAutoUpdate(bool value) {
|
||||
_prefs.setBool('patchesAutoUpdate', value);
|
||||
}
|
||||
|
||||
List<Patch> getSavedPatches(String packageName) {
|
||||
@ -494,7 +516,11 @@ class ManagerAPI {
|
||||
|
||||
Future<String> getCurrentManagerVersion() async {
|
||||
final PackageInfo packageInfo = await PackageInfo.fromPlatform();
|
||||
return packageInfo.version;
|
||||
String version = packageInfo.version;
|
||||
if (!version.startsWith('v')) {
|
||||
version = 'v$version';
|
||||
}
|
||||
return version;
|
||||
}
|
||||
|
||||
Future<String> getCurrentPatchesVersion() async {
|
||||
@ -582,8 +608,8 @@ class ManagerAPI {
|
||||
return showDialog(
|
||||
barrierDismissible: false,
|
||||
context: context,
|
||||
builder: (context) => WillPopScope(
|
||||
onWillPop: () async => false,
|
||||
builder: (context) => PopScope(
|
||||
canPop: false,
|
||||
child: AlertDialog(
|
||||
title: I18nText('warning'),
|
||||
content: ValueListenableBuilder(
|
||||
@ -604,7 +630,7 @@ class ManagerAPI {
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
CheckboxListTile(
|
||||
HapticCheckboxListTile(
|
||||
value: value,
|
||||
contentPadding: EdgeInsets.zero,
|
||||
title: I18nText(
|
||||
@ -632,7 +658,7 @@ class ManagerAPI {
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> reAssessSavedApps() async {
|
||||
Future<void> rePatchedSavedApps() async {
|
||||
final List<PatchedApplication> patchedApps = getPatchedApps();
|
||||
|
||||
// Remove apps that are not installed anymore.
|
||||
|
@ -335,13 +335,14 @@ class PatcherAPI {
|
||||
Navigator.pop(context);
|
||||
},
|
||||
child: I18nText('cancelButton'),
|
||||
)
|
||||
else
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
Navigator.pop(context);
|
||||
},
|
||||
child: I18nText('cancelButton'),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
Navigator.pop(context);
|
||||
},
|
||||
child: I18nText('cancelButton'),
|
||||
),
|
||||
if (isFixable)
|
||||
FilledButton(
|
||||
onPressed: () async {
|
||||
|
@ -8,6 +8,7 @@ import 'package:flutter_cache_manager/flutter_cache_manager.dart';
|
||||
import 'package:injectable/injectable.dart';
|
||||
import 'package:revanced_manager/app/app.locator.dart';
|
||||
import 'package:revanced_manager/services/download_manager.dart';
|
||||
import 'package:revanced_manager/services/manager_api.dart';
|
||||
import 'package:synchronized/synchronized.dart';
|
||||
import 'package:timeago/timeago.dart';
|
||||
|
||||
@ -32,7 +33,7 @@ class RevancedAPI {
|
||||
final response = await _dio.get('/contributors');
|
||||
final List<dynamic> repositories = response.data['repositories'];
|
||||
for (final Map<String, dynamic> repo in repositories) {
|
||||
final String name = repo['name'];
|
||||
final String name = repo['name'].toLowerCase();
|
||||
contributors[name] = repo['contributors'];
|
||||
}
|
||||
} on Exception catch (e) {
|
||||
@ -48,13 +49,16 @@ class RevancedAPI {
|
||||
String extension,
|
||||
String repoName,
|
||||
) {
|
||||
if (!locator<ManagerAPI>().getDownloadConsent()) {
|
||||
return Future(() => null);
|
||||
}
|
||||
return getToolsLock.synchronized(() async {
|
||||
try {
|
||||
final response = await _dio.get('/tools');
|
||||
final List<dynamic> tools = response.data['tools'];
|
||||
return tools.firstWhereOrNull(
|
||||
(t) =>
|
||||
t['repository'] == repoName &&
|
||||
(t['repository'] as String).toLowerCase() == repoName.toLowerCase() &&
|
||||
(t['name'] as String).endsWith(extension),
|
||||
);
|
||||
} on Exception catch (e) {
|
||||
|
@ -2,9 +2,6 @@ import 'package:flutter/foundation.dart';
|
||||
import 'package:root/root.dart';
|
||||
|
||||
class RootAPI {
|
||||
// TODO(aAbed): remove in the future, keep it for now during migration.
|
||||
final String _postFsDataDirPath = '/data/adb/post-fs-data.d';
|
||||
|
||||
final String _revancedDirPath = '/data/adb/revanced';
|
||||
final String _serviceDDirPath = '/data/adb/service.d';
|
||||
|
||||
@ -99,18 +96,9 @@ class RootAPI {
|
||||
);
|
||||
}
|
||||
|
||||
// TODO(aAbed): remove in the future, keep it for now during migration.
|
||||
Future<void> removeOrphanedFiles() async {
|
||||
await Root.exec(
|
||||
cmd: '''
|
||||
find $_revancedDirPath -type f -name original.apk -delete
|
||||
for file in "$_serviceDDirPath"/*; do
|
||||
filename=\$(basename "\$file")
|
||||
if [ -f "$_postFsDataDirPath/\$filename" ]; then
|
||||
rm "$_postFsDataDirPath/\$filename"
|
||||
fi
|
||||
done
|
||||
''',
|
||||
cmd: 'find $_revancedDirPath -type f -name original.apk -delete',
|
||||
);
|
||||
}
|
||||
|
||||
@ -144,8 +132,12 @@ class RootAPI {
|
||||
);
|
||||
final String mountScript = '''
|
||||
#!/system/bin/sh
|
||||
MAGISKTMP="\$(magisk --path)" || MAGISKTMP=/sbin
|
||||
# Mount using Magisk mirror, if available.
|
||||
MAGISKTMP="\$( magisk --path )" || MAGISKTMP=/sbin
|
||||
MIRROR="\$MAGISKTMP/.magisk/mirror"
|
||||
if [ ! -f \$MIRROR ]; then
|
||||
MIRROR=""
|
||||
fi
|
||||
|
||||
until [ "\$(getprop sys.boot_completed)" = 1 ]; do sleep 3; done
|
||||
until [ -d "/sdcard/Android" ]; do sleep 1; done
|
||||
@ -171,7 +163,7 @@ class RootAPI {
|
||||
}
|
||||
|
||||
Future<void> installPatchedApk(
|
||||
String packageName, String patchedFilePath) async {
|
||||
String packageName, String patchedFilePath,) async {
|
||||
final String newPatchedFilePath = '$_revancedDirPath/$packageName/base.apk';
|
||||
await Root.exec(
|
||||
cmd: '''
|
||||
|
@ -4,6 +4,7 @@ import 'package:revanced_manager/ui/views/app_selector/app_selector_viewmodel.da
|
||||
import 'package:revanced_manager/ui/widgets/appSelectorView/app_skeleton_loader.dart';
|
||||
import 'package:revanced_manager/ui/widgets/appSelectorView/installed_app_item.dart';
|
||||
import 'package:revanced_manager/ui/widgets/appSelectorView/not_installed_app_item.dart';
|
||||
import 'package:revanced_manager/ui/widgets/shared/haptics/haptic_floating_action_button_extended.dart';
|
||||
import 'package:revanced_manager/ui/widgets/shared/search_bar.dart';
|
||||
import 'package:stacked/stacked.dart' hide SkeletonLoader;
|
||||
|
||||
@ -23,7 +24,7 @@ class _AppSelectorViewState extends State<AppSelectorView> {
|
||||
onViewModelReady: (model) => model.initialize(),
|
||||
viewModelBuilder: () => AppSelectorViewModel(),
|
||||
builder: (context, model, child) => Scaffold(
|
||||
floatingActionButton: FloatingActionButton.extended(
|
||||
floatingActionButton: HapticFloatingActionButtonExtended(
|
||||
label: I18nText('appSelectorView.storageButton'),
|
||||
icon: const Icon(Icons.sd_storage),
|
||||
onPressed: () {
|
||||
|
@ -76,7 +76,7 @@ class AppSelectorViewModel extends BaseViewModel {
|
||||
final String suggestedVersion = getSuggestedVersion(packageName);
|
||||
|
||||
if (suggestedVersion.isNotEmpty) {
|
||||
await openDefaultBrowser('$packageName apk version v$suggestedVersion');
|
||||
await openDefaultBrowser('$packageName apk version $suggestedVersion');
|
||||
} else {
|
||||
await openDefaultBrowser('$packageName apk');
|
||||
}
|
||||
|
@ -14,9 +14,9 @@ class ContributorsViewModel extends BaseViewModel {
|
||||
final Map<String, List<dynamic>> contributors =
|
||||
await _managerAPI.getContributors();
|
||||
patcherContributors = contributors[_managerAPI.defaultPatcherRepo] ?? [];
|
||||
patchesContributors = contributors[_managerAPI.getPatchesRepo()] ?? [];
|
||||
patchesContributors = contributors[_managerAPI.getPatchesRepo().toLowerCase()] ?? [];
|
||||
integrationsContributors =
|
||||
contributors[_managerAPI.getIntegrationsRepo()] ?? [];
|
||||
contributors[_managerAPI.getIntegrationsRepo().toLowerCase()] ?? [];
|
||||
cliContributors = contributors[_managerAPI.defaultCliRepo] ?? [];
|
||||
managerContributors = contributors[_managerAPI.defaultManagerRepo] ?? [];
|
||||
notifyListeners();
|
||||
|
@ -19,7 +19,8 @@ import 'package:revanced_manager/services/revanced_api.dart';
|
||||
import 'package:revanced_manager/services/toast.dart';
|
||||
import 'package:revanced_manager/ui/views/navigation/navigation_viewmodel.dart';
|
||||
import 'package:revanced_manager/ui/views/patcher/patcher_viewmodel.dart';
|
||||
import 'package:revanced_manager/ui/widgets/homeView/update_confirmation_dialog.dart';
|
||||
import 'package:revanced_manager/ui/widgets/homeView/update_confirmation_sheet.dart';
|
||||
import 'package:revanced_manager/ui/widgets/shared/haptics/haptic_checkbox_list_tile.dart';
|
||||
import 'package:stacked/stacked.dart';
|
||||
import 'package:stacked_services/stacked_services.dart';
|
||||
|
||||
@ -34,15 +35,32 @@ class HomeViewModel extends BaseViewModel {
|
||||
final flutterLocalNotificationsPlugin = FlutterLocalNotificationsPlugin();
|
||||
bool showUpdatableApps = false;
|
||||
List<PatchedApplication> patchedInstalledApps = [];
|
||||
String _currentManagerVersion = '';
|
||||
String _currentPatchesVersion = '';
|
||||
String? _latestManagerVersion = '';
|
||||
File? downloadedApk;
|
||||
|
||||
Future<void> initialize(BuildContext context) async {
|
||||
_latestManagerVersion = await _managerAPI.getLatestManagerVersion();
|
||||
if (!_managerAPI.getPatchesConsent()) {
|
||||
await showPatchesConsent(context);
|
||||
_managerAPI.rePatchedSavedApps().then((_) => _getPatchedApps());
|
||||
_currentManagerVersion = await _managerAPI.getCurrentManagerVersion();
|
||||
if (!_managerAPI.getDownloadConsent()) {
|
||||
await showDownloadConsent(context);
|
||||
await forceRefresh(context);
|
||||
return;
|
||||
}
|
||||
_latestManagerVersion = await _managerAPI.getLatestManagerVersion();
|
||||
_currentPatchesVersion = await _managerAPI.getCurrentPatchesVersion();
|
||||
if (_managerAPI.showUpdateDialog() && await hasManagerUpdates()) {
|
||||
showUpdateDialog(context, false);
|
||||
}
|
||||
if (!_managerAPI.isPatchesAutoUpdate() &&
|
||||
_managerAPI.showUpdateDialog() &&
|
||||
await hasPatchesUpdates()) {
|
||||
showUpdateDialog(context, true);
|
||||
}
|
||||
|
||||
await _patcherAPI.initialize();
|
||||
|
||||
await flutterLocalNotificationsPlugin.initialize(
|
||||
const InitializationSettings(
|
||||
android: AndroidInitializationSettings('ic_notification'),
|
||||
@ -63,11 +81,13 @@ class HomeViewModel extends BaseViewModel {
|
||||
.resolvePlatformSpecificImplementation<
|
||||
AndroidFlutterLocalNotificationsPlugin>()
|
||||
?.requestNotificationsPermission();
|
||||
|
||||
final bool isConnected =
|
||||
await Connectivity().checkConnectivity() != ConnectivityResult.none;
|
||||
if (!isConnected) {
|
||||
_toast.showBottom('homeView.noConnection');
|
||||
}
|
||||
|
||||
final NotificationAppLaunchDetails? notificationAppLaunchDetails =
|
||||
await flutterLocalNotificationsPlugin.getNotificationAppLaunchDetails();
|
||||
if (notificationAppLaunchDetails?.didNotificationLaunchApp ?? false) {
|
||||
@ -79,8 +99,6 @@ class HomeViewModel extends BaseViewModel {
|
||||
_toast.showBottom('homeView.errorDownloadMessage');
|
||||
}
|
||||
}
|
||||
|
||||
_managerAPI.reAssessSavedApps().then((_) => _getPatchedApps());
|
||||
}
|
||||
|
||||
void navigateToAppInfo(PatchedApplication app) {
|
||||
@ -109,17 +127,10 @@ class HomeViewModel extends BaseViewModel {
|
||||
}
|
||||
|
||||
Future<bool> hasManagerUpdates() async {
|
||||
String currentVersion = await _managerAPI.getCurrentManagerVersion();
|
||||
|
||||
// add v to current version
|
||||
if (!currentVersion.startsWith('v')) {
|
||||
currentVersion = 'v$currentVersion';
|
||||
}
|
||||
|
||||
_latestManagerVersion =
|
||||
await _managerAPI.getLatestManagerVersion() ?? currentVersion;
|
||||
await _managerAPI.getLatestManagerVersion() ?? _currentManagerVersion;
|
||||
|
||||
if (_latestManagerVersion != currentVersion) {
|
||||
if (_latestManagerVersion != _currentManagerVersion) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
@ -127,13 +138,12 @@ class HomeViewModel extends BaseViewModel {
|
||||
|
||||
Future<bool> hasPatchesUpdates() async {
|
||||
final String? latestVersion = await _managerAPI.getLatestPatchesVersion();
|
||||
final String currentVersion = await _managerAPI.getCurrentPatchesVersion();
|
||||
if (latestVersion != null) {
|
||||
try {
|
||||
final int latestVersionInt =
|
||||
int.parse(latestVersion.replaceAll(RegExp('[^0-9]'), ''));
|
||||
final int currentVersionInt =
|
||||
int.parse(currentVersion.replaceAll(RegExp('[^0-9]'), ''));
|
||||
int.parse(_currentPatchesVersion.replaceAll(RegExp('[^0-9]'), ''));
|
||||
return latestVersionInt > currentVersionInt;
|
||||
} on Exception catch (e) {
|
||||
if (kDebugMode) {
|
||||
@ -161,22 +171,98 @@ class HomeViewModel extends BaseViewModel {
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> showPatchesConsent(BuildContext context) async {
|
||||
Future<void> showDownloadConsent(BuildContext context) async {
|
||||
final ValueNotifier<bool> autoUpdate = ValueNotifier(true);
|
||||
await showDialog(
|
||||
context: context,
|
||||
barrierDismissible: false,
|
||||
builder: (context) => AlertDialog(
|
||||
title: const Text('Download ReVanced Patches?'),
|
||||
builder: (context) => PopScope(
|
||||
canPop: false,
|
||||
child: AlertDialog(
|
||||
title: I18nText('homeView.downloadConsentDialogTitle'),
|
||||
content: ValueListenableBuilder(
|
||||
valueListenable: autoUpdate,
|
||||
builder: (context, value, child) {
|
||||
return Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
I18nText(
|
||||
'homeView.downloadConsentDialogText',
|
||||
child: Text(
|
||||
'',
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Theme.of(context).colorScheme.secondary,
|
||||
),
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 10),
|
||||
child: I18nText(
|
||||
'homeView.downloadConsentDialogText2',
|
||||
translationParams: {
|
||||
'url': _managerAPI.defaultApiUrl.split('/')[2],
|
||||
},
|
||||
child: Text(
|
||||
'',
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Theme.of(context).colorScheme.error,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () async {
|
||||
_managerAPI.setDownloadConsent(false);
|
||||
SystemNavigator.pop();
|
||||
},
|
||||
child: I18nText('quitButton'),
|
||||
),
|
||||
FilledButton(
|
||||
onPressed: () async {
|
||||
_managerAPI.setDownloadConsent(true);
|
||||
_managerAPI.setPatchesAutoUpdate(autoUpdate.value);
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
child: I18nText('okButton'),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void showUpdateDialog(BuildContext context, bool isPatches) {
|
||||
final ValueNotifier<bool> noShow =
|
||||
ValueNotifier(!_managerAPI.showUpdateDialog());
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (innerContext) => AlertDialog(
|
||||
title: I18nText('homeView.updateDialogTitle'),
|
||||
content: ValueListenableBuilder(
|
||||
valueListenable: autoUpdate,
|
||||
valueListenable: noShow,
|
||||
builder: (context, value, child) {
|
||||
return Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
I18nText(
|
||||
'homeView.patchesConsentDialogText',
|
||||
'homeView.updateDialogText',
|
||||
translationParams: {
|
||||
'file': isPatches ? 'ReVanced Patches' : 'ReVanced Manager',
|
||||
'version': isPatches
|
||||
? _currentPatchesVersion
|
||||
: _currentManagerVersion,
|
||||
},
|
||||
child: Text(
|
||||
'',
|
||||
style: TextStyle(
|
||||
@ -186,34 +272,18 @@ class HomeViewModel extends BaseViewModel {
|
||||
),
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 10),
|
||||
child: I18nText(
|
||||
'homeView.patchesConsentDialogText2',
|
||||
translationParams: {
|
||||
'url': _managerAPI.defaultApiUrl.split('/')[2],
|
||||
},
|
||||
child: Text(
|
||||
'',
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Theme.of(context).colorScheme.error,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
CheckboxListTile(
|
||||
const SizedBox(height: 10),
|
||||
HapticCheckboxListTile(
|
||||
value: value,
|
||||
contentPadding: EdgeInsets.zero,
|
||||
title: I18nText(
|
||||
'homeView.patchesConsentDialogText3',
|
||||
'noShowAgain',
|
||||
),
|
||||
subtitle: I18nText(
|
||||
'homeView.patchesConsentDialogText3Sub',
|
||||
'homeView.changeLaterSubtitle',
|
||||
),
|
||||
onChanged: (selected) {
|
||||
autoUpdate.value = selected!;
|
||||
noShow.value = selected!;
|
||||
},
|
||||
),
|
||||
],
|
||||
@ -223,18 +293,18 @@ class HomeViewModel extends BaseViewModel {
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () async {
|
||||
await _managerAPI.setPatchesConsent(false);
|
||||
SystemNavigator.pop();
|
||||
_managerAPI.setShowUpdateDialog(!noShow.value);
|
||||
Navigator.pop(innerContext);
|
||||
},
|
||||
child: I18nText('quitButton'),
|
||||
child: I18nText('dismissButton'), // Decide later
|
||||
),
|
||||
FilledButton(
|
||||
onPressed: () async {
|
||||
await _managerAPI.setPatchesConsent(true);
|
||||
await _managerAPI.setPatchesAutoUpdate(autoUpdate.value);
|
||||
Navigator.of(context).pop();
|
||||
_managerAPI.setShowUpdateDialog(!noShow.value);
|
||||
Navigator.pop(innerContext);
|
||||
await showUpdateConfirmationDialog(context, isPatches);
|
||||
},
|
||||
child: I18nText('okButton'),
|
||||
child: I18nText('showUpdateButton'),
|
||||
),
|
||||
],
|
||||
),
|
||||
@ -266,120 +336,91 @@ class HomeViewModel extends BaseViewModel {
|
||||
builder: (context) => ValueListenableBuilder(
|
||||
valueListenable: downloaded,
|
||||
builder: (context, value, child) {
|
||||
return SimpleDialog(
|
||||
backgroundColor: Theme.of(context).colorScheme.secondaryContainer,
|
||||
contentPadding: const EdgeInsets.all(16.0),
|
||||
return AlertDialog(
|
||||
title: I18nText(
|
||||
!value
|
||||
? 'homeView.downloadingMessage'
|
||||
: 'homeView.downloadedMessage',
|
||||
child: Text(
|
||||
'',
|
||||
style: TextStyle(
|
||||
fontSize: 20,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Theme.of(context).colorScheme.secondary,
|
||||
),
|
||||
),
|
||||
),
|
||||
children: [
|
||||
Column(
|
||||
children: [
|
||||
Row(
|
||||
content: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
if (!value)
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Icon(
|
||||
Icons.new_releases_outlined,
|
||||
color: Theme.of(context).colorScheme.secondary,
|
||||
StreamBuilder<double>(
|
||||
initialData: 0.0,
|
||||
stream: _revancedAPI.managerUpdateProgress.stream,
|
||||
builder: (context, snapshot) {
|
||||
return LinearProgressIndicator(
|
||||
value: snapshot.data! * 0.01,
|
||||
valueColor: AlwaysStoppedAnimation<Color>(
|
||||
Theme.of(context).colorScheme.secondary,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
const SizedBox(width: 8.0),
|
||||
Text(
|
||||
'$_latestManagerVersion',
|
||||
style: TextStyle(
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Theme.of(context).colorScheme.secondary,
|
||||
const SizedBox(height: 16.0),
|
||||
Align(
|
||||
alignment: Alignment.centerRight,
|
||||
child: FilledButton(
|
||||
onPressed: () {
|
||||
_revancedAPI.disposeManagerUpdateProgress();
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
child: I18nText('cancelButton'),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 16.0),
|
||||
if (!value)
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
StreamBuilder<double>(
|
||||
initialData: 0.0,
|
||||
stream: _revancedAPI.managerUpdateProgress.stream,
|
||||
builder: (context, snapshot) {
|
||||
return LinearProgressIndicator(
|
||||
value: snapshot.data! * 0.01,
|
||||
valueColor: AlwaysStoppedAnimation<Color>(
|
||||
Theme.of(context).colorScheme.secondary,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
const SizedBox(height: 16.0),
|
||||
Align(
|
||||
alignment: Alignment.centerRight,
|
||||
child: FilledButton(
|
||||
onPressed: () {
|
||||
_revancedAPI.disposeManagerUpdateProgress();
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
child: I18nText('cancelButton'),
|
||||
if (value)
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
I18nText(
|
||||
'homeView.installUpdate',
|
||||
child: Text(
|
||||
'',
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Theme.of(context).colorScheme.secondary,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
if (value)
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
I18nText(
|
||||
'homeView.installUpdate',
|
||||
child: Text(
|
||||
'',
|
||||
style: TextStyle(
|
||||
fontSize: 20,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Theme.of(context).colorScheme.secondary,
|
||||
),
|
||||
const SizedBox(height: 16.0),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
Align(
|
||||
alignment: Alignment.centerRight,
|
||||
child: TextButton(
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
child: I18nText('cancelButton'),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16.0),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
Align(
|
||||
alignment: Alignment.centerRight,
|
||||
child: TextButton(
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
child: I18nText('cancelButton'),
|
||||
),
|
||||
const SizedBox(width: 8.0),
|
||||
Align(
|
||||
alignment: Alignment.centerRight,
|
||||
child: FilledButton(
|
||||
onPressed: () async {
|
||||
await _patcherAPI.installApk(
|
||||
context,
|
||||
downloadedApk!.path,
|
||||
);
|
||||
},
|
||||
child: I18nText('updateButton'),
|
||||
),
|
||||
const SizedBox(width: 8.0),
|
||||
Align(
|
||||
alignment: Alignment.centerRight,
|
||||
child: FilledButton(
|
||||
onPressed: () async {
|
||||
await _patcherAPI.installApk(
|
||||
context,
|
||||
downloadedApk!.path,
|
||||
);
|
||||
},
|
||||
child: I18nText('updateButton'),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
@ -431,16 +472,18 @@ class HomeViewModel extends BaseViewModel {
|
||||
|
||||
Future<void> showUpdateConfirmationDialog(
|
||||
BuildContext parentContext,
|
||||
bool isPatches,
|
||||
) {
|
||||
bool isPatches, [
|
||||
bool changelog = false,
|
||||
]) {
|
||||
return showModalBottomSheet(
|
||||
context: parentContext,
|
||||
isScrollControlled: true,
|
||||
shape: const RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.vertical(top: Radius.circular(24.0)),
|
||||
),
|
||||
builder: (context) => UpdateConfirmationDialog(
|
||||
builder: (context) => UpdateConfirmationSheet(
|
||||
isPatches: isPatches,
|
||||
changelog: changelog,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
@ -5,6 +5,7 @@ import 'package:revanced_manager/ui/views/installer/installer_viewmodel.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_sliver_app_bar.dart';
|
||||
import 'package:revanced_manager/ui/widgets/shared/haptics/haptic_floating_action_button_extended.dart';
|
||||
import 'package:stacked/stacked.dart';
|
||||
|
||||
class InstallerView extends StatelessWidget {
|
||||
@ -15,14 +16,14 @@ class InstallerView extends StatelessWidget {
|
||||
return ViewModelBuilder<InstallerViewModel>.reactive(
|
||||
onViewModelReady: (model) => model.initialize(context),
|
||||
viewModelBuilder: () => InstallerViewModel(),
|
||||
builder: (context, model, child) => WillPopScope(
|
||||
builder: (context, model, child) => PopScope(
|
||||
child: SafeArea(
|
||||
top: false,
|
||||
bottom: model.isPatching,
|
||||
child: Scaffold(
|
||||
floatingActionButton: Visibility(
|
||||
visible: !model.isPatching && !model.hasErrors,
|
||||
child: FloatingActionButton.extended(
|
||||
child: HapticFloatingActionButtonExtended(
|
||||
label: I18nText(
|
||||
model.isInstalled
|
||||
? 'installerView.openButton'
|
||||
@ -111,7 +112,7 @@ class InstallerView extends StatelessWidget {
|
||||
),
|
||||
),
|
||||
),
|
||||
onWillPop: () => model.onWillPop(context),
|
||||
onPopInvoked: (bool didPop) => model.onWillPop(context),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
@ -220,8 +220,6 @@ class InstallerViewModel extends BaseViewModel {
|
||||
String suggestedVersion = _patcherAPI.getSuggestedVersion(_app.packageName);
|
||||
if (suggestedVersion.isEmpty) {
|
||||
suggestedVersion = 'Any';
|
||||
} else {
|
||||
suggestedVersion = 'v$suggestedVersion';
|
||||
}
|
||||
return suggestedVersion;
|
||||
}
|
||||
|
@ -13,14 +13,10 @@ class NavigationView extends StatelessWidget {
|
||||
return ViewModelBuilder<NavigationViewModel>.reactive(
|
||||
onViewModelReady: (model) => model.initialize(context),
|
||||
viewModelBuilder: () => locator<NavigationViewModel>(),
|
||||
builder: (context, model, child) => WillPopScope(
|
||||
onWillPop: () async {
|
||||
if (model.currentIndex == 0) {
|
||||
return true;
|
||||
} else {
|
||||
model.setIndex(0);
|
||||
return false;
|
||||
}
|
||||
builder: (context, model, child) => PopScope(
|
||||
canPop: model.currentIndex == 0,
|
||||
onPopInvoked: (bool didPop) => {
|
||||
if (!didPop) model.setIndex(0),
|
||||
},
|
||||
child: Scaffold(
|
||||
body: PageTransitionSwitcher(
|
||||
|
@ -4,6 +4,7 @@ import 'package:google_fonts/google_fonts.dart';
|
||||
import 'package:revanced_manager/models/patch.dart';
|
||||
import 'package:revanced_manager/ui/views/patch_options/patch_options_viewmodel.dart';
|
||||
import 'package:revanced_manager/ui/widgets/patchesSelectorView/patch_options_fields.dart';
|
||||
import 'package:revanced_manager/ui/widgets/shared/haptics/haptic_floating_action_button_extended.dart';
|
||||
import 'package:stacked/stacked.dart';
|
||||
|
||||
class PatchOptionsView extends StatelessWidget {
|
||||
@ -17,7 +18,7 @@ class PatchOptionsView extends StatelessWidget {
|
||||
builder: (context, model, child) => GestureDetector(
|
||||
onTap: () => FocusScope.of(context).unfocus(),
|
||||
child: Scaffold(
|
||||
floatingActionButton: FloatingActionButton.extended(
|
||||
floatingActionButton: HapticFloatingActionButtonExtended(
|
||||
onPressed: () async {
|
||||
final bool saved = model.saveOptions(context);
|
||||
if (saved && context.mounted) {
|
||||
|
@ -184,8 +184,9 @@ class PatchOptionsViewModel extends BaseViewModel {
|
||||
children: [
|
||||
Text(
|
||||
e.title,
|
||||
style: const TextStyle(
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
color: Theme.of(context).colorScheme.onSurface,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
|
@ -6,6 +6,7 @@ import 'package:revanced_manager/ui/views/patcher/patcher_viewmodel.dart';
|
||||
import 'package:revanced_manager/ui/widgets/patcherView/app_selector_card.dart';
|
||||
import 'package:revanced_manager/ui/widgets/patcherView/patch_selector_card.dart';
|
||||
import 'package:revanced_manager/ui/widgets/shared/custom_sliver_app_bar.dart';
|
||||
import 'package:revanced_manager/ui/widgets/shared/haptics/haptic_floating_action_button_extended.dart';
|
||||
import 'package:stacked/stacked.dart';
|
||||
|
||||
class PatcherView extends StatelessWidget {
|
||||
@ -19,7 +20,7 @@ class PatcherView extends StatelessWidget {
|
||||
builder: (context, model, child) => Scaffold(
|
||||
floatingActionButton: Visibility(
|
||||
visible: model.showPatchButton(),
|
||||
child: FloatingActionButton.extended(
|
||||
child: HapticFloatingActionButtonExtended(
|
||||
label: I18nText('patcherView.patchButton'),
|
||||
icon: const Icon(Icons.build),
|
||||
onPressed: () async {
|
||||
|
@ -148,52 +148,17 @@ class PatcherViewModel extends BaseViewModel {
|
||||
}
|
||||
|
||||
String getAppSelectionString() {
|
||||
String text = '${selectedApp!.name} (${selectedApp!.packageName})';
|
||||
if (text.length > 32) {
|
||||
text = '${text.substring(0, 32)}...)';
|
||||
}
|
||||
return text;
|
||||
return '${selectedApp!.name} ${selectedApp!.version}';
|
||||
}
|
||||
|
||||
String getCurrentVersionString(BuildContext context) {
|
||||
return '${FlutterI18n.translate(
|
||||
context,
|
||||
'appSelectorCard.currentVersion',
|
||||
)}: v${selectedApp!.version}';
|
||||
}
|
||||
|
||||
Future<void> searchSuggestedVersionOnWeb() async {
|
||||
final String suggestedVersion =
|
||||
_patcherAPI.getSuggestedVersion(selectedApp!.packageName);
|
||||
|
||||
if (suggestedVersion.isNotEmpty) {
|
||||
await openDefaultBrowser(
|
||||
'${selectedApp!.packageName} apk version v$suggestedVersion',
|
||||
);
|
||||
} else {
|
||||
await openDefaultBrowser('${selectedApp!.packageName} apk');
|
||||
}
|
||||
}
|
||||
|
||||
String getSuggestedVersion() {
|
||||
return _patcherAPI.getSuggestedVersion(selectedApp!.packageName);
|
||||
Future<void> queryVersion(String suggestedVersion) async {
|
||||
await openDefaultBrowser(
|
||||
'${selectedApp!.packageName} apk version $suggestedVersion',
|
||||
);
|
||||
}
|
||||
|
||||
String getSuggestedVersionString(BuildContext context) {
|
||||
String suggestedVersion =
|
||||
_patcherAPI.getSuggestedVersion(selectedApp!.packageName);
|
||||
if (suggestedVersion.isEmpty) {
|
||||
suggestedVersion = FlutterI18n.translate(
|
||||
context,
|
||||
'appSelectorCard.allVersions',
|
||||
);
|
||||
} else {
|
||||
suggestedVersion = 'v$suggestedVersion';
|
||||
}
|
||||
return '${FlutterI18n.translate(
|
||||
context,
|
||||
'appSelectorCard.suggestedVersion',
|
||||
)}: $suggestedVersion';
|
||||
return _patcherAPI.getSuggestedVersion(selectedApp!.packageName);
|
||||
}
|
||||
|
||||
Future<void> openDefaultBrowser(String query) async {
|
||||
|
@ -3,6 +3,7 @@ import 'package:flutter_i18n/flutter_i18n.dart';
|
||||
import 'package:revanced_manager/app/app.locator.dart';
|
||||
import 'package:revanced_manager/services/manager_api.dart';
|
||||
import 'package:revanced_manager/ui/views/patches_selector/patches_selector_viewmodel.dart';
|
||||
import 'package:revanced_manager/ui/widgets/shared/haptics/haptic_floating_action_button_extended.dart';
|
||||
import 'package:revanced_manager/ui/widgets/shared/search_bar.dart';
|
||||
import 'package:stacked/stacked.dart';
|
||||
|
||||
@ -36,7 +37,7 @@ class _PatchesSelectorViewState extends State<PatchesSelectorView> {
|
||||
builder: (context, model, child) => Scaffold(
|
||||
floatingActionButton: Visibility(
|
||||
visible: model.patches.isNotEmpty,
|
||||
child: FloatingActionButton.extended(
|
||||
child: HapticFloatingActionButtonExtended(
|
||||
label: Row(
|
||||
children: <Widget>[
|
||||
I18nText('patchesSelectorView.doneButton'),
|
||||
|
@ -4,14 +4,11 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter_i18n/flutter_i18n.dart';
|
||||
import 'package:revanced_manager/app/app.locator.dart';
|
||||
import 'package:revanced_manager/services/manager_api.dart';
|
||||
import 'package:revanced_manager/services/toast.dart';
|
||||
import 'package:revanced_manager/ui/widgets/settingsView/custom_text_field.dart';
|
||||
import 'package:revanced_manager/ui/widgets/settingsView/settings_tile_dialog.dart';
|
||||
import 'package:stacked/stacked.dart';
|
||||
|
||||
class SManageApiUrl extends BaseViewModel {
|
||||
final ManagerAPI _managerAPI = locator<ManagerAPI>();
|
||||
final Toast _toast = locator<Toast>();
|
||||
|
||||
final TextEditingController _apiUrlController = TextEditingController();
|
||||
|
||||
@ -35,15 +32,22 @@ class SManageApiUrl extends BaseViewModel {
|
||||
content: SingleChildScrollView(
|
||||
child: Column(
|
||||
children: <Widget>[
|
||||
CustomTextField(
|
||||
leadingIcon: Icon(
|
||||
Icons.api_outlined,
|
||||
color: Theme.of(context).colorScheme.secondary,
|
||||
),
|
||||
inputController: _apiUrlController,
|
||||
label: I18nText('settingsView.selectApiURL'),
|
||||
hint: apiUrl,
|
||||
TextField(
|
||||
controller: _apiUrlController,
|
||||
autocorrect: false,
|
||||
onChanged: (value) => notifyListeners(),
|
||||
decoration: InputDecoration(
|
||||
icon: Icon(
|
||||
Icons.api_outlined,
|
||||
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||
),
|
||||
border: const OutlineInputBorder(),
|
||||
labelText: FlutterI18n.translate(
|
||||
context,
|
||||
'settingsView.selectApiURL',
|
||||
),
|
||||
hintText: apiUrl,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
@ -63,7 +67,6 @@ class SManageApiUrl extends BaseViewModel {
|
||||
apiUrl = 'https://$apiUrl';
|
||||
}
|
||||
_managerAPI.setApiUrl(apiUrl);
|
||||
_toast.showBottom('settingsView.restartAppForChanges');
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
child: I18nText('okButton'),
|
||||
@ -87,7 +90,6 @@ class SManageApiUrl extends BaseViewModel {
|
||||
FilledButton(
|
||||
onPressed: () {
|
||||
_managerAPI.setApiUrl('');
|
||||
_toast.showBottom('settingsView.restartAppForChanges');
|
||||
Navigator.of(context)
|
||||
..pop()
|
||||
..pop();
|
||||
|
@ -4,7 +4,6 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter_i18n/flutter_i18n.dart';
|
||||
import 'package:revanced_manager/app/app.locator.dart';
|
||||
import 'package:revanced_manager/services/manager_api.dart';
|
||||
import 'package:revanced_manager/ui/widgets/settingsView/custom_text_field.dart';
|
||||
import 'package:revanced_manager/ui/widgets/settingsView/settings_tile_dialog.dart';
|
||||
import 'package:stacked/stacked.dart';
|
||||
|
||||
@ -35,11 +34,18 @@ class SManageKeystorePassword extends BaseViewModel {
|
||||
content: SingleChildScrollView(
|
||||
child: Column(
|
||||
children: <Widget>[
|
||||
CustomTextField(
|
||||
inputController: _keystorePasswordController,
|
||||
label: I18nText('settingsView.selectKeystorePassword'),
|
||||
hint: '',
|
||||
TextField(
|
||||
controller: _keystorePasswordController,
|
||||
autocorrect: false,
|
||||
obscureText: true,
|
||||
onChanged: (value) => notifyListeners(),
|
||||
decoration: InputDecoration(
|
||||
border: const OutlineInputBorder(),
|
||||
labelText: FlutterI18n.translate(
|
||||
context,
|
||||
'settingsView.selectKeystorePassword',
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
@ -5,7 +5,6 @@ import 'package:flutter_i18n/flutter_i18n.dart';
|
||||
import 'package:revanced_manager/app/app.locator.dart';
|
||||
import 'package:revanced_manager/services/manager_api.dart';
|
||||
import 'package:revanced_manager/services/toast.dart';
|
||||
import 'package:revanced_manager/ui/widgets/settingsView/custom_text_field.dart';
|
||||
import 'package:revanced_manager/ui/widgets/settingsView/settings_tile_dialog.dart';
|
||||
import 'package:stacked/stacked.dart';
|
||||
|
||||
@ -45,59 +44,102 @@ class SManageSources extends BaseViewModel {
|
||||
content: SingleChildScrollView(
|
||||
child: Column(
|
||||
children: <Widget>[
|
||||
CustomTextField(
|
||||
leadingIcon: const Icon(
|
||||
Icons.extension_outlined,
|
||||
color: Colors.transparent,
|
||||
),
|
||||
inputController: _hostSourceController,
|
||||
label: I18nText('settingsView.hostRepositoryLabel'),
|
||||
hint: hostRepository,
|
||||
/*
|
||||
API for accessing the specified repositories
|
||||
If default is used, will use the ReVanced API
|
||||
*/
|
||||
TextField(
|
||||
controller: _hostSourceController,
|
||||
autocorrect: false,
|
||||
onChanged: (value) => notifyListeners(),
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
CustomTextField(
|
||||
leadingIcon: Icon(
|
||||
Icons.extension_outlined,
|
||||
color: Theme.of(context).colorScheme.secondary,
|
||||
decoration: InputDecoration(
|
||||
icon: Icon(
|
||||
Icons.rocket_launch_outlined,
|
||||
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||
),
|
||||
border: const OutlineInputBorder(),
|
||||
labelText: FlutterI18n.translate(
|
||||
context,
|
||||
'settingsView.hostRepositoryLabel',
|
||||
),
|
||||
hintText: hostRepository,
|
||||
),
|
||||
inputController: _orgPatSourceController,
|
||||
label: I18nText('settingsView.orgPatchesLabel'),
|
||||
hint: patchesRepo.split('/')[0],
|
||||
onChanged: (value) => notifyListeners(),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
CustomTextField(
|
||||
leadingIcon: const Icon(
|
||||
Icons.extension_outlined,
|
||||
color: Colors.transparent,
|
||||
),
|
||||
inputController: _patSourceController,
|
||||
label: I18nText('settingsView.sourcesPatchesLabel'),
|
||||
hint: patchesRepo.split('/')[1],
|
||||
// Patches owner's name
|
||||
TextField(
|
||||
controller: _orgPatSourceController,
|
||||
autocorrect: false,
|
||||
onChanged: (value) => notifyListeners(),
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
CustomTextField(
|
||||
leadingIcon: Icon(
|
||||
Icons.merge_outlined,
|
||||
color: Theme.of(context).colorScheme.secondary,
|
||||
decoration: InputDecoration(
|
||||
icon: Icon(
|
||||
Icons.extension_outlined,
|
||||
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||
),
|
||||
border: const OutlineInputBorder(),
|
||||
labelText: FlutterI18n.translate(
|
||||
context,
|
||||
'settingsView.orgPatchesLabel',
|
||||
),
|
||||
hintText: patchesRepo.split('/')[0],
|
||||
),
|
||||
inputController: _orgIntSourceController,
|
||||
label: I18nText('settingsView.orgIntegrationsLabel'),
|
||||
hint: integrationsRepo.split('/')[0],
|
||||
onChanged: (value) => notifyListeners(),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
CustomTextField(
|
||||
leadingIcon: const Icon(
|
||||
Icons.merge_outlined,
|
||||
color: Colors.transparent,
|
||||
),
|
||||
inputController: _intSourceController,
|
||||
label: I18nText('settingsView.sourcesIntegrationsLabel'),
|
||||
hint: integrationsRepo.split('/')[1],
|
||||
// Patches repository's name
|
||||
TextField(
|
||||
controller: _patSourceController,
|
||||
autocorrect: false,
|
||||
onChanged: (value) => notifyListeners(),
|
||||
decoration: InputDecoration(
|
||||
icon: const Icon(
|
||||
Icons.extension_outlined,
|
||||
color: Colors.transparent,
|
||||
),
|
||||
border: const OutlineInputBorder(),
|
||||
labelText: FlutterI18n.translate(
|
||||
context,
|
||||
'settingsView.sourcesPatchesLabel',
|
||||
),
|
||||
hintText: patchesRepo.split('/')[1],
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
// Integrations owner's name
|
||||
TextField(
|
||||
controller: _orgIntSourceController,
|
||||
autocorrect: false,
|
||||
onChanged: (value) => notifyListeners(),
|
||||
decoration: InputDecoration(
|
||||
icon: Icon(
|
||||
Icons.merge_outlined,
|
||||
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||
),
|
||||
border: const OutlineInputBorder(),
|
||||
labelText: FlutterI18n.translate(
|
||||
context,
|
||||
'settingsView.orgIntegrationsLabel',
|
||||
),
|
||||
hintText: integrationsRepo.split('/')[0],
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
// Integrations repository's name
|
||||
TextField(
|
||||
controller: _intSourceController,
|
||||
autocorrect: false,
|
||||
onChanged: (value) => notifyListeners(),
|
||||
decoration: InputDecoration(
|
||||
icon: const Icon(
|
||||
Icons.merge_outlined,
|
||||
color: Colors.transparent,
|
||||
),
|
||||
border: const OutlineInputBorder(),
|
||||
labelText: FlutterI18n.translate(
|
||||
context,
|
||||
'settingsView.sourcesIntegrationsLabel',
|
||||
),
|
||||
hintText: integrationsRepo.split('/')[1],
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 20),
|
||||
I18nText('settingsView.sourcesUpdateNote'),
|
||||
|
@ -7,6 +7,8 @@ import 'package:flutter_i18n/widgets/I18nText.dart';
|
||||
import 'package:revanced_manager/app/app.locator.dart';
|
||||
import 'package:revanced_manager/services/manager_api.dart';
|
||||
import 'package:revanced_manager/ui/widgets/settingsView/settings_section.dart';
|
||||
import 'package:revanced_manager/ui/widgets/shared/haptics/haptic_radio_list_tile.dart';
|
||||
import 'package:revanced_manager/ui/widgets/shared/haptics/haptic_switch_list_tile.dart';
|
||||
|
||||
class SUpdateThemeUI extends StatefulWidget {
|
||||
const SUpdateThemeUI({super.key});
|
||||
@ -42,7 +44,7 @@ class _SUpdateThemeUIState extends State<SUpdateThemeUI> {
|
||||
onTap: () => {showThemeDialog(context)},
|
||||
),
|
||||
if (managerAPI.isDynamicThemeAvailable)
|
||||
SwitchListTile(
|
||||
HapticSwitchListTile(
|
||||
contentPadding: const EdgeInsets.symmetric(horizontal: 20.0),
|
||||
title: I18nText(
|
||||
'settingsView.dynamicThemeLabel',
|
||||
@ -129,7 +131,7 @@ class _SUpdateThemeUIState extends State<SUpdateThemeUI> {
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
RadioListTile(
|
||||
HapticRadioListTile(
|
||||
title: I18nText('settingsView.systemThemeLabel'),
|
||||
contentPadding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
value: 0,
|
||||
@ -138,7 +140,7 @@ class _SUpdateThemeUIState extends State<SUpdateThemeUI> {
|
||||
newTheme.value = value!;
|
||||
},
|
||||
),
|
||||
RadioListTile(
|
||||
HapticRadioListTile(
|
||||
title: I18nText('settingsView.lightThemeLabel'),
|
||||
contentPadding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
value: 1,
|
||||
@ -147,7 +149,7 @@ class _SUpdateThemeUIState extends State<SUpdateThemeUI> {
|
||||
newTheme.value = value!;
|
||||
},
|
||||
),
|
||||
RadioListTile(
|
||||
HapticRadioListTile(
|
||||
title: I18nText('settingsView.darkThemeLabel'),
|
||||
contentPadding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
value: 2,
|
||||
|
@ -39,6 +39,15 @@ class SettingsViewModel extends BaseViewModel {
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
bool showUpdateDialog() {
|
||||
return _managerAPI.showUpdateDialog();
|
||||
}
|
||||
|
||||
void setShowUpdateDialog(bool value) {
|
||||
_managerAPI.setShowUpdateDialog(value);
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
bool isPatchesChangeEnabled() {
|
||||
return _managerAPI.isPatchesChangeEnabled();
|
||||
}
|
||||
|
@ -54,25 +54,43 @@ class _InstalledAppItemState extends State<InstalledAppItem> {
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
Wrap(
|
||||
crossAxisAlignment: WrapCrossAlignment.center,
|
||||
spacing: 4,
|
||||
children: [
|
||||
Text(
|
||||
widget.name,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: const TextStyle(
|
||||
fontSize: 16,
|
||||
),
|
||||
),
|
||||
Text(
|
||||
widget.installedVersion,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: const TextStyle(
|
||||
fontSize: 16,
|
||||
),
|
||||
),
|
||||
Text(
|
||||
widget.patchesCount == 1
|
||||
? '• ${widget.patchesCount} patch'
|
||||
: '• ${widget.patchesCount} patches',
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
color: Theme.of(context).colorScheme.secondary,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
Text(
|
||||
widget.name,
|
||||
maxLines: 2,
|
||||
overflow: TextOverflow.visible,
|
||||
style: const TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
Text(widget.pkgName),
|
||||
I18nText(
|
||||
FlutterI18n.translate(
|
||||
context,
|
||||
'installed',
|
||||
translationParams: {
|
||||
'version': 'v${widget.installedVersion}',
|
||||
},
|
||||
),
|
||||
widget.pkgName,
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Wrap(
|
||||
crossAxisAlignment: WrapCrossAlignment.center,
|
||||
children: [
|
||||
@ -85,7 +103,7 @@ class _InstalledAppItemState extends State<InstalledAppItem> {
|
||||
borderRadius:
|
||||
const BorderRadius.all(Radius.circular(8)),
|
||||
child: Container(
|
||||
padding: const EdgeInsets.all(4),
|
||||
padding: const EdgeInsets.fromLTRB(8, 4, 8, 4),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
@ -95,18 +113,10 @@ class _InstalledAppItemState extends State<InstalledAppItem> {
|
||||
'version': widget.suggestedVersion.isEmpty
|
||||
? FlutterI18n.translate(
|
||||
context,
|
||||
'appSelectorCard.allVersions',
|
||||
'appSelectorCard.anyVersion',
|
||||
)
|
||||
: 'v${widget.suggestedVersion}',
|
||||
: widget.suggestedVersion,
|
||||
},
|
||||
child: Text(
|
||||
'',
|
||||
style: TextStyle(
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
.onSecondaryContainer,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 4),
|
||||
Icon(
|
||||
@ -121,17 +131,6 @@ class _InstalledAppItemState extends State<InstalledAppItem> {
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 4),
|
||||
Text(
|
||||
widget.patchesCount == 1
|
||||
? '• ${widget.patchesCount} patch'
|
||||
: '• ${widget.patchesCount} patches',
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).colorScheme.secondary,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
|
@ -33,103 +33,91 @@ class _NotInstalledAppItem extends State<NotInstalledAppItem> {
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: <Widget>[
|
||||
Container(
|
||||
width: 48,
|
||||
height: 48,
|
||||
padding: const EdgeInsets.symmetric(vertical: 4.0),
|
||||
alignment: Alignment.center,
|
||||
child: const CircleAvatar(
|
||||
backgroundColor: Colors.transparent,
|
||||
child: Icon(
|
||||
Icons.square_rounded,
|
||||
color: Colors.grey,
|
||||
size: 44,
|
||||
size: 48,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
Text(
|
||||
widget.name,
|
||||
style: const TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w500,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
Wrap(
|
||||
crossAxisAlignment: WrapCrossAlignment.center,
|
||||
spacing: 4,
|
||||
children: [
|
||||
Text(
|
||||
widget.name,
|
||||
style: const TextStyle(
|
||||
fontSize: 16,
|
||||
),
|
||||
),
|
||||
Text(
|
||||
widget.patchesCount == 1
|
||||
? '• ${widget.patchesCount} patch'
|
||||
: '• ${widget.patchesCount} patches',
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
color: Theme.of(context).colorScheme.secondary,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
I18nText(
|
||||
'appSelectorCard.notInstalled',
|
||||
child: Text(
|
||||
'',
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).textTheme.titleLarge!.color,
|
||||
),
|
||||
),
|
||||
),
|
||||
Wrap(
|
||||
crossAxisAlignment: WrapCrossAlignment.center,
|
||||
children: [
|
||||
Material(
|
||||
color: Theme.of(context).colorScheme.secondaryContainer,
|
||||
borderRadius:
|
||||
const BorderRadius.all(Radius.circular(8)),
|
||||
child: InkWell(
|
||||
onTap: widget.onLinkTap,
|
||||
const SizedBox(height: 4),
|
||||
Wrap(
|
||||
crossAxisAlignment: WrapCrossAlignment.center,
|
||||
children: [
|
||||
Material(
|
||||
color:
|
||||
Theme.of(context).colorScheme.secondaryContainer,
|
||||
borderRadius:
|
||||
const BorderRadius.all(Radius.circular(8)),
|
||||
child: Container(
|
||||
padding: const EdgeInsets.all(4),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
I18nText(
|
||||
'suggested',
|
||||
translationParams: {
|
||||
'version': widget.suggestedVersion.isEmpty
|
||||
? FlutterI18n.translate(
|
||||
context,
|
||||
'appSelectorCard.allVersions',
|
||||
)
|
||||
: 'v${widget.suggestedVersion}',
|
||||
},
|
||||
child: Text(
|
||||
'',
|
||||
style: TextStyle(
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
.onSecondaryContainer,
|
||||
),
|
||||
child: InkWell(
|
||||
onTap: widget.onLinkTap,
|
||||
borderRadius:
|
||||
const BorderRadius.all(Radius.circular(8)),
|
||||
child: Container(
|
||||
padding: const EdgeInsets.fromLTRB(8, 4, 8, 4),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
I18nText(
|
||||
'suggested',
|
||||
translationParams: {
|
||||
'version': widget.suggestedVersion.isEmpty
|
||||
? FlutterI18n.translate(
|
||||
context,
|
||||
'appSelectorCard.anyVersion',
|
||||
)
|
||||
: widget.suggestedVersion,
|
||||
},
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 4),
|
||||
Icon(
|
||||
Icons.search,
|
||||
size: 16,
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
.onSecondaryContainer,
|
||||
),
|
||||
],
|
||||
const SizedBox(width: 4),
|
||||
Icon(
|
||||
Icons.search,
|
||||
size: 16,
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
.onSecondaryContainer,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 4),
|
||||
Text(
|
||||
widget.patchesCount == 1
|
||||
? '• ${widget.patchesCount} patch'
|
||||
: '• ${widget.patchesCount} patches',
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: TextStyle(
|
||||
color: Theme.of(context).colorScheme.secondary,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
]),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
@ -55,17 +55,15 @@ class _LatestCommitCardState extends State<LatestCommitCard> {
|
||||
FutureBuilder<bool>(
|
||||
future: model.hasManagerUpdates(),
|
||||
initialData: false,
|
||||
builder: (context, snapshot) => Opacity(
|
||||
opacity: snapshot.hasData && snapshot.data! ? 1.0 : 0.25,
|
||||
child: FilledButton(
|
||||
onPressed: snapshot.hasData && snapshot.data!
|
||||
? () => widget.model.showUpdateConfirmationDialog(
|
||||
widget.parentContext,
|
||||
false,
|
||||
)
|
||||
: () => {},
|
||||
child: I18nText('updateButton'),
|
||||
builder: (context, snapshot) => FilledButton(
|
||||
onPressed: () => widget.model.showUpdateConfirmationDialog(
|
||||
widget.parentContext,
|
||||
false,
|
||||
!snapshot.data!,
|
||||
),
|
||||
child: (snapshot.hasData && !snapshot.data!)
|
||||
? I18nText('showChangelogButton')
|
||||
: I18nText('showUpdateButton'),
|
||||
),
|
||||
),
|
||||
],
|
||||
@ -83,7 +81,7 @@ class _LatestCommitCardState extends State<LatestCommitCard> {
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
const Text('Patches'),
|
||||
const Text('ReVanced Patches'),
|
||||
const SizedBox(height: 4),
|
||||
Row(
|
||||
children: <Widget>[
|
||||
@ -108,19 +106,17 @@ class _LatestCommitCardState extends State<LatestCommitCard> {
|
||||
),
|
||||
),
|
||||
FutureBuilder<bool>(
|
||||
future: locator<HomeViewModel>().hasPatchesUpdates(),
|
||||
future: model.hasPatchesUpdates(),
|
||||
initialData: false,
|
||||
builder: (context, snapshot) => Opacity(
|
||||
opacity: snapshot.hasData && snapshot.data! ? 1.0 : 0.25,
|
||||
child: FilledButton(
|
||||
onPressed: snapshot.hasData && snapshot.data!
|
||||
? () => widget.model.showUpdateConfirmationDialog(
|
||||
widget.parentContext,
|
||||
true,
|
||||
)
|
||||
: () => {},
|
||||
child: I18nText('updateButton'),
|
||||
builder: (context, snapshot) => FilledButton(
|
||||
onPressed: () => widget.model.showUpdateConfirmationDialog(
|
||||
widget.parentContext,
|
||||
true,
|
||||
!snapshot.data!,
|
||||
),
|
||||
child: (snapshot.hasData && !snapshot.data!)
|
||||
? I18nText('showChangelogButton')
|
||||
: I18nText('showUpdateButton'),
|
||||
),
|
||||
),
|
||||
],
|
||||
|
@ -4,10 +4,11 @@ import 'package:flutter_markdown/flutter_markdown.dart';
|
||||
import 'package:revanced_manager/app/app.locator.dart';
|
||||
import 'package:revanced_manager/ui/views/home/home_viewmodel.dart';
|
||||
|
||||
class UpdateConfirmationDialog extends StatelessWidget {
|
||||
const UpdateConfirmationDialog({super.key, required this.isPatches});
|
||||
class UpdateConfirmationSheet extends StatelessWidget {
|
||||
const UpdateConfirmationSheet({super.key, required this.isPatches, this.changelog = false});
|
||||
|
||||
final bool isPatches;
|
||||
final bool changelog;
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final HomeViewModel model = locator<HomeViewModel>();
|
||||
@ -36,6 +37,7 @@ class UpdateConfirmationDialog extends StatelessWidget {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
if (!changelog)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
top: 40.0,
|
||||
@ -51,8 +53,8 @@ class UpdateConfirmationDialog extends StatelessWidget {
|
||||
children: [
|
||||
I18nText(
|
||||
isPatches
|
||||
? 'homeView.updatePatchesDialogTitle'
|
||||
: 'homeView.updateDialogTitle',
|
||||
? 'homeView.updatePatchesSheetTitle'
|
||||
: 'homeView.updateSheetTitle',
|
||||
child: const Text(
|
||||
'',
|
||||
style: TextStyle(
|
||||
@ -98,7 +100,7 @@ class UpdateConfirmationDialog extends StatelessWidget {
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(left: 24.0, bottom: 12.0),
|
||||
padding: const EdgeInsets.only(top: 12.0, left: 24.0, bottom: 12.0),
|
||||
child: I18nText(
|
||||
'homeView.updateChangelogTitle',
|
||||
child: Text(
|
@ -15,13 +15,21 @@ class AppSelectorCard extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final vm = locator<PatcherViewModel>();
|
||||
|
||||
|
||||
String? suggestedVersion;
|
||||
if (vm.selectedApp != null) {
|
||||
suggestedVersion = vm.getSuggestedVersionString(context);
|
||||
}
|
||||
|
||||
return CustomCard(
|
||||
onTap: onPressed,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
I18nText(
|
||||
locator<PatcherViewModel>().selectedApp == null
|
||||
vm.selectedApp == null
|
||||
? 'appSelectorCard.widgetTitle'
|
||||
: 'appSelectorCard.widgetTitleSelected',
|
||||
child: const Text(
|
||||
@ -33,7 +41,7 @@ class AppSelectorCard extends StatelessWidget {
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
if (locator<PatcherViewModel>().selectedApp == null)
|
||||
if (vm.selectedApp == null)
|
||||
I18nText('appSelectorCard.widgetSubtitle')
|
||||
else
|
||||
Row(
|
||||
@ -42,9 +50,9 @@ class AppSelectorCard extends StatelessWidget {
|
||||
height: 18.0,
|
||||
child: ClipOval(
|
||||
child: Image.memory(
|
||||
locator<PatcherViewModel>().selectedApp == null
|
||||
vm.selectedApp == null
|
||||
? Uint8List(0)
|
||||
: locator<PatcherViewModel>().selectedApp!.icon,
|
||||
: vm.selectedApp!.icon,
|
||||
fit: BoxFit.cover,
|
||||
),
|
||||
),
|
||||
@ -52,13 +60,13 @@ class AppSelectorCard extends StatelessWidget {
|
||||
const SizedBox(width: 6),
|
||||
Flexible(
|
||||
child: Text(
|
||||
locator<PatcherViewModel>().getAppSelectionString(),
|
||||
vm.getAppSelectionString(),
|
||||
style: const TextStyle(fontWeight: FontWeight.w600),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
if (locator<PatcherViewModel>().selectedApp == null)
|
||||
if (vm.selectedApp == null)
|
||||
Container()
|
||||
else
|
||||
Column(
|
||||
@ -66,49 +74,50 @@ class AppSelectorCard extends StatelessWidget {
|
||||
children: [
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
locator<PatcherViewModel>().getCurrentVersionString(context),
|
||||
vm.selectedApp!.packageName,
|
||||
),
|
||||
Row(
|
||||
children: [
|
||||
Material(
|
||||
color: Theme.of(context).colorScheme.secondaryContainer,
|
||||
borderRadius: const BorderRadius.all(Radius.circular(8)),
|
||||
child: InkWell(
|
||||
onTap: () {
|
||||
locator<PatcherViewModel>()
|
||||
.searchSuggestedVersionOnWeb();
|
||||
},
|
||||
if (suggestedVersion!.isNotEmpty &&
|
||||
suggestedVersion != vm.selectedApp!.version) ...[
|
||||
const SizedBox(height: 4),
|
||||
Row(
|
||||
children: [
|
||||
Material(
|
||||
color: Theme.of(context).colorScheme.secondaryContainer,
|
||||
borderRadius:
|
||||
const BorderRadius.all(Radius.circular(8)),
|
||||
child: Container(
|
||||
padding: const EdgeInsets.all(4),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Text(
|
||||
locator<PatcherViewModel>()
|
||||
.getSuggestedVersionString(context),
|
||||
style: TextStyle(
|
||||
child: InkWell(
|
||||
onTap: () {
|
||||
vm.queryVersion(suggestedVersion!);
|
||||
},
|
||||
borderRadius:
|
||||
const BorderRadius.all(Radius.circular(8)),
|
||||
child: Container(
|
||||
padding: const EdgeInsets.fromLTRB(8, 4, 8, 4),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
I18nText(
|
||||
'suggested',
|
||||
translationParams: {
|
||||
'version': suggestedVersion,
|
||||
},
|
||||
),
|
||||
const SizedBox(width: 4),
|
||||
Icon(
|
||||
Icons.search,
|
||||
size: 16,
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
.onSecondaryContainer,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 4),
|
||||
Icon(
|
||||
Icons.search,
|
||||
size: 16,
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
.onSecondaryContainer,
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
]
|
||||
],
|
||||
),
|
||||
],
|
||||
|
@ -4,7 +4,8 @@ import 'package:revanced_manager/app/app.locator.dart';
|
||||
import 'package:revanced_manager/models/patch.dart';
|
||||
import 'package:revanced_manager/services/manager_api.dart';
|
||||
import 'package:revanced_manager/services/toast.dart';
|
||||
import 'package:revanced_manager/ui/widgets/shared/custom_card.dart';
|
||||
import 'package:revanced_manager/ui/widgets/shared/haptics/haptic_checkbox.dart';
|
||||
import 'package:revanced_manager/ui/widgets/shared/haptics/haptic_custom_card.dart';
|
||||
|
||||
// ignore: must_be_immutable
|
||||
class PatchItem extends StatefulWidget {
|
||||
@ -56,7 +57,7 @@ class _PatchItemState extends State<PatchItem> {
|
||||
widget._managerAPI.isVersionCompatibilityCheckEnabled() == true
|
||||
? 0.5
|
||||
: 1,
|
||||
child: CustomCard(
|
||||
child: HapticCustomCard(
|
||||
padding: EdgeInsets.only(
|
||||
top: 12,
|
||||
bottom: 16,
|
||||
@ -88,7 +89,7 @@ class _PatchItemState extends State<PatchItem> {
|
||||
children: [
|
||||
Transform.scale(
|
||||
scale: 1.2,
|
||||
child: Checkbox(
|
||||
child: HapticCheckbox(
|
||||
value: widget.isSelected,
|
||||
activeColor: Theme.of(context).colorScheme.primary,
|
||||
checkColor: Theme.of(context).colorScheme.secondaryContainer,
|
||||
|
@ -426,8 +426,11 @@ class _TextFieldForPatchOptionState extends State<TextFieldForPatchOption> {
|
||||
child: RichText(
|
||||
text: TextSpan(
|
||||
text: e.key,
|
||||
style: const TextStyle(
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
.onSecondaryContainer,
|
||||
),
|
||||
children: [
|
||||
TextSpan(
|
||||
|
@ -1,63 +0,0 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class CustomSwitch extends StatelessWidget {
|
||||
const CustomSwitch({
|
||||
super.key,
|
||||
required this.onChanged,
|
||||
required this.value,
|
||||
});
|
||||
final ValueChanged<bool> onChanged;
|
||||
final bool value;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return GestureDetector(
|
||||
onTap: () => onChanged(!value),
|
||||
child: SizedBox(
|
||||
height: 25,
|
||||
width: 50,
|
||||
child: Stack(
|
||||
children: <Widget>[
|
||||
AnimatedContainer(
|
||||
height: 25,
|
||||
width: 50,
|
||||
curve: Curves.ease,
|
||||
duration: const Duration(milliseconds: 400),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: const BorderRadius.all(
|
||||
Radius.circular(25.0),
|
||||
),
|
||||
color: value
|
||||
? Theme.of(context).colorScheme.primary
|
||||
: Theme.of(context).colorScheme.secondary,
|
||||
),
|
||||
),
|
||||
AnimatedAlign(
|
||||
curve: Curves.ease,
|
||||
duration: const Duration(milliseconds: 400),
|
||||
alignment: !value ? Alignment.centerLeft : Alignment.centerRight,
|
||||
child: Container(
|
||||
height: 20,
|
||||
width: 20,
|
||||
margin: const EdgeInsets.symmetric(horizontal: 3),
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
color: value
|
||||
? Theme.of(context).colorScheme.primaryContainer
|
||||
: Theme.of(context).colorScheme.surface,
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.black12.withOpacity(0.1),
|
||||
spreadRadius: 0.5,
|
||||
blurRadius: 1,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -1,32 +0,0 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:revanced_manager/ui/widgets/settingsView/custom_switch.dart';
|
||||
|
||||
class CustomSwitchTile extends StatelessWidget {
|
||||
const CustomSwitchTile({
|
||||
super.key,
|
||||
required this.title,
|
||||
required this.subtitle,
|
||||
required this.value,
|
||||
required this.onTap,
|
||||
this.padding,
|
||||
});
|
||||
final Widget title;
|
||||
final Widget subtitle;
|
||||
final bool value;
|
||||
final Function(bool) onTap;
|
||||
final EdgeInsetsGeometry? padding;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ListTile(
|
||||
contentPadding: padding ?? EdgeInsets.zero,
|
||||
title: title,
|
||||
subtitle: subtitle,
|
||||
onTap: () => onTap(!value),
|
||||
trailing: CustomSwitch(
|
||||
value: value,
|
||||
onChanged: onTap,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -1,73 +0,0 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class CustomTextField extends StatelessWidget {
|
||||
const CustomTextField({
|
||||
super.key,
|
||||
required this.inputController,
|
||||
required this.label,
|
||||
required this.hint,
|
||||
this.leadingIcon,
|
||||
required this.onChanged,
|
||||
});
|
||||
final TextEditingController inputController;
|
||||
final Widget label;
|
||||
final String hint;
|
||||
final Widget? leadingIcon;
|
||||
final Function(String)? onChanged;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(top: 4.0),
|
||||
child: TextField(
|
||||
controller: inputController,
|
||||
onChanged: onChanged,
|
||||
keyboardType: TextInputType.text,
|
||||
decoration: InputDecoration(
|
||||
icon: leadingIcon,
|
||||
label: label,
|
||||
filled: true,
|
||||
fillColor: Theme.of(context).colorScheme.secondaryContainer,
|
||||
hintText: hint,
|
||||
hintStyle: TextStyle(
|
||||
color: Theme.of(context).colorScheme.secondary,
|
||||
),
|
||||
floatingLabelStyle: MaterialStateTextStyle.resolveWith(
|
||||
(states) => states.contains(MaterialState.focused)
|
||||
? TextStyle(color: Theme.of(context).colorScheme.primary)
|
||||
: TextStyle(color: Theme.of(context).colorScheme.secondary),
|
||||
),
|
||||
contentPadding: const EdgeInsets.symmetric(
|
||||
vertical: 8.0,
|
||||
horizontal: 16.0,
|
||||
),
|
||||
border: OutlineInputBorder(
|
||||
borderSide: BorderSide(
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
),
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
),
|
||||
focusedBorder: OutlineInputBorder(
|
||||
borderSide: BorderSide(
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
width: 2.0,
|
||||
),
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
),
|
||||
errorBorder: OutlineInputBorder(
|
||||
borderSide: BorderSide(
|
||||
color: Theme.of(context).colorScheme.error,
|
||||
),
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
),
|
||||
enabledBorder: OutlineInputBorder(
|
||||
borderSide: BorderSide(
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
),
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -7,6 +7,7 @@ import 'package:revanced_manager/ui/widgets/settingsView/settings_auto_update_pa
|
||||
import 'package:revanced_manager/ui/widgets/settingsView/settings_enable_patches_selection.dart';
|
||||
import 'package:revanced_manager/ui/widgets/settingsView/settings_require_suggested_app_version.dart';
|
||||
import 'package:revanced_manager/ui/widgets/settingsView/settings_section.dart';
|
||||
import 'package:revanced_manager/ui/widgets/settingsView/settings_show_update_dialog.dart';
|
||||
import 'package:revanced_manager/ui/widgets/settingsView/settings_universal_patches.dart';
|
||||
import 'package:revanced_manager/ui/widgets/settingsView/settings_version_compatibility_check.dart';
|
||||
|
||||
@ -19,6 +20,7 @@ class SAdvancedSection extends StatelessWidget {
|
||||
title: 'settingsView.advancedSectionTitle',
|
||||
children: const <Widget>[
|
||||
SAutoUpdatePatches(),
|
||||
SShowUpdateDialog(),
|
||||
SEnablePatchesSelection(),
|
||||
SRequireSuggestedAppVersion(),
|
||||
SVersionCompatibilityCheck(),
|
||||
|
@ -1,6 +1,7 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_i18n/widgets/I18nText.dart';
|
||||
import 'package:revanced_manager/ui/views/settings/settings_viewmodel.dart';
|
||||
import 'package:revanced_manager/ui/widgets/shared/haptics/haptic_switch_list_tile.dart';
|
||||
|
||||
class SAutoUpdatePatches extends StatefulWidget {
|
||||
const SAutoUpdatePatches({super.key});
|
||||
@ -14,7 +15,7 @@ final _settingsViewModel = SettingsViewModel();
|
||||
class _SAutoUpdatePatchesState extends State<SAutoUpdatePatches> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SwitchListTile(
|
||||
return HapticSwitchListTile(
|
||||
contentPadding: const EdgeInsets.symmetric(horizontal: 20.0),
|
||||
title: I18nText(
|
||||
'settingsView.autoUpdatePatchesLabel',
|
||||
|
@ -1,6 +1,7 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_i18n/widgets/I18nText.dart';
|
||||
import 'package:revanced_manager/ui/views/settings/settings_viewmodel.dart';
|
||||
import 'package:revanced_manager/ui/widgets/shared/haptics/haptic_switch_list_tile.dart';
|
||||
|
||||
class SEnablePatchesSelection extends StatefulWidget {
|
||||
const SEnablePatchesSelection({super.key});
|
||||
@ -15,7 +16,7 @@ final _settingsViewModel = SettingsViewModel();
|
||||
class _SEnablePatchesSelectionState extends State<SEnablePatchesSelection> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SwitchListTile(
|
||||
return HapticSwitchListTile(
|
||||
contentPadding: const EdgeInsets.symmetric(horizontal: 20.0),
|
||||
title: I18nText(
|
||||
'settingsView.enablePatchesSelectionLabel',
|
||||
|
@ -1,6 +1,7 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_i18n/widgets/I18nText.dart';
|
||||
import 'package:revanced_manager/ui/views/settings/settings_viewmodel.dart';
|
||||
import 'package:revanced_manager/ui/widgets/shared/haptics/haptic_switch_list_tile.dart';
|
||||
|
||||
class SRequireSuggestedAppVersion extends StatefulWidget {
|
||||
const SRequireSuggestedAppVersion({super.key});
|
||||
@ -16,7 +17,7 @@ class _SRequireSuggestedAppVersionState
|
||||
extends State<SRequireSuggestedAppVersion> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SwitchListTile(
|
||||
return HapticSwitchListTile(
|
||||
contentPadding: const EdgeInsets.symmetric(horizontal: 20.0),
|
||||
title: I18nText(
|
||||
'settingsView.requireSuggestedAppVersionLabel',
|
||||
|
39
lib/ui/widgets/settingsView/settings_show_update_dialog.dart
Normal file
39
lib/ui/widgets/settingsView/settings_show_update_dialog.dart
Normal file
@ -0,0 +1,39 @@
|
||||
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/shared/haptics/haptic_switch_list_tile.dart';
|
||||
|
||||
class SShowUpdateDialog extends StatefulWidget {
|
||||
const SShowUpdateDialog({super.key});
|
||||
|
||||
@override
|
||||
State<SShowUpdateDialog> createState() => _SShowUpdateDialogState();
|
||||
}
|
||||
|
||||
final _settingsViewModel = SettingsViewModel();
|
||||
|
||||
class _SShowUpdateDialogState extends State<SShowUpdateDialog> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return HapticSwitchListTile(
|
||||
contentPadding: const EdgeInsets.symmetric(horizontal: 20.0),
|
||||
title: I18nText(
|
||||
'settingsView.showUpdateDialogLabel',
|
||||
child: const Text(
|
||||
'',
|
||||
style: TextStyle(
|
||||
fontSize: 20,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
),
|
||||
subtitle: I18nText('settingsView.showUpdateDialogHint'),
|
||||
value: _settingsViewModel.showUpdateDialog(),
|
||||
onChanged: (value) {
|
||||
setState(() {
|
||||
_settingsViewModel.setShowUpdateDialog(value);
|
||||
});
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
@ -3,6 +3,7 @@ import 'package:flutter_i18n/widgets/I18nText.dart';
|
||||
import 'package:revanced_manager/ui/views/patcher/patcher_viewmodel.dart';
|
||||
import 'package:revanced_manager/ui/views/patches_selector/patches_selector_viewmodel.dart';
|
||||
import 'package:revanced_manager/ui/views/settings/settings_viewmodel.dart';
|
||||
import 'package:revanced_manager/ui/widgets/shared/haptics/haptic_switch_list_tile.dart';
|
||||
|
||||
class SUniversalPatches extends StatefulWidget {
|
||||
const SUniversalPatches({super.key});
|
||||
@ -18,7 +19,7 @@ final _patcherViewModel = PatcherViewModel();
|
||||
class _SUniversalPatchesState extends State<SUniversalPatches> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SwitchListTile(
|
||||
return HapticSwitchListTile(
|
||||
contentPadding: const EdgeInsets.symmetric(horizontal: 20.0),
|
||||
title: I18nText(
|
||||
'settingsView.universalPatchesLabel',
|
||||
|
@ -3,6 +3,7 @@ import 'package:flutter_i18n/widgets/I18nText.dart';
|
||||
import 'package:revanced_manager/ui/views/patcher/patcher_viewmodel.dart';
|
||||
import 'package:revanced_manager/ui/views/patches_selector/patches_selector_viewmodel.dart';
|
||||
import 'package:revanced_manager/ui/views/settings/settings_viewmodel.dart';
|
||||
import 'package:revanced_manager/ui/widgets/shared/haptics/haptic_switch_list_tile.dart';
|
||||
import 'package:revanced_manager/utils/check_for_supported_patch.dart';
|
||||
|
||||
class SVersionCompatibilityCheck extends StatefulWidget {
|
||||
@ -21,7 +22,7 @@ class _SVersionCompatibilityCheckState
|
||||
extends State<SVersionCompatibilityCheck> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SwitchListTile(
|
||||
return HapticSwitchListTile(
|
||||
contentPadding: const EdgeInsets.symmetric(horizontal: 20.0),
|
||||
title: I18nText(
|
||||
'settingsView.versionCompatibilityCheckLabel',
|
||||
|
32
lib/ui/widgets/shared/haptics/haptic_checkbox.dart
Normal file
32
lib/ui/widgets/shared/haptics/haptic_checkbox.dart
Normal file
@ -0,0 +1,32 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
class HapticCheckbox extends StatelessWidget {
|
||||
const HapticCheckbox({
|
||||
super.key,
|
||||
required this.value,
|
||||
required this.onChanged,
|
||||
this.activeColor,
|
||||
this.checkColor,
|
||||
this.side,
|
||||
});
|
||||
final bool value;
|
||||
final Function(bool?)? onChanged;
|
||||
final Color? activeColor;
|
||||
final Color? checkColor;
|
||||
final BorderSide? side;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Checkbox(
|
||||
value: value,
|
||||
onChanged: (value) => {
|
||||
HapticFeedback.selectionClick(),
|
||||
if (onChanged != null) onChanged!(value),
|
||||
},
|
||||
activeColor: activeColor,
|
||||
checkColor: checkColor,
|
||||
side: side,
|
||||
);
|
||||
}
|
||||
}
|
32
lib/ui/widgets/shared/haptics/haptic_checkbox_list_tile.dart
Normal file
32
lib/ui/widgets/shared/haptics/haptic_checkbox_list_tile.dart
Normal file
@ -0,0 +1,32 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
class HapticCheckboxListTile extends StatelessWidget {
|
||||
const HapticCheckboxListTile({
|
||||
super.key,
|
||||
required this.value,
|
||||
required this.onChanged,
|
||||
this.title,
|
||||
this.subtitle,
|
||||
this.contentPadding,
|
||||
});
|
||||
final bool value;
|
||||
final Function(bool?)? onChanged;
|
||||
final Widget? title;
|
||||
final Widget? subtitle;
|
||||
final EdgeInsetsGeometry? contentPadding;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return CheckboxListTile(
|
||||
contentPadding: contentPadding ?? EdgeInsets.zero,
|
||||
title: title,
|
||||
subtitle: subtitle,
|
||||
value: value,
|
||||
onChanged: (value) => {
|
||||
HapticFeedback.lightImpact(),
|
||||
if (onChanged != null) onChanged!(value),
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
33
lib/ui/widgets/shared/haptics/haptic_custom_card.dart
Normal file
33
lib/ui/widgets/shared/haptics/haptic_custom_card.dart
Normal file
@ -0,0 +1,33 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:revanced_manager/ui/widgets/shared/custom_card.dart';
|
||||
|
||||
class HapticCustomCard extends StatelessWidget {
|
||||
const HapticCustomCard({
|
||||
super.key,
|
||||
this.isFilled = true,
|
||||
required this.child,
|
||||
this.onTap,
|
||||
this.padding,
|
||||
this.backgroundColor,
|
||||
});
|
||||
final bool isFilled;
|
||||
final Widget child;
|
||||
final Function()? onTap;
|
||||
final EdgeInsetsGeometry? padding;
|
||||
final Color? backgroundColor;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return CustomCard(
|
||||
isFilled: isFilled,
|
||||
onTap: () => {
|
||||
HapticFeedback.selectionClick(),
|
||||
if (onTap != null) onTap!(),
|
||||
},
|
||||
padding: padding,
|
||||
backgroundColor: backgroundColor,
|
||||
child: child,
|
||||
);
|
||||
}
|
||||
}
|
@ -0,0 +1,29 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
class HapticFloatingActionButtonExtended extends StatelessWidget {
|
||||
const HapticFloatingActionButtonExtended({
|
||||
super.key,
|
||||
required this.onPressed,
|
||||
required this.label,
|
||||
this.icon,
|
||||
this.elevation,
|
||||
});
|
||||
final Function()? onPressed;
|
||||
final Widget label;
|
||||
final Widget? icon;
|
||||
final double? elevation;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return FloatingActionButton.extended(
|
||||
onPressed: () => {
|
||||
HapticFeedback.lightImpact(),
|
||||
if (onPressed != null) onPressed!(),
|
||||
},
|
||||
label: label,
|
||||
icon: icon,
|
||||
elevation: elevation,
|
||||
);
|
||||
}
|
||||
}
|
38
lib/ui/widgets/shared/haptics/haptic_radio_list_tile.dart
Normal file
38
lib/ui/widgets/shared/haptics/haptic_radio_list_tile.dart
Normal file
@ -0,0 +1,38 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
class HapticRadioListTile extends StatelessWidget {
|
||||
const HapticRadioListTile({
|
||||
super.key,
|
||||
required this.title,
|
||||
required this.value,
|
||||
required this.groupValue,
|
||||
this.subtitle,
|
||||
this.onChanged,
|
||||
this.contentPadding,
|
||||
});
|
||||
final Widget title;
|
||||
final Widget? subtitle;
|
||||
final int value;
|
||||
final Function(int?)? onChanged;
|
||||
final int groupValue;
|
||||
final EdgeInsetsGeometry? contentPadding;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return RadioListTile(
|
||||
contentPadding: contentPadding ?? EdgeInsets.zero,
|
||||
title: title,
|
||||
subtitle: subtitle,
|
||||
value: value,
|
||||
groupValue: groupValue,
|
||||
onChanged: (val) => {
|
||||
if (val == value) {
|
||||
HapticFeedback.lightImpact(),
|
||||
},
|
||||
|
||||
if (onChanged != null) onChanged!(val),
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
36
lib/ui/widgets/shared/haptics/haptic_switch_list_tile.dart
Normal file
36
lib/ui/widgets/shared/haptics/haptic_switch_list_tile.dart
Normal file
@ -0,0 +1,36 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
class HapticSwitchListTile extends StatelessWidget {
|
||||
const HapticSwitchListTile({
|
||||
super.key,
|
||||
required this.value,
|
||||
required this.onChanged,
|
||||
this.title,
|
||||
this.subtitle,
|
||||
this.contentPadding,
|
||||
});
|
||||
final bool value;
|
||||
final Function(bool)? onChanged;
|
||||
final Widget? title;
|
||||
final Widget? subtitle;
|
||||
final EdgeInsetsGeometry? contentPadding;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SwitchListTile(
|
||||
contentPadding: contentPadding ?? EdgeInsets.zero,
|
||||
title: title,
|
||||
subtitle: subtitle,
|
||||
value: value,
|
||||
onChanged: (value) => {
|
||||
if (value) {
|
||||
HapticFeedback.mediumImpact(),
|
||||
} else {
|
||||
HapticFeedback.lightImpact(),
|
||||
},
|
||||
if (onChanged != null) onChanged!(value),
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
6861
package-lock.json
generated
Normal file
6861
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
11
package.json
Normal file
11
package.json
Normal file
@ -0,0 +1,11 @@
|
||||
{
|
||||
"devDependencies": {
|
||||
"@droidsolutions-oss/semantic-release-update-file": "^1.3.2",
|
||||
"@saithodev/semantic-release-backmerge": "^4.0.1",
|
||||
"@semantic-release/changelog": "^6.0.3",
|
||||
"@semantic-release/exec": "^6.0.3",
|
||||
"@semantic-release/git": "^10.0.1",
|
||||
"conventional-changelog-conventionalcommits": "^7.0.2",
|
||||
"semantic-release": "^22.0.12"
|
||||
}
|
||||
}
|
37
pubspec.yaml
37
pubspec.yaml
@ -4,7 +4,7 @@ homepage: https://github.com/ReVanced/revanced-manager
|
||||
|
||||
publish_to: 'none'
|
||||
|
||||
version: 1.18.0+101800000
|
||||
version: 1.19.0-dev.5+101900005
|
||||
|
||||
environment:
|
||||
sdk: '>=3.0.0 <4.0.0'
|
||||
@ -12,16 +12,17 @@ environment:
|
||||
dependencies:
|
||||
animations: ^2.0.7
|
||||
collection: ^1.17.0
|
||||
connectivity_plus: ^5.0.1
|
||||
device_apps:
|
||||
git: # switch back to ponces fork once https://github.com/ponces/flutter_plugin_device_apps/pull/1 is merged
|
||||
url: https://github.com/BenjaminHalko/flutter_plugin_device_apps
|
||||
ref: 0efbeba41657158a66bbc92c55d1226df56d0f1b # Branch: revanced-manager
|
||||
device_info_plus: ^9.1.0
|
||||
dynamic_color: ^1.6.3
|
||||
dio: ^5.0.0
|
||||
dio_cache_interceptor: ^3.4.0
|
||||
dynamic_color: ^1.6.3
|
||||
dynamic_themes: ^1.1.0
|
||||
expandable: ^5.0.1
|
||||
flex_color_scheme: ^7.0.1
|
||||
flutter:
|
||||
sdk: flutter
|
||||
flutter_background:
|
||||
@ -29,16 +30,15 @@ dependencies:
|
||||
url: https://github.com/BenjaminHalko/flutter_background
|
||||
ref: 560d21c4148b53933313573e7eafca0b0eb9aadf # Branch: specify-namespace
|
||||
flutter_cache_manager: ^3.3.0
|
||||
flutter_file_dialog: ^3.0.2
|
||||
flutter_i18n: ^0.34.0
|
||||
flutter_local_notifications: ^16.1.0
|
||||
flutter_localizations:
|
||||
sdk: flutter
|
||||
flutter_svg: ^2.0.4
|
||||
flutter_markdown: ^0.6.14
|
||||
fluttertoast: ^8.2.4
|
||||
font_awesome_flutter: ^10.4.0
|
||||
get_it: ^7.6.4
|
||||
google_fonts: ^6.1.0
|
||||
http: ^1.1.0
|
||||
injectable: ^2.1.1
|
||||
intl: ^0.18.0
|
||||
json_annotation: ^4.8.0
|
||||
@ -49,42 +49,31 @@ dependencies:
|
||||
package_info_plus: ^5.0.1
|
||||
path_provider: ^2.0.14
|
||||
permission_handler: ^11.0.1
|
||||
pull_to_refresh: ^2.0.0
|
||||
root:
|
||||
git:
|
||||
url: https://github.com/validcube/root
|
||||
ref: 68e5678a535a2a3344828a14a39017fa74b9098c # Branch: libsu-521
|
||||
screenshot_callback:
|
||||
git: # remove once https://github.com/flutter-moum/flutter_screenshot_callback/pull/81 is merged
|
||||
url: https://github.com/BenjaminHalko/flutter_screenshot_callback
|
||||
ref: 1a1616ac91e16cd1f3dd170a81febf27ffce3587 # Branch: master
|
||||
share_plus: ^7.2.1
|
||||
shared_preferences: ^2.1.0
|
||||
skeletons: ^0.0.3
|
||||
stacked: ^3.2.0
|
||||
stacked_generator: ^1.1.0
|
||||
stacked_services: ^1.0.0
|
||||
synchronized: ^3.1.0
|
||||
timeago: ^3.3.0
|
||||
timezone: ^0.9.0
|
||||
url_launcher: ^6.1.10
|
||||
flutter_dotenv: ^5.0.2
|
||||
flutter_markdown: ^0.6.14
|
||||
dio_cache_interceptor: ^3.4.0
|
||||
screenshot_callback:
|
||||
git: # remove once https://github.com/flutter-moum/flutter_screenshot_callback/pull/81 is merged
|
||||
url: https://github.com/BenjaminHalko/flutter_screenshot_callback
|
||||
ref: 1a1616ac91e16cd1f3dd170a81febf27ffce3587 # Branch: master
|
||||
synchronized: ^3.1.0
|
||||
connectivity_plus: ^5.0.1
|
||||
flutter_file_dialog: ^3.0.2
|
||||
wakelock_plus: ^1.1.3
|
||||
share_plus: ^7.2.1
|
||||
|
||||
dev_dependencies:
|
||||
json_serializable: ^6.6.1
|
||||
build_runner: any
|
||||
flutter_launcher_icons: ^0.13.0
|
||||
flutter_lints: ^3.0.1
|
||||
flutter_test:
|
||||
sdk: flutter
|
||||
injectable_generator: ^2.1.5
|
||||
|
||||
|
||||
json_serializable: ^6.6.1
|
||||
|
||||
flutter:
|
||||
uses-material-design: true
|
||||
|
Reference in New Issue
Block a user