feat: update rules of analysis_options.yaml. and solved all problems (#665)

* update rules of analysis_options.yaml. and solved all problems

* refactor: fix remaining problems

---------

Co-authored-by: Ushie <ushiekane@gmail.com>
This commit is contained in:
Jay Gajjar 2023-01-30 18:05:06 +05:30 committed by GitHub
parent f5bc1a996f
commit a54ca799b9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
62 changed files with 804 additions and 643 deletions

View File

@ -14,20 +14,149 @@ analyzer:
- lib/utils/env_class.g.dart
linter:
# The lint rules applied to this project can be customized in the
# section below to disable rules from the `package:flutter_lints/flutter.yaml`
# included above or to enable additional rules. A list of all available lints
# and their documentation is published at
# https://dart-lang.github.io/linter/lints/index.html.
#
# Instead of disabling a lint rule for the entire project in the
# section below, it can also be suppressed for a single line of code
# or a specific dart file by using the `// ignore: name_of_lint` and
# `// ignore_for_file: name_of_lint` syntax on the line or in the file
# producing the lint.
rules:
# avoid_print: false # Uncomment to disable the `avoid_print` rule
# prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule
# Additional information about this file can be found at
# https://dart.dev/guides/language/analysis-options
- 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
- avoid_double_and_int_checks
- avoid_empty_else
- avoid_equals_and_hash_code_on_mutable_classes
- avoid_escaping_inner_quotes
- avoid_field_initializers_in_const_classes
- avoid_function_literals_in_foreach_calls
- avoid_implementing_value_types
- avoid_init_to_null
- avoid_js_rounded_ints
- avoid_null_checks_in_equality_operators
- avoid_print
- avoid_redundant_argument_values
- avoid_relative_lib_imports
- avoid_renaming_method_parameters
- avoid_return_types_on_setters
- avoid_returning_null
- avoid_returning_null_for_future
- avoid_returning_null_for_void
- avoid_setters_without_getters
- avoid_shadowing_type_parameters
- avoid_single_cascade_in_expression_statements
- avoid_type_to_string
- avoid_types_as_parameter_names
- avoid_unnecessary_containers
- avoid_void_async
- avoid_web_libraries_in_flutter # we use web libraries in web-specific code, and our tests prevent us from using them elsewhere
- await_only_futures
- camel_case_extensions
- camel_case_types
- cancel_subscriptions
- cast_nullable_to_non_nullable
- close_sinks # not reliable enough
- control_flow_in_finally
- curly_braces_in_flow_control_structures
- depend_on_referenced_packages
- deprecated_consistency
- directives_ordering
- empty_catches
- empty_constructor_bodies
- empty_statements
- eol_at_end_of_file
- exhaustive_cases
- file_names
- flutter_style_todos
- hash_and_equals
- implementation_imports
- iterable_contains_unrelated_type
- leading_newlines_in_multiline_strings
- library_names
- library_prefixes
- library_private_types_in_public_api
- list_remove_unrelated_type
- missing_whitespace_between_adjacent_strings
- no_adjacent_strings_in_list
- no_duplicate_case_values
- no_logic_in_create_state
- non_constant_identifier_names
- noop_primitive_operations
- null_check_on_nullable_type_parameter
- null_closures
- overridden_fields
- package_api_docs
- package_names
- package_prefixed_library_names
- prefer_adjacent_string_concatenation
- prefer_asserts_in_initializer_lists
- prefer_collection_literals
- prefer_conditional_assignment
- prefer_const_constructors
- prefer_const_constructors_in_immutables
- prefer_const_declarations
- prefer_const_literals_to_create_immutables
- prefer_contains
- prefer_equal_for_default_values
- prefer_final_fields
- prefer_final_in_for_each
- prefer_final_locals
- prefer_for_elements_to_map_fromIterable
- prefer_foreach
- prefer_function_declarations_over_variables
- prefer_generic_function_type_aliases
- prefer_if_elements_to_conditional_expressions
- prefer_if_null_operators
- prefer_initializing_formals
- prefer_inlined_adds
- prefer_interpolation_to_compose_strings
- prefer_is_empty
- prefer_is_not_empty
- prefer_is_not_operator
- prefer_iterable_whereType
- prefer_mixin # Has false positives, see https://github.com/dart-lang/linter/issues/3018
- prefer_null_aware_method_calls # "call()" is confusing to people new to the language since it's not documented anywhere
- prefer_null_aware_operators
- prefer_single_quotes
- prefer_spread_collections
- prefer_typing_uninitialized_variables
- prefer_void_to_null
- provide_deprecation_message
- recursive_getters
- sized_box_for_whitespace
- slash_for_doc_comments
- sort_child_properties_last
- sort_constructors_first
- sort_unnamed_constructors_first
- test_types_in_equals
- throw_in_finally
- tighten_type_of_initializing_formals
- type_init_formals
- unnecessary_brace_in_string_interps
- unnecessary_const
- unnecessary_getters_setters
- unnecessary_new
- unnecessary_null_aware_assignments
- unnecessary_null_checks
- unnecessary_null_in_if_null_operators
- unnecessary_nullable_for_final_variable_declarations
- unnecessary_overrides
- unnecessary_parenthesis
- unnecessary_statements
- unnecessary_string_escapes
- unnecessary_string_interpolations
- unnecessary_this
- unrelated_type_equality_checks
- unsafe_html
- use_build_context_synchronously
- use_full_hex_values_for_flutter_colors
- use_function_type_syntax_for_parameters
- use_if_null_to_convert_nulls_to_bools
- use_is_even_rather_than_modulo
- use_key_in_widget_constructors
- use_late_for_private_fields_and_variables
- use_named_constants
- use_raw_strings
- use_rethrow_when_possible
- use_setters_to_change_properties
- use_test_throws_matchers
- valid_regexps
- void_checks

View File

@ -1,3 +1,5 @@
import 'dart:developer';
import 'package:flutter/material.dart';
import 'package:flutter_i18n/flutter_i18n.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
@ -10,9 +12,9 @@ import 'package:revanced_manager/services/revanced_api.dart';
import 'package:revanced_manager/ui/theme/dynamic_theme_builder.dart';
import 'package:revanced_manager/ui/views/navigation/navigation_view.dart';
import 'package:revanced_manager/utils/environment.dart';
import 'package:sentry_flutter/sentry_flutter.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:stacked_themes/stacked_themes.dart';
import 'package:sentry_flutter/sentry_flutter.dart';
import 'package:timezone/data/latest.dart' as tz;
late SharedPreferences prefs;
@ -21,11 +23,11 @@ Future main() async {
await setupLocator();
WidgetsFlutterBinding.ensureInitialized();
await locator<ManagerAPI>().initialize();
String apiUrl = locator<ManagerAPI>().getApiUrl();
final String apiUrl = locator<ManagerAPI>().getApiUrl();
await locator<RevancedAPI>().initialize(apiUrl);
await locator<CrowdinAPI>().initialize();
bool isSentryEnabled = locator<ManagerAPI>().isSentryEnabled();
String repoUrl = locator<ManagerAPI>().getRepoUrl();
final bool isSentryEnabled = locator<ManagerAPI>().isSentryEnabled();
final String repoUrl = locator<ManagerAPI>().getRepoUrl();
locator<GithubAPI>().initialize(repoUrl);
await locator<PatcherAPI>().initialize();
tz.initializeTimeZones();
@ -65,7 +67,7 @@ class MyApp extends StatelessWidget {
// String replaceLocale = rawLocale.replaceAll('_', '-');
// List<String> localeList = replaceLocale.split('-');
// Locale locale = Locale(localeList[0], localeList[1]);
Locale locale = const Locale('en', 'US');
const Locale locale = Locale('en', 'US');
return DynamicThemeBuilder(
title: 'ReVanced Manager',
@ -79,8 +81,9 @@ class MyApp extends StatelessWidget {
useCountryCode: true,
),
missingTranslationHandler: (key, locale) {
print(
'--> Missing translation: key: $key, languageCode: ${locale?.languageCode}');
log(
'--> Missing translation: key: $key, languageCode: ${locale?.languageCode}',
);
},
),
GlobalMaterialLocalizations.delegate,

View File

@ -5,13 +5,6 @@ part 'patch.g.dart';
@JsonSerializable()
class Patch {
final String name;
final String description;
final String version;
final bool excluded;
final List<String> dependencies;
final List<Package> compatiblePackages;
Patch({
required this.name,
required this.description,
@ -22,6 +15,12 @@ class Patch {
});
factory Patch.fromJson(Map<String, dynamic> json) => _$PatchFromJson(json);
final String name;
final String description;
final String version;
final bool excluded;
final List<String> dependencies;
final List<Package> compatiblePackages;
Map<String, dynamic> toJson() => _$PatchToJson(this);
@ -37,9 +36,6 @@ class Patch {
@JsonSerializable()
class Package {
final String name;
final List<String> versions;
Package({
required this.name,
required this.versions,
@ -47,6 +43,8 @@ class Package {
factory Package.fromJson(Map<String, dynamic> json) =>
_$PackageFromJson(json);
final String name;
final List<String> versions;
Map toJson() => _$PackageToJson(this);
}

View File

@ -6,23 +6,6 @@ part 'patched_application.g.dart';
@JsonSerializable()
class PatchedApplication {
String name;
String packageName;
String originalPackageName;
String version;
final String apkFilePath;
@JsonKey(
fromJson: decodeBase64,
toJson: encodeBase64,
)
Uint8List icon;
DateTime patchDate;
bool isRooted;
bool isFromStorage;
bool hasUpdates;
List<String> appliedPatches;
List<String> changelog;
PatchedApplication({
required this.name,
required this.packageName,
@ -40,6 +23,22 @@ class PatchedApplication {
factory PatchedApplication.fromJson(Map<String, dynamic> json) =>
_$PatchedApplicationFromJson(json);
String name;
String packageName;
String originalPackageName;
String version;
final String apkFilePath;
@JsonKey(
fromJson: decodeBase64,
toJson: encodeBase64,
)
Uint8List icon;
DateTime patchDate;
bool isRooted;
bool isFromStorage;
bool hasUpdates;
List<String> appliedPatches;
List<String> changelog;
Map<String, dynamic> toJson() => _$PatchedApplicationToJson(this);

View File

@ -13,9 +13,11 @@ class CrowdinAPI {
Future<void> initialize() async {
try {
_dio = Dio(BaseOptions(
baseUrl: 'https://api.crowdin.com/api/v2',
));
_dio = Dio(
BaseOptions(
baseUrl: 'https://api.crowdin.com/api/v2',
),
);
_dio.interceptors.add(_dioCacheManager.interceptor);
_dio.addSentry(
@ -36,7 +38,7 @@ class CrowdinAPI {
Future<List> getLanguages() async {
try {
var response = await _dio.get(
final response = await _dio.get(
'/projects',
options: buildCacheOptions(
const Duration(hours: 6),
@ -49,7 +51,7 @@ class CrowdinAPI {
),
),
);
List targetLanguages =
final List targetLanguages =
await response.data['data'][0]['data']['targetLanguages'];
return targetLanguages;

View File

@ -1,13 +1,14 @@
import 'dart:convert';
import 'dart:io';
import 'package:collection/collection.dart';
import 'package:dio/dio.dart';
import 'package:dio_http_cache_lts/dio_http_cache_lts.dart';
import 'package:flutter_cache_manager/flutter_cache_manager.dart';
import 'package:injectable/injectable.dart';
import 'package:revanced_manager/models/patch.dart';
import 'package:sentry_flutter/sentry_flutter.dart';
import 'package:sentry_dio/sentry_dio.dart';
import 'package:sentry_flutter/sentry_flutter.dart';
@lazySingleton
class GithubAPI {
@ -28,11 +29,13 @@ class GithubAPI {
'com.spotify.music': 'spotify',
};
void initialize(String repoUrl) async {
Future<void> initialize(String repoUrl) async {
try {
_dio = Dio(BaseOptions(
baseUrl: repoUrl,
));
_dio = Dio(
BaseOptions(
baseUrl: repoUrl,
),
);
_dio.interceptors.add(_dioCacheManager.interceptor);
_dio.addSentry(
@ -53,7 +56,7 @@ class GithubAPI {
Future<Map<String, dynamic>?> getLatestRelease(String repoName) async {
try {
var response = await _dio.get(
final response = await _dio.get(
'/repos/$repoName/releases',
options: _cacheOptions,
);
@ -69,10 +72,10 @@ class GithubAPI {
String repoName,
DateTime since,
) async {
String path =
final String path =
'src/main/kotlin/app/revanced/patches/${repoAppPath[packageName]}';
try {
var response = await _dio.get(
final response = await _dio.get(
'/repos/$repoName/commits',
queryParameters: {
'path': path,
@ -80,7 +83,7 @@ class GithubAPI {
},
options: _cacheOptions,
);
List<dynamic> commits = response.data;
final List<dynamic> commits = response.data;
return commits
.map(
(commit) => (commit['commit']['message']).split('\n')[0] +
@ -97,9 +100,9 @@ class GithubAPI {
Future<File?> getLatestReleaseFile(String extension, String repoName) async {
try {
Map<String, dynamic>? release = await getLatestRelease(repoName);
final Map<String, dynamic>? release = await getLatestRelease(repoName);
if (release != null) {
Map<String, dynamic>? asset =
final Map<String, dynamic>? asset =
(release['assets'] as List<dynamic>).firstWhereOrNull(
(asset) => (asset['name'] as String).endsWith(extension),
);
@ -119,9 +122,9 @@ class GithubAPI {
Future<List<Patch>> getPatches(String repoName) async {
List<Patch> patches = [];
try {
File? f = await getLatestReleaseFile('.json', repoName);
final File? f = await getLatestReleaseFile('.json', repoName);
if (f != null) {
List<dynamic> list = jsonDecode(f.readAsStringSync());
final List<dynamic> list = jsonDecode(f.readAsStringSync());
patches = list.map((patch) => Patch.fromJson(patch)).toList();
}
} on Exception catch (e, s) {
@ -133,7 +136,7 @@ class GithubAPI {
Future<String> getLastestReleaseVersion(String repoName) async {
try {
Map<String, dynamic>? release = await getLatestRelease(repoName);
final Map<String, dynamic>? release = await getLatestRelease(repoName);
if (release != null) {
return release['tag_name'];
} else {

View File

@ -131,14 +131,15 @@ class ManagerAPI {
Future<void> deleteKeystore() async {
final File keystore = File(
'/sdcard/Android/data/app.revanced.manager.flutter/files/revanced-manager.keystore');
'/sdcard/Android/data/app.revanced.manager.flutter/files/revanced-manager.keystore',
);
if (await keystore.exists()) {
await keystore.delete();
}
}
List<PatchedApplication> getPatchedApps() {
List<String> apps = _prefs.getStringList('patchedApps') ?? [];
final List<String> apps = _prefs.getStringList('patchedApps') ?? [];
return apps.map((a) => PatchedApplication.fromJson(jsonDecode(a))).toList();
}
@ -146,14 +147,16 @@ class ManagerAPI {
if (patchedApps.length > 1) {
patchedApps.sort((a, b) => a.name.compareTo(b.name));
}
await _prefs.setStringList('patchedApps',
patchedApps.map((a) => json.encode(a.toJson())).toList());
await _prefs.setStringList(
'patchedApps',
patchedApps.map((a) => json.encode(a.toJson())).toList(),
);
}
Future<void> savePatchedApp(PatchedApplication app) async {
List<PatchedApplication> patchedApps = getPatchedApps();
final List<PatchedApplication> patchedApps = getPatchedApps();
patchedApps.removeWhere((a) => a.packageName == app.packageName);
ApplicationWithIcon? installed = await DeviceApps.getApp(
final ApplicationWithIcon? installed = await DeviceApps.getApp(
app.packageName,
true,
) as ApplicationWithIcon?;
@ -167,12 +170,12 @@ class ManagerAPI {
}
Future<void> deletePatchedApp(PatchedApplication app) async {
List<PatchedApplication> patchedApps = getPatchedApps();
final List<PatchedApplication> patchedApps = getPatchedApps();
patchedApps.removeWhere((a) => a.packageName == app.packageName);
await setPatchedApps(patchedApps);
}
void clearAllData() async {
Future<void> clearAllData() async {
try {
_revancedAPI.clearAllCache();
_githubAPI.clearAllCache();
@ -187,7 +190,7 @@ class ManagerAPI {
Future<List<Patch>> getPatches() async {
try {
String repoName = getPatchesRepo();
final String repoName = getPatchesRepo();
if (repoName == defaultPatchesRepo) {
return await _revancedAPI.getPatches();
} else {
@ -201,7 +204,7 @@ class ManagerAPI {
Future<File?> downloadPatches() async {
try {
String repoName = getPatchesRepo();
final String repoName = getPatchesRepo();
if (repoName == defaultPatchesRepo) {
return await _revancedAPI.getLatestReleaseFile(
'.jar',
@ -218,7 +221,7 @@ class ManagerAPI {
Future<File?> downloadIntegrations() async {
try {
String repoName = getIntegrationsRepo();
final String repoName = getIntegrationsRepo();
if (repoName == defaultIntegrationsRepo) {
return await _revancedAPI.getLatestReleaseFile(
'.apk',
@ -260,16 +263,16 @@ class ManagerAPI {
}
Future<String> getCurrentManagerVersion() async {
PackageInfo packageInfo = await PackageInfo.fromPlatform();
final PackageInfo packageInfo = await PackageInfo.fromPlatform();
return packageInfo.version;
}
Future<List<PatchedApplication>> getAppsToRemove(
List<PatchedApplication> patchedApps,
) async {
List<PatchedApplication> toRemove = [];
for (PatchedApplication app in patchedApps) {
bool isRemove = await isAppUninstalled(app);
final List<PatchedApplication> toRemove = [];
for (final PatchedApplication app in patchedApps) {
final bool isRemove = await isAppUninstalled(app);
if (isRemove) {
toRemove.add(app);
}
@ -280,13 +283,13 @@ class ManagerAPI {
Future<List<PatchedApplication>> getUnsavedApps(
List<PatchedApplication> patchedApps,
) async {
List<PatchedApplication> unsavedApps = [];
bool hasRootPermissions = await _rootAPI.hasRootPermissions();
final List<PatchedApplication> unsavedApps = [];
final bool hasRootPermissions = await _rootAPI.hasRootPermissions();
if (hasRootPermissions) {
List<String> installedApps = await _rootAPI.getInstalledApps();
for (String packageName in installedApps) {
final List<String> installedApps = await _rootAPI.getInstalledApps();
for (final String packageName in installedApps) {
if (!patchedApps.any((app) => app.packageName == packageName)) {
ApplicationWithIcon? application = await DeviceApps.getApp(
final ApplicationWithIcon? application = await DeviceApps.getApp(
packageName,
true,
) as ApplicationWithIcon?;
@ -307,15 +310,13 @@ class ManagerAPI {
}
}
}
List<Application> userApps = await DeviceApps.getInstalledApplications(
includeSystemApps: false,
includeAppIcons: false,
);
for (Application app in userApps) {
final List<Application> userApps =
await DeviceApps.getInstalledApplications();
for (final Application app in userApps) {
if (app.packageName.startsWith('app.revanced') &&
!app.packageName.startsWith('app.revanced.manager.') &&
!patchedApps.any((uapp) => uapp.packageName == app.packageName)) {
ApplicationWithIcon? application = await DeviceApps.getApp(
final ApplicationWithIcon? application = await DeviceApps.getApp(
app.packageName,
true,
) as ApplicationWithIcon?;
@ -329,7 +330,6 @@ class ManagerAPI {
apkFilePath: application.apkFilePath,
icon: application.icon,
patchDate: DateTime.now(),
isRooted: false,
),
);
}
@ -339,24 +339,27 @@ class ManagerAPI {
}
Future<void> reAssessSavedApps() async {
List<PatchedApplication> patchedApps = getPatchedApps();
List<PatchedApplication> unsavedApps = await getUnsavedApps(patchedApps);
final List<PatchedApplication> patchedApps = getPatchedApps();
final List<PatchedApplication> unsavedApps =
await getUnsavedApps(patchedApps);
patchedApps.addAll(unsavedApps);
List<PatchedApplication> toRemove = await getAppsToRemove(patchedApps);
final List<PatchedApplication> toRemove =
await getAppsToRemove(patchedApps);
patchedApps.removeWhere((a) => toRemove.contains(a));
for (PatchedApplication app in patchedApps) {
for (final PatchedApplication app in patchedApps) {
app.hasUpdates =
await hasAppUpdates(app.originalPackageName, app.patchDate);
app.changelog =
await getAppChangelog(app.originalPackageName, app.patchDate);
if (!app.hasUpdates) {
String? currentInstalledVersion =
final String? currentInstalledVersion =
(await DeviceApps.getApp(app.packageName))?.versionName;
if (currentInstalledVersion != null) {
String currentSavedVersion = app.version;
int currentInstalledVersionInt = int.parse(
currentInstalledVersion.replaceAll(RegExp('[^0-9]'), ''));
int currentSavedVersionInt =
final String currentSavedVersion = app.version;
final int currentInstalledVersionInt = int.parse(
currentInstalledVersion.replaceAll(RegExp('[^0-9]'), ''),
);
final int currentSavedVersionInt =
int.parse(currentSavedVersion.replaceAll(RegExp('[^0-9]'), ''));
if (currentInstalledVersionInt > currentSavedVersionInt) {
app.hasUpdates = true;
@ -369,9 +372,9 @@ class ManagerAPI {
Future<bool> isAppUninstalled(PatchedApplication app) async {
bool existsRoot = false;
bool existsNonRoot = await DeviceApps.isAppInstalled(app.packageName);
final bool existsNonRoot = await DeviceApps.isAppInstalled(app.packageName);
if (app.isRooted) {
bool hasRootPermissions = await _rootAPI.hasRootPermissions();
final bool hasRootPermissions = await _rootAPI.hasRootPermissions();
if (hasRootPermissions) {
existsRoot = await _rootAPI.isAppInstalled(app.packageName);
}
@ -381,7 +384,7 @@ class ManagerAPI {
}
Future<bool> hasAppUpdates(String packageName, DateTime patchDate) async {
List<String> commits = await _githubAPI.getCommits(
final List<String> commits = await _githubAPI.getCommits(
packageName,
getPatchesRepo(),
patchDate,
@ -390,7 +393,9 @@ class ManagerAPI {
}
Future<List<String>> getAppChangelog(
String packageName, DateTime patchDate) async {
String packageName,
DateTime patchDate,
) async {
List<String> newCommits = await _githubAPI.getCommits(
packageName,
getPatchesRepo(),
@ -418,7 +423,7 @@ class ManagerAPI {
Future<void> setSelectedPatches(String app, List<String> patches) async {
final File selectedPatchesFile = File(storedPatchesFile);
Map<String, dynamic> patchesMap = await readSelectedPatchesFile();
final Map<String, dynamic> patchesMap = await readSelectedPatchesFile();
if (patches.isEmpty) {
patchesMap.remove(app);
} else {
@ -428,15 +433,19 @@ class ManagerAPI {
}
Future<List<String>> getSelectedPatches(String app) async {
Map<String, dynamic> patchesMap = await readSelectedPatchesFile();
final Map<String, dynamic> patchesMap = await readSelectedPatchesFile();
return List.from(patchesMap.putIfAbsent(app, () => List.empty()));
}
Future<Map<String, dynamic>> readSelectedPatchesFile() async {
final File selectedPatchesFile = File(storedPatchesFile);
if (!selectedPatchesFile.existsSync()) return {};
String string = selectedPatchesFile.readAsStringSync();
if (string.trim().isEmpty) return {};
if (!selectedPatchesFile.existsSync()) {
return {};
}
final String string = selectedPatchesFile.readAsStringSync();
if (string.trim().isEmpty) {
return {};
}
return jsonDecode(string);
}

View File

@ -1,7 +1,10 @@
import 'dart:io';
import 'package:app_installer/app_installer.dart';
import 'package:collection/collection.dart';
import 'package:cr_file_saver/file_saver.dart';
import 'package:device_apps/device_apps.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/services.dart';
import 'package:injectable/injectable.dart';
import 'package:path_provider/path_provider.dart';
@ -12,7 +15,6 @@ import 'package:revanced_manager/services/manager_api.dart';
import 'package:revanced_manager/services/root_api.dart';
import 'package:sentry_flutter/sentry_flutter.dart';
import 'package:share_extend/share_extend.dart';
import 'package:cr_file_saver/file_saver.dart';
@lazySingleton
class PatcherAPI {
@ -29,7 +31,7 @@ class PatcherAPI {
Future<void> initialize() async {
await _loadPatches();
Directory appCache = await getTemporaryDirectory();
final Directory appCache = await getTemporaryDirectory();
_dataDir = await getExternalStorageDirectory() ?? appCache;
_tmpDir = Directory('${appCache.path}/patcher');
_keyStoreFile = File('${_dataDir.path}/revanced-manager.keystore');
@ -53,18 +55,21 @@ class PatcherAPI {
}
}
Future<List<ApplicationWithIcon>> getFilteredInstalledApps(bool showUniversalPatches) async {
List<ApplicationWithIcon> filteredApps = [];
bool? allAppsIncluded =
_patches.any((patch) => patch.compatiblePackages.isEmpty) && showUniversalPatches;
Future<List<ApplicationWithIcon>> getFilteredInstalledApps(
bool showUniversalPatches,
) async {
final List<ApplicationWithIcon> filteredApps = [];
final bool allAppsIncluded =
_patches.any((patch) => patch.compatiblePackages.isEmpty) &&
showUniversalPatches;
if (allAppsIncluded) {
var allPackages = await DeviceApps.getInstalledApplications(
final allPackages = await DeviceApps.getInstalledApplications(
includeAppIcons: true,
onlyAppsWithLaunchIntent: true,
);
allPackages.forEach((pkg) async {
for (final pkg in allPackages) {
if (!filteredApps.any((app) => app.packageName == pkg.packageName)) {
var appInfo = await DeviceApps.getApp(
final appInfo = await DeviceApps.getApp(
pkg.packageName,
true,
) as ApplicationWithIcon?;
@ -72,13 +77,13 @@ class PatcherAPI {
filteredApps.add(appInfo);
}
}
});
}
}
for (Patch patch in _patches) {
for (Package package in patch.compatiblePackages) {
for (final Patch patch in _patches) {
for (final Package package in patch.compatiblePackages) {
try {
if (!filteredApps.any((app) => app.packageName == package.name)) {
ApplicationWithIcon? app = await DeviceApps.getApp(
final ApplicationWithIcon? app = await DeviceApps.getApp(
package.name,
true,
) as ApplicationWithIcon?;
@ -97,12 +102,14 @@ class PatcherAPI {
List<Patch> getFilteredPatches(String packageName) {
if (!filteredPatches.keys.contains(packageName)) {
List<Patch> patches = _patches
.where((patch) =>
patch.compatiblePackages.isEmpty ||
!patch.name.contains('settings') &&
patch.compatiblePackages
.any((pack) => pack.name == packageName))
final List<Patch> patches = _patches
.where(
(patch) =>
patch.compatiblePackages.isEmpty ||
!patch.name.contains('settings') &&
patch.compatiblePackages
.any((pack) => pack.name == packageName),
)
.toList();
filteredPatches[packageName] = patches;
}
@ -155,7 +162,7 @@ class PatcherAPI {
String originalFilePath,
) async {
try {
bool hasRootPermissions = await _rootAPI.hasRootPermissions();
final bool hasRootPermissions = await _rootAPI.hasRootPermissions();
if (hasRootPermissions) {
originalFilePath = await _rootAPI.getOriginalFilePath(
packageName,
@ -174,11 +181,11 @@ class PatcherAPI {
String originalFilePath,
List<Patch> selectedPatches,
) async {
bool mergeIntegrations = await needsIntegrations(selectedPatches);
bool includeSettings = await needsSettingsPatch(selectedPatches);
final bool mergeIntegrations = await needsIntegrations(selectedPatches);
final bool includeSettings = await needsSettingsPatch(selectedPatches);
if (includeSettings) {
try {
Patch? settingsPatch = _patches.firstWhereOrNull(
final Patch? settingsPatch = _patches.firstWhereOrNull(
(patch) =>
patch.name.contains('settings') &&
patch.compatiblePackages.any((pack) => pack.name == packageName),
@ -191,7 +198,7 @@ class PatcherAPI {
// ignore
}
}
File? patchBundleFile = await _managerAPI.downloadPatches();
final File? patchBundleFile = await _managerAPI.downloadPatches();
File? integrationsFile;
if (mergeIntegrations) {
integrationsFile = await _managerAPI.downloadIntegrations();
@ -199,11 +206,11 @@ class PatcherAPI {
if (patchBundleFile != null) {
_dataDir.createSync();
_tmpDir.createSync();
Directory workDir = _tmpDir.createTempSync('tmp-');
File inputFile = File('${workDir.path}/base.apk');
File patchedFile = File('${workDir.path}/patched.apk');
final Directory workDir = _tmpDir.createTempSync('tmp-');
final File inputFile = File('${workDir.path}/base.apk');
final File patchedFile = File('${workDir.path}/patched.apk');
_outFile = File('${workDir.path}/out.apk');
Directory cacheDir = Directory('${workDir.path}/cache');
final Directory cacheDir = Directory('${workDir.path}/cache');
cacheDir.createSync();
try {
await patcherChannel.invokeMethod(
@ -225,7 +232,9 @@ class PatcherAPI {
},
);
} on Exception catch (e, s) {
print(e);
if (kDebugMode) {
print(e);
}
throw await Sentry.captureException(e, stackTrace: s);
}
}
@ -235,7 +244,7 @@ class PatcherAPI {
if (_outFile != null) {
try {
if (patchedApp.isRooted) {
bool hasRootPermissions = await _rootAPI.hasRootPermissions();
final bool hasRootPermissions = await _rootAPI.hasRootPermissions();
if (hasRootPermissions) {
return _rootAPI.installApp(
patchedApp.packageName,
@ -258,9 +267,9 @@ class PatcherAPI {
void exportPatchedFile(String appName, String version) {
try {
if (_outFile != null) {
String newName = _getFileName(appName, version);
final String newName = _getFileName(appName, version);
CRFileSaver.saveFileWithDialog(SaveFileDialogParams(
sourceFilePath: _outFile!.path, destinationFileName: newName));
sourceFilePath: _outFile!.path, destinationFileName: newName,),);
}
} on Exception catch (e, s) {
Sentry.captureException(e, stackTrace: s);
@ -270,11 +279,11 @@ class PatcherAPI {
void sharePatchedFile(String appName, String version) {
try {
if (_outFile != null) {
String newName = _getFileName(appName, version);
int lastSeparator = _outFile!.path.lastIndexOf('/');
String newPath =
final String newName = _getFileName(appName, version);
final int lastSeparator = _outFile!.path.lastIndexOf('/');
final String newPath =
_outFile!.path.substring(0, lastSeparator + 1) + newName;
File shareFile = _outFile!.copySync(newPath);
final File shareFile = _outFile!.copySync(newPath);
ShareExtend.share(shareFile.path, 'file');
}
} on Exception catch (e, s) {
@ -283,34 +292,35 @@ class PatcherAPI {
}
String _getFileName(String appName, String version) {
String prefix = appName.toLowerCase().replaceAll(' ', '-');
String newName = '$prefix-revanced_v$version.apk';
final String prefix = appName.toLowerCase().replaceAll(' ', '-');
final String newName = '$prefix-revanced_v$version.apk';
return newName;
}
Future<void> sharePatcherLog(String logs) async {
Directory appCache = await getTemporaryDirectory();
Directory logDir = Directory('${appCache.path}/logs');
final Directory appCache = await getTemporaryDirectory();
final Directory logDir = Directory('${appCache.path}/logs');
logDir.createSync();
String dateTime = DateTime.now()
final String dateTime = DateTime.now()
.toIso8601String()
.replaceAll('-', '')
.replaceAll(':', '')
.replaceAll('T', '')
.replaceAll('.', '');
File log = File('${logDir.path}/revanced-manager_patcher_$dateTime.log');
final File log =
File('${logDir.path}/revanced-manager_patcher_$dateTime.log');
log.writeAsStringSync(logs);
ShareExtend.share(log.path, 'file');
}
String getRecommendedVersion(String packageName) {
Map<String, int> versions = {};
for (Patch patch in _patches) {
Package? package = patch.compatiblePackages.firstWhereOrNull(
final Map<String, int> versions = {};
for (final Patch patch in _patches) {
final Package? package = patch.compatiblePackages.firstWhereOrNull(
(pack) => pack.name == packageName,
);
if (package != null) {
for (String version in package.versions) {
for (final String version in package.versions) {
versions.update(
version,
(value) => versions[version]! + 1,
@ -320,7 +330,7 @@ class PatcherAPI {
}
}
if (versions.isNotEmpty) {
var entries = versions.entries.toList()
final entries = versions.entries.toList()
..sort((a, b) => a.value.compareTo(b.value));
versions
..clear()

View File

@ -1,15 +1,17 @@
import 'dart:developer';
import 'dart:io';
import 'package:collection/collection.dart';
import 'package:native_dio_client/native_dio_client.dart';
import 'package:dio/dio.dart';
import 'package:dio_http_cache_lts/dio_http_cache_lts.dart';
import 'package:flutter_cache_manager/flutter_cache_manager.dart';
import 'package:injectable/injectable.dart';
import 'package:native_dio_client/native_dio_client.dart';
import 'package:revanced_manager/models/patch.dart';
import 'package:revanced_manager/utils/check_for_gms.dart';
import 'package:timeago/timeago.dart';
import 'package:sentry_flutter/sentry_flutter.dart';
import 'package:sentry_dio/sentry_dio.dart';
import 'package:sentry_flutter/sentry_flutter.dart';
import 'package:timeago/timeago.dart';
@lazySingleton
class RevancedAPI {
@ -22,19 +24,22 @@ class RevancedAPI {
Future<void> initialize(String apiUrl) async {
try {
bool isGMSInstalled = await checkForGMS();
final bool isGMSInstalled = await checkForGMS();
if (!isGMSInstalled) {
_dio = Dio(BaseOptions(
baseUrl: apiUrl,
));
print('ReVanced API: Using default engine + $isGMSInstalled');
_dio = Dio(
BaseOptions(
baseUrl: apiUrl,
),
);
log('ReVanced API: Using default engine + $isGMSInstalled');
} else {
_dio = Dio(BaseOptions(
baseUrl: apiUrl,
))
..httpClientAdapter = NativeAdapter();
print('ReVanced API: Using CronetEngine + $isGMSInstalled');
_dio = Dio(
BaseOptions(
baseUrl: apiUrl,
),
)..httpClientAdapter = NativeAdapter();
log('ReVanced API: Using CronetEngine + $isGMSInstalled');
}
_dio.interceptors.add(_dioCacheManager.interceptor);
_dio.addSentry(
@ -54,12 +59,12 @@ class RevancedAPI {
}
Future<Map<String, List<dynamic>>> getContributors() async {
Map<String, List<dynamic>> contributors = {};
final Map<String, List<dynamic>> contributors = {};
try {
var response = await _dio.get('/contributors', options: _cacheOptions);
List<dynamic> repositories = response.data['repositories'];
for (Map<String, dynamic> repo in repositories) {
String name = repo['name'];
final response = await _dio.get('/contributors', options: _cacheOptions);
final List<dynamic> repositories = response.data['repositories'];
for (final Map<String, dynamic> repo in repositories) {
final String name = repo['name'];
contributors[name] = repo['contributors'];
}
} on Exception catch (e, s) {
@ -71,8 +76,8 @@ class RevancedAPI {
Future<List<Patch>> getPatches() async {
try {
var response = await _dio.get('/patches', options: _cacheOptions);
List<dynamic> patches = response.data;
final response = await _dio.get('/patches', options: _cacheOptions);
final List<dynamic> patches = response.data;
return patches.map((patch) => Patch.fromJson(patch)).toList();
} on Exception catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
@ -85,8 +90,8 @@ class RevancedAPI {
String repoName,
) async {
try {
var response = await _dio.get('/tools', options: _cacheOptions);
List<dynamic> tools = response.data['tools'];
final response = await _dio.get('/tools', options: _cacheOptions);
final List<dynamic> tools = response.data['tools'];
return tools.firstWhereOrNull(
(t) =>
t['repository'] == repoName &&
@ -103,7 +108,7 @@ class RevancedAPI {
String repoName,
) async {
try {
Map<String, dynamic>? release = await _getLatestRelease(
final Map<String, dynamic>? release = await _getLatestRelease(
extension,
repoName,
);
@ -119,12 +124,12 @@ class RevancedAPI {
Future<File?> getLatestReleaseFile(String extension, String repoName) async {
try {
Map<String, dynamic>? release = await _getLatestRelease(
final Map<String, dynamic>? release = await _getLatestRelease(
extension,
repoName,
);
if (release != null) {
String url = release['browser_download_url'];
final String url = release['browser_download_url'];
return await DefaultCacheManager().getSingleFile(url);
}
} on Exception catch (e, s) {
@ -139,12 +144,13 @@ class RevancedAPI {
String repoName,
) async {
try {
Map<String, dynamic>? release = await _getLatestRelease(
final Map<String, dynamic>? release = await _getLatestRelease(
extension,
repoName,
);
if (release != null) {
DateTime timestamp = DateTime.parse(release['timestamp'] as String);
final DateTime timestamp =
DateTime.parse(release['timestamp'] as String);
return format(timestamp, locale: 'en_short');
}
} on Exception catch (e, s) {

View File

@ -8,7 +8,7 @@ class RootAPI {
Future<bool> isRooted() async {
try {
bool? isRooted = await Root.isRootAvailable();
final bool? isRooted = await Root.isRootAvailable();
return isRooted != null && isRooted;
} on Exception catch (e, s) {
await Sentry.captureException(e, stackTrace: s);
@ -70,11 +70,11 @@ class RootAPI {
Future<List<String>> getInstalledApps() async {
try {
String? res = await Root.exec(
final String? res = await Root.exec(
cmd: 'ls "$_managerDirPath"',
);
if (res != null) {
List<String> apps = res.split('\n');
final List<String> apps = res.split('\n');
apps.removeWhere((pack) => pack.isEmpty);
return apps.map((pack) => pack.trim()).toList();
}
@ -132,12 +132,12 @@ class RootAPI {
}
Future<void> installServiceDScript(String packageName) async {
String content = '#!/system/bin/sh\n'
final String content = '#!/system/bin/sh\n'
'while [ "\$(getprop sys.boot_completed | tr -d \'"\'"\'\\\\r\'"\'"\')" != "1" ]; do sleep 3; done\n'
'base_path=$_managerDirPath/$packageName/base.apk\n'
'stock_path=\$(pm path $packageName | grep base | sed \'"\'"\'s/package://g\'"\'"\')\n'
'[ ! -z \$stock_path ] && mount -o bind \$base_path \$stock_path';
String scriptFilePath = '$_serviceDDirPath/$packageName.sh';
r'[ ! -z $stock_path ] && mount -o bind $base_path $stock_path';
final String scriptFilePath = '$_serviceDDirPath/$packageName.sh';
await Root.exec(
cmd: 'echo \'$content\' > "$scriptFilePath"',
);
@ -145,10 +145,10 @@ class RootAPI {
}
Future<void> installPostFsDataScript(String packageName) async {
String content = '#!/system/bin/sh\n'
final String content = '#!/system/bin/sh\n'
'stock_path=\$(pm path $packageName | grep base | sed \'"\'"\'s/package://g\'"\'"\')\n'
'[ ! -z \$stock_path ] && umount -l \$stock_path';
String scriptFilePath = '$_postFsDataDirPath/$packageName.sh';
r'[ ! -z $stock_path ] && umount -l $stock_path';
final String scriptFilePath = '$_postFsDataDirPath/$packageName.sh';
await Root.exec(
cmd: 'echo \'$content\' > "$scriptFilePath"',
);
@ -156,7 +156,7 @@ class RootAPI {
}
Future<void> installApk(String packageName, String patchedFilePath) async {
String newPatchedFilePath = '$_managerDirPath/$packageName/base.apk';
final String newPatchedFilePath = '$_managerDirPath/$packageName/base.apk';
await Root.exec(
cmd: 'cp "$patchedFilePath" "$newPatchedFilePath"',
);
@ -169,7 +169,7 @@ class RootAPI {
}
Future<void> mountApk(String packageName, String originalFilePath) async {
String newPatchedFilePath = '$_managerDirPath/$packageName/base.apk';
final String newPatchedFilePath = '$_managerDirPath/$packageName/base.apk';
await Root.exec(
cmd: 'am force-stop "$packageName"',
);
@ -182,7 +182,7 @@ class RootAPI {
}
Future<bool> isMounted(String packageName) async {
String? res = await Root.exec(
final String? res = await Root.exec(
cmd: 'cat /proc/mounts | grep $packageName',
);
return res != null && res.isNotEmpty;
@ -192,7 +192,7 @@ class RootAPI {
String packageName,
String originalFilePath,
) async {
bool isInstalled = await isAppInstalled(packageName);
final bool isInstalled = await isAppInstalled(packageName);
if (isInstalled && await isMounted(packageName)) {
originalFilePath = '$_managerDirPath/$packageName/original.apk';
await setPermissions(
@ -209,7 +209,8 @@ class RootAPI {
String packageName,
String originalFilePath,
) async {
String originalRootPath = '$_managerDirPath/$packageName/original.apk';
final String originalRootPath =
'$_managerDirPath/$packageName/original.apk';
await Root.exec(
cmd: 'mkdir -p "$_managerDirPath/$packageName"',
);

View File

@ -3,7 +3,6 @@ import 'package:google_fonts/google_fonts.dart';
var lightCustomColorScheme = ColorScheme.fromSeed(
seedColor: Colors.blue,
brightness: Brightness.light,
primary: const Color(0xff1B73E8),
);

View File

@ -7,22 +7,21 @@ import 'package:revanced_manager/theme.dart';
import 'package:stacked_services/stacked_services.dart';
class DynamicThemeBuilder extends StatelessWidget {
final String title;
final Widget home;
final Iterable<LocalizationsDelegate> localizationsDelegates;
const DynamicThemeBuilder({
Key? key,
required this.title,
required this.home,
required this.localizationsDelegates,
}) : super(key: key);
final String title;
final Widget home;
final Iterable<LocalizationsDelegate> localizationsDelegates;
@override
Widget build(BuildContext context) {
return DynamicColorBuilder(
builder: (lightColorScheme, darkColorScheme) {
ThemeData lightDynamicTheme = ThemeData(
final ThemeData lightDynamicTheme = ThemeData(
useMaterial3: true,
canvasColor: lightColorScheme?.background,
navigationBarTheme: NavigationBarThemeData(
@ -45,7 +44,7 @@ class DynamicThemeBuilder extends StatelessWidget {
toggleableActiveColor: lightColorScheme?.primary,
textTheme: GoogleFonts.robotoTextTheme(ThemeData.light().textTheme),
);
ThemeData darkDynamicTheme = ThemeData(
final ThemeData darkDynamicTheme = ThemeData(
useMaterial3: true,
canvasColor: darkColorScheme?.background,
navigationBarTheme: NavigationBarThemeData(

View File

@ -1,10 +1,10 @@
import 'package:flutter/material.dart';
import 'package:flutter_i18n/flutter_i18n.dart';
import 'package:revanced_manager/ui/views/app_selector/app_selector_viewmodel.dart';
import 'package:revanced_manager/ui/widgets/appSelectorView/app_skeleton_loader.dart';
import 'package:revanced_manager/ui/widgets/appSelectorView/installed_app_item.dart';
import 'package:revanced_manager/ui/widgets/shared/search_bar.dart';
import 'package:revanced_manager/ui/widgets/appSelectorView/app_skeleton_loader.dart';
import 'package:stacked/stacked.dart' hide SkeletonLoader;
import 'package:revanced_manager/ui/views/app_selector/app_selector_viewmodel.dart';
class AppSelectorView extends StatefulWidget {
const AppSelectorView({Key? key}) : super(key: key);
@ -19,7 +19,7 @@ class _AppSelectorViewState extends State<AppSelectorView> {
@override
Widget build(BuildContext context) {
return ViewModelBuilder<AppSelectorViewModel>.reactive(
onModelReady: (model) => model.initialize(),
onViewModelReady: (model) => model.initialize(),
viewModelBuilder: () => AppSelectorViewModel(),
builder: (context, model, child) => Scaffold(
resizeToAvoidBottomInset: false,
@ -36,7 +36,6 @@ class _AppSelectorViewState extends State<AppSelectorView> {
SliverAppBar(
pinned: true,
floating: true,
snap: false,
title: I18nText(
'appSelectorView.viewTitle',
child: Text(
@ -61,7 +60,6 @@ class _AppSelectorViewState extends State<AppSelectorView> {
horizontal: 12.0,
),
child: SearchBar(
showSelectIcon: false,
hintText: FlutterI18n.translate(
context,
'appSelectorView.searchBarHint',
@ -88,17 +86,19 @@ class _AppSelectorViewState extends State<AppSelectorView> {
child: Column(
children: model
.getFilteredApps(_query)
.map((app) => InstalledAppItem(
name: app.appName,
pkgName: app.packageName,
icon: app.icon,
patchesCount:
model.patchesCount(app.packageName),
onTap: () {
model.selectApp(app);
Navigator.of(context).pop();
},
))
.map(
(app) => InstalledAppItem(
name: app.appName,
pkgName: app.packageName,
icon: app.icon,
patchesCount:
model.patchesCount(app.packageName),
onTap: () {
model.selectApp(app);
Navigator.of(context).pop();
},
),
)
.toList(),
),
),

View File

@ -1,15 +1,16 @@
import 'dart:io';
import 'package:device_apps/device_apps.dart';
import 'package:file_picker/file_picker.dart';
import 'package:flutter/material.dart';
import 'package:revanced_manager/app/app.locator.dart';
import 'package:revanced_manager/models/patched_application.dart';
import 'package:revanced_manager/services/manager_api.dart';
import 'package:revanced_manager/services/patcher_api.dart';
import 'package:revanced_manager/services/toast.dart';
import 'package:revanced_manager/ui/views/patcher/patcher_viewmodel.dart';
import 'package:sentry_flutter/sentry_flutter.dart';
import 'package:stacked/stacked.dart';
import '../../../services/manager_api.dart';
class AppSelectorViewModel extends BaseViewModel {
final PatcherAPI _patcherAPI = locator<PatcherAPI>();
@ -22,16 +23,21 @@ class AppSelectorViewModel extends BaseViewModel {
}
Future<void> initialize() async {
apps.addAll(await _patcherAPI.getFilteredInstalledApps(_managerAPI.areUniversalPatchesEnabled()));
apps.sort(((a, b) => _patcherAPI
.getFilteredPatches(b.packageName)
.length
.compareTo(_patcherAPI.getFilteredPatches(a.packageName).length)));
apps.addAll(
await _patcherAPI
.getFilteredInstalledApps(_managerAPI.areUniversalPatchesEnabled()),
);
apps.sort(
(a, b) => _patcherAPI
.getFilteredPatches(b.packageName)
.length
.compareTo(_patcherAPI.getFilteredPatches(a.packageName).length),
);
noApps = apps.isEmpty;
notifyListeners();
}
void selectApp(ApplicationWithIcon application) async {
Future<void> selectApp(ApplicationWithIcon application) async {
locator<PatcherViewModel>().selectedApp = PatchedApplication(
name: application.appName,
packageName: application.packageName,
@ -46,22 +52,24 @@ class AppSelectorViewModel extends BaseViewModel {
Future<void> selectAppFromStorage(BuildContext context) async {
try {
FilePickerResult? result = await FilePicker.platform.pickFiles(
final FilePickerResult? result = await FilePicker.platform.pickFiles(
type: FileType.custom,
allowedExtensions: ['apk'],
);
if (result != null && result.files.single.path != null) {
File apkFile = File(result.files.single.path!);
List<String> pathSplit = result.files.single.path!.split("/");
final File apkFile = File(result.files.single.path!);
final List<String> pathSplit = result.files.single.path!.split('/');
pathSplit.removeLast();
Directory filePickerCacheDir = Directory(pathSplit.join("/"));
Iterable<File> deletableFiles =
final Directory filePickerCacheDir = Directory(pathSplit.join('/'));
final Iterable<File> deletableFiles =
(await filePickerCacheDir.list().toList()).whereType<File>();
for (var file in deletableFiles) {
if (file.path != apkFile.path && file.path.endsWith(".apk"))
for (final file in deletableFiles) {
if (file.path != apkFile.path && file.path.endsWith('.apk')) {
file.delete();
}
}
ApplicationWithIcon? application = await DeviceApps.getAppFromStorage(
final ApplicationWithIcon? application =
await DeviceApps.getAppFromStorage(
apkFile.path,
true,
) as ApplicationWithIcon?;
@ -87,10 +95,12 @@ class AppSelectorViewModel extends BaseViewModel {
List<ApplicationWithIcon> getFilteredApps(String query) {
return apps
.where((app) =>
query.isEmpty ||
query.length < 2 ||
app.appName.toLowerCase().contains(query.toLowerCase()))
.where(
(app) =>
query.isEmpty ||
query.length < 2 ||
app.appName.toLowerCase().contains(query.toLowerCase()),
)
.toList();
}
}

View File

@ -13,7 +13,7 @@ class ContributorsView extends StatelessWidget {
Widget build(BuildContext context) {
return ViewModelBuilder<ContributorsViewModel>.reactive(
viewModelBuilder: () => ContributorsViewModel(),
onModelReady: (model) => model.getContributors(),
onViewModelReady: (model) => model.getContributors(),
builder: (context, model, child) => Scaffold(
body: CustomScrollView(
slivers: <Widget>[

View File

@ -11,7 +11,7 @@ class ContributorsViewModel extends BaseViewModel {
List<dynamic> managerContributors = [];
Future<void> getContributors() async {
Map<String, List<dynamic>> contributors =
final Map<String, List<dynamic>> contributors =
await _managerAPI.getContributors();
patcherContributors = contributors[_managerAPI.defaultPatcherRepo] ?? [];
patchesContributors = contributors[_managerAPI.getPatchesRepo()] ?? [];

View File

@ -1,7 +1,7 @@
import 'package:animations/animations.dart';
import 'package:flutter/material.dart';
import 'package:flutter_i18n/flutter_i18n.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:animations/animations.dart';
import 'package:revanced_manager/app/app.locator.dart';
import 'package:revanced_manager/ui/views/home/home_viewmodel.dart';
import 'package:revanced_manager/ui/widgets/homeView/available_updates_card.dart';
@ -18,7 +18,7 @@ class HomeView extends StatelessWidget {
Widget build(BuildContext context) {
return ViewModelBuilder<HomeViewModel>.reactive(
disposeViewModel: false,
onModelReady: (model) => model.initialize(context),
onViewModelReady: (model) => model.initialize(context),
viewModelBuilder: () => locator<HomeViewModel>(),
builder: (context, model, child) => Scaffold(
body: RefreshIndicator(
@ -48,7 +48,7 @@ class HomeView extends StatelessWidget {
'homeView.updatesSubtitle',
child: Text(
'',
style: Theme.of(context).textTheme.headline6!,
style: Theme.of(context).textTheme.headline6,
),
),
const SizedBox(height: 10),
@ -61,7 +61,7 @@ class HomeView extends StatelessWidget {
'homeView.patchedSubtitle',
child: Text(
'',
style: Theme.of(context).textTheme.headline6!,
style: Theme.of(context).textTheme.headline6,
),
),
const SizedBox(height: 8),

View File

@ -1,5 +1,6 @@
// ignore_for_file: use_build_context_synchronously
import 'dart:io';
import 'package:app_installer/app_installer.dart';
import 'package:cross_connectivity/cross_connectivity.dart';
import 'package:device_apps/device_apps.dart';
@ -10,9 +11,9 @@ import 'package:injectable/injectable.dart';
import 'package:revanced_manager/app/app.locator.dart';
import 'package:revanced_manager/app/app.router.dart';
import 'package:revanced_manager/models/patched_application.dart';
import 'package:revanced_manager/services/github_api.dart';
import 'package:revanced_manager/services/manager_api.dart';
import 'package:revanced_manager/services/patcher_api.dart';
import 'package:revanced_manager/services/github_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';
@ -47,7 +48,7 @@ class HomeViewModel extends BaseViewModel {
.resolvePlatformSpecificImplementation<
AndroidFlutterLocalNotificationsPlugin>()
?.requestPermission();
bool isConnected = await Connectivity().checkConnection();
final bool isConnected = await Connectivity().checkConnection();
if (!isConnected) {
_toast.showBottom('homeView.noConnection');
}
@ -67,7 +68,7 @@ class HomeViewModel extends BaseViewModel {
notifyListeners();
}
void navigateToPatcher(PatchedApplication app) async {
Future<void> navigateToPatcher(PatchedApplication app) async {
locator<PatcherViewModel>().selectedApp = app;
locator<PatcherViewModel>().selectedPatches =
await _patcherAPI.getAppliedPatches(app.appliedPatches);
@ -88,13 +89,13 @@ class HomeViewModel extends BaseViewModel {
}
Future<bool> hasManagerUpdates() async {
String? latestVersion = await _managerAPI.getLatestManagerVersion();
String currentVersion = await _managerAPI.getCurrentManagerVersion();
final String? latestVersion = await _managerAPI.getLatestManagerVersion();
final String currentVersion = await _managerAPI.getCurrentManagerVersion();
if (latestVersion != null) {
try {
int latestVersionInt =
final int latestVersionInt =
int.parse(latestVersion.replaceAll(RegExp('[^0-9]'), ''));
int currentVersionInt =
final int currentVersionInt =
int.parse(currentVersion.replaceAll(RegExp('[^0-9]'), ''));
return latestVersionInt > currentVersionInt;
} on Exception catch (e, s) {
@ -108,7 +109,7 @@ class HomeViewModel extends BaseViewModel {
Future<void> updateManager(BuildContext context) async {
try {
_toast.showBottom('homeView.downloadingMessage');
File? managerApk = await _managerAPI.downloadManager();
final File? managerApk = await _managerAPI.downloadManager();
if (managerApk != null) {
await flutterLocalNotificationsPlugin.zonedSchedule(
0,

View File

@ -2,9 +2,9 @@ import 'package:flutter/material.dart';
import 'package:flutter_i18n/flutter_i18n.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:revanced_manager/ui/views/installer/installer_viewmodel.dart';
import 'package:revanced_manager/ui/widgets/shared/custom_material_button.dart';
import 'package:revanced_manager/ui/widgets/installerView/gradient_progress_indicator.dart';
import 'package:revanced_manager/ui/widgets/shared/custom_card.dart';
import 'package:revanced_manager/ui/widgets/shared/custom_material_button.dart';
import 'package:revanced_manager/ui/widgets/shared/custom_popup_menu.dart';
import 'package:revanced_manager/ui/widgets/shared/custom_sliver_app_bar.dart';
import 'package:stacked/stacked.dart';
@ -15,7 +15,7 @@ class InstallerView extends StatelessWidget {
@override
Widget build(BuildContext context) {
return ViewModelBuilder<InstallerViewModel>.reactive(
onModelReady: (model) => model.initialize(context),
onViewModelReady: (model) => model.initialize(context),
viewModelBuilder: () => InstallerViewModel(),
builder: (context, model, child) => WillPopScope(
child: SafeArea(
@ -48,15 +48,15 @@ class InstallerView extends StatelessWidget {
),
),
),
1: I18nText(
'installerView.exportApkMenuOption',
child: const Text(
'',
style: TextStyle(
fontWeight: FontWeight.bold,
),
1: I18nText(
'installerView.exportApkMenuOption',
child: const Text(
'',
style: TextStyle(
fontWeight: FontWeight.bold,
),
),
),
2: I18nText(
'installerView.shareLogMenuOption',
child: const Text(
@ -71,9 +71,9 @@ class InstallerView extends StatelessWidget {
),
],
bottom: PreferredSize(
preferredSize: const Size(double.infinity, 1.0),
child:
GradientProgressIndicator(progress: model.progress!)),
preferredSize: const Size(double.infinity, 1.0),
child: GradientProgressIndicator(progress: model.progress),
),
),
SliverPadding(
padding: const EdgeInsets.all(20.0),

View File

@ -51,10 +51,8 @@ class InstallerViewModel extends BaseViewModel {
context,
'installerView.notificationText',
),
notificationImportance: AndroidNotificationImportance.Default,
notificationIcon: const AndroidResource(
name: 'ic_notification',
defType: 'drawable',
),
),
).then((value) => FlutterBackground.enableBackgroundExecution());
@ -73,10 +71,10 @@ class InstallerViewModel extends BaseViewModel {
switch (call.method) {
case 'update':
if (call.arguments != null) {
Map<dynamic, dynamic> arguments = call.arguments;
double progress = arguments['progress'];
String header = arguments['header'];
String log = arguments['log'];
final Map<dynamic, dynamic> arguments = call.arguments;
final double progress = arguments['progress'];
final String header = arguments['header'];
final String log = arguments['log'];
update(progress, header, log);
}
break;
@ -159,13 +157,14 @@ class InstallerViewModel extends BaseViewModel {
}
}
void installResult(BuildContext context, bool installAsRoot) async {
Future<void> installResult(BuildContext context, bool installAsRoot) async {
try {
_app.isRooted = installAsRoot;
bool hasMicroG = _patches.any((p) => p.name.endsWith('microg-support'));
bool rootMicroG = installAsRoot && hasMicroG;
bool rootFromStorage = installAsRoot && _app.isFromStorage;
bool ytWithoutRootMicroG =
final bool hasMicroG =
_patches.any((p) => p.name.endsWith('microg-support'));
final bool rootMicroG = installAsRoot && hasMicroG;
final bool rootFromStorage = installAsRoot && _app.isFromStorage;
final bool ytWithoutRootMicroG =
!installAsRoot && !hasMicroG && _app.packageName.contains('youtube');
if (rootMicroG || rootFromStorage || ytWithoutRootMicroG) {
return showDialog(

View File

@ -11,7 +11,7 @@ class NavigationView extends StatelessWidget {
@override
Widget build(BuildContext context) {
return ViewModelBuilder<NavigationViewModel>.reactive(
onModelReady: (model) => model.initialize(context),
onViewModelReady: (model) => model.initialize(context),
viewModelBuilder: () => locator<NavigationViewModel>(),
builder: (context, model, child) => Scaffold(
body: PageTransitionSwitcher(

View File

@ -15,9 +15,9 @@ import 'package:stacked/stacked.dart';
@lazySingleton
class NavigationViewModel extends IndexTrackingViewModel {
void initialize(BuildContext context) async {
Future<void> initialize(BuildContext context) async {
locator<Toast>().initialize(context);
SharedPreferences prefs = await SharedPreferences.getInstance();
final SharedPreferences prefs = await SharedPreferences.getInstance();
if (prefs.getBool('permissionsRequested') == null) {
await prefs.setBool('permissionsRequested', true);
RootAPI().hasRootPermissions().then(
@ -27,7 +27,7 @@ class NavigationViewModel extends IndexTrackingViewModel {
);
}
if (prefs.getBool('useDarkTheme') == null) {
bool isDark =
final bool isDark =
MediaQuery.of(context).platformBrightness != Brightness.light;
await prefs.setBool('useDarkTheme', isDark);
await DynamicTheme.of(context)!.setTheme(isDark ? 1 : 0);

View File

@ -40,18 +40,18 @@ class PatcherViewModel extends BaseViewModel {
}
Future<bool> isValidPatchConfig() async {
bool needsResourcePatching = await _patcherAPI.needsResourcePatching(
final bool needsResourcePatching = await _patcherAPI.needsResourcePatching(
selectedPatches,
);
if (needsResourcePatching && selectedApp != null) {
bool isSplit = await _managerAPI.isSplitApk(selectedApp!);
final bool isSplit = await _managerAPI.isSplitApk(selectedApp!);
return !isSplit;
}
return true;
}
Future<void> showPatchConfirmationDialog(BuildContext context) async {
bool isValid = await isValidPatchConfig();
final bool isValid = await isValidPatchConfig();
if (isValid) {
navigateToInstaller();
} else {
@ -110,9 +110,9 @@ class PatcherViewModel extends BaseViewModel {
Future<void> loadLastSelectedPatches() async {
this.selectedPatches.clear();
List<String> selectedPatches =
final List<String> selectedPatches =
await _managerAPI.getSelectedPatches(selectedApp!.originalPackageName);
List<Patch> patches =
final List<Patch> patches =
_patcherAPI.getFilteredPatches(selectedApp!.originalPackageName);
this.selectedPatches
.addAll(patches.where((patch) => selectedPatches.contains(patch.name)));

View File

@ -20,7 +20,7 @@ class _PatchesSelectorViewState extends State<PatchesSelectorView> {
@override
Widget build(BuildContext context) {
return ViewModelBuilder<PatchesSelectorViewModel>.reactive(
onModelReady: (model) => model.initialize(),
onViewModelReady: (model) => model.initialize(),
viewModelBuilder: () => PatchesSelectorViewModel(),
builder: (context, model, child) => Scaffold(
resizeToAvoidBottomInset: false,
@ -45,7 +45,6 @@ class _PatchesSelectorViewState extends State<PatchesSelectorView> {
SliverAppBar(
pinned: true,
floating: true,
snap: false,
title: I18nText(
'patchesSelectorView.viewTitle',
child: Text(

View File

@ -1,4 +1,5 @@
import 'package:collection/collection.dart';
import 'package:flutter/material.dart';
import 'package:flutter_i18n/widgets/I18nText.dart';
import 'package:revanced_manager/app/app.locator.dart';
import 'package:revanced_manager/models/patch.dart';
@ -10,7 +11,6 @@ import 'package:revanced_manager/services/toast.dart';
import 'package:revanced_manager/ui/views/patcher/patcher_viewmodel.dart';
import 'package:revanced_manager/ui/widgets/shared/custom_material_button.dart';
import 'package:stacked/stacked.dart';
import 'package:flutter/material.dart';
class PatchesSelectorViewModel extends BaseViewModel {
final PatcherAPI _patcherAPI = locator<PatcherAPI>();
@ -26,9 +26,11 @@ class PatchesSelectorViewModel extends BaseViewModel {
Future<void> initialize() async {
getPatchesVersion();
patches.addAll(_patcherAPI.getFilteredPatches(
locator<PatcherViewModel>().selectedApp!.originalPackageName,
));
patches.addAll(
_patcherAPI.getFilteredPatches(
locator<PatcherViewModel>().selectedApp!.originalPackageName,
),
);
patches.sort((a, b) => a.name.compareTo(b.name));
notifyListeners();
}
@ -84,8 +86,11 @@ class PatchesSelectorViewModel extends BaseViewModel {
selectedPatches.clear();
if (_managerAPI.areExperimentalPatchesEnabled() == false) {
selectedPatches.addAll(patches.where(
(element) => element.excluded == false && isPatchSupported(element)));
selectedPatches.addAll(
patches.where(
(element) => element.excluded == false && isPatchSupported(element),
),
);
}
if (_managerAPI.areExperimentalPatchesEnabled()) {
@ -117,15 +122,18 @@ class PatchesSelectorViewModel extends BaseViewModel {
patchesVersion = await _githubAPI
.getLastestReleaseVersion(_managerAPI.getPatchesRepo());
}
return null;
}
List<Patch> getQueriedPatches(String query) {
return patches
.where((patch) =>
query.isEmpty ||
query.length < 2 ||
patch.name.toLowerCase().contains(query.toLowerCase()) ||
patch.getSimpleName().toLowerCase().contains(query.toLowerCase()))
.where(
(patch) =>
query.isEmpty ||
query.length < 2 ||
patch.name.toLowerCase().contains(query.toLowerCase()) ||
patch.getSimpleName().toLowerCase().contains(query.toLowerCase()),
)
.toList();
}
@ -134,8 +142,8 @@ class PatchesSelectorViewModel extends BaseViewModel {
}
List<String> getSupportedVersions(Patch patch) {
PatchedApplication app = locator<PatcherViewModel>().selectedApp!;
Package? package = patch.compatiblePackages.firstWhereOrNull(
final PatchedApplication app = locator<PatcherViewModel>().selectedApp!;
final Package? package = patch.compatiblePackages.firstWhereOrNull(
(pack) => pack.name == app.packageName,
);
if (package != null) {
@ -146,10 +154,13 @@ class PatchesSelectorViewModel extends BaseViewModel {
}
bool isPatchSupported(Patch patch) {
PatchedApplication app = locator<PatcherViewModel>().selectedApp!;
return patch.compatiblePackages.isEmpty || patch.compatiblePackages.any((pack) =>
pack.name == app.packageName &&
(pack.versions.isEmpty || pack.versions.contains(app.version)));
final PatchedApplication app = locator<PatcherViewModel>().selectedApp!;
return patch.compatiblePackages.isEmpty ||
patch.compatiblePackages.any(
(pack) =>
pack.name == app.packageName &&
(pack.versions.isEmpty || pack.versions.contains(app.version)),
);
}
void onMenuSelection(value) {
@ -161,20 +172,23 @@ class PatchesSelectorViewModel extends BaseViewModel {
}
Future<void> saveSelectedPatches() async {
List<String> selectedPatches =
final List<String> selectedPatches =
this.selectedPatches.map((patch) => patch.name).toList();
await _managerAPI.setSelectedPatches(
locator<PatcherViewModel>().selectedApp!.originalPackageName,
selectedPatches);
locator<PatcherViewModel>().selectedApp!.originalPackageName,
selectedPatches,
);
}
Future<void> loadSelectedPatches() async {
List<String> selectedPatches = await _managerAPI.getSelectedPatches(
locator<PatcherViewModel>().selectedApp!.originalPackageName);
final List<String> selectedPatches = await _managerAPI.getSelectedPatches(
locator<PatcherViewModel>().selectedApp!.originalPackageName,
);
if (selectedPatches.isNotEmpty) {
this.selectedPatches.clear();
this.selectedPatches.addAll(
patches.where((patch) => selectedPatches.contains(patch.name)));
patches.where((patch) => selectedPatches.contains(patch.name)),
);
} else {
locator<Toast>().showBottom('patchesSelectorView.noSavedPatches');
}

View File

@ -15,7 +15,7 @@ class SManageApiUrl extends BaseViewModel {
final TextEditingController _apiUrlController = TextEditingController();
Future<void> showApiUrlDialog(BuildContext context) async {
String apiUrl = _managerAPI.getApiUrl();
final String apiUrl = _managerAPI.getApiUrl();
_apiUrlController.text = apiUrl.replaceAll('https://', '');
return showDialog(
context: context,

View File

@ -19,9 +19,9 @@ class SManageSources extends BaseViewModel {
final TextEditingController _intSourceController = TextEditingController();
Future<void> showSourcesDialog(BuildContext context) async {
String hostRepository = _managerAPI.getRepoUrl();
String patchesRepo = _managerAPI.getPatchesRepo();
String integrationsRepo = _managerAPI.getIntegrationsRepo();
final String hostRepository = _managerAPI.getRepoUrl();
final String patchesRepo = _managerAPI.getPatchesRepo();
final String integrationsRepo = _managerAPI.getIntegrationsRepo();
_hostSourceController.text = hostRepository;
_orgPatSourceController.text = patchesRepo.split('/')[0];
_patSourceController.text = patchesRepo.split('/')[1];

View File

@ -23,9 +23,9 @@ class SUpdateTheme extends BaseViewModel {
return _managerAPI.getUseDynamicTheme();
}
void setUseDynamicTheme(BuildContext context, bool value) async {
Future<void> setUseDynamicTheme(BuildContext context, bool value) async {
await _managerAPI.setUseDynamicTheme(value);
int currentTheme = DynamicTheme.of(context)!.themeId;
final int currentTheme = DynamicTheme.of(context)!.themeId;
if (currentTheme.isEven) {
await DynamicTheme.of(context)!.setTheme(value ? 2 : 0);
} else {
@ -38,9 +38,9 @@ class SUpdateTheme extends BaseViewModel {
return _managerAPI.getUseDarkTheme();
}
void setUseDarkTheme(BuildContext context, bool value) async {
Future<void> setUseDarkTheme(BuildContext context, bool value) async {
await _managerAPI.setUseDarkTheme(value);
int currentTheme = DynamicTheme.of(context)!.themeId;
final int currentTheme = DynamicTheme.of(context)!.themeId;
if (currentTheme < 2) {
await DynamicTheme.of(context)!.setTheme(value ? 1 : 0);
} else {

View File

@ -3,7 +3,6 @@
import 'package:flutter/material.dart';
import 'package:flutter_i18n/flutter_i18n.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:revanced_manager/ui/views/settings/settingsFragement/settings_update_language.dart';
import 'package:revanced_manager/ui/views/settings/settingsFragement/settings_update_theme.dart';
import 'package:revanced_manager/ui/views/settings/settings_viewmodel.dart';
import 'package:revanced_manager/ui/widgets/settingsView/settings_advanced_section.dart';

View File

@ -70,12 +70,12 @@ class SettingsViewModel extends BaseViewModel {
Future<void> exportPatches() async {
try {
File outFile = File(_managerAPI.storedPatchesFile);
final File outFile = File(_managerAPI.storedPatchesFile);
if (outFile.existsSync()) {
String dateTime =
final String dateTime =
DateTime.now().toString().replaceAll(' ', '_').split('.').first;
await CRFileSaver.saveFileWithDialog(SaveFileDialogParams(
sourceFilePath: outFile.path, destinationFileName: 'selected_patches_$dateTime.json'));
sourceFilePath: outFile.path, destinationFileName: 'selected_patches_$dateTime.json',),);
_toast.showBottom('settingsView.exportedPatches');
} else {
_toast.showBottom('settingsView.noExportFileFound');
@ -87,12 +87,12 @@ class SettingsViewModel extends BaseViewModel {
Future<void> importPatches() async {
try {
FilePickerResult? result = await FilePicker.platform.pickFiles(
final FilePickerResult? result = await FilePicker.platform.pickFiles(
type: FileType.custom,
allowedExtensions: ['json'],
);
if (result != null && result.files.single.path != null) {
File inFile = File(result.files.single.path!);
final File inFile = File(result.files.single.path!);
inFile.copySync(_managerAPI.storedPatchesFile);
inFile.delete();
if (locator<PatcherViewModel>().selectedApp != null) {
@ -112,13 +112,13 @@ class SettingsViewModel extends BaseViewModel {
}
Future<int> getSdkVersion() async {
AndroidDeviceInfo info = await DeviceInfoPlugin().androidInfo;
final AndroidDeviceInfo info = await DeviceInfoPlugin().androidInfo;
return info.version.sdkInt ?? -1;
}
Future<void> deleteLogs() async {
Directory appCacheDir = await getTemporaryDirectory();
Directory logsDir = Directory('${appCacheDir.path}/logs');
final Directory appCacheDir = await getTemporaryDirectory();
final Directory logsDir = Directory('${appCacheDir.path}/logs');
if (logsDir.existsSync()) {
logsDir.deleteSync(recursive: true);
}
@ -126,17 +126,18 @@ class SettingsViewModel extends BaseViewModel {
}
Future<void> exportLogcatLogs() async {
Directory appCache = await getTemporaryDirectory();
Directory logDir = Directory('${appCache.path}/logs');
final Directory appCache = await getTemporaryDirectory();
final Directory logDir = Directory('${appCache.path}/logs');
logDir.createSync();
String dateTime = DateTime.now()
final String dateTime = DateTime.now()
.toIso8601String()
.replaceAll('-', '')
.replaceAll(':', '')
.replaceAll('T', '')
.replaceAll('.', '');
File logcat = File('${logDir.path}/revanced-manager_logcat_$dateTime.log');
String logs = await Logcat.execute();
final File logcat =
File('${logDir.path}/revanced-manager_logcat_$dateTime.log');
final String logs = await Logcat.execute();
logcat.writeAsStringSync(logs);
ShareExtend.share(logcat.path, 'file');
}

View File

@ -8,12 +8,11 @@ import 'package:revanced_manager/ui/widgets/shared/custom_sliver_app_bar.dart';
import 'package:stacked/stacked.dart';
class AppInfoView extends StatelessWidget {
final PatchedApplication app;
const AppInfoView({
Key? key,
required this.app,
}) : super(key: key);
final PatchedApplication app;
@override
Widget build(BuildContext context) {

View File

@ -29,7 +29,7 @@ class AppInfoViewModel extends BaseViewModel {
) async {
bool isUninstalled = true;
if (app.isRooted) {
bool hasRootPermissions = await _rootAPI.hasRootPermissions();
final bool hasRootPermissions = await _rootAPI.hasRootPermissions();
if (hasRootPermissions) {
await _rootAPI.deleteApp(app.packageName, app.apkFilePath);
if (!onlyUnpatch) {
@ -45,7 +45,7 @@ class AppInfoViewModel extends BaseViewModel {
}
}
void navigateToPatcher(PatchedApplication app) async {
Future<void> navigateToPatcher(PatchedApplication app) async {
locator<PatcherViewModel>().selectedApp = app;
locator<PatcherViewModel>().selectedPatches =
await _patcherAPI.getAppliedPatches(app.appliedPatches);
@ -62,7 +62,7 @@ class AppInfoViewModel extends BaseViewModel {
PatchedApplication app,
bool onlyUnpatch,
) async {
bool hasRootPermissions = await _rootAPI.hasRootPermissions();
final bool hasRootPermissions = await _rootAPI.hasRootPermissions();
if (app.isRooted && !hasRootPermissions) {
return showDialog(
context: context,
@ -134,7 +134,8 @@ class AppInfoViewModel extends BaseViewModel {
title: I18nText('appInfoView.appliedPatchesLabel'),
backgroundColor: Theme.of(context).colorScheme.secondaryContainer,
content: SingleChildScrollView(
child: Text(getAppliedPatchesString(app.appliedPatches))),
child: Text(getAppliedPatchesString(app.appliedPatches)),
),
actions: <Widget>[
CustomMaterialButton(
label: I18nText('okButton'),
@ -146,13 +147,15 @@ class AppInfoViewModel extends BaseViewModel {
}
String getAppliedPatchesString(List<String> appliedPatches) {
List<String> names = appliedPatches
.map((p) => p
.replaceAll('-', ' ')
.split('-')
.join(' ')
.toTitleCase()
.replaceFirst('Microg', 'MicroG'))
final List<String> names = appliedPatches
.map(
(p) => p
.replaceAll('-', ' ')
.split('-')
.join(' ')
.toTitleCase()
.replaceFirst('Microg', 'MicroG'),
)
.toList();
return '\u2022 ${names.join('\n\u2022 ')}';
}

View File

@ -21,7 +21,6 @@ class AppSkeletonLoader extends StatelessWidget {
style: SkeletonAvatarStyle(
width: screenWidth * 0.15,
height: screenWidth * 0.15,
shape: BoxShape.rectangle,
borderRadius: const BorderRadius.all(Radius.circular(12)),
),
),

View File

@ -3,12 +3,6 @@ import 'package:flutter/material.dart';
import 'package:revanced_manager/ui/widgets/shared/custom_card.dart';
class InstalledAppItem extends StatefulWidget {
final String name;
final String pkgName;
final Uint8List icon;
final int patchesCount;
final Function()? onTap;
const InstalledAppItem({
Key? key,
required this.name,
@ -17,6 +11,11 @@ class InstalledAppItem extends StatefulWidget {
required this.patchesCount,
this.onTap,
}) : super(key: key);
final String name;
final String pkgName;
final Uint8List icon;
final int patchesCount;
final Function()? onTap;
@override
State<InstalledAppItem> createState() => _InstalledAppItemState();
@ -48,7 +47,6 @@ class _InstalledAppItemState extends State<InstalledAppItem> {
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Text(
widget.name,
@ -62,8 +60,8 @@ class _InstalledAppItemState extends State<InstalledAppItem> {
const SizedBox(width: 6),
Text(
widget.patchesCount == 1
? "${widget.patchesCount} patch"
: "${widget.patchesCount} patches",
? '${widget.patchesCount} patch'
: '${widget.patchesCount} patches',
style: TextStyle(
fontSize: 8,
color: Theme.of(context).colorScheme.secondary,

View File

@ -6,14 +6,13 @@ import 'package:revanced_manager/ui/widgets/shared/custom_card.dart';
import 'package:url_launcher/url_launcher.dart';
class ContributorsCard extends StatefulWidget {
final String title;
final List<dynamic> contributors;
const ContributorsCard({
Key? key,
required this.title,
required this.contributors,
}) : super(key: key);
final String title;
final List<dynamic> contributors;
@override
State<ContributorsCard> createState() => _ContributorsCardState();

View File

@ -3,7 +3,6 @@ import 'package:flutter_i18n/flutter_i18n.dart';
import 'package:revanced_manager/app/app.locator.dart';
import 'package:revanced_manager/models/patched_application.dart';
import 'package:revanced_manager/ui/views/home/home_viewmodel.dart';
import 'package:revanced_manager/ui/widgets/shared/application_item.dart';
import 'package:revanced_manager/ui/widgets/shared/custom_card.dart';
class AvailableUpdatesCard extends StatelessWidget {

View File

@ -2,16 +2,15 @@ import 'package:flutter/material.dart';
import 'package:flutter_i18n/flutter_i18n.dart';
import 'package:revanced_manager/app/app.locator.dart';
import 'package:revanced_manager/ui/views/home/home_viewmodel.dart';
import 'package:revanced_manager/ui/widgets/shared/custom_material_button.dart';
import 'package:revanced_manager/ui/widgets/shared/custom_card.dart';
import 'package:revanced_manager/ui/widgets/shared/custom_material_button.dart';
class LatestCommitCard extends StatefulWidget {
final Function() onPressed;
const LatestCommitCard({
Key? key,
required this.onPressed,
}) : super(key: key);
final Function() onPressed;
@override
State<LatestCommitCard> createState() => _LatestCommitCardState();
@ -73,7 +72,6 @@ class _LatestCommitCardState extends State<LatestCommitCard> {
builder: (context, snapshot) => Opacity(
opacity: snapshot.hasData && snapshot.data! ? 1.0 : 0.25,
child: CustomMaterialButton(
isExpanded: false,
label: I18nText('latestCommitCard.updateButton'),
onPressed: snapshot.hasData && snapshot.data!
? widget.onPressed

View File

@ -14,7 +14,6 @@ class UpdateConfirmationDialog extends StatelessWidget {
return DraggableScrollableSheet(
expand: false,
initialChildSize: 0.5,
snap: true,
snapSizes: const [0.5],
builder: (context, scrollController) => SingleChildScrollView(
@ -37,7 +36,11 @@ class UpdateConfirmationDialog extends StatelessWidget {
children: [
Padding(
padding: const EdgeInsets.only(
top: 40.0, left: 24.0, right: 24.0, bottom: 32.0),
top: 40.0,
left: 24.0,
right: 24.0,
bottom: 32.0,
),
child: Row(
children: [
Expanded(
@ -47,10 +50,11 @@ class UpdateConfirmationDialog extends StatelessWidget {
I18nText(
'homeView.updateDialogTitle',
child: const Text(
"",
'',
style: TextStyle(
fontSize: 24,
fontWeight: FontWeight.bold),
fontSize: 24,
fontWeight: FontWeight.bold,
),
),
),
const SizedBox(height: 4.0),
@ -63,7 +67,7 @@ class UpdateConfirmationDialog extends StatelessWidget {
),
const SizedBox(width: 8.0),
Text(
snapshot.data!["tag_name"] ?? "Unknown",
snapshot.data!['tag_name'] ?? 'Unknown',
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.w500,
@ -93,13 +97,14 @@ class UpdateConfirmationDialog extends StatelessWidget {
child: I18nText(
'homeView.updateChangelogTitle',
child: Text(
"",
'',
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.w500,
color: Theme.of(context)
.colorScheme
.onSecondaryContainer),
fontSize: 20,
fontWeight: FontWeight.w500,
color: Theme.of(context)
.colorScheme
.onSecondaryContainer,
),
),
),
),
@ -113,7 +118,7 @@ class UpdateConfirmationDialog extends StatelessWidget {
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
padding: const EdgeInsets.all(20.0),
data: snapshot.data!["body"] ?? "",
data: snapshot.data!['body'] ?? '',
),
),
],

View File

@ -1,8 +1,8 @@
import 'package:flutter/material.dart';
class GradientProgressIndicator extends StatefulWidget {
final double? progress;
const GradientProgressIndicator({required this.progress, super.key});
final double? progress;
@override
State<GradientProgressIndicator> createState() =>

View File

@ -6,12 +6,11 @@ import 'package:revanced_manager/ui/views/patcher/patcher_viewmodel.dart';
import 'package:revanced_manager/ui/widgets/shared/custom_card.dart';
class AppSelectorCard extends StatelessWidget {
final Function() onPressed;
const AppSelectorCard({
Key? key,
required this.onPressed,
}) : super(key: key);
final Function() onPressed;
@override
Widget build(BuildContext context) {
@ -33,40 +32,41 @@ class AppSelectorCard extends StatelessWidget {
),
),
const SizedBox(height: 8),
locator<PatcherViewModel>().selectedApp == null
? I18nText('appSelectorCard.widgetSubtitle')
: Row(
children: <Widget>[
SizedBox(
height: 18.0,
child: ClipOval(
child: Image.memory(
locator<PatcherViewModel>().selectedApp == null
? Uint8List(0)
: locator<PatcherViewModel>().selectedApp!.icon,
fit: BoxFit.cover,
),
),
if (locator<PatcherViewModel>().selectedApp == null)
I18nText('appSelectorCard.widgetSubtitle')
else
Row(
children: <Widget>[
SizedBox(
height: 18.0,
child: ClipOval(
child: Image.memory(
locator<PatcherViewModel>().selectedApp == null
? Uint8List(0)
: locator<PatcherViewModel>().selectedApp!.icon,
fit: BoxFit.cover,
),
const SizedBox(width: 6),
Text(
locator<PatcherViewModel>()
.getAppSelectionString(),
style: const TextStyle(fontWeight: FontWeight.w600),
),
],
),
),
locator<PatcherViewModel>().selectedApp == null
? Container()
: Column(
children: [
const SizedBox(height: 4),
Text(
locator<PatcherViewModel>()
.getRecommendedVersionString(context),
),
],
const SizedBox(width: 6),
Text(
locator<PatcherViewModel>().getAppSelectionString(),
style: const TextStyle(fontWeight: FontWeight.w600),
),
],
),
if (locator<PatcherViewModel>().selectedApp == null)
Container()
else
Column(
children: [
const SizedBox(height: 4),
Text(
locator<PatcherViewModel>()
.getRecommendedVersionString(context),
),
],
),
],
),
);

View File

@ -6,12 +6,11 @@ import 'package:revanced_manager/ui/views/patcher/patcher_viewmodel.dart';
import 'package:revanced_manager/ui/widgets/shared/custom_card.dart';
class PatchSelectorCard extends StatelessWidget {
final Function() onPressed;
const PatchSelectorCard({
Key? key,
required this.onPressed,
}) : super(key: key);
final Function() onPressed;
@override
Widget build(BuildContext context) {
@ -46,11 +45,12 @@ class PatchSelectorCard extends StatelessWidget {
],
),
const SizedBox(height: 4),
locator<PatcherViewModel>().selectedApp == null
? I18nText('patchSelectorCard.widgetSubtitle')
: locator<PatcherViewModel>().selectedPatches.isEmpty
? I18nText('patchSelectorCard.widgetEmptySubtitle')
: Text(_getPatchesSelection()),
if (locator<PatcherViewModel>().selectedApp == null)
I18nText('patchSelectorCard.widgetSubtitle')
else
locator<PatcherViewModel>().selectedPatches.isEmpty
? I18nText('patchSelectorCard.widgetEmptySubtitle')
: Text(_getPatchesSelection()),
],
),
);
@ -58,7 +58,7 @@ class PatchSelectorCard extends StatelessWidget {
String _getPatchesSelection() {
String text = '';
for (Patch p in locator<PatcherViewModel>().selectedPatches) {
for (final Patch p in locator<PatcherViewModel>().selectedPatches) {
text += '\u2022 ${p.getSimpleName()} (v${p.version})\n';
}
return text.substring(0, text.length - 1);

View File

@ -3,11 +3,24 @@ 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/shared/custom_material_button.dart';
import 'package:revanced_manager/ui/widgets/shared/custom_card.dart';
import 'package:revanced_manager/ui/widgets/shared/custom_material_button.dart';
// ignore: must_be_immutable
class PatchItem extends StatefulWidget {
PatchItem({
Key? key,
required this.name,
required this.simpleName,
required this.description,
required this.version,
required this.packageVersion,
required this.supportedPackageVersions,
required this.isUnsupported,
required this.isSelected,
required this.onChanged,
this.child,
}) : super(key: key);
final String name;
final String simpleName;
final String description;
@ -21,20 +34,6 @@ class PatchItem extends StatefulWidget {
final toast = locator<Toast>();
final _managerAPI = locator<ManagerAPI>();
PatchItem(
{Key? key,
required this.name,
required this.simpleName,
required this.description,
required this.version,
required this.packageVersion,
required this.supportedPackageVersions,
required this.isUnsupported,
required this.isSelected,
required this.onChanged,
this.child})
: super(key: key);
@override
State<PatchItem> createState() => _PatchItemState();
}
@ -141,38 +140,37 @@ class _PatchItemState extends State<PatchItem> {
)
],
),
widget.isUnsupported
? Row(
children: <Widget>[
Padding(
padding: const EdgeInsets.only(top: 8),
child: TextButton.icon(
label: I18nText('warning'),
icon: const Icon(Icons.warning, size: 20.0),
onPressed: () => _showUnsupportedWarningDialog(),
style: ButtonStyle(
shape: MaterialStateProperty.all(
RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
side: BorderSide(
width: 1,
color:
Theme.of(context).colorScheme.secondary,
),
),
),
backgroundColor: MaterialStateProperty.all(
Colors.transparent,
),
foregroundColor: MaterialStateProperty.all(
Theme.of(context).colorScheme.secondary,
if (widget.isUnsupported)
Row(
children: <Widget>[
Padding(
padding: const EdgeInsets.only(top: 8),
child: TextButton.icon(
label: I18nText('warning'),
icon: const Icon(Icons.warning, size: 20.0),
onPressed: () => _showUnsupportedWarningDialog(),
style: ButtonStyle(
shape: MaterialStateProperty.all(
RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
side: BorderSide(
color: Theme.of(context).colorScheme.secondary,
),
),
),
backgroundColor: MaterialStateProperty.all(
Colors.transparent,
),
foregroundColor: MaterialStateProperty.all(
Theme.of(context).colorScheme.secondary,
),
),
],
)
: Container(),
),
),
],
)
else
Container(),
widget.child ?? const SizedBox(),
],
),

View File

@ -3,8 +3,8 @@ import 'package:flutter_i18n/flutter_i18n.dart';
import 'package:google_fonts/google_fonts.dart';
class OptionsTextField extends StatelessWidget {
final String hint;
const OptionsTextField({Key? key, required this.hint}) : super(key: key);
final String hint;
@override
Widget build(BuildContext context) {
@ -12,7 +12,7 @@ class OptionsTextField extends StatelessWidget {
final sWidth = MediaQuery.of(context).size.width;
return Container(
margin: const EdgeInsets.only(top: 12, bottom: 6),
padding: const EdgeInsets.all(0),
padding: EdgeInsets.zero,
child: TextField(
decoration: InputDecoration(
constraints: BoxConstraints(
@ -28,9 +28,9 @@ class OptionsTextField extends StatelessWidget {
}
class OptionsFilePicker extends StatelessWidget {
final String optionName;
const OptionsFilePicker({Key? key, required this.optionName})
: super(key: key);
final String optionName;
@override
Widget build(BuildContext context) {

View File

@ -1,7 +1,7 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_i18n/flutter_i18n.dart';
import 'package:revanced_manager/utils/about_info.dart';
import 'package:flutter/services.dart';
class AboutWidget extends StatefulWidget {
const AboutWidget({Key? key, this.padding}) : super(key: key);

View File

@ -1,14 +1,13 @@
import 'package:flutter/material.dart';
class CustomSwitch extends StatelessWidget {
final ValueChanged<bool> onChanged;
final bool value;
const CustomSwitch({
Key? key,
required this.onChanged,
required this.value,
}) : super(key: key);
final ValueChanged<bool> onChanged;
final bool value;
@override
Widget build(BuildContext context) {

View File

@ -2,12 +2,6 @@ import 'package:flutter/material.dart';
import 'package:revanced_manager/ui/widgets/settingsView/custom_switch.dart';
class CustomSwitchTile extends StatelessWidget {
final Widget title;
final Widget subtitle;
final bool value;
final Function(bool) onTap;
final EdgeInsetsGeometry? padding;
const CustomSwitchTile({
Key? key,
required this.title,
@ -16,6 +10,11 @@ class CustomSwitchTile extends StatelessWidget {
required this.onTap,
this.padding,
}) : super(key: key);
final Widget title;
final Widget subtitle;
final bool value;
final Function(bool) onTap;
final EdgeInsetsGeometry? padding;
@override
Widget build(BuildContext context) {

View File

@ -1,12 +1,6 @@
import 'package:flutter/material.dart';
class CustomTextField extends StatelessWidget {
final TextEditingController inputController;
final Widget label;
final String hint;
final Widget? leadingIcon;
final Function(String)? onChanged;
const CustomTextField({
Key? key,
required this.inputController,
@ -15,6 +9,11 @@ class CustomTextField extends StatelessWidget {
this.leadingIcon,
required this.onChanged,
}) : super(key: key);
final TextEditingController inputController;
final Widget label;
final String hint;
final Widget? leadingIcon;
final Function(String)? onChanged;
@override
Widget build(BuildContext context) {
@ -45,10 +44,8 @@ class CustomTextField extends StatelessWidget {
border: OutlineInputBorder(
borderSide: BorderSide(
color: Theme.of(context).colorScheme.primary,
width: 1.0,
),
borderRadius: BorderRadius.circular(10),
gapPadding: 4.0,
),
focusedBorder: OutlineInputBorder(
borderSide: BorderSide(
@ -60,14 +57,12 @@ class CustomTextField extends StatelessWidget {
errorBorder: OutlineInputBorder(
borderSide: BorderSide(
color: Theme.of(context).colorScheme.error,
width: 1.0,
),
borderRadius: BorderRadius.circular(10),
),
enabledBorder: OutlineInputBorder(
borderSide: BorderSide(
color: Theme.of(context).colorScheme.primary,
width: 1.0,
),
borderRadius: BorderRadius.circular(10),
),

View File

@ -7,12 +7,14 @@ class SExperimentalUniversalPatches extends StatefulWidget {
const SExperimentalUniversalPatches({super.key});
@override
State<SExperimentalUniversalPatches> createState() => _SExperimentalUniversalPatchesState();
State<SExperimentalUniversalPatches> createState() =>
_SExperimentalUniversalPatchesState();
}
final _settingsViewModel = SettingsViewModel();
class _SExperimentalUniversalPatchesState extends State<SExperimentalUniversalPatches> {
class _SExperimentalUniversalPatchesState
extends State<SExperimentalUniversalPatches> {
@override
Widget build(BuildContext context) {
return CustomSwitchTile(

View File

@ -2,14 +2,13 @@ import 'package:flutter/material.dart';
import 'package:flutter_i18n/flutter_i18n.dart';
class SettingsSection extends StatelessWidget {
final String title;
final List<Widget> children;
const SettingsSection({
Key? key,
required this.title,
required this.children,
}) : super(key: key);
final String title;
final List<Widget> children;
@override
Widget build(BuildContext context) {

View File

@ -2,11 +2,6 @@ import 'package:flutter/material.dart';
import 'package:flutter_i18n/flutter_i18n.dart';
class SettingsTileDialog extends StatelessWidget {
final String title;
final String subtitle;
final Function()? onTap;
final EdgeInsetsGeometry? padding;
const SettingsTileDialog({
Key? key,
required this.title,
@ -14,6 +9,10 @@ class SettingsTileDialog extends StatelessWidget {
required this.onTap,
this.padding,
}) : super(key: key);
final String title;
final String subtitle;
final Function()? onTap;
final EdgeInsetsGeometry? padding;
@override
Widget build(BuildContext context) {

View File

@ -1,13 +1,7 @@
import 'package:flutter/material.dart';
import 'package:url_launcher/url_launcher.dart';
class SocialMediaItem extends StatelessWidget {
final Widget? icon;
final Widget title;
final Widget? subtitle;
final String? url;
const SocialMediaItem({
Key? key,
this.icon,
@ -15,6 +9,10 @@ class SocialMediaItem extends StatelessWidget {
this.subtitle,
this.url,
}) : super(key: key);
final Widget? icon;
final Widget title;
final Widget? subtitle;
final String? url;
@override
Widget build(BuildContext context) {

View File

@ -6,12 +6,11 @@ import 'package:revanced_manager/ui/widgets/settingsView/social_media_item.dart'
import 'package:revanced_manager/ui/widgets/shared/custom_card.dart';
class SocialMediaWidget extends StatelessWidget {
final EdgeInsetsGeometry? padding;
const SocialMediaWidget({
Key? key,
this.padding,
}) : super(key: key);
final EdgeInsetsGeometry? padding;
@override
Widget build(BuildContext context) {

View File

@ -1,19 +1,13 @@
import 'dart:typed_data';
import 'package:expandable/expandable.dart';
import 'package:flutter/material.dart';
import 'package:flutter_i18n/flutter_i18n.dart';
import 'package:revanced_manager/ui/widgets/shared/custom_material_button.dart';
import 'package:revanced_manager/ui/widgets/shared/custom_card.dart';
import 'package:expandable/expandable.dart';
import 'package:revanced_manager/ui/widgets/shared/custom_material_button.dart';
import 'package:timeago/timeago.dart';
class ApplicationItem extends StatefulWidget {
final Uint8List icon;
final String name;
final DateTime patchDate;
final List<String> changelog;
final bool isUpdatableApp;
final Function() onPressed;
const ApplicationItem({
Key? key,
required this.icon,
@ -23,6 +17,12 @@ class ApplicationItem extends StatefulWidget {
required this.isUpdatableApp,
required this.onPressed,
}) : super(key: key);
final Uint8List icon;
final String name;
final DateTime patchDate;
final List<String> changelog;
final bool isUpdatableApp;
final Function() onPressed;
@override
State<ApplicationItem> createState() => _ApplicationItemState();
@ -33,7 +33,7 @@ class _ApplicationItemState extends State<ApplicationItem>
late AnimationController _animationController;
@override
initState() {
void initState() {
super.initState();
_animationController = AnimationController(
vsync: this,
@ -49,95 +49,100 @@ class _ApplicationItemState extends State<ApplicationItem>
@override
Widget build(BuildContext context) {
ExpandableController expController = ExpandableController();
final ExpandableController expController = ExpandableController();
return Container(
margin: const EdgeInsets.only(bottom: 16.0),
child: CustomCard(
onTap: () {
expController.toggle();
_animationController.isCompleted
? _animationController.reverse()
: _animationController.forward();
},
child: ExpandablePanel(
controller: expController,
theme: const ExpandableThemeData(
inkWellBorderRadius: BorderRadius.all(Radius.circular(16)),
tapBodyToCollapse: false,
tapBodyToExpand: false,
tapHeaderToExpand: false,
hasIcon: false,
animationDuration: Duration(milliseconds: 450),
),
header: Row(
children: <Widget>[
SizedBox(
width: 40,
child: Image.memory(widget.icon, height: 40, width: 40),
),
const SizedBox(width: 4),
Padding(
padding: const EdgeInsets.only(left: 15.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text(
widget.name.length > 12
? '${widget.name.substring(0, 12)}...'
: widget.name,
style: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.w500,
),
),
Text(format(widget.patchDate)),
],
),
),
const Spacer(),
RotationTransition(
turns: Tween(begin: 0.0, end: 0.50)
.animate(_animationController),
child: const Padding(
padding: EdgeInsets.all(8.0),
child: Icon(Icons.arrow_drop_down),
),
),
const SizedBox(width: 8),
Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.end,
margin: const EdgeInsets.only(bottom: 16.0),
child: CustomCard(
onTap: () {
expController.toggle();
_animationController.isCompleted
? _animationController.reverse()
: _animationController.forward();
},
child: ExpandablePanel(
controller: expController,
theme: const ExpandableThemeData(
inkWellBorderRadius: BorderRadius.all(Radius.circular(16)),
tapBodyToCollapse: false,
tapBodyToExpand: false,
tapHeaderToExpand: false,
hasIcon: false,
animationDuration: Duration(milliseconds: 450),
),
header: Row(
children: <Widget>[
SizedBox(
width: 40,
child: Image.memory(widget.icon, height: 40, width: 40),
),
const SizedBox(width: 4),
Padding(
padding: const EdgeInsets.only(left: 15.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
CustomMaterialButton(
label: widget.isUpdatableApp
? I18nText('applicationItem.patchButton')
: I18nText('applicationItem.infoButton'),
onPressed: widget.onPressed,
Text(
widget.name.length > 12
? '${widget.name.substring(0, 12)}...'
: widget.name,
style: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.w500,
),
),
Text(format(widget.patchDate)),
],
),
],
),
collapsed: const SizedBox(),
expanded: Padding(
padding: const EdgeInsets.only(
top: 16.0, left: 4.0, right: 4.0, bottom: 4.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
),
const Spacer(),
RotationTransition(
turns:
Tween(begin: 0.0, end: 0.50).animate(_animationController),
child: const Padding(
padding: EdgeInsets.all(8.0),
child: Icon(Icons.arrow_drop_down),
),
),
const SizedBox(width: 8),
Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.end,
children: <Widget>[
I18nText(
'applicationItem.changelogLabel',
child: const Text(
'',
style: TextStyle(fontWeight: FontWeight.w700),
),
CustomMaterialButton(
label: widget.isUpdatableApp
? I18nText('applicationItem.patchButton')
: I18nText('applicationItem.infoButton'),
onPressed: widget.onPressed,
),
const SizedBox(height: 4),
Text('\u2022 ${widget.changelog.join('\n\u2022 ')}'),
],
),
],
),
collapsed: const SizedBox(),
expanded: Padding(
padding: const EdgeInsets.only(
top: 16.0,
left: 4.0,
right: 4.0,
bottom: 4.0,
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
I18nText(
'applicationItem.changelogLabel',
child: const Text(
'',
style: TextStyle(fontWeight: FontWeight.w700),
),
),
const SizedBox(height: 4),
Text('\u2022 ${widget.changelog.join('\n\u2022 ')}'),
],
),
),
));
),
),
);
}
}

View File

@ -1,21 +1,20 @@
import 'package:flutter/material.dart';
class CustomCard extends StatelessWidget {
const CustomCard({
Key? key,
this.isFilled = true,
required this.child,
this.onTap,
this.padding,
this.backgroundColor,
}) : super(key: key);
final bool isFilled;
final Widget child;
final Function()? onTap;
final EdgeInsetsGeometry? padding;
final Color? backgroundColor;
const CustomCard(
{Key? key,
this.isFilled = true,
required this.child,
this.onTap,
this.padding,
this.backgroundColor})
: super(key: key);
@override
Widget build(BuildContext context) {
return Material(

View File

@ -1,16 +1,15 @@
import 'package:flutter/material.dart';
class CustomChip extends StatelessWidget {
final Widget label;
final bool isSelected;
final Function(bool)? onSelected;
const CustomChip({
Key? key,
required this.label,
this.isSelected = false,
this.onSelected,
}) : super(key: key);
final Widget label;
final bool isSelected;
final Function(bool)? onSelected;
@override
Widget build(BuildContext context) {

View File

@ -1,11 +1,6 @@
import 'package:flutter/material.dart';
class CustomMaterialButton extends StatelessWidget {
final Widget label;
final bool isFilled;
final bool isExpanded;
final Function()? onPressed;
const CustomMaterialButton({
Key? key,
required this.label,
@ -13,6 +8,10 @@ class CustomMaterialButton extends StatelessWidget {
this.isExpanded = false,
required this.onPressed,
}) : super(key: key);
final Widget label;
final bool isFilled;
final bool isExpanded;
final Function()? onPressed;
@override
Widget build(BuildContext context) {
@ -28,7 +27,6 @@ class CustomMaterialButton extends StatelessWidget {
side: isFilled
? BorderSide.none
: BorderSide(
width: 1,
color: Theme.of(context).colorScheme.primary,
),
),
@ -50,12 +48,6 @@ class CustomMaterialButton extends StatelessWidget {
// ignore: must_be_immutable
class TimerButton extends StatefulWidget {
Widget label;
bool isFilled;
int seconds;
final bool isRunning;
final Function()? onTimerEnd;
TimerButton({
Key? key,
required this.seconds,
@ -64,6 +56,11 @@ class TimerButton extends StatefulWidget {
this.label = const Text(''),
this.isFilled = true,
}) : super(key: key);
Widget label;
bool isFilled;
int seconds;
final bool isRunning;
final Function()? onTimerEnd;
@override
State<TimerButton> createState() => _TimerButtonState();
@ -101,7 +98,6 @@ class _TimerButtonState extends State<TimerButton> {
side: widget.isFilled
? BorderSide.none
: BorderSide(
width: 1,
color: Theme.of(context).colorScheme.primary,
),
),

View File

@ -1,14 +1,13 @@
import 'package:flutter/material.dart';
class CustomPopupMenu extends StatelessWidget {
final Function(dynamic) onSelected;
final Map<int, Widget> children;
const CustomPopupMenu({
Key? key,
required this.onSelected,
required this.children,
}) : super(key: key);
final Function(dynamic) onSelected;
final Map<int, Widget> children;
@override
Widget build(BuildContext context) {

View File

@ -1,12 +1,6 @@
import 'package:flutter/material.dart';
class CustomSliverAppBar extends StatelessWidget {
final Widget title;
final List<Widget>? actions;
final PreferredSizeWidget? bottom;
final bool isMainView;
final Function()? onBackButtonPressed;
const CustomSliverAppBar({
Key? key,
required this.title,
@ -15,13 +9,16 @@ class CustomSliverAppBar extends StatelessWidget {
this.isMainView = false,
this.onBackButtonPressed,
}) : super(key: key);
final Widget title;
final List<Widget>? actions;
final PreferredSizeWidget? bottom;
final bool isMainView;
final Function()? onBackButtonPressed;
@override
Widget build(BuildContext context) {
return SliverAppBar(
pinned: true,
snap: false,
floating: false,
expandedHeight: 100.0,
automaticallyImplyLeading: !isMainView,
flexibleSpace: FlexibleSpaceBar(

View File

@ -2,21 +2,19 @@ import 'package:animations/animations.dart';
import 'package:flutter/material.dart';
class OpenContainerWrapper extends StatelessWidget {
final OpenContainerBuilder openBuilder;
final CloseContainerBuilder closedBuilder;
const OpenContainerWrapper({
Key? key,
required this.openBuilder,
required this.closedBuilder,
}) : super(key: key);
final OpenContainerBuilder openBuilder;
final CloseContainerBuilder closedBuilder;
@override
Widget build(BuildContext context) {
return OpenContainer(
openBuilder: openBuilder,
closedBuilder: closedBuilder,
transitionType: ContainerTransitionType.fade,
transitionDuration: const Duration(milliseconds: 400),
openColor: Theme.of(context).colorScheme.primary,
closedColor: Colors.transparent,

View File

@ -1,10 +1,6 @@
import 'package:flutter/material.dart';
class SearchBar extends StatefulWidget {
final String? hintText;
final bool showSelectIcon;
final Function(bool)? onSelectAll;
const SearchBar({
Key? key,
required this.hintText,
@ -12,6 +8,9 @@ class SearchBar extends StatefulWidget {
this.onSelectAll,
required this.onQueryChanged,
}) : super(key: key);
final String? hintText;
final bool showSelectIcon;
final Function(bool)? onSelectAll;
final Function(String) onQueryChanged;

View File

@ -1,6 +1,6 @@
import 'package:device_info_plus/device_info_plus.dart';
import 'package:flutter/foundation.dart';
import 'package:package_info_plus/package_info_plus.dart';
import 'package:device_info_plus/device_info_plus.dart';
class AboutInfo {
static Future<Map<String, dynamic>> getInfo() async {

View File

@ -2,4 +2,4 @@
class Environment {
static const sentryDSN = '';
static const crowdinKEY = '';
}
}