Compare commits

..

1 Commits

Author SHA1 Message Date
c0446eac35 build: bump version to v1.9.5 2023-09-04 04:15:55 +03:00
19 changed files with 203 additions and 365 deletions

View File

@ -12,7 +12,7 @@ body:
- type: textarea - type: textarea
attributes: attributes:
label: Bug description label: Bug description
description: | description:
- Describe your bug in detail - Describe your bug in detail
- Add steps to reproduce the bug if possible (Step 1. Download some files. Step 2. ...) - Add steps to reproduce the bug if possible (Step 1. Download some files. Step 2. ...)
- Add images and videos if possible - Add images and videos if possible

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

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

View File

@ -14,7 +14,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v4 uses: actions/checkout@v3
with: with:
# Make sure the release step uses its own credentials: # Make sure the release step uses its own credentials:
# https://github.com/cycjimmy/semantic-release-action#private-packages # https://github.com/cycjimmy/semantic-release-action#private-packages

View File

@ -9,7 +9,7 @@ jobs:
release: release:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v3
- name: Set env - name: Set env
run: echo "RELEASE_VERSION=${GITHUB_REF#refs/*/}" >> $GITHUB_ENV run: echo "RELEASE_VERSION=${GITHUB_REF#refs/*/}" >> $GITHUB_ENV
- name: Set up JDK 11 - name: Set up JDK 11

View File

@ -3,33 +3,29 @@
The official ReVanced Manager based on Flutter. The official ReVanced Manager based on Flutter.
## 🔽 Download ## 🔽 Download
To download latest Manager, go [here](https://github.com/revanced/revanced-manager/releases/latest) and install the provided APK file.
You can obtain ReVanced Manager by downloading it from either [revanced.app/download](https://revanced.app/download) or [GitHub Releases](https://github.com/ReVanced/revanced-manager/releases)
## 📝 Prerequisites ## 📝 Prerequisites
1. Android 8 or higher 1. Android 8 or higher
2. Incompatible with certain ARMv7 devices 2. Does not work on some armv7 devices
## 📃 Documentation
The documentation can be found [here](https://github.com/revanced/revanced-manager/tree/main/docs).
## 🔴 Issues ## 🔴 Issues
For suggestions and bug reports, open an issue [here](https://github.com/revanced/revanced-manager/issues/new/choose). For suggestions and bug reports, open an issue [here](https://github.com/revanced/revanced-manager/issues/new/choose).
## 🌐 Translation ## 💭 Discussion
If you wish to discuss the Manager, a thread has been made under the [#development](https://discord.com/channels/952946952348270622/1002922226443632761) channel in the Discord server, please note that this thread may be temporary and may be removed in the future.
## 🌐 Translation
[![Crowdin](https://badges.crowdin.net/revanced/localized.svg)](https://crowdin.com/project/revanced) [![Crowdin](https://badges.crowdin.net/revanced/localized.svg)](https://crowdin.com/project/revanced)
We're accepting translations on [Crowdin](https://translate.revanced.app). If you wish to translate ReVanced Manager, we're accepting translations on [Crowdin](https://translate.revanced.app)
## 🛠️ Building Manager from source ## 🛠️ Building Manager from source
1. Setup flutter environment for your [platform](https://docs.flutter.dev/get-started/install) 1. Setup flutter environment for your [platform](https://docs.flutter.dev/get-started/install)
2. Clone the repository locally 2. Clone the repository locally
3. Add your GitHub token in gradle.properties like [this](/docs/4_building.md) 3. Add your github token in gradle.properties like [this](/docs/4_building.md)
4. Open the project in terminal 4. Open the project in terminal
5. Run `flutter pub get` in terminal 5. Run `flutter pub get` in terminal
6. Then `flutter packages pub run build_runner build --delete-conflicting-outputs` (Must be done on each git pull) 6. Then `flutter packages pub run build_runner build --delete-conflicting-outputs` (Must be done on each git pull)
7. To build release APK run `flutter build apk` 7. To build release apk run `flutter build apk`

View File

@ -25,7 +25,8 @@
android:icon="@mipmap/ic_launcher" android:icon="@mipmap/ic_launcher"
android:largeHeap="true" android:largeHeap="true"
android:requestLegacyExternalStorage="true" android:requestLegacyExternalStorage="true"
android:extractNativeLibs="true"> android:extractNativeLibs="true"
android:enableOnBackInvokedCallback="true">
<activity <activity
android:name=".MainActivity" android:name=".MainActivity"
android:exported="true" android:exported="true"

View File

@ -11,9 +11,6 @@ import app.revanced.patcher.PatchBundleLoader
import app.revanced.patcher.Patcher import app.revanced.patcher.Patcher
import app.revanced.patcher.PatcherOptions import app.revanced.patcher.PatcherOptions
import app.revanced.patcher.extensions.PatchExtensions.compatiblePackages import app.revanced.patcher.extensions.PatchExtensions.compatiblePackages
import app.revanced.patcher.extensions.PatchExtensions.dependencies
import app.revanced.patcher.extensions.PatchExtensions.description
import app.revanced.patcher.extensions.PatchExtensions.include
import app.revanced.patcher.extensions.PatchExtensions.patchName import app.revanced.patcher.extensions.PatchExtensions.patchName
import app.revanced.patcher.patch.PatchResult import app.revanced.patcher.patch.PatchResult
import io.flutter.embedding.android.FlutterActivity import io.flutter.embedding.android.FlutterActivity
@ -92,32 +89,6 @@ class MainActivity : FlutterActivity() {
stopResult = result stopResult = result
} }
"getPatches" -> {
val patchBundleFilePath = call.argument<String>("patchBundleFilePath")
val cacheDirPath = call.argument<String>("cacheDirPath")
if (patchBundleFilePath != null) {
val patches = PatchBundleLoader.Dex(
File(patchBundleFilePath),
optimizedDexDirectory = File(cacheDirPath)
).map { patch ->
val map = HashMap<String, Any>()
map["\"name\""] = "\"${patch.patchName.replace("\"","\\\"")}\""
map["\"description\""] = "\"${patch.description?.replace("\"","\\\"")}\""
map["\"excluded\""] = !patch.include
map["\"dependencies\""] = patch.dependencies?.map { "\"${it.java.patchName}\"" } ?: emptyList<Any>()
map["\"compatiblePackages\""] = patch.compatiblePackages?.map {
val map2 = HashMap<String, Any>()
map2["\"name\""] = "\"${it.name}\""
map2["\"versions\""] = it.versions.map { version -> "\"${version}\"" }
map2
} ?: emptyList<Any>()
map
}
result.success(patches)
} else result.notImplemented()
}
else -> result.notImplemented() else -> result.notImplemented()
} }
} }

View File

@ -178,10 +178,8 @@
"exportSectionTitle": "Import & export", "exportSectionTitle": "Import & export",
"logsSectionTitle": "Logs", "logsSectionTitle": "Logs",
"themeModeLabel": "App theme", "darkThemeLabel": "Dark mode",
"systemThemeLabel": "System", "darkThemeHint": "Welcome to the dark side",
"lightThemeLabel": "Light",
"darkThemeLabel": "Dark",
"dynamicThemeLabel": "Material You", "dynamicThemeLabel": "Material You",
"dynamicThemeHint": "Enjoy an experience closer to your device", "dynamicThemeHint": "Enjoy an experience closer to your device",

View File

@ -222,8 +222,10 @@ class GithubAPI {
final String downloadUrl = asset['browser_download_url']; final String downloadUrl = asset['browser_download_url'];
if (extension == '.apk') { if (extension == '.apk') {
_managerAPI.setIntegrationsDownloadURL(downloadUrl); _managerAPI.setIntegrationsDownloadURL(downloadUrl);
} else if (extension == '.json') {
_managerAPI.setPatchesDownloadURL(downloadUrl, false);
} else { } else {
_managerAPI.setPatchesDownloadURL(downloadUrl); _managerAPI.setPatchesDownloadURL(downloadUrl, true);
} }
return await DefaultCacheManager().getSingleFile( return await DefaultCacheManager().getSingleFile(
downloadUrl, downloadUrl,

View File

@ -11,7 +11,6 @@ import 'package:revanced_manager/app/app.locator.dart';
import 'package:revanced_manager/models/patch.dart'; import 'package:revanced_manager/models/patch.dart';
import 'package:revanced_manager/models/patched_application.dart'; import 'package:revanced_manager/models/patched_application.dart';
import 'package:revanced_manager/services/github_api.dart'; 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/revanced_api.dart';
import 'package:revanced_manager/services/root_api.dart'; import 'package:revanced_manager/services/root_api.dart';
import 'package:revanced_manager/ui/widgets/shared/custom_material_button.dart'; import 'package:revanced_manager/ui/widgets/shared/custom_material_button.dart';
@ -27,7 +26,6 @@ class ManagerAPI {
final String patcherRepo = 'revanced-patcher'; final String patcherRepo = 'revanced-patcher';
final String cliRepo = 'revanced-cli'; final String cliRepo = 'revanced-cli';
late SharedPreferences _prefs; late SharedPreferences _prefs;
List<Patch> patches = [];
bool isRooted = false; bool isRooted = false;
String storedPatchesFile = '/selected-patches.json'; String storedPatchesFile = '/selected-patches.json';
String keystoreFile = String keystoreFile =
@ -43,11 +41,11 @@ class ManagerAPI {
String? patchesVersion = ''; String? patchesVersion = '';
String? integrationsVersion = ''; String? integrationsVersion = '';
bool isDefaultPatchesRepo() { bool isDefaultPatchesRepo() {
return getPatchesRepo().toLowerCase() == 'revanced/revanced-patches'; return getPatchesRepo() == 'revanced/revanced-patches';
} }
bool isDefaultIntegrationsRepo() { bool isDefaultIntegrationsRepo() {
return getIntegrationsRepo().toLowerCase() == 'revanced/revanced-integrations'; return getIntegrationsRepo() == 'revanced/revanced-integrations';
} }
Future<void> initialize() async { Future<void> initialize() async {
@ -81,12 +79,12 @@ class ManagerAPI {
await _prefs.setString('repoUrl', url); await _prefs.setString('repoUrl', url);
} }
String getPatchesDownloadURL() { String getPatchesDownloadURL(bool bundle) {
return _prefs.getString('patchesDownloadURL') ?? ''; return _prefs.getString('patchesDownloadURL-$bundle') ?? '';
} }
Future<void> setPatchesDownloadURL(String value) async { Future<void> setPatchesDownloadURL(String value, bool bundle) async {
await _prefs.setString('patchesDownloadURL', value); await _prefs.setString('patchesDownloadURL-$bundle', value);
} }
String getPatchesRepo() { String getPatchesRepo() {
@ -199,12 +197,12 @@ class ManagerAPI {
await _prefs.setBool('useDynamicTheme', value); await _prefs.setBool('useDynamicTheme', value);
} }
int getThemeMode() { bool getUseDarkTheme() {
return _prefs.getInt('themeMode') ?? 2; return _prefs.getBool('useDarkTheme') ?? false;
} }
Future<void> setThemeMode(int value) async { Future<void> setUseDarkTheme(bool value) async {
await _prefs.setInt('themeMode', value); await _prefs.setBool('useDarkTheme', value);
} }
bool areUniversalPatchesEnabled() { bool areUniversalPatchesEnabled() {
@ -302,46 +300,28 @@ class ManagerAPI {
} }
Future<List<Patch>> getPatches() async { Future<List<Patch>> getPatches() async {
if (patches.isNotEmpty) { try {
return patches; final String repoName = getPatchesRepo();
} final String currentVersion = await getCurrentPatchesVersion();
final File? patchBundleFile = await downloadPatches(); final String url = getPatchesDownloadURL(false);
final Directory appCache = await getTemporaryDirectory(); return await _githubAPI.getPatches(
Directory('${appCache.path}/cache').createSync(); repoName,
final Directory workDir = currentVersion,
Directory('${appCache.path}/cache').createTempSync('tmp-'); url,
final Directory cacheDir = Directory('${workDir.path}/cache'); );
cacheDir.createSync(); } on Exception catch (e) {
if (kDebugMode) {
if (patchBundleFile != null) { print(e);
try {
final patchesObject = await PatcherAPI.patcherChannel.invokeMethod(
'getPatches',
{
'patchBundleFilePath': patchBundleFile.path,
'cacheDirPath': cacheDir.path,
},
);
final List<Map<String, dynamic>> patchesMap = [];
patchesObject.forEach((patch) {
patchesMap.add(jsonDecode('$patch'));
});
patches = patchesMap.map((patch) => Patch.fromJson(patch)).toList();
return patches;
} on Exception catch (e) {
if (kDebugMode) {
print(e);
}
} }
return [];
} }
return List.empty();
} }
Future<File?> downloadPatches() async { Future<File?> downloadPatches() async {
try { try {
final String repoName = getPatchesRepo(); final String repoName = getPatchesRepo();
final String currentVersion = await getCurrentPatchesVersion(); final String currentVersion = await getCurrentPatchesVersion();
final String url = getPatchesDownloadURL(); final String url = getPatchesDownloadURL(true);
return await _githubAPI.getPatchesReleaseFile( return await _githubAPI.getPatchesReleaseFile(
'.jar', '.jar',
repoName, repoName,
@ -467,7 +447,8 @@ class ManagerAPI {
Future<void> setCurrentPatchesVersion(String version) async { Future<void> setCurrentPatchesVersion(String version) async {
await _prefs.setString('patchesVersion', version); await _prefs.setString('patchesVersion', version);
await setPatchesDownloadURL(''); await setPatchesDownloadURL('', false);
await setPatchesDownloadURL('', true);
await downloadPatches(); await downloadPatches();
} }

View File

@ -25,13 +25,12 @@ class PatcherAPI {
late Directory _tmpDir; late Directory _tmpDir;
late File _keyStoreFile; late File _keyStoreFile;
List<Patch> _patches = []; List<Patch> _patches = [];
List<Patch> _universalPatches = [];
List<String> _compatiblePackages = [];
Map filteredPatches = <String, List<Patch>>{}; Map filteredPatches = <String, List<Patch>>{};
File? _outFile; File? _outFile;
Future<void> initialize() async { Future<void> initialize() async {
await _loadPatches(); await _loadPatches();
await _managerAPI.downloadPatches();
await _managerAPI.downloadIntegrations(); await _managerAPI.downloadIntegrations();
final Directory appCache = await getTemporaryDirectory(); final Directory appCache = await getTemporaryDirectory();
_dataDir = await getExternalStorageDirectory() ?? appCache; _dataDir = await getExternalStorageDirectory() ?? appCache;
@ -46,24 +45,6 @@ class PatcherAPI {
} }
} }
List<String> getCompatiblePackages() {
final List<String> compatiblePackages = [];
for (final Patch patch in _patches) {
for (final Package package in patch.compatiblePackages) {
if (!compatiblePackages.contains(package.name)) {
compatiblePackages.add(package.name);
}
}
}
return compatiblePackages;
}
List<Patch> getUniversalPatches() {
return _patches
.where((patch) => patch.compatiblePackages.isEmpty)
.toList();
}
Future<void> _loadPatches() async { Future<void> _loadPatches() async {
try { try {
if (_patches.isEmpty) { if (_patches.isEmpty) {
@ -75,9 +56,6 @@ class PatcherAPI {
} }
_patches = List.empty(); _patches = List.empty();
} }
_compatiblePackages = getCompatiblePackages();
_universalPatches = getUniversalPatches();
} }
Future<List<ApplicationWithIcon>> getFilteredInstalledApps( Future<List<ApplicationWithIcon>> getFilteredInstalledApps(
@ -85,32 +63,41 @@ class PatcherAPI {
) async { ) async {
final List<ApplicationWithIcon> filteredApps = []; final List<ApplicationWithIcon> filteredApps = [];
final bool allAppsIncluded = final bool allAppsIncluded =
_universalPatches.isNotEmpty && _patches.any((patch) => patch.compatiblePackages.isEmpty) &&
showUniversalPatches; showUniversalPatches;
if (allAppsIncluded) { if (allAppsIncluded) {
final appList = await DeviceApps.getInstalledApplications( final allPackages = await DeviceApps.getInstalledApplications(
includeAppIcons: true, includeAppIcons: true,
onlyAppsWithLaunchIntent: true, onlyAppsWithLaunchIntent: true,
); );
for (final pkg in allPackages) {
for(final app in appList) { if (!filteredApps.any((app) => app.packageName == pkg.packageName)) {
filteredApps.add(app as ApplicationWithIcon); final appInfo = await DeviceApps.getApp(
} pkg.packageName,
}
for (final packageName in _compatiblePackages) {
try {
if (!filteredApps.any((app) => app.packageName == packageName)) {
final ApplicationWithIcon? app = await DeviceApps.getApp(
packageName,
true, true,
) as ApplicationWithIcon?; ) as ApplicationWithIcon?;
if (app != null) { if (appInfo != null) {
filteredApps.add(app); filteredApps.add(appInfo);
} }
} }
} on Exception catch (e) { }
if (kDebugMode) { }
print(e); for (final Patch patch in _patches) {
for (final Package package in patch.compatiblePackages) {
try {
if (!filteredApps.any((app) => app.packageName == package.name)) {
final ApplicationWithIcon? app = await DeviceApps.getApp(
package.name,
true,
) as ApplicationWithIcon?;
if (app != null) {
filteredApps.add(app);
}
}
} on Exception catch (e) {
if (kDebugMode) {
print(e);
}
} }
} }
} }
@ -118,10 +105,6 @@ class PatcherAPI {
} }
List<Patch> getFilteredPatches(String packageName) { List<Patch> getFilteredPatches(String packageName) {
if (!_compatiblePackages.contains(packageName)) {
return _universalPatches;
}
final List<Patch> patches = _patches final List<Patch> patches = _patches
.where( .where(
(patch) => (patch) =>

View File

@ -1,16 +1,12 @@
import 'dart:ui';
import 'package:dynamic_color/dynamic_color.dart'; import 'package:dynamic_color/dynamic_color.dart';
import 'package:dynamic_themes/dynamic_themes.dart'; import 'package:dynamic_themes/dynamic_themes.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:google_fonts/google_fonts.dart'; import 'package:google_fonts/google_fonts.dart';
import 'package:revanced_manager/app/app.locator.dart';
import 'package:revanced_manager/app/app.router.dart'; import 'package:revanced_manager/app/app.router.dart';
import 'package:revanced_manager/services/manager_api.dart';
import 'package:revanced_manager/theme.dart'; import 'package:revanced_manager/theme.dart';
import 'package:stacked_services/stacked_services.dart'; import 'package:stacked_services/stacked_services.dart';
class DynamicThemeBuilder extends StatefulWidget { class DynamicThemeBuilder extends StatelessWidget {
const DynamicThemeBuilder({ const DynamicThemeBuilder({
Key? key, Key? key,
required this.title, required this.title,
@ -21,35 +17,6 @@ class DynamicThemeBuilder extends StatefulWidget {
final Widget home; final Widget home;
final Iterable<LocalizationsDelegate> localizationsDelegates; final Iterable<LocalizationsDelegate> localizationsDelegates;
@override
State<DynamicThemeBuilder> createState() => _DynamicThemeBuilderState();
}
class _DynamicThemeBuilderState extends State<DynamicThemeBuilder> with WidgetsBindingObserver {
Brightness brightness = PlatformDispatcher.instance.platformBrightness;
final ManagerAPI _managerAPI = locator<ManagerAPI>();
@override
void initState() {
super.initState();
WidgetsBinding.instance.addObserver(this);
}
@override
void didChangePlatformBrightness() {
setState(() {
brightness = PlatformDispatcher.instance.platformBrightness;
});
if (_managerAPI.getThemeMode() < 2) {
SystemChrome.setSystemUIOverlayStyle(
SystemUiOverlayStyle(
systemNavigationBarIconBrightness:
brightness == Brightness.light ? Brightness.dark : Brightness.light,
),
);
}
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return DynamicColorBuilder( return DynamicColorBuilder(
@ -83,32 +50,24 @@ class _DynamicThemeBuilderState extends State<DynamicThemeBuilder> with WidgetsB
return DynamicTheme( return DynamicTheme(
themeCollection: ThemeCollection( themeCollection: ThemeCollection(
themes: { themes: {
0: brightness == Brightness.light ? lightCustomTheme : darkCustomTheme, 0: lightCustomTheme,
1: brightness == Brightness.light ? lightDynamicTheme : darkDynamicTheme, 1: darkCustomTheme,
2: lightCustomTheme, 2: lightDynamicTheme,
3: lightDynamicTheme, 3: darkDynamicTheme,
4: darkCustomTheme,
5: darkDynamicTheme,
}, },
fallbackTheme: PlatformDispatcher.instance.platformBrightness == Brightness.light ? lightCustomTheme : darkCustomTheme, fallbackTheme: lightCustomTheme,
), ),
builder: (context, theme) => MaterialApp( builder: (context, theme) => MaterialApp(
debugShowCheckedModeBanner: false, debugShowCheckedModeBanner: false,
title: widget.title, title: title,
navigatorKey: StackedService.navigatorKey, navigatorKey: StackedService.navigatorKey,
onGenerateRoute: StackedRouter().onGenerateRoute, onGenerateRoute: StackedRouter().onGenerateRoute,
theme: theme, theme: theme,
home: widget.home, home: home,
localizationsDelegates: widget.localizationsDelegates, localizationsDelegates: localizationsDelegates,
), ),
); );
}, },
); );
} }
@override
void dispose() {
WidgetsBinding.instance.removeObserver(this);
super.dispose();
}
} }

View File

@ -54,7 +54,7 @@ class _AppSelectorViewState extends State<AppSelectorView> {
onPressed: () => Navigator.of(context).pop(), onPressed: () => Navigator.of(context).pop(),
), ),
bottom: PreferredSize( bottom: PreferredSize(
preferredSize: const Size.fromHeight(66.0), preferredSize: const Size.fromHeight(64.0),
child: Padding( child: Padding(
padding: const EdgeInsets.symmetric( padding: const EdgeInsets.symmetric(
vertical: 8.0, vertical: 8.0,

View File

@ -182,54 +182,52 @@ class InstallerViewModel extends BaseViewModel {
backgroundColor: Theme.of(context).colorScheme.secondaryContainer, backgroundColor: Theme.of(context).colorScheme.secondaryContainer,
icon: const Icon(Icons.file_download_outlined), icon: const Icon(Icons.file_download_outlined),
contentPadding: const EdgeInsets.symmetric(vertical: 16), contentPadding: const EdgeInsets.symmetric(vertical: 16),
content: SingleChildScrollView( content: ValueListenableBuilder(
child: ValueListenableBuilder( valueListenable: installType,
valueListenable: installType, builder: (context, value, child) {
builder: (context, value, child) { return Column(
return Column( mainAxisSize: MainAxisSize.min,
mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start, children: [
children: [ Padding(
Padding( padding: const EdgeInsets.symmetric(
padding: const EdgeInsets.symmetric( horizontal: 20,
horizontal: 20, vertical: 10,
vertical: 10, ),
), child: I18nText(
child: I18nText( 'installerView.installTypeDescription',
'installerView.installTypeDescription', child: Text(
child: Text( '',
'', style: TextStyle(
style: TextStyle( fontSize: 16,
fontSize: 16, fontWeight: FontWeight.w500,
fontWeight: FontWeight.w500, color: Theme.of(context).colorScheme.secondary,
color: Theme.of(context).colorScheme.secondary,
),
), ),
), ),
), ),
RadioListTile( ),
title: I18nText('installerView.installNonRootType'), RadioListTile(
subtitle: I18nText('installerView.installRecommendedType'), title: I18nText('installerView.installNonRootType'),
contentPadding: const EdgeInsets.symmetric(horizontal: 16), subtitle: I18nText('installerView.installRecommendedType'),
value: 0, contentPadding: const EdgeInsets.symmetric(horizontal: 10),
groupValue: value, value: 0,
onChanged: (selected) { groupValue: value,
installType.value = selected!; onChanged: (selected) {
}, installType.value = selected!;
), },
RadioListTile( ),
title: I18nText('installerView.installRootType'), RadioListTile(
contentPadding: const EdgeInsets.symmetric(horizontal: 16), title: I18nText('installerView.installRootType'),
value: 1, contentPadding: const EdgeInsets.symmetric(horizontal: 10),
groupValue: value, value: 1,
onChanged: (selected) { groupValue: value,
installType.value = selected!; onChanged: (selected) {
}, installType.value = selected!;
), },
], ),
); ],
}, );
), },
), ),
actions: [ actions: [
CustomMaterialButton( CustomMaterialButton(

View File

@ -1,5 +1,4 @@
// ignore_for_file: use_build_context_synchronously // ignore_for_file: use_build_context_synchronously
import 'package:device_info_plus/device_info_plus.dart';
import 'package:dynamic_themes/dynamic_themes.dart'; import 'package:dynamic_themes/dynamic_themes.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
@ -31,23 +30,12 @@ class NavigationViewModel extends IndexTrackingViewModel {
); );
} }
final dynamicTheme = DynamicTheme.of(context)!; if (prefs.getBool('useDarkTheme') == null) {
if (prefs.getInt('themeMode') == null) { final bool isDark =
await prefs.setInt('themeMode', 0); MediaQuery.platformBrightnessOf(context) != Brightness.light;
await dynamicTheme.setTheme(0); await prefs.setBool('useDarkTheme', isDark);
await DynamicTheme.of(context)!.setTheme(isDark ? 1 : 0);
} }
// Force disable Material You on Android 11 and below
if (dynamicTheme.themeId.isOdd) {
const int ANDROID_12_SDK_VERSION = 31;
final AndroidDeviceInfo info = await DeviceInfoPlugin().androidInfo;
if (info.version.sdkInt < ANDROID_12_SDK_VERSION) {
await prefs.setInt('themeMode', 0);
await prefs.setBool('useDynamicTheme', false);
await dynamicTheme.setTheme(0);
}
}
SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge); SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge);
SystemChrome.setSystemUIOverlayStyle( SystemChrome.setSystemUIOverlayStyle(
SystemUiOverlayStyle( SystemUiOverlayStyle(

View File

@ -81,7 +81,7 @@ class _PatchesSelectorViewState extends State<PatchesSelectorView> {
child: Container( child: Container(
margin: const EdgeInsets.only(top: 12, bottom: 12), margin: const EdgeInsets.only(top: 12, bottom: 12),
padding: padding:
const EdgeInsets.symmetric(horizontal: 6, vertical: 6), const EdgeInsets.symmetric(horizontal: 6, vertical: 6),
decoration: BoxDecoration( decoration: BoxDecoration(
color: Theme.of(context) color: Theme.of(context)
.colorScheme .colorScheme
@ -99,7 +99,7 @@ class _PatchesSelectorViewState extends State<PatchesSelectorView> {
), ),
CustomPopupMenu( CustomPopupMenu(
onSelected: (value) => onSelected: (value) =>
{model.onMenuSelection(value, context)}, {model.onMenuSelection(value, context)},
children: { children: {
0: I18nText( 0: I18nText(
'patchesSelectorView.loadPatchesSelection', 'patchesSelectorView.loadPatchesSelection',
@ -114,7 +114,7 @@ class _PatchesSelectorViewState extends State<PatchesSelectorView> {
), ),
], ],
bottom: PreferredSize( bottom: PreferredSize(
preferredSize: const Size.fromHeight(66.0), preferredSize: const Size.fromHeight(64.0),
child: Padding( child: Padding(
padding: const EdgeInsets.symmetric( padding: const EdgeInsets.symmetric(
vertical: 8.0, vertical: 8.0,

View File

@ -8,7 +8,6 @@ import 'package:revanced_manager/app/app.locator.dart';
import 'package:revanced_manager/services/manager_api.dart'; import 'package:revanced_manager/services/manager_api.dart';
import 'package:revanced_manager/ui/views/settings/settings_viewmodel.dart'; import 'package:revanced_manager/ui/views/settings/settings_viewmodel.dart';
import 'package:revanced_manager/ui/widgets/settingsView/settings_section.dart'; import 'package:revanced_manager/ui/widgets/settingsView/settings_section.dart';
import 'package:revanced_manager/ui/widgets/shared/custom_material_button.dart';
import 'package:stacked/stacked.dart'; import 'package:stacked/stacked.dart';
final _settingViewModel = SettingsViewModel(); final _settingViewModel = SettingsViewModel();
@ -25,114 +24,37 @@ class SUpdateTheme extends BaseViewModel {
Future<void> setUseDynamicTheme(BuildContext context, bool value) async { Future<void> setUseDynamicTheme(BuildContext context, bool value) async {
await _managerAPI.setUseDynamicTheme(value); await _managerAPI.setUseDynamicTheme(value);
final int currentTheme = (DynamicTheme.of(context)!.themeId ~/ 2) * 2; final int currentTheme = DynamicTheme.of(context)!.themeId;
await DynamicTheme.of(context)!.setTheme(currentTheme + (value ? 1 : 0)); if (currentTheme.isEven) {
await DynamicTheme.of(context)!.setTheme(value ? 2 : 0);
} else {
await DynamicTheme.of(context)!.setTheme(value ? 3 : 1);
}
notifyListeners(); notifyListeners();
} }
int getThemeMode() { bool getDarkThemeStatus() {
return _managerAPI.getThemeMode(); return _managerAPI.getUseDarkTheme();
} }
Future<void> setThemeMode(BuildContext context, int value) async { Future<void> setUseDarkTheme(BuildContext context, bool value) async {
await _managerAPI.setThemeMode(value); await _managerAPI.setUseDarkTheme(value);
final bool isDynamicTheme = DynamicTheme.of(context)!.themeId.isEven; final int currentTheme = DynamicTheme.of(context)!.themeId;
await DynamicTheme.of(context)!.setTheme(value * 2 + (isDynamicTheme ? 0 : 1)); if (currentTheme < 2) {
final bool isLight = value != 2 && (value == 1 || DynamicTheme.of(context)!.theme.brightness == Brightness.light); await DynamicTheme.of(context)!.setTheme(value ? 1 : 0);
} else {
await DynamicTheme.of(context)!.setTheme(value ? 3 : 2);
}
SystemChrome.setSystemUIOverlayStyle( SystemChrome.setSystemUIOverlayStyle(
SystemUiOverlayStyle( SystemUiOverlayStyle(
systemNavigationBarIconBrightness: systemNavigationBarIconBrightness:
isLight ? Brightness.dark : Brightness.light, value ? Brightness.light : Brightness.dark,
), ),
); );
notifyListeners(); notifyListeners();
} }
I18nText getThemeModeName() {
switch (getThemeMode()) {
case 0:
return I18nText('settingsView.systemThemeLabel');
case 1:
return I18nText('settingsView.lightThemeLabel');
case 2:
return I18nText('settingsView.darkThemeLabel');
default:
return I18nText('settingsView.systemThemeLabel');
}
}
Future<void> showThemeDialog(BuildContext context) async {
final ValueNotifier<int> newTheme = ValueNotifier(getThemeMode());
return showDialog(
context: context,
builder: (context) => AlertDialog(
title: I18nText('settingsView.themeModeLabel'),
icon: const Icon(Icons.palette),
contentPadding: const EdgeInsets.symmetric(vertical: 16),
backgroundColor: Theme.of(context).colorScheme.secondaryContainer,
content: SingleChildScrollView(
child: ValueListenableBuilder(
valueListenable: newTheme,
builder: (context, value, child) {
return Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
RadioListTile(
title: I18nText('settingsView.systemThemeLabel'),
contentPadding: const EdgeInsets.symmetric(horizontal: 16),
value: 0,
groupValue: value,
onChanged: (value) {
newTheme.value = value!;
},
),
RadioListTile(
title: I18nText('settingsView.lightThemeLabel'),
contentPadding: const EdgeInsets.symmetric(horizontal: 16),
value: 1,
groupValue: value,
onChanged: (value) {
newTheme.value = value!;
},
),
RadioListTile(
title: I18nText('settingsView.darkThemeLabel'),
contentPadding: const EdgeInsets.symmetric(horizontal: 16),
value: 2,
groupValue: value,
onChanged: (value) {
newTheme.value = value!;
},
),
],
);
},
),
),
actions: <Widget>[
CustomMaterialButton(
isFilled: false,
label: I18nText('cancelButton'),
onPressed: () {
Navigator.of(context).pop();
},
),
CustomMaterialButton(
label: I18nText('okButton'),
onPressed: () {
setThemeMode(context, newTheme.value);
Navigator.of(context).pop();
},
),
],
),
);
}
} }
final sUpdateTheme = SUpdateTheme();
class SUpdateThemeUI extends StatelessWidget { class SUpdateThemeUI extends StatelessWidget {
const SUpdateThemeUI({super.key}); const SUpdateThemeUI({super.key});
@ -141,10 +63,10 @@ class SUpdateThemeUI extends StatelessWidget {
return SettingsSection( return SettingsSection(
title: 'settingsView.appearanceSectionTitle', title: 'settingsView.appearanceSectionTitle',
children: <Widget>[ children: <Widget>[
ListTile( SwitchListTile(
contentPadding: const EdgeInsets.symmetric(horizontal: 20.0), contentPadding: const EdgeInsets.symmetric(horizontal: 20.0),
title: I18nText( title: I18nText(
'settingsView.themeModeLabel', 'settingsView.darkThemeLabel',
child: const Text( child: const Text(
'', '',
style: TextStyle( style: TextStyle(
@ -153,11 +75,12 @@ class SUpdateThemeUI extends StatelessWidget {
), ),
), ),
), ),
trailing: CustomMaterialButton( subtitle: I18nText('settingsView.darkThemeHint'),
label: sUpdateTheme.getThemeModeName(), value: SUpdateTheme().getDarkThemeStatus(),
onPressed: () => { sUpdateTheme.showThemeDialog(context) }, onChanged: (value) => SUpdateTheme().setUseDarkTheme(
context,
value,
), ),
onTap: () => { sUpdateTheme.showThemeDialog(context) },
), ),
FutureBuilder<int>( FutureBuilder<int>(
future: _settingViewModel.getSdkVersion(), future: _settingViewModel.getSdkVersion(),

View File

@ -75,8 +75,8 @@ class SocialMediaWidget extends StatelessWidget {
SocialMediaItem( SocialMediaItem(
icon: FaIcon(FontAwesomeIcons.youtube), icon: FaIcon(FontAwesomeIcons.youtube),
title: Text('YouTube'), title: Text('YouTube'),
subtitle: Text('youtube.com/@revanced'), subtitle: Text('youtube.com/revanced'),
url: 'https://youtube.com/@revanced', url: 'https://youtube.com/revanced',
), ),
], ],
), ),

View File

@ -4,7 +4,7 @@ homepage: https://github.com/revanced/revanced-manager
publish_to: 'none' publish_to: 'none'
version: 1.10.3+101000300 version: 1.9.5+100900500
environment: environment:
sdk: '>=3.0.0 <4.0.0' sdk: '>=3.0.0 <4.0.0'