diff --git a/.github/workflows/analyze.yml b/.github/workflows/analyze.yml deleted file mode 100644 index e3f0f063..00000000 --- a/.github/workflows/analyze.yml +++ /dev/null @@ -1,38 +0,0 @@ -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 diff --git a/.github/workflows/pr-build.yml b/.github/workflows/pr-build.yml index 0bb9bdb1..dbc20e29 100644 --- a/.github/workflows/pr-build.yml +++ b/.github/workflows/pr-build.yml @@ -14,7 +14,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: # Make sure the release step uses its own credentials: # https://github.com/cycjimmy/semantic-release-action#private-packages diff --git a/.github/workflows/release-build.yml b/.github/workflows/release-build.yml index 4b414eb7..7d31e5a0 100644 --- a/.github/workflows/release-build.yml +++ b/.github/workflows/release-build.yml @@ -9,7 +9,7 @@ jobs: release: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Set env run: echo "RELEASE_VERSION=${GITHUB_REF#refs/*/}" >> $GITHUB_ENV - name: Set up JDK 11 diff --git a/README.md b/README.md index 07ed4a79..89f41ea6 100644 --- a/README.md +++ b/README.md @@ -3,29 +3,33 @@ The official ReVanced Manager based on Flutter. ## 🔽 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 + 1. Android 8 or higher -2. Does not work on some armv7 devices +2. Incompatible with certain ARMv7 devices + +## 📃 Documentation +The documentation can be found [here](https://github.com/revanced/revanced-manager/tree/main/docs). ## 🔴 Issues + For suggestions and bug reports, open an issue [here](https://github.com/revanced/revanced-manager/issues/new/choose). -## 💭 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) -If you wish to translate ReVanced Manager, we're accepting translations on [Crowdin](https://translate.revanced.app) +We're accepting translations on [Crowdin](https://translate.revanced.app). ## 🛠️ Building Manager from source + 1. Setup flutter environment for your [platform](https://docs.flutter.dev/get-started/install) 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 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) -7. To build release apk run `flutter build apk` +7. To build release APK run `flutter build apk` diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 85483241..08e9e77c 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -25,8 +25,7 @@ android:icon="@mipmap/ic_launcher" android:largeHeap="true" android:requestLegacyExternalStorage="true" - android:extractNativeLibs="true" - android:enableOnBackInvokedCallback="true"> + android:extractNativeLibs="true"> { + val patchBundleFilePath = call.argument("patchBundleFilePath") + if (patchBundleFilePath != null) { + val patches = PatchBundleLoader.Dex( + File(patchBundleFilePath) + ).map { patch -> + val map = HashMap() + map["\"name\""] = "\"${patch.patchName.replace("\"","\\\"")}\"" + map["\"description\""] = "\"${patch.description?.replace("\"","\\\"")}\"" + map["\"excluded\""] = !patch.include + map["\"dependencies\""] = patch.dependencies?.map { "\"${it.java.patchName}\"" } ?: emptyList() + map["\"compatiblePackages\""] = patch.compatiblePackages?.map { + val map2 = HashMap() + map2["\"name\""] = "\"${it.name}\"" + map2["\"versions\""] = it.versions.map { version -> "\"${version}\"" } + map2 + } ?: emptyList() + map + } + result.success(patches) + } else result.notImplemented() + } + else -> result.notImplemented() } } diff --git a/assets/i18n/en_US.json b/assets/i18n/en_US.json index 63e82677..05694ced 100644 --- a/assets/i18n/en_US.json +++ b/assets/i18n/en_US.json @@ -178,8 +178,10 @@ "exportSectionTitle": "Import & export", "logsSectionTitle": "Logs", - "darkThemeLabel": "Dark mode", - "darkThemeHint": "Welcome to the dark side", + "themeModeLabel": "App theme", + "systemThemeLabel": "System", + "lightThemeLabel": "Light", + "darkThemeLabel": "Dark", "dynamicThemeLabel": "Material You", "dynamicThemeHint": "Enjoy an experience closer to your device", diff --git a/lib/services/github_api.dart b/lib/services/github_api.dart index de8c160b..0949f1b9 100644 --- a/lib/services/github_api.dart +++ b/lib/services/github_api.dart @@ -222,10 +222,8 @@ class GithubAPI { final String downloadUrl = asset['browser_download_url']; if (extension == '.apk') { _managerAPI.setIntegrationsDownloadURL(downloadUrl); - } else if (extension == '.json') { - _managerAPI.setPatchesDownloadURL(downloadUrl, false); } else { - _managerAPI.setPatchesDownloadURL(downloadUrl, true); + _managerAPI.setPatchesDownloadURL(downloadUrl); } return await DefaultCacheManager().getSingleFile( downloadUrl, diff --git a/lib/services/manager_api.dart b/lib/services/manager_api.dart index 6551d0f4..af996fe2 100644 --- a/lib/services/manager_api.dart +++ b/lib/services/manager_api.dart @@ -11,6 +11,7 @@ import 'package:revanced_manager/app/app.locator.dart'; import 'package:revanced_manager/models/patch.dart'; import 'package:revanced_manager/models/patched_application.dart'; import 'package:revanced_manager/services/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/ui/widgets/shared/custom_material_button.dart'; @@ -26,6 +27,7 @@ class ManagerAPI { final String patcherRepo = 'revanced-patcher'; final String cliRepo = 'revanced-cli'; late SharedPreferences _prefs; + List patches = []; bool isRooted = false; String storedPatchesFile = '/selected-patches.json'; String keystoreFile = @@ -41,11 +43,11 @@ class ManagerAPI { String? patchesVersion = ''; String? integrationsVersion = ''; bool isDefaultPatchesRepo() { - return getPatchesRepo() == 'revanced/revanced-patches'; + return getPatchesRepo().toLowerCase() == 'revanced/revanced-patches'; } bool isDefaultIntegrationsRepo() { - return getIntegrationsRepo() == 'revanced/revanced-integrations'; + return getIntegrationsRepo().toLowerCase() == 'revanced/revanced-integrations'; } Future initialize() async { @@ -79,12 +81,12 @@ class ManagerAPI { await _prefs.setString('repoUrl', url); } - String getPatchesDownloadURL(bool bundle) { - return _prefs.getString('patchesDownloadURL-$bundle') ?? ''; + String getPatchesDownloadURL() { + return _prefs.getString('patchesDownloadURL') ?? ''; } - Future setPatchesDownloadURL(String value, bool bundle) async { - await _prefs.setString('patchesDownloadURL-$bundle', value); + Future setPatchesDownloadURL(String value) async { + await _prefs.setString('patchesDownloadURL', value); } String getPatchesRepo() { @@ -197,12 +199,12 @@ class ManagerAPI { await _prefs.setBool('useDynamicTheme', value); } - bool getUseDarkTheme() { - return _prefs.getBool('useDarkTheme') ?? false; + int getThemeMode() { + return _prefs.getInt('themeMode') ?? 2; } - Future setUseDarkTheme(bool value) async { - await _prefs.setBool('useDarkTheme', value); + Future setThemeMode(int value) async { + await _prefs.setInt('themeMode', value); } bool areUniversalPatchesEnabled() { @@ -300,28 +302,38 @@ class ManagerAPI { } Future> getPatches() async { - try { - final String repoName = getPatchesRepo(); - final String currentVersion = await getCurrentPatchesVersion(); - final String url = getPatchesDownloadURL(false); - return await _githubAPI.getPatches( - repoName, - currentVersion, - url, - ); - } on Exception catch (e) { - if (kDebugMode) { - print(e); - } - return []; + if (patches.isNotEmpty) { + return patches; } + final File? patchBundleFile = await downloadPatches(); + if (patchBundleFile != null) { + try { + final patchesObject = await PatcherAPI.patcherChannel.invokeMethod( + 'getPatches', + { + 'patchBundleFilePath': patchBundleFile.path, + }, + ); + final List> 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 List.empty(); } Future downloadPatches() async { try { final String repoName = getPatchesRepo(); final String currentVersion = await getCurrentPatchesVersion(); - final String url = getPatchesDownloadURL(true); + final String url = getPatchesDownloadURL(); return await _githubAPI.getPatchesReleaseFile( '.jar', repoName, @@ -447,8 +459,7 @@ class ManagerAPI { Future setCurrentPatchesVersion(String version) async { await _prefs.setString('patchesVersion', version); - await setPatchesDownloadURL('', false); - await setPatchesDownloadURL('', true); + await setPatchesDownloadURL(''); await downloadPatches(); } diff --git a/lib/services/patcher_api.dart b/lib/services/patcher_api.dart index dfaaf3ec..5838ab3b 100644 --- a/lib/services/patcher_api.dart +++ b/lib/services/patcher_api.dart @@ -25,12 +25,13 @@ class PatcherAPI { late Directory _tmpDir; late File _keyStoreFile; List _patches = []; + List _universalPatches = []; + List _compatiblePackages = []; Map filteredPatches = >{}; File? _outFile; Future initialize() async { await _loadPatches(); - await _managerAPI.downloadPatches(); await _managerAPI.downloadIntegrations(); final Directory appCache = await getTemporaryDirectory(); _dataDir = await getExternalStorageDirectory() ?? appCache; @@ -45,6 +46,24 @@ class PatcherAPI { } } + List getCompatiblePackages() { + final List 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 getUniversalPatches() { + return _patches + .where((patch) => patch.compatiblePackages.isEmpty) + .toList(); + } + Future _loadPatches() async { try { if (_patches.isEmpty) { @@ -56,6 +75,9 @@ class PatcherAPI { } _patches = List.empty(); } + + _compatiblePackages = getCompatiblePackages(); + _universalPatches = getUniversalPatches(); } Future> getFilteredInstalledApps( @@ -63,48 +85,43 @@ class PatcherAPI { ) async { final List filteredApps = []; final bool allAppsIncluded = - _patches.any((patch) => patch.compatiblePackages.isEmpty) && + _universalPatches.isNotEmpty && showUniversalPatches; if (allAppsIncluded) { - final allPackages = await DeviceApps.getInstalledApplications( + final appList = await DeviceApps.getInstalledApplications( includeAppIcons: true, onlyAppsWithLaunchIntent: true, ); - for (final pkg in allPackages) { - if (!filteredApps.any((app) => app.packageName == pkg.packageName)) { - final appInfo = await DeviceApps.getApp( - pkg.packageName, - true, - ) as ApplicationWithIcon?; - if (appInfo != null) { - filteredApps.add(appInfo); - } - } + + for(final app in appList) { + filteredApps.add(app as ApplicationWithIcon); } } - 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); + for (final packageName in _compatiblePackages) { + try { + if (!filteredApps.any((app) => app.packageName == packageName)) { + final ApplicationWithIcon? app = await DeviceApps.getApp( + packageName, + true, + ) as ApplicationWithIcon?; + if (app != null) { + filteredApps.add(app); } } + } on Exception catch (e) { + if (kDebugMode) { + print(e); + } } } return filteredApps; } List getFilteredPatches(String packageName) { + if (!_compatiblePackages.contains(packageName)) { + return _universalPatches; + } + final List patches = _patches .where( (patch) => diff --git a/lib/ui/theme/dynamic_theme_builder.dart b/lib/ui/theme/dynamic_theme_builder.dart index 65d74c2c..5fcd8831 100644 --- a/lib/ui/theme/dynamic_theme_builder.dart +++ b/lib/ui/theme/dynamic_theme_builder.dart @@ -1,12 +1,16 @@ +import 'dart:ui'; import 'package:dynamic_color/dynamic_color.dart'; import 'package:dynamic_themes/dynamic_themes.dart'; import 'package:flutter/material.dart'; +import 'package:flutter/services.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/services/manager_api.dart'; import 'package:revanced_manager/theme.dart'; import 'package:stacked_services/stacked_services.dart'; -class DynamicThemeBuilder extends StatelessWidget { +class DynamicThemeBuilder extends StatefulWidget { const DynamicThemeBuilder({ Key? key, required this.title, @@ -17,6 +21,35 @@ class DynamicThemeBuilder extends StatelessWidget { final Widget home; final Iterable localizationsDelegates; + @override + State createState() => _DynamicThemeBuilderState(); +} + +class _DynamicThemeBuilderState extends State with WidgetsBindingObserver { + Brightness brightness = PlatformDispatcher.instance.platformBrightness; + final ManagerAPI _managerAPI = locator(); + + @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 Widget build(BuildContext context) { return DynamicColorBuilder( @@ -50,24 +83,32 @@ class DynamicThemeBuilder extends StatelessWidget { return DynamicTheme( themeCollection: ThemeCollection( themes: { - 0: lightCustomTheme, - 1: darkCustomTheme, - 2: lightDynamicTheme, - 3: darkDynamicTheme, + 0: brightness == Brightness.light ? lightCustomTheme : darkCustomTheme, + 1: brightness == Brightness.light ? lightDynamicTheme : darkDynamicTheme, + 2: lightCustomTheme, + 3: lightDynamicTheme, + 4: darkCustomTheme, + 5: darkDynamicTheme, }, - fallbackTheme: lightCustomTheme, + fallbackTheme: brightness == Brightness.light ? lightCustomTheme : darkCustomTheme, ), builder: (context, theme) => MaterialApp( - debugShowCheckedModeBanner: false, - title: title, - navigatorKey: StackedService.navigatorKey, - onGenerateRoute: StackedRouter().onGenerateRoute, - theme: theme, - home: home, - localizationsDelegates: localizationsDelegates, - ), + debugShowCheckedModeBanner: false, + title: widget.title, + navigatorKey: StackedService.navigatorKey, + onGenerateRoute: StackedRouter().onGenerateRoute, + theme: theme, + home: widget.home, + localizationsDelegates: widget.localizationsDelegates, + ), ); }, ); } + + @override + void dispose() { + WidgetsBinding.instance.removeObserver(this); + super.dispose(); + } } diff --git a/lib/ui/views/installer/installer_viewmodel.dart b/lib/ui/views/installer/installer_viewmodel.dart index b11d47da..f523f2a0 100644 --- a/lib/ui/views/installer/installer_viewmodel.dart +++ b/lib/ui/views/installer/installer_viewmodel.dart @@ -182,52 +182,54 @@ class InstallerViewModel extends BaseViewModel { backgroundColor: Theme.of(context).colorScheme.secondaryContainer, icon: const Icon(Icons.file_download_outlined), contentPadding: const EdgeInsets.symmetric(vertical: 16), - content: ValueListenableBuilder( - valueListenable: installType, - builder: (context, value, child) { - return Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Padding( - padding: const EdgeInsets.symmetric( - horizontal: 20, - vertical: 10, - ), - child: I18nText( - 'installerView.installTypeDescription', - child: Text( - '', - style: TextStyle( - fontSize: 16, - fontWeight: FontWeight.w500, - color: Theme.of(context).colorScheme.secondary, + content: SingleChildScrollView( + child: ValueListenableBuilder( + valueListenable: installType, + builder: (context, value, child) { + return Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: const EdgeInsets.symmetric( + horizontal: 20, + vertical: 10, + ), + child: I18nText( + 'installerView.installTypeDescription', + child: Text( + '', + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w500, + color: Theme.of(context).colorScheme.secondary, + ), ), ), ), - ), - RadioListTile( - title: I18nText('installerView.installNonRootType'), - subtitle: I18nText('installerView.installRecommendedType'), - contentPadding: const EdgeInsets.symmetric(horizontal: 10), - value: 0, - groupValue: value, - onChanged: (selected) { - installType.value = selected!; - }, - ), - RadioListTile( - title: I18nText('installerView.installRootType'), - contentPadding: const EdgeInsets.symmetric(horizontal: 10), - value: 1, - groupValue: value, - onChanged: (selected) { - installType.value = selected!; - }, - ), - ], - ); - }, + RadioListTile( + title: I18nText('installerView.installNonRootType'), + subtitle: I18nText('installerView.installRecommendedType'), + contentPadding: const EdgeInsets.symmetric(horizontal: 16), + value: 0, + groupValue: value, + onChanged: (selected) { + installType.value = selected!; + }, + ), + RadioListTile( + title: I18nText('installerView.installRootType'), + contentPadding: const EdgeInsets.symmetric(horizontal: 16), + value: 1, + groupValue: value, + onChanged: (selected) { + installType.value = selected!; + }, + ), + ], + ); + }, + ), ), actions: [ CustomMaterialButton( diff --git a/lib/ui/views/navigation/navigation_viewmodel.dart b/lib/ui/views/navigation/navigation_viewmodel.dart index 0967501d..8e36a630 100644 --- a/lib/ui/views/navigation/navigation_viewmodel.dart +++ b/lib/ui/views/navigation/navigation_viewmodel.dart @@ -30,11 +30,8 @@ class NavigationViewModel extends IndexTrackingViewModel { ); } - if (prefs.getBool('useDarkTheme') == null) { - final bool isDark = - MediaQuery.platformBrightnessOf(context) != Brightness.light; - await prefs.setBool('useDarkTheme', isDark); - await DynamicTheme.of(context)!.setTheme(isDark ? 1 : 0); + if (prefs.getInt('themeMode') == null) { + await prefs.setInt('themeMode', 0); } SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge); SystemChrome.setSystemUIOverlayStyle( diff --git a/lib/ui/views/settings/settingsFragment/settings_update_theme.dart b/lib/ui/views/settings/settingsFragment/settings_update_theme.dart index 684abc96..1abd0f8c 100644 --- a/lib/ui/views/settings/settingsFragment/settings_update_theme.dart +++ b/lib/ui/views/settings/settingsFragment/settings_update_theme.dart @@ -8,6 +8,7 @@ import 'package:revanced_manager/app/app.locator.dart'; import 'package:revanced_manager/services/manager_api.dart'; import 'package:revanced_manager/ui/views/settings/settings_viewmodel.dart'; import 'package:revanced_manager/ui/widgets/settingsView/settings_section.dart'; +import 'package:revanced_manager/ui/widgets/shared/custom_material_button.dart'; import 'package:stacked/stacked.dart'; final _settingViewModel = SettingsViewModel(); @@ -24,37 +25,114 @@ class SUpdateTheme extends BaseViewModel { Future setUseDynamicTheme(BuildContext context, bool value) async { await _managerAPI.setUseDynamicTheme(value); - final int currentTheme = DynamicTheme.of(context)!.themeId; - if (currentTheme.isEven) { - await DynamicTheme.of(context)!.setTheme(value ? 2 : 0); - } else { - await DynamicTheme.of(context)!.setTheme(value ? 3 : 1); - } + final int currentTheme = (DynamicTheme.of(context)!.themeId ~/ 2) * 2; + await DynamicTheme.of(context)!.setTheme(currentTheme + (value ? 1 : 0)); notifyListeners(); } - bool getDarkThemeStatus() { - return _managerAPI.getUseDarkTheme(); + int getThemeMode() { + return _managerAPI.getThemeMode(); } - Future setUseDarkTheme(BuildContext context, bool value) async { - await _managerAPI.setUseDarkTheme(value); - final int currentTheme = DynamicTheme.of(context)!.themeId; - if (currentTheme < 2) { - await DynamicTheme.of(context)!.setTheme(value ? 1 : 0); - } else { - await DynamicTheme.of(context)!.setTheme(value ? 3 : 2); - } + Future setThemeMode(BuildContext context, int value) async { + await _managerAPI.setThemeMode(value); + final bool isDynamicTheme = DynamicTheme.of(context)!.themeId.isEven; + await DynamicTheme.of(context)!.setTheme(value * 2 + (isDynamicTheme ? 0 : 1)); + final bool isLight = value != 2 && (value == 1 || DynamicTheme.of(context)!.theme.brightness == Brightness.light); SystemChrome.setSystemUIOverlayStyle( SystemUiOverlayStyle( systemNavigationBarIconBrightness: - value ? Brightness.light : Brightness.dark, + isLight ? Brightness.dark : Brightness.light, ), ); 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 showThemeDialog(BuildContext context) async { + final ValueNotifier 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: [ + 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: [ + 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 { const SUpdateThemeUI({super.key}); @@ -63,10 +141,10 @@ class SUpdateThemeUI extends StatelessWidget { return SettingsSection( title: 'settingsView.appearanceSectionTitle', children: [ - SwitchListTile( + ListTile( contentPadding: const EdgeInsets.symmetric(horizontal: 20.0), title: I18nText( - 'settingsView.darkThemeLabel', + 'settingsView.themeModeLabel', child: const Text( '', style: TextStyle( @@ -75,11 +153,9 @@ class SUpdateThemeUI extends StatelessWidget { ), ), ), - subtitle: I18nText('settingsView.darkThemeHint'), - value: SUpdateTheme().getDarkThemeStatus(), - onChanged: (value) => SUpdateTheme().setUseDarkTheme( - context, - value, + trailing: CustomMaterialButton( + label: sUpdateTheme.getThemeModeName(), + onPressed: () => { sUpdateTheme.showThemeDialog(context) }, ), ), FutureBuilder( diff --git a/lib/ui/widgets/settingsView/social_media_widget.dart b/lib/ui/widgets/settingsView/social_media_widget.dart index 73a6a2ee..7cc686f1 100644 --- a/lib/ui/widgets/settingsView/social_media_widget.dart +++ b/lib/ui/widgets/settingsView/social_media_widget.dart @@ -75,8 +75,8 @@ class SocialMediaWidget extends StatelessWidget { SocialMediaItem( icon: FaIcon(FontAwesomeIcons.youtube), title: Text('YouTube'), - subtitle: Text('youtube.com/revanced'), - url: 'https://youtube.com/revanced', + subtitle: Text('youtube.com/@revanced'), + url: 'https://youtube.com/@revanced', ), ], ),