feat: improve ux (#752)

* feat: restart app toast when changing sources, api url

* fix: potentially fix manager stuck on black screen

* feat: remove select all patches chip

* feat: show all apps and recommended versions

* chore(i18n): remove unused strings

remove unused strings left out in 7e05bca

* feat: select install type before patching

* feat: update patches button (#782)

* feat: update patches button

* feat: show toast when force refresh

* chore: don't translate "ReVanced Manager" and "ReVanced Patches"

* Revert "feat: select install type before patching"

This reverts commit 74e0c09b54.

* feat: rename recommended patches to default patches

* feat: add missing localization

* feat: display restart app toast for resetting source

---------

Co-authored-by: EvadeMaster <93124920+EvadeMaster@users.noreply.github.com>
This commit is contained in:
Aunali321 2023-04-18 13:27:26 +05:30 committed by GitHub
parent 0b952578d1
commit 3b677f8ae3
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
16 changed files with 397 additions and 167 deletions

View file

@ -13,6 +13,7 @@
"settingsTab": "Settings" "settingsTab": "Settings"
}, },
"homeView": { "homeView": {
"refreshSuccess": "Refresh successfully",
"widgetTitle": "Dashboard", "widgetTitle": "Dashboard",
"updatesSubtitle": "Updates", "updatesSubtitle": "Updates",
"patchedSubtitle": "Patched applications", "patchedSubtitle": "Patched applications",
@ -72,16 +73,15 @@
"viewTitle": "Select an application", "viewTitle": "Select an application",
"searchBarHint": "Search applications", "searchBarHint": "Search applications",
"storageButton": "Storage", "storageButton": "Storage",
"errorMessage": "Unable to use selected application" "errorMessage": "Unable to use selected application",
"downloadToast": "Download function is not available yet"
}, },
"patchesSelectorView": { "patchesSelectorView": {
"viewTitle": "Select patches", "viewTitle": "Select patches",
"searchBarHint": "Search patches", "searchBarHint": "Search patches",
"doneButton": "Done", "doneButton": "Done",
"recommended": "Recommended", "default": "Default",
"recommendedTooltip": "Select all recommended patches", "defaultTooltip": "Select all default patches",
"all": "All",
"allTooltip": "Select all patches",
"none": "None", "none": "None",
"noneTooltip": "Deselect all patches", "noneTooltip": "Deselect all patches",
"loadPatchesSelection": "Load patches selection", "loadPatchesSelection": "Load patches selection",

View file

@ -3,20 +3,23 @@ import 'dart:io';
import 'package:collection/collection.dart'; import 'package:collection/collection.dart';
import 'package:dio/dio.dart'; import 'package:dio/dio.dart';
import 'package:dio_http_cache_lts/dio_http_cache_lts.dart'; import 'package:dio_cache_interceptor/dio_cache_interceptor.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter_cache_manager/flutter_cache_manager.dart'; import 'package:flutter_cache_manager/flutter_cache_manager.dart';
import 'package:injectable/injectable.dart'; import 'package:injectable/injectable.dart';
import 'package:native_dio_adapter/native_dio_adapter.dart';
import 'package:revanced_manager/models/patch.dart'; import 'package:revanced_manager/models/patch.dart';
@lazySingleton @lazySingleton
class GithubAPI { class GithubAPI {
late Dio _dio = Dio(); late Dio _dio = Dio();
final DioCacheManager _dioCacheManager = DioCacheManager(CacheConfig());
final Options _cacheOptions = buildCacheOptions( final _cacheOptions = CacheOptions(
const Duration(hours: 6), store: MemCacheStore(),
maxStale: const Duration(days: 1), maxStale: const Duration(days: 1),
priority: CachePriority.high,
); );
final Map<String, String> repoAppPath = { final Map<String, String> repoAppPath = {
'com.google.android.youtube': 'youtube', 'com.google.android.youtube': 'youtube',
'com.google.android.apps.youtube.music': 'music', 'com.google.android.apps.youtube.music': 'music',
@ -30,13 +33,29 @@ class GithubAPI {
Future<void> initialize(String repoUrl) async { Future<void> initialize(String repoUrl) async {
try { try {
if (Platform.isIOS || Platform.isMacOS || Platform.isAndroid) {
final CronetEngine androidCronetEngine = await CronetEngine.build(
userAgent: 'ReVanced Manager',
enableBrotli: true,
enableQuic: true,
);
_dio.httpClientAdapter =
NativeAdapter(androidCronetEngine: androidCronetEngine);
_dio = Dio(
BaseOptions(
baseUrl: repoUrl,
),
);
}
_dio = Dio( _dio = Dio(
BaseOptions( BaseOptions(
baseUrl: repoUrl, baseUrl: repoUrl,
), ),
); );
_dio.interceptors.add(_dioCacheManager.interceptor); _dio.interceptors.add(DioCacheInterceptor(options: _cacheOptions));
} on Exception catch (e) { } on Exception catch (e) {
if (kDebugMode) { if (kDebugMode) {
print(e); print(e);
@ -46,7 +65,7 @@ class GithubAPI {
Future<void> clearAllCache() async { Future<void> clearAllCache() async {
try { try {
await _dioCacheManager.clearAll(); await _cacheOptions.store!.clean();
} on Exception catch (e) { } on Exception catch (e) {
if (kDebugMode) { if (kDebugMode) {
print(e); print(e);
@ -58,7 +77,6 @@ class GithubAPI {
try { try {
final response = await _dio.get( final response = await _dio.get(
'/repos/$repoName/releases', '/repos/$repoName/releases',
options: _cacheOptions,
); );
return response.data[0]; return response.data[0];
} on Exception catch (e) { } on Exception catch (e) {
@ -83,7 +101,6 @@ class GithubAPI {
'path': path, 'path': path,
'since': since.toIso8601String(), 'since': since.toIso8601String(),
}, },
options: _cacheOptions,
); );
final List<dynamic> commits = response.data; final List<dynamic> commits = response.data;
return commits return commits

View file

@ -29,6 +29,10 @@ class ManagerAPI {
String defaultIntegrationsRepo = 'revanced/revanced-integrations'; String defaultIntegrationsRepo = 'revanced/revanced-integrations';
String defaultCliRepo = 'revanced/revanced-cli'; String defaultCliRepo = 'revanced/revanced-cli';
String defaultManagerRepo = 'revanced/revanced-manager'; String defaultManagerRepo = 'revanced/revanced-manager';
String? patchesVersion = '';
bool isDefaultPatchesRepo() {
return getPatchesRepo() == 'revanced/revanced-patches';
}
Future<void> initialize() async { Future<void> initialize() async {
_prefs = await SharedPreferences.getInstance(); _prefs = await SharedPreferences.getInstance();
@ -267,6 +271,19 @@ class ManagerAPI {
return packageInfo.version; return packageInfo.version;
} }
Future<String?> getCurrentPatchesVersion() async {
if (isDefaultPatchesRepo()) {
patchesVersion = await getLatestPatchesVersion();
// print('Patches version: $patchesVersion');
return patchesVersion ?? '0.0.0';
} else {
// fetch from github
patchesVersion =
await _githubAPI.getLastestReleaseVersion(getPatchesRepo());
}
return null;
}
Future<List<PatchedApplication>> getAppsToRemove( Future<List<PatchedApplication>> getAppsToRemove(
List<PatchedApplication> patchedApps, List<PatchedApplication> patchedApps,
) async { ) async {

View file

@ -3,11 +3,11 @@ import 'dart:io';
import 'package:collection/collection.dart'; import 'package:collection/collection.dart';
import 'package:dio/dio.dart'; import 'package:dio/dio.dart';
import 'package:dio_http_cache_lts/dio_http_cache_lts.dart'; import 'package:dio_cache_interceptor/dio_cache_interceptor.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter_cache_manager/flutter_cache_manager.dart'; import 'package:flutter_cache_manager/flutter_cache_manager.dart';
import 'package:injectable/injectable.dart'; import 'package:injectable/injectable.dart';
import 'package:native_dio_client/native_dio_client.dart'; import 'package:native_dio_adapter/native_dio_adapter.dart';
import 'package:revanced_manager/models/patch.dart'; import 'package:revanced_manager/models/patch.dart';
import 'package:revanced_manager/utils/check_for_gms.dart'; import 'package:revanced_manager/utils/check_for_gms.dart';
import 'package:timeago/timeago.dart'; import 'package:timeago/timeago.dart';
@ -15,10 +15,11 @@ import 'package:timeago/timeago.dart';
@lazySingleton @lazySingleton
class RevancedAPI { class RevancedAPI {
late Dio _dio = Dio(); late Dio _dio = Dio();
final DioCacheManager _dioCacheManager = DioCacheManager(CacheConfig());
final Options _cacheOptions = buildCacheOptions( final _cacheOptions = CacheOptions(
const Duration(hours: 6), store: MemCacheStore(),
maxStale: const Duration(days: 1), maxStale: const Duration(days: 1),
priority: CachePriority.high,
); );
Future<void> initialize(String apiUrl) async { Future<void> initialize(String apiUrl) async {
@ -33,14 +34,25 @@ class RevancedAPI {
); );
log('ReVanced API: Using default engine + $isGMSInstalled'); log('ReVanced API: Using default engine + $isGMSInstalled');
} else { } else {
_dio = Dio( if (Platform.isIOS || Platform.isMacOS || Platform.isAndroid) {
BaseOptions( final CronetEngine androidCronetEngine = await CronetEngine.build(
baseUrl: apiUrl, userAgent: 'ReVanced Manager',
), enableBrotli: true,
)..httpClientAdapter = NativeAdapter(); enableQuic: true,
);
_dio.httpClientAdapter =
NativeAdapter(androidCronetEngine: androidCronetEngine);
_dio = Dio(
BaseOptions(
baseUrl: apiUrl,
),
);
}
log('ReVanced API: Using CronetEngine + $isGMSInstalled'); log('ReVanced API: Using CronetEngine + $isGMSInstalled');
} }
_dio.interceptors.add(_dioCacheManager.interceptor); _dio.interceptors.add(DioCacheInterceptor(options: _cacheOptions));
} on Exception catch (e) { } on Exception catch (e) {
if (kDebugMode) { if (kDebugMode) {
print(e); print(e);
@ -50,7 +62,7 @@ class RevancedAPI {
Future<void> clearAllCache() async { Future<void> clearAllCache() async {
try { try {
await _dioCacheManager.clearAll(); await _cacheOptions.store!.clean();
} on Exception catch (e) { } on Exception catch (e) {
if (kDebugMode) { if (kDebugMode) {
print(e); print(e);
@ -61,7 +73,7 @@ class RevancedAPI {
Future<Map<String, List<dynamic>>> getContributors() async { Future<Map<String, List<dynamic>>> getContributors() async {
final Map<String, List<dynamic>> contributors = {}; final Map<String, List<dynamic>> contributors = {};
try { try {
final response = await _dio.get('/contributors', options: _cacheOptions); final response = await _dio.get('/contributors');
final List<dynamic> repositories = response.data['repositories']; final List<dynamic> repositories = response.data['repositories'];
for (final Map<String, dynamic> repo in repositories) { for (final Map<String, dynamic> repo in repositories) {
final String name = repo['name']; final String name = repo['name'];
@ -78,7 +90,7 @@ class RevancedAPI {
Future<List<Patch>> getPatches() async { Future<List<Patch>> getPatches() async {
try { try {
final response = await _dio.get('/patches', options: _cacheOptions); final response = await _dio.get('/patches');
final List<dynamic> patches = response.data; final List<dynamic> patches = response.data;
return patches.map((patch) => Patch.fromJson(patch)).toList(); return patches.map((patch) => Patch.fromJson(patch)).toList();
} on Exception catch (e) { } on Exception catch (e) {
@ -94,7 +106,7 @@ class RevancedAPI {
String repoName, String repoName,
) async { ) async {
try { try {
final response = await _dio.get('/tools', options: _cacheOptions); final response = await _dio.get('/tools');
final List<dynamic> tools = response.data['tools']; final List<dynamic> tools = response.data['tools'];
return tools.firstWhereOrNull( return tools.firstWhereOrNull(
(t) => (t) =>

View file

@ -3,6 +3,7 @@ import 'package:flutter_i18n/flutter_i18n.dart';
import 'package:revanced_manager/ui/views/app_selector/app_selector_viewmodel.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/app_skeleton_loader.dart';
import 'package:revanced_manager/ui/widgets/appSelectorView/installed_app_item.dart'; import 'package:revanced_manager/ui/widgets/appSelectorView/installed_app_item.dart';
import 'package:revanced_manager/ui/widgets/appSelectorView/not_installed_app_item.dart';
import 'package:revanced_manager/ui/widgets/shared/search_bar.dart'; import 'package:revanced_manager/ui/widgets/shared/search_bar.dart';
import 'package:stacked/stacked.dart' hide SkeletonLoader; import 'package:stacked/stacked.dart' hide SkeletonLoader;
@ -76,7 +77,16 @@ class _AppSelectorViewState extends State<AppSelectorView> {
SliverToBoxAdapter( SliverToBoxAdapter(
child: model.noApps child: model.noApps
? Center( ? Center(
child: I18nText('appSelectorCard.noAppsLabel'), child: I18nText(
'appSelectorView.noApps',
child: Text(
'',
style: TextStyle(
color:
Theme.of(context).textTheme.titleLarge!.color,
),
),
),
) )
: model.apps.isEmpty : model.apps.isEmpty
? const AppSkeletonLoader() ? const AppSkeletonLoader()
@ -84,22 +94,42 @@ class _AppSelectorViewState extends State<AppSelectorView> {
padding: const EdgeInsets.symmetric(horizontal: 12.0) padding: const EdgeInsets.symmetric(horizontal: 12.0)
.copyWith(bottom: 80), .copyWith(bottom: 80),
child: Column( child: Column(
children: model children: [
.getFilteredApps(_query) ...model
.map( .getFilteredApps(_query)
(app) => InstalledAppItem( .map(
name: app.appName, (app) => InstalledAppItem(
pkgName: app.packageName, name: app.appName,
icon: app.icon, pkgName: app.packageName,
patchesCount: icon: app.icon,
model.patchesCount(app.packageName), patchesCount:
onTap: () { model.patchesCount(app.packageName),
model.selectApp(app); recommendedVersion:
Navigator.of(context).pop(); model.getRecommendedVersion(
}, app.packageName,
), ),
) onTap: () {
.toList(), model.selectApp(app);
Navigator.of(context).pop();
},
),
)
.toList(),
...model
.getFilteredAppsNames(_query)
.map(
(app) => NotInstalledAppItem(
name: app,
patchesCount: model.patchesCount(app),
recommendedVersion:
model.getRecommendedVersion(app),
onTap: () {
model.showDownloadToast();
},
),
)
.toList(),
],
), ),
), ),
), ),

View file

@ -5,9 +5,11 @@ import 'package:file_picker/file_picker.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:revanced_manager/app/app.locator.dart'; import 'package:revanced_manager/app/app.locator.dart';
import 'package:revanced_manager/models/patch.dart';
import 'package:revanced_manager/models/patched_application.dart'; import 'package:revanced_manager/models/patched_application.dart';
import 'package:revanced_manager/services/manager_api.dart'; import 'package:revanced_manager/services/manager_api.dart';
import 'package:revanced_manager/services/patcher_api.dart'; import 'package:revanced_manager/services/patcher_api.dart';
import 'package:revanced_manager/services/revanced_api.dart';
import 'package:revanced_manager/services/toast.dart'; import 'package:revanced_manager/services/toast.dart';
import 'package:revanced_manager/ui/views/patcher/patcher_viewmodel.dart'; import 'package:revanced_manager/ui/views/patcher/patcher_viewmodel.dart';
import 'package:stacked/stacked.dart'; import 'package:stacked/stacked.dart';
@ -15,14 +17,20 @@ import 'package:stacked/stacked.dart';
class AppSelectorViewModel extends BaseViewModel { class AppSelectorViewModel extends BaseViewModel {
final PatcherAPI _patcherAPI = locator<PatcherAPI>(); final PatcherAPI _patcherAPI = locator<PatcherAPI>();
final ManagerAPI _managerAPI = locator<ManagerAPI>(); final ManagerAPI _managerAPI = locator<ManagerAPI>();
final RevancedAPI _revancedAPI = locator<RevancedAPI>();
final Toast _toast = locator<Toast>(); final Toast _toast = locator<Toast>();
final List<ApplicationWithIcon> apps = []; final List<ApplicationWithIcon> apps = [];
List<String> allApps = [];
bool noApps = false; bool noApps = false;
int patchesCount(String packageName) { int patchesCount(String packageName) {
return _patcherAPI.getFilteredPatches(packageName).length; return _patcherAPI.getFilteredPatches(packageName).length;
} }
List<Patch> patches = [];
Future<void> initialize() async { Future<void> initialize() async {
patches = await _revancedAPI.getPatches();
apps.addAll( apps.addAll(
await _patcherAPI await _patcherAPI
.getFilteredInstalledApps(_managerAPI.areUniversalPatchesEnabled()), .getFilteredInstalledApps(_managerAPI.areUniversalPatchesEnabled()),
@ -34,9 +42,25 @@ class AppSelectorViewModel extends BaseViewModel {
.compareTo(_patcherAPI.getFilteredPatches(a.packageName).length), .compareTo(_patcherAPI.getFilteredPatches(a.packageName).length),
); );
noApps = apps.isEmpty; noApps = apps.isEmpty;
getAllApps();
notifyListeners(); notifyListeners();
} }
List<String> getAllApps() {
allApps = patches
.expand((e) => e.compatiblePackages.map((p) => p.name))
.toSet()
.where((name) => !apps.any((app) => app.packageName == name))
.toList();
return allApps;
}
String getRecommendedVersion(String packageName) {
return _patcherAPI.getRecommendedVersion(packageName);
}
Future<void> selectApp(ApplicationWithIcon application) async { Future<void> selectApp(ApplicationWithIcon application) async {
locator<PatcherViewModel>().selectedApp = PatchedApplication( locator<PatcherViewModel>().selectedApp = PatchedApplication(
name: application.appName, name: application.appName,
@ -105,4 +129,18 @@ class AppSelectorViewModel extends BaseViewModel {
) )
.toList(); .toList();
} }
List<String> getFilteredAppsNames(String query) {
return allApps
.where(
(app) =>
query.isEmpty ||
query.length < 2 ||
app.toLowerCase().contains(query.toLowerCase()),
)
.toList();
}
void showDownloadToast() =>
_toast.showBottom('appSelectorView.downloadToast');
} }

View file

@ -50,8 +50,9 @@ class HomeView extends StatelessWidget {
), ),
const SizedBox(height: 10), const SizedBox(height: 10),
LatestCommitCard( LatestCommitCard(
onPressed: () => onPressedManager: () =>
model.showUpdateConfirmationDialog(context), model.showUpdateConfirmationDialog(context),
onPressedPatches: () => model.forceRefresh(context),
), ),
const SizedBox(height: 23), const SizedBox(height: 23),
I18nText( I18nText(

View file

@ -105,6 +105,26 @@ class HomeViewModel extends BaseViewModel {
return false; return false;
} }
Future<bool> hasPatchesUpdates() async {
final String? latestVersion = await _managerAPI.getLatestPatchesVersion();
final String? currentVersion = await _managerAPI.getCurrentPatchesVersion();
if (latestVersion != null) {
try {
final int latestVersionInt =
int.parse(latestVersion.replaceAll(RegExp('[^0-9]'), ''));
final int currentVersionInt =
int.parse(currentVersion!.replaceAll(RegExp('[^0-9]'), ''));
return latestVersionInt > currentVersionInt;
} on Exception catch (e) {
if (kDebugMode) {
print(e);
}
return false;
}
}
return false;
}
Future<void> updateManager(BuildContext context) async { Future<void> updateManager(BuildContext context) async {
try { try {
_toast.showBottom('homeView.downloadingMessage'); _toast.showBottom('homeView.downloadingMessage');
@ -180,6 +200,7 @@ class HomeViewModel extends BaseViewModel {
_lastUpdate!.difference(DateTime.now()).inSeconds > 2) { _lastUpdate!.difference(DateTime.now()).inSeconds > 2) {
_managerAPI.clearAllData(); _managerAPI.clearAllData();
} }
_toast.showBottom('homeView.refreshSuccess');
initialize(context); initialize(context);
} }
} }

View file

@ -135,25 +135,13 @@ class _PatchesSelectorViewState extends State<PatchesSelectorView> {
Row( Row(
children: [ children: [
ActionChip( ActionChip(
label: I18nText('patchesSelectorView.recommended'), label: I18nText('patchesSelectorView.default'),
tooltip: FlutterI18n.translate( tooltip: FlutterI18n.translate(
context, context,
'patchesSelectorView.recommendedTooltip', 'patchesSelectorView.defaultTooltip',
), ),
onPressed: () { onPressed: () {
model.selectRecommendedPatches(); model.selectDefaultPatches();
},
),
const SizedBox(width: 8),
ActionChip(
label: I18nText('patchesSelectorView.all'),
tooltip: FlutterI18n.translate(
context,
'patchesSelectorView.allTooltip',
),
onPressed: () {
model.selectAllPatcherWarning(context);
model.selectAllPatches(true);
}, },
), ),
const SizedBox(width: 8), const SizedBox(width: 8),

View file

@ -1,6 +1,4 @@
import 'package:collection/collection.dart'; 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/app/app.locator.dart';
import 'package:revanced_manager/models/patch.dart'; import 'package:revanced_manager/models/patch.dart';
import 'package:revanced_manager/models/patched_application.dart'; import 'package:revanced_manager/models/patched_application.dart';
@ -9,7 +7,6 @@ import 'package:revanced_manager/services/manager_api.dart';
import 'package:revanced_manager/services/patcher_api.dart'; import 'package:revanced_manager/services/patcher_api.dart';
import 'package:revanced_manager/services/toast.dart'; import 'package:revanced_manager/services/toast.dart';
import 'package:revanced_manager/ui/views/patcher/patcher_viewmodel.dart'; import 'package:revanced_manager/ui/views/patcher/patcher_viewmodel.dart';
import 'package:revanced_manager/ui/widgets/shared/custom_material_button.dart';
import 'package:stacked/stacked.dart'; import 'package:stacked/stacked.dart';
class PatchesSelectorViewModel extends BaseViewModel { class PatchesSelectorViewModel extends BaseViewModel {
@ -50,39 +47,7 @@ class PatchesSelectorViewModel extends BaseViewModel {
notifyListeners(); notifyListeners();
} }
Future<void> selectAllPatcherWarning(BuildContext context) { void selectDefaultPatches() {
return showDialog(
context: context,
builder: (context) => AlertDialog(
title: I18nText('warning'),
backgroundColor: Theme.of(context).colorScheme.secondaryContainer,
content: I18nText('patchesSelectorView.selectAllPatchesWarningContent'),
actions: <Widget>[
CustomMaterialButton(
label: I18nText('okButton'),
onPressed: () => Navigator.of(context).pop(),
)
],
),
);
}
void selectAllPatches(bool isSelected) {
selectedPatches.clear();
if (isSelected && _managerAPI.areExperimentalPatchesEnabled() == false) {
selectedPatches
.addAll(patches.where((element) => isPatchSupported(element)));
}
if (isSelected && _managerAPI.areExperimentalPatchesEnabled()) {
selectedPatches.addAll(patches);
}
notifyListeners();
}
void selectRecommendedPatches() {
selectedPatches.clear(); selectedPatches.clear();
if (_managerAPI.areExperimentalPatchesEnabled() == false) { if (_managerAPI.areExperimentalPatchesEnabled() == false) {

View file

@ -4,6 +4,7 @@ import 'package:flutter/material.dart';
import 'package:flutter_i18n/flutter_i18n.dart'; import 'package:flutter_i18n/flutter_i18n.dart';
import 'package:revanced_manager/app/app.locator.dart'; import 'package:revanced_manager/app/app.locator.dart';
import 'package:revanced_manager/services/manager_api.dart'; import 'package:revanced_manager/services/manager_api.dart';
import 'package:revanced_manager/services/toast.dart';
import 'package:revanced_manager/ui/widgets/settingsView/custom_text_field.dart'; import 'package:revanced_manager/ui/widgets/settingsView/custom_text_field.dart';
import 'package:revanced_manager/ui/widgets/settingsView/settings_tile_dialog.dart'; import 'package:revanced_manager/ui/widgets/settingsView/settings_tile_dialog.dart';
import 'package:revanced_manager/ui/widgets/shared/custom_material_button.dart'; import 'package:revanced_manager/ui/widgets/shared/custom_material_button.dart';
@ -11,6 +12,7 @@ import 'package:stacked/stacked.dart';
class SManageApiUrl extends BaseViewModel { class SManageApiUrl extends BaseViewModel {
final ManagerAPI _managerAPI = locator<ManagerAPI>(); final ManagerAPI _managerAPI = locator<ManagerAPI>();
final Toast _toast = locator<Toast>();
final TextEditingController _apiUrlController = TextEditingController(); final TextEditingController _apiUrlController = TextEditingController();
@ -90,7 +92,7 @@ class SManageApiUrl extends BaseViewModel {
label: I18nText('yesButton'), label: I18nText('yesButton'),
onPressed: () { onPressed: () {
_managerAPI.setApiUrl(''); _managerAPI.setApiUrl('');
Navigator.of(context).pop(); _toast.showBottom('settingsView.restartAppForChanges');
Navigator.of(context).pop(); Navigator.of(context).pop();
}, },
) )

View file

@ -4,6 +4,7 @@ import 'package:flutter/material.dart';
import 'package:flutter_i18n/flutter_i18n.dart'; import 'package:flutter_i18n/flutter_i18n.dart';
import 'package:revanced_manager/app/app.locator.dart'; import 'package:revanced_manager/app/app.locator.dart';
import 'package:revanced_manager/services/manager_api.dart'; import 'package:revanced_manager/services/manager_api.dart';
import 'package:revanced_manager/services/toast.dart';
import 'package:revanced_manager/ui/widgets/settingsView/custom_text_field.dart'; import 'package:revanced_manager/ui/widgets/settingsView/custom_text_field.dart';
import 'package:revanced_manager/ui/widgets/settingsView/settings_tile_dialog.dart'; import 'package:revanced_manager/ui/widgets/settingsView/settings_tile_dialog.dart';
import 'package:revanced_manager/ui/widgets/shared/custom_material_button.dart'; import 'package:revanced_manager/ui/widgets/shared/custom_material_button.dart';
@ -11,6 +12,7 @@ import 'package:stacked/stacked.dart';
class SManageSources extends BaseViewModel { class SManageSources extends BaseViewModel {
final ManagerAPI _managerAPI = locator<ManagerAPI>(); final ManagerAPI _managerAPI = locator<ManagerAPI>();
final Toast _toast = locator<Toast>();
final TextEditingController _hostSourceController = TextEditingController(); final TextEditingController _hostSourceController = TextEditingController();
final TextEditingController _orgPatSourceController = TextEditingController(); final TextEditingController _orgPatSourceController = TextEditingController();
@ -124,6 +126,7 @@ class SManageSources extends BaseViewModel {
_managerAPI.setIntegrationsRepo( _managerAPI.setIntegrationsRepo(
'${_orgIntSourceController.text}/${_intSourceController.text}', '${_orgIntSourceController.text}/${_intSourceController.text}',
); );
_toast.showBottom('settingsView.restartAppForChanges');
Navigator.of(context).pop(); Navigator.of(context).pop();
}, },
) )
@ -151,6 +154,7 @@ class SManageSources extends BaseViewModel {
_managerAPI.setRepoUrl(''); _managerAPI.setRepoUrl('');
_managerAPI.setPatchesRepo(''); _managerAPI.setPatchesRepo('');
_managerAPI.setIntegrationsRepo(''); _managerAPI.setIntegrationsRepo('');
_toast.showBottom('settingsView.restartAppForChanges');
Navigator.of(context).pop(); Navigator.of(context).pop();
}, },
) )

View file

@ -9,12 +9,14 @@ class InstalledAppItem extends StatefulWidget {
required this.pkgName, required this.pkgName,
required this.icon, required this.icon,
required this.patchesCount, required this.patchesCount,
required this.recommendedVersion,
this.onTap, this.onTap,
}) : super(key: key); }) : super(key: key);
final String name; final String name;
final String pkgName; final String pkgName;
final Uint8List icon; final Uint8List icon;
final int patchesCount; final int patchesCount;
final String recommendedVersion;
final Function()? onTap; final Function()? onTap;
@override @override
@ -46,31 +48,35 @@ class _InstalledAppItemState extends State<InstalledAppItem> {
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[ children: <Widget>[
Text(
widget.name,
maxLines: 2,
overflow: TextOverflow.visible,
style: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.w500,
),
),
const SizedBox(height: 4),
Text(widget.pkgName),
Row( Row(
children: <Widget>[ children: [
Text( Text(
widget.name, widget.recommendedVersion.isEmpty
maxLines: 2, ? 'All versions'
overflow: TextOverflow.visible, : widget.recommendedVersion,
style: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.w500,
),
), ),
const SizedBox(width: 6), const SizedBox(width: 4),
Text( Text(
widget.patchesCount == 1 widget.patchesCount == 1
? '${widget.patchesCount} patch' ? '${widget.patchesCount} patch'
: '${widget.patchesCount} patches', : '${widget.patchesCount} patches',
style: TextStyle( style: TextStyle(
fontSize: 8,
color: Theme.of(context).colorScheme.secondary, color: Theme.of(context).colorScheme.secondary,
), ),
), ),
], ],
), ),
const SizedBox(height: 4),
Text(widget.pkgName),
], ],
), ),
), ),

View file

@ -0,0 +1,85 @@
import 'package:flutter/material.dart';
import 'package:revanced_manager/ui/widgets/shared/custom_card.dart';
class NotInstalledAppItem extends StatefulWidget {
const NotInstalledAppItem({
Key? key,
required this.name,
required this.patchesCount,
required this.recommendedVersion,
this.onTap,
}) : super(key: key);
final String name;
final int patchesCount;
final String recommendedVersion;
final Function()? onTap;
@override
State<NotInstalledAppItem> createState() => _NotInstalledAppItem();
}
class _NotInstalledAppItem extends State<NotInstalledAppItem> {
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 4.0),
child: CustomCard(
onTap: widget.onTap,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Container(
height: 48,
padding: const EdgeInsets.symmetric(vertical: 4.0),
alignment: Alignment.center,
child: const CircleAvatar(
backgroundColor: Colors.transparent,
child: Icon(
Icons.square_rounded,
color: Colors.grey,
size: 44,
),
),
),
const SizedBox(width: 12),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text(
widget.name,
style: const TextStyle(
fontSize: 16,
fontWeight: FontWeight.w500,
),
),
const SizedBox(height: 4),
const Text('App not installed.'),
const SizedBox(height: 4),
Row(
children: [
Text(
widget.recommendedVersion.isEmpty
? 'All versions'
: widget.recommendedVersion,
),
const SizedBox(width: 4),
Text(
widget.patchesCount == 1
? '${widget.patchesCount} patch'
: '${widget.patchesCount} patches',
style: TextStyle(
color: Theme.of(context).colorScheme.secondary,
),
),
],
),
],
),
),
],
),
),
);
}
}

View file

@ -8,9 +8,11 @@ import 'package:revanced_manager/ui/widgets/shared/custom_material_button.dart';
class LatestCommitCard extends StatefulWidget { class LatestCommitCard extends StatefulWidget {
const LatestCommitCard({ const LatestCommitCard({
Key? key, Key? key,
required this.onPressed, required this.onPressedManager,
required this.onPressedPatches,
}) : super(key: key); }) : super(key: key);
final Function() onPressed; final Function() onPressedManager;
final Function() onPressedPatches;
@override @override
State<LatestCommitCard> createState() => _LatestCommitCardState(); State<LatestCommitCard> createState() => _LatestCommitCardState();
@ -21,66 +23,109 @@ class _LatestCommitCardState extends State<LatestCommitCard> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return CustomCard( return Column(
child: Row( children: [
mainAxisAlignment: MainAxisAlignment.spaceBetween, // ReVanced Manager
children: <Widget>[ CustomCard(
Column( child: Row(
crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[ children: <Widget>[
Row( Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[ children: <Widget>[
I18nText('latestCommitCard.patcherLabel'), Row(
FutureBuilder<String?>( children: const <Widget>[
future: model.getLatestPatcherReleaseTime(), Text('ReVanced Manager'),
builder: (context, snapshot) => Text( ],
snapshot.hasData && snapshot.data!.isNotEmpty ),
? FlutterI18n.translate( const SizedBox(height: 4),
context, Row(
'latestCommitCard.timeagoLabel', children: <Widget>[
translationParams: {'time': snapshot.data!}, FutureBuilder<String?>(
) future: model.getLatestManagerReleaseTime(),
: FlutterI18n.translate( builder: (context, snapshot) =>
context, snapshot.hasData && snapshot.data!.isNotEmpty
'latestCommitCard.loadingLabel', ? I18nText(
), 'latestCommitCard.timeagoLabel',
), translationParams: {'time': snapshot.data!},
)
: I18nText('latestCommitCard.loadingLabel'),
),
],
), ),
], ],
), ),
const SizedBox(height: 4), FutureBuilder<bool>(
Row( future: locator<HomeViewModel>().hasManagerUpdates(),
children: <Widget>[ initialData: false,
I18nText('latestCommitCard.managerLabel'), builder: (context, snapshot) => Opacity(
FutureBuilder<String?>( opacity: snapshot.hasData && snapshot.data! ? 1.0 : 0.25,
future: model.getLatestManagerReleaseTime(), child: CustomMaterialButton(
builder: (context, snapshot) => label: I18nText('updateButton'),
snapshot.hasData && snapshot.data!.isNotEmpty onPressed: snapshot.hasData && snapshot.data!
? I18nText( ? widget.onPressedManager
'latestCommitCard.timeagoLabel', : () => {},
translationParams: {'time': snapshot.data!},
)
: I18nText('latestCommitCard.loadingLabel'),
), ),
], ),
), ),
], ],
), ),
FutureBuilder<bool>( ),
future: locator<HomeViewModel>().hasManagerUpdates(),
initialData: false, const SizedBox(height: 16),
builder: (context, snapshot) => Opacity(
opacity: snapshot.hasData && snapshot.data! ? 1.0 : 0.25, // ReVanced Patches
child: CustomMaterialButton( CustomCard(
label: I18nText('latestCommitCard.updateButton'), child: Row(
onPressed: snapshot.hasData && snapshot.data! mainAxisAlignment: MainAxisAlignment.spaceBetween,
? widget.onPressed children: <Widget>[
: () => {}, Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Row(
children: const <Widget>[
Text('ReVanced Patches'),
],
),
const SizedBox(height: 4),
Row(
children: <Widget>[
FutureBuilder<String?>(
future: model.getLatestPatcherReleaseTime(),
builder: (context, snapshot) => Text(
snapshot.hasData && snapshot.data!.isNotEmpty
? FlutterI18n.translate(
context,
'latestCommitCard.timeagoLabel',
translationParams: {'time': snapshot.data!},
)
: FlutterI18n.translate(
context,
'latestCommitCard.loadingLabel',
),
),
),
],
),
],
), ),
), FutureBuilder<bool>(
future: locator<HomeViewModel>().hasPatchesUpdates(),
initialData: false,
builder: (context, snapshot) => Opacity(
opacity: snapshot.hasData && snapshot.data! ? 1.0 : 0.25,
child: CustomMaterialButton(
label: I18nText('updateButton'),
onPressed: snapshot.hasData && snapshot.data!
? widget.onPressedPatches
: () => {},
),
),
),
],
), ),
], ),
), ],
); );
} }
} }

View file

@ -20,9 +20,7 @@ dependencies:
url: https://github.com/ponces/flutter_plugin_device_apps url: https://github.com/ponces/flutter_plugin_device_apps
ref: revanced-manager ref: revanced-manager
device_info_plus: ^4.1.2 device_info_plus: ^4.1.2
dio: ^4.0.6 dio: ^5.0.0
dio_brotli_transformer: ^1.0.1
dio_http_cache_lts: ^0.4.1
dynamic_color: ^1.5.4 dynamic_color: ^1.5.4
dynamic_themes: ^1.1.0 dynamic_themes: ^1.1.0
expandable: ^5.0.1 expandable: ^5.0.1
@ -52,7 +50,7 @@ dependencies:
git: git:
url: https://github.com/SuaMusica/logcat url: https://github.com/SuaMusica/logcat
ref: feature/nullSafe ref: feature/nullSafe
native_dio_client: ^0.0.1-dev+1 native_dio_adapter: ^0.1.0
package_info_plus: ^1.4.3+1 package_info_plus: ^1.4.3+1
path_provider: ^2.0.11 path_provider: ^2.0.11
permission_handler: ^10.0.0 permission_handler: ^10.0.0
@ -75,6 +73,7 @@ dependencies:
flutter_dotenv: ^5.0.2 flutter_dotenv: ^5.0.2
pub_release: ^8.0.3 pub_release: ^8.0.3
flutter_markdown: ^0.6.13 flutter_markdown: ^0.6.13
dio_cache_interceptor: ^3.4.0
dev_dependencies: dev_dependencies:
json_serializable: ^6.3.1 json_serializable: ^6.3.1