From efb2d5ef3292b834dd76cddfe28850eafe1a38ee Mon Sep 17 00:00:00 2001 From: oSumAtrIX Date: Mon, 22 Jan 2024 09:20:17 +0100 Subject: [PATCH] feat: Improve app selector and patcher UI (#1616) --- assets/i18n/en_US.json | 22 +-- .../app_selector/app_selector_viewmodel.dart | 2 +- .../views/installer/installer_viewmodel.dart | 2 - lib/ui/views/patcher/patcher_viewmodel.dart | 47 +----- .../appSelectorView/installed_app_item.dart | 77 +++++----- .../not_installed_app_item.dart | 142 ++++++++---------- .../patcherView/app_selector_card.dart | 87 ++++++----- 7 files changed, 169 insertions(+), 210 deletions(-) diff --git a/assets/i18n/en_US.json b/assets/i18n/en_US.json index eab9e144da..b08cd50035 100644 --- a/assets/i18n/en_US.json +++ b/assets/i18n/en_US.json @@ -72,22 +72,21 @@ "widgetTitle": "Patcher", "patchButton": "Patch", - "armv7WarningDialogText": "Patching on ARMv7 devices is not yet supported and might fail. Proceed anyways?", + "armv7WarningDialogText": "Patching on ARMv7 devices is not yet supported and might fail. Continue anyways?", - "removedPatchesWarningDialogText": "The following patches have been removed since the last time you used them.\n\n{patches}\n\nProceed anyways?", + "removedPatchesWarningDialogText": "The following patches have been removed since the last time you used them.\n\n{patches}\n\nContinue anyways?", "requiredOptionDialogText" : "Some patch options have to be set." }, "appSelectorCard": { - "widgetTitle": "Select an application", - "widgetTitleSelected": "Selected application", - "widgetSubtitle": "No application selected", + "widgetTitle": "Select an app", + "widgetTitleSelected": "Selected app", + "widgetSubtitle": "No app selected", "noAppsLabel": "No applications found", - "notInstalled":"App not installed", "currentVersion": "Current", "suggestedVersion": "Suggested", - "allVersions": "All versions" + "anyVersion": "Any version" }, "patchSelectorCard": { "widgetTitle": "Select patches", @@ -101,8 +100,8 @@ "widgetSubtitle": "We are online!" }, "appSelectorView": { - "viewTitle": "Select an application", - "searchBarHint": "Search applications", + "viewTitle": "Select an app", + "searchBarHint": "Search app", "storageButton": "Storage", "selectFromStorageButton": "Select from storage", @@ -111,7 +110,7 @@ "downloadToast": "Download function is not available yet", - "requireSuggestedAppVersionDialogText": "The version of the app you have selected does not match the suggested version. Please select the app that matches the suggested version.\n\nSelected version: v{selected}\nSuggested version: v{suggested}\n\nTo proceed anyway, disable \"Require suggested app version\" in the settings.", + "requireSuggestedAppVersionDialogText": "The version of the app you have selected does not match the suggested version which can lead to unexpected issues. Please use the suggested version.\n\nSelected version: {selected}\nSuggested version: {suggested}\n\nTo continue anyway, disable \"Require suggested app version\" in the settings.", "featureNotAvailable": "Feature not implemented", "featureNotAvailableText": "This application is a split APK and cannot be selected. Unfortunately, this feature is only available for rooted users at the moment. However, you can still install the application by selecting its APK files from your device's storage instead" @@ -164,7 +163,7 @@ "installerView": { "widgetTitle": "Installer", "installType": "Select install type", - "installTypeDescription": "Select the installation type to proceed with.", + "installTypeDescription": "Select the installation type to continue with.", "installButton": "Install", "installRootType": "Mount", @@ -241,6 +240,7 @@ "versionCompatibilityCheckLabel": "Version compatibility check", "versionCompatibilityCheckHint": "Prevent selecting patches that are not compatible with the selected app version", + "requireSuggestedAppVersionLabel": "Require suggested app version", "requireSuggestedAppVersionHint": "Prevent selecting an app with a version that is not the suggested", "requireSuggestedAppVersionDialogText": "Selecting an app that is not the suggested version may cause unexpected issues.\n\nDo you want to proceed anyways?", diff --git a/lib/ui/views/app_selector/app_selector_viewmodel.dart b/lib/ui/views/app_selector/app_selector_viewmodel.dart index 5b94eee7d8..dc3d03de8b 100644 --- a/lib/ui/views/app_selector/app_selector_viewmodel.dart +++ b/lib/ui/views/app_selector/app_selector_viewmodel.dart @@ -76,7 +76,7 @@ class AppSelectorViewModel extends BaseViewModel { final String suggestedVersion = getSuggestedVersion(packageName); if (suggestedVersion.isNotEmpty) { - await openDefaultBrowser('$packageName apk version v$suggestedVersion'); + await openDefaultBrowser('$packageName apk version $suggestedVersion'); } else { await openDefaultBrowser('$packageName apk'); } diff --git a/lib/ui/views/installer/installer_viewmodel.dart b/lib/ui/views/installer/installer_viewmodel.dart index 8919d06254..d345f2c62a 100644 --- a/lib/ui/views/installer/installer_viewmodel.dart +++ b/lib/ui/views/installer/installer_viewmodel.dart @@ -220,8 +220,6 @@ class InstallerViewModel extends BaseViewModel { String suggestedVersion = _patcherAPI.getSuggestedVersion(_app.packageName); if (suggestedVersion.isEmpty) { suggestedVersion = 'Any'; - } else { - suggestedVersion = 'v$suggestedVersion'; } return suggestedVersion; } diff --git a/lib/ui/views/patcher/patcher_viewmodel.dart b/lib/ui/views/patcher/patcher_viewmodel.dart index 2c3940fdc8..f20ccc652c 100644 --- a/lib/ui/views/patcher/patcher_viewmodel.dart +++ b/lib/ui/views/patcher/patcher_viewmodel.dart @@ -148,52 +148,17 @@ class PatcherViewModel extends BaseViewModel { } String getAppSelectionString() { - String text = '${selectedApp!.name} (${selectedApp!.packageName})'; - if (text.length > 32) { - text = '${text.substring(0, 32)}...)'; - } - return text; - } - - String getCurrentVersionString(BuildContext context) { - return '${FlutterI18n.translate( - context, - 'appSelectorCard.currentVersion', - )}: v${selectedApp!.version}'; + return '${selectedApp!.name} ${selectedApp!.version}'; } - Future searchSuggestedVersionOnWeb() async { - final String suggestedVersion = - _patcherAPI.getSuggestedVersion(selectedApp!.packageName); - - if (suggestedVersion.isNotEmpty) { - await openDefaultBrowser( - '${selectedApp!.packageName} apk version v$suggestedVersion', - ); - } else { - await openDefaultBrowser('${selectedApp!.packageName} apk'); - } - } - - String getSuggestedVersion() { - return _patcherAPI.getSuggestedVersion(selectedApp!.packageName); + Future queryVersion(String suggestedVersion) async { + await openDefaultBrowser( + '${selectedApp!.packageName} apk version $suggestedVersion', + ); } String getSuggestedVersionString(BuildContext context) { - String suggestedVersion = - _patcherAPI.getSuggestedVersion(selectedApp!.packageName); - if (suggestedVersion.isEmpty) { - suggestedVersion = FlutterI18n.translate( - context, - 'appSelectorCard.allVersions', - ); - } else { - suggestedVersion = 'v$suggestedVersion'; - } - return '${FlutterI18n.translate( - context, - 'appSelectorCard.suggestedVersion', - )}: $suggestedVersion'; + return _patcherAPI.getSuggestedVersion(selectedApp!.packageName); } Future openDefaultBrowser(String query) async { diff --git a/lib/ui/widgets/appSelectorView/installed_app_item.dart b/lib/ui/widgets/appSelectorView/installed_app_item.dart index 62dc1e1c86..ab96d4f5a5 100644 --- a/lib/ui/widgets/appSelectorView/installed_app_item.dart +++ b/lib/ui/widgets/appSelectorView/installed_app_item.dart @@ -54,25 +54,43 @@ class _InstalledAppItemState extends State { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text( - widget.name, - maxLines: 2, - overflow: TextOverflow.visible, - style: const TextStyle( - fontSize: 16, - fontWeight: FontWeight.w500, - ), + Wrap( + crossAxisAlignment: WrapCrossAlignment.center, + spacing: 4, + children: [ + Text( + widget.name, + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: const TextStyle( + fontSize: 16, + ), + ), + Text( + widget.installedVersion, + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: const TextStyle( + fontSize: 16, + ), + ), + Text( + widget.patchesCount == 1 + ? '• ${widget.patchesCount} patch' + : '• ${widget.patchesCount} patches', + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: TextStyle( + fontSize: 16, + color: Theme.of(context).colorScheme.secondary, + ), + ), + ], ), - Text(widget.pkgName), - I18nText( - FlutterI18n.translate( - context, - 'installed', - translationParams: { - 'version': 'v${widget.installedVersion}', - }, - ), + Text( + widget.pkgName, ), + const SizedBox(height: 4), Wrap( crossAxisAlignment: WrapCrossAlignment.center, children: [ @@ -85,7 +103,7 @@ class _InstalledAppItemState extends State { borderRadius: const BorderRadius.all(Radius.circular(8)), child: Container( - padding: const EdgeInsets.all(4), + padding: const EdgeInsets.fromLTRB(8, 4, 8, 4), child: Row( mainAxisSize: MainAxisSize.min, children: [ @@ -95,18 +113,10 @@ class _InstalledAppItemState extends State { 'version': widget.suggestedVersion.isEmpty ? FlutterI18n.translate( context, - 'appSelectorCard.allVersions', + 'appSelectorCard.anyVersion', ) - : 'v${widget.suggestedVersion}', + : widget.suggestedVersion, }, - child: Text( - '', - style: TextStyle( - color: Theme.of(context) - .colorScheme - .onSecondaryContainer, - ), - ), ), const SizedBox(width: 4), Icon( @@ -121,17 +131,6 @@ class _InstalledAppItemState extends State { ), ), ), - const SizedBox(width: 4), - Text( - widget.patchesCount == 1 - ? '• ${widget.patchesCount} patch' - : '• ${widget.patchesCount} patches', - maxLines: 1, - overflow: TextOverflow.ellipsis, - style: TextStyle( - color: Theme.of(context).colorScheme.secondary, - ), - ), ], ), ], diff --git a/lib/ui/widgets/appSelectorView/not_installed_app_item.dart b/lib/ui/widgets/appSelectorView/not_installed_app_item.dart index 83ac734219..6e500b3db1 100644 --- a/lib/ui/widgets/appSelectorView/not_installed_app_item.dart +++ b/lib/ui/widgets/appSelectorView/not_installed_app_item.dart @@ -33,103 +33,91 @@ class _NotInstalledAppItem extends State { mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Container( + width: 48, height: 48, - padding: const EdgeInsets.symmetric(vertical: 4.0), alignment: Alignment.center, child: const CircleAvatar( backgroundColor: Colors.transparent, child: Icon( Icons.square_rounded, color: Colors.grey, - size: 44, + size: 48, ), ), ), const SizedBox(width: 12), Expanded( child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - widget.name, - style: const TextStyle( - fontSize: 16, - fontWeight: FontWeight.w500, - ), - ), - const SizedBox(height: 4), - I18nText( - 'appSelectorCard.notInstalled', - child: Text( - '', - style: TextStyle( - color: Theme.of(context).textTheme.titleLarge!.color, - ), + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Wrap( + crossAxisAlignment: WrapCrossAlignment.center, + spacing: 4, + children: [ + Text( + widget.name, + style: const TextStyle( + fontSize: 16, + ), + ), + Text( + widget.patchesCount == 1 + ? '• ${widget.patchesCount} patch' + : '• ${widget.patchesCount} patches', + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: TextStyle( + fontSize: 16, + color: Theme.of(context).colorScheme.secondary, + ), + ), + ], ), - ), - Wrap( - crossAxisAlignment: WrapCrossAlignment.center, - children: [ - Material( - color: Theme.of(context).colorScheme.secondaryContainer, - borderRadius: - const BorderRadius.all(Radius.circular(8)), - child: InkWell( - onTap: widget.onLinkTap, + const SizedBox(height: 4), + Wrap( + crossAxisAlignment: WrapCrossAlignment.center, + children: [ + Material( + color: + Theme.of(context).colorScheme.secondaryContainer, borderRadius: const BorderRadius.all(Radius.circular(8)), - child: Container( - padding: const EdgeInsets.all(4), - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - I18nText( - 'suggested', - translationParams: { - 'version': widget.suggestedVersion.isEmpty - ? FlutterI18n.translate( - context, - 'appSelectorCard.allVersions', - ) - : 'v${widget.suggestedVersion}', - }, - child: Text( - '', - style: TextStyle( - color: Theme.of(context) - .colorScheme - .onSecondaryContainer, - ), + child: InkWell( + onTap: widget.onLinkTap, + borderRadius: + const BorderRadius.all(Radius.circular(8)), + child: Container( + padding: const EdgeInsets.fromLTRB(8, 4, 8, 4), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + I18nText( + 'suggested', + translationParams: { + 'version': widget.suggestedVersion.isEmpty + ? FlutterI18n.translate( + context, + 'appSelectorCard.anyVersion', + ) + : widget.suggestedVersion, + }, ), - ), - const SizedBox(width: 4), - Icon( - Icons.search, - size: 16, - color: Theme.of(context) - .colorScheme - .onSecondaryContainer, - ), - ], + const SizedBox(width: 4), + Icon( + Icons.search, + size: 16, + color: Theme.of(context) + .colorScheme + .onSecondaryContainer, + ), + ], + ), ), ), ), - ), - const SizedBox(width: 4), - Text( - widget.patchesCount == 1 - ? '• ${widget.patchesCount} patch' - : '• ${widget.patchesCount} patches', - maxLines: 1, - overflow: TextOverflow.ellipsis, - style: TextStyle( - color: Theme.of(context).colorScheme.secondary, - ), - ), - ], - ), - ], - ), + ], + ), + ]), ), ], ), diff --git a/lib/ui/widgets/patcherView/app_selector_card.dart b/lib/ui/widgets/patcherView/app_selector_card.dart index f0c05b8378..a15359c09b 100644 --- a/lib/ui/widgets/patcherView/app_selector_card.dart +++ b/lib/ui/widgets/patcherView/app_selector_card.dart @@ -15,13 +15,21 @@ class AppSelectorCard extends StatelessWidget { @override Widget build(BuildContext context) { + final vm = locator(); + + + String? suggestedVersion; + if (vm.selectedApp != null) { + suggestedVersion = vm.getSuggestedVersionString(context); + } + return CustomCard( onTap: onPressed, child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ I18nText( - locator().selectedApp == null + vm.selectedApp == null ? 'appSelectorCard.widgetTitle' : 'appSelectorCard.widgetTitleSelected', child: const Text( @@ -33,7 +41,7 @@ class AppSelectorCard extends StatelessWidget { ), ), const SizedBox(height: 8), - if (locator().selectedApp == null) + if (vm.selectedApp == null) I18nText('appSelectorCard.widgetSubtitle') else Row( @@ -42,9 +50,9 @@ class AppSelectorCard extends StatelessWidget { height: 18.0, child: ClipOval( child: Image.memory( - locator().selectedApp == null + vm.selectedApp == null ? Uint8List(0) - : locator().selectedApp!.icon, + : vm.selectedApp!.icon, fit: BoxFit.cover, ), ), @@ -52,13 +60,13 @@ class AppSelectorCard extends StatelessWidget { const SizedBox(width: 6), Flexible( child: Text( - locator().getAppSelectionString(), + vm.getAppSelectionString(), style: const TextStyle(fontWeight: FontWeight.w600), ), ), ], ), - if (locator().selectedApp == null) + if (vm.selectedApp == null) Container() else Column( @@ -66,49 +74,50 @@ class AppSelectorCard extends StatelessWidget { children: [ const SizedBox(height: 4), Text( - locator().getCurrentVersionString(context), + vm.selectedApp!.packageName, ), - Row( - children: [ - Material( - color: Theme.of(context).colorScheme.secondaryContainer, - borderRadius: const BorderRadius.all(Radius.circular(8)), - child: InkWell( - onTap: () { - locator() - .searchSuggestedVersionOnWeb(); - }, + if (suggestedVersion!.isNotEmpty && + suggestedVersion != vm.selectedApp!.version) ...[ + const SizedBox(height: 4), + Row( + children: [ + Material( + color: Theme.of(context).colorScheme.secondaryContainer, borderRadius: const BorderRadius.all(Radius.circular(8)), - child: Container( - padding: const EdgeInsets.all(4), - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - Text( - locator() - .getSuggestedVersionString(context), - style: TextStyle( + child: InkWell( + onTap: () { + vm.queryVersion(suggestedVersion!); + }, + borderRadius: + const BorderRadius.all(Radius.circular(8)), + child: Container( + padding: const EdgeInsets.fromLTRB(8, 4, 8, 4), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + I18nText( + 'suggested', + translationParams: { + 'version': suggestedVersion, + }, + ), + const SizedBox(width: 4), + Icon( + Icons.search, + size: 16, color: Theme.of(context) .colorScheme .onSecondaryContainer, ), - ), - const SizedBox(width: 4), - Icon( - Icons.search, - size: 16, - color: Theme.of(context) - .colorScheme - .onSecondaryContainer, - ), - ], + ], + ), ), ), ), - ), - ], - ), + ], + ), + ] ], ), ],