From ea81b0e66eaa23f4213833a20b58465c6c2fb87e Mon Sep 17 00:00:00 2001 From: Imran Remtulla Date: Sat, 18 Feb 2023 18:31:42 -0500 Subject: [PATCH 1/9] Bugfix for different ID same URL Apps (#299) --- lib/pages/add_app.dart | 2 +- lib/pages/app.dart | 10 +++++++++- lib/providers/apps_provider.dart | 2 +- lib/providers/source_provider.dart | 21 ++++++--------------- 4 files changed, 17 insertions(+), 18 deletions(-) diff --git a/lib/pages/add_app.dart b/lib/pages/add_app.dart index 80720a1..165910b 100644 --- a/lib/pages/add_app.dart +++ b/lib/pages/add_app.dart @@ -118,7 +118,7 @@ class _AddAppPageState extends State { await settingsProvider.getInstallPermission(); } // Only download the APK here if you need to for the package ID - if (sourceProvider.isTempId(app.id) && + if (sourceProvider.isTempId(app) && app.additionalSettings['trackOnly'] != true) { // ignore: use_build_context_synchronously var apkUrl = await appsProvider.confirmApkUrl(app, context); diff --git a/lib/pages/app.dart b/lib/pages/app.dart index 312dfdb..46d9828 100644 --- a/lib/pages/app.dart +++ b/lib/pages/app.dart @@ -113,7 +113,7 @@ class _AppPageState extends State { mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.stretch, children: [ - const SizedBox(height: 150), + const SizedBox(height: 125), app?.installedInfo != null ? Row(mainAxisAlignment: MainAxisAlignment.center, children: [ Image.memory( @@ -136,6 +136,14 @@ class _AppPageState extends State { textAlign: TextAlign.center, style: Theme.of(context).textTheme.headlineMedium, ), + const SizedBox( + height: 8, + ), + Text( + app?.app.id ?? '', + textAlign: TextAlign.center, + style: Theme.of(context).textTheme.labelSmall, + ), const SizedBox( height: 32, ), diff --git a/lib/providers/apps_provider.dart b/lib/providers/apps_provider.dart index 2fc89a3..f24cc98 100644 --- a/lib/providers/apps_provider.dart +++ b/lib/providers/apps_provider.dart @@ -182,7 +182,7 @@ class AppsProvider with ChangeNotifier { // The former case should be handled (give the App its real ID), the latter is a security issue var newInfo = await PackageArchiveInfo.fromPath(downloadedFile.path); if (app.id != newInfo.packageName) { - if (apps[app.id] != null && !SourceProvider().isTempId(app.id)) { + if (apps[app.id] != null && !SourceProvider().isTempId(app)) { throw IDChangedError(); } var originalAppId = app.id; diff --git a/lib/providers/source_provider.dart b/lib/providers/source_provider.dart index e4c76e7..955a82d 100644 --- a/lib/providers/source_provider.dart +++ b/lib/providers/source_provider.dart @@ -350,21 +350,12 @@ class SourceProvider { return false; } - String generateTempID(AppNames names, AppSource source) => - '${names.author.toLowerCase()}_${names.name.toLowerCase()}_${source.host}'; + String generateTempID( + String standardUrl, Map additionalSettings) => + (standardUrl + additionalSettings.toString()).hashCode.toString(); - bool isTempId(String id) { - List parts = id.split('_'); - if (parts.length < 3) { - return false; - } - for (int i = 0; i < parts.length - 1; i++) { - if (RegExp('.*[A-Z].*').hasMatch(parts[i])) { - // TODO: Look into RegEx for non-Latin characters - return false; - } - } - return true; + bool isTempId(App app) { + return app.id == generateTempID(app.url, app.additionalSettings); } Future getApp( @@ -400,7 +391,7 @@ class SourceProvider { currentApp?.id ?? source.tryInferringAppId(standardUrl, additionalSettings: additionalSettings) ?? - generateTempID(apk.names, source), + generateTempID(standardUrl, additionalSettings), standardUrl, apk.names.author[0].toUpperCase() + apk.names.author.substring(1), name.trim().isNotEmpty From 191776d0d5990cab00c2f0fe24e4df93dda5db03 Mon Sep 17 00:00:00 2001 From: Imran Remtulla Date: Sat, 18 Feb 2023 20:37:30 -0500 Subject: [PATCH 2/9] Initial release date support --- assets/translations/de.json | 3 + assets/translations/en.json | 3 + assets/translations/fa.json | 3 + assets/translations/hu.json | 3 + assets/translations/it.json | 3 + assets/translations/ja.json | 3 + assets/translations/zh.json | 3 + lib/app_sources/github.dart | 6 +- lib/pages/add_app.dart | 22 ++++++- lib/pages/app.dart | 38 ++++++++++++ lib/pages/apps.dart | 98 +++++++++++++++++++++--------- lib/providers/source_provider.dart | 90 ++++++++++++++++----------- 12 files changed, 209 insertions(+), 66 deletions(-) diff --git a/assets/translations/de.json b/assets/translations/de.json index 69ce123..6cff611 100644 --- a/assets/translations/de.json +++ b/assets/translations/de.json @@ -213,6 +213,9 @@ "removeFromObtainium": "Remove from Obtainium", "uninstallFromDevice": "Uninstall from Device", "onlyWorksWithNonVersionDetectApps": "Only works for Apps with version detection disabled.", + "useReleaseDateAsVersion": "Use Release Date as Version", + "releaseDateAsVersionExplanation": "This option should only be used for Apps where version detection does not work correctly, but a release date is available.", + "changes": "Changes", "removeAppQuestion": { "one": "App entfernen?", "other": "App entfernen?" diff --git a/assets/translations/en.json b/assets/translations/en.json index 95a41ef..eeed384 100644 --- a/assets/translations/en.json +++ b/assets/translations/en.json @@ -213,6 +213,9 @@ "removeFromObtainium": "Remove from Obtainium", "uninstallFromDevice": "Uninstall from Device", "onlyWorksWithNonVersionDetectApps": "Only works for Apps with version detection disabled.", + "useReleaseDateAsVersion": "Use Release Date as Version", + "releaseDateAsVersionExplanation": "This option should only be used for Apps where version detection does not work correctly, but a release date is available.", + "changes": "Changes", "removeAppQuestion": { "one": "Remove App?", "other": "Remove Apps?" diff --git a/assets/translations/fa.json b/assets/translations/fa.json index 57f1d4f..fcafb95 100644 --- a/assets/translations/fa.json +++ b/assets/translations/fa.json @@ -213,6 +213,9 @@ "removeFromObtainium": "از Obtainium حذف کنید", "uninstallFromDevice": "حذف نصب از دستگاه", "onlyWorksWithNonVersionDetectApps": "فقط برای برنامه‌هایی کار می‌کند که تشخیص نسخه غیرفعال است.", + "useReleaseDateAsVersion": "Use Release Date as Version", + "releaseDateAsVersionExplanation": "This option should only be used for Apps where version detection does not work correctly, but a release date is available.", + "changes": "Changes", "removeAppQuestion": { "one": "برنامه حذف شود؟", "other": "برنامه ها حذف شوند؟" diff --git a/assets/translations/hu.json b/assets/translations/hu.json index 9126c61..6982f24 100644 --- a/assets/translations/hu.json +++ b/assets/translations/hu.json @@ -212,6 +212,9 @@ "removeFromObtainium": "Eltávolítás az Obtainiumból", "uninstallFromDevice": "Eltávolítás a készülékről", "onlyWorksWithNonVersionDetectApps": "Csak azoknál az alkalmazásoknál működik, amelyeknél a verzióérzékelés le van tiltva.", + "useReleaseDateAsVersion": "Use Release Date as Version", + "releaseDateAsVersionExplanation": "This option should only be used for Apps where version detection does not work correctly, but a release date is available.", + "changes": "Changes", "removeAppQuestion": { "one": "Eltávolítja az alkalmazást?", "other": "Eltávolítja az alkalmazást?" diff --git a/assets/translations/it.json b/assets/translations/it.json index fab4c95..ad67d63 100644 --- a/assets/translations/it.json +++ b/assets/translations/it.json @@ -213,6 +213,9 @@ "removeFromObtainium": "Rimuovi da Obtainium", "uninstallFromDevice": "Disinstalla dal dispositivo", "onlyWorksWithNonVersionDetectApps": "Funziona solo per le App con il rilevamento della versione disattivato.", + "useReleaseDateAsVersion": "Use Release Date as Version", + "releaseDateAsVersionExplanation": "This option should only be used for Apps where version detection does not work correctly, but a release date is available.", + "changes": "Changes", "removeAppQuestion": { "one": "Rimuovere l'App?", "other": "Rimuovere le App?" diff --git a/assets/translations/ja.json b/assets/translations/ja.json index 7804ded..ca15558 100644 --- a/assets/translations/ja.json +++ b/assets/translations/ja.json @@ -213,6 +213,9 @@ "removeFromObtainium": "Obtainiumから削除する", "uninstallFromDevice": "デバイスからアンインストールする", "onlyWorksWithNonVersionDetectApps": "バージョン検出を無効にしているアプリにのみ動作します。", + "useReleaseDateAsVersion": "Use Release Date as Version", + "releaseDateAsVersionExplanation": "This option should only be used for Apps where version detection does not work correctly, but a release date is available.", + "changes": "Changes", "removeAppQuestion": { "one": "アプリを削除しますか?", "other": "アプリを削除しますか?" diff --git a/assets/translations/zh.json b/assets/translations/zh.json index 89b6ea6..ba97af8 100644 --- a/assets/translations/zh.json +++ b/assets/translations/zh.json @@ -213,6 +213,9 @@ "filterAPKsByRegEx": "Filter APKs by Regular Expression", "removeFromObtainium": "Remove from Obtainium", "uninstallFromDevice": "Uninstall from Device", + "useReleaseDateAsVersion": "Use Release Date as Version", + "releaseDateAsVersionExplanation": "This option should only be used for Apps where version detection does not work correctly, but a release date is available.", + "changes": "Changes", "removeAppQuestion": { "one": "删除应用?", "other": "删除应用?" diff --git a/lib/app_sources/github.dart b/lib/app_sources/github.dart index f4660a5..1ce8cfc 100644 --- a/lib/app_sources/github.dart +++ b/lib/app_sources/github.dart @@ -154,11 +154,15 @@ class GitHub extends AppSource { throw NoReleasesError(); } String? version = targetRelease['tag_name']; + DateTime? releaseDate = targetRelease['published_at'] != null + ? DateTime.parse(targetRelease['published_at']) + : null; if (version == null) { throw NoVersionError(); } return APKDetails(version, targetRelease['apkUrls'] as List, - getAppNames(standardUrl)); + getAppNames(standardUrl), + releaseDate: releaseDate); } else { rateLimitErrorCheck(res); throw getObtainiumHttpError(res); diff --git a/lib/pages/add_app.dart b/lib/pages/add_app.dart index 165910b..0208c9e 100644 --- a/lib/pages/add_app.dart +++ b/lib/pages/add_app.dart @@ -73,6 +73,8 @@ class _AddAppPageState extends State { var userPickedTrackOnly = additionalSettings['trackOnly'] == true; var userPickedNoVersionDetection = additionalSettings['noVersionDetection'] == true; + var userPickedReleaseDateAsVersion = + additionalSettings['releaseDateAsVersion'] == true; var cont = true; if ((userPickedTrackOnly || pickedSource!.enforceTrackOnly) && // ignore: use_build_context_synchronously @@ -93,7 +95,22 @@ class _AddAppPageState extends State { null) { cont = false; } - if (userPickedNoVersionDetection && + if (userPickedReleaseDateAsVersion && // ignore: use_build_context_synchronously + // ignore: use_build_context_synchronously + await showDialog( + context: context, + builder: (BuildContext ctx) { + return GeneratedFormModal( + title: tr('useReleaseDateAsVersion'), + items: const [], + message: tr('releaseDateAsVersionExplanation'), + ); + }) == + null) { + cont = false; + } + if (!userPickedReleaseDateAsVersion && + userPickedNoVersionDetection && // ignore: use_build_context_synchronously await showDialog( context: context, @@ -113,7 +130,8 @@ class _AddAppPageState extends State { App app = await sourceProvider.getApp( pickedSource!, userInput, additionalSettings, trackOnlyOverride: trackOnly, - noVersionDetectionOverride: userPickedNoVersionDetection); + noVersionDetectionOverride: userPickedNoVersionDetection, + releaseDateAsVersionOverride: userPickedReleaseDateAsVersion); if (!trackOnly) { await settingsProvider.getInstallPermission(); } diff --git a/lib/pages/app.dart b/lib/pages/app.dart index 46d9828..4e0fa5a 100644 --- a/lib/pages/app.dart +++ b/lib/pages/app.dart @@ -144,6 +144,13 @@ class _AppPageState extends State { textAlign: TextAlign.center, style: Theme.of(context).textTheme.labelSmall, ), + app?.app.releaseDate == null + ? const SizedBox.shrink() + : Text( + app!.app.releaseDate.toString(), + textAlign: TextAlign.center, + style: Theme.of(context).textTheme.labelSmall, + ), const SizedBox( height: 32, ), @@ -286,6 +293,37 @@ class _AppPageState extends State { tr('appsFromSourceAreTrackOnly'), context); } + if (changedApp.additionalSettings[ + 'releaseDateAsVersion'] == + true) { + changedApp.additionalSettings[ + 'noVersionDetection'] = true; + if (app.app.additionalSettings[ + 'releaseDateAsVersion'] != + true) { + if (app.app.releaseDate != null) { + changedApp.latestVersion = app + .app + .releaseDate! + .microsecondsSinceEpoch + .toString(); + if (app.app.installedVersion == + app.app.latestVersion) { + changedApp.installedVersion = + changedApp.latestVersion; + } + } + } + } else if (app.app.additionalSettings[ + 'releaseDateAsVersion'] == + true) { + changedApp.additionalSettings[ + 'noVersionDetection'] = false; + changedApp.installedVersion = app + .installedInfo + ?.versionName ?? + changedApp.installedVersion; + } appsProvider.saveApps( [changedApp]).then((value) { getUpdate(changedApp.id); diff --git a/lib/pages/apps.dart b/lib/pages/apps.dart index 06a7ae7..0b49c0d 100644 --- a/lib/pages/apps.dart +++ b/lib/pages/apps.dart @@ -264,6 +264,7 @@ class AppsPageState extends State { sortedApps[index].installedInfo?.name ?? sortedApps[index].app.name, style: TextStyle( + overflow: TextOverflow.ellipsis, fontWeight: sortedApps[index].app.pinned ? FontWeight.bold : FontWeight.normal, @@ -289,12 +290,35 @@ class AppsPageState extends State { mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.end, children: [ - SizedBox( - width: 100, + Row( + mainAxisSize: MainAxisSize.min, + children: [ + Text( + '${sortedApps[index].app.installedVersion ?? tr('notInstalled')}${sortedApps[index].app.additionalSettings['trackOnly'] == true ? ' ${tr('estimateInBrackets')}' : ''}', + overflow: TextOverflow.ellipsis, + textAlign: TextAlign.end, + ) + ]), + GestureDetector( + onTap: changesUrl == null + ? null + : () { + launchUrlString(changesUrl, + mode: LaunchMode + .externalApplication); + }, child: Text( - '${sortedApps[index].app.installedVersion ?? tr('notInstalled')}${sortedApps[index].app.additionalSettings['trackOnly'] == true ? ' ${tr('estimateInBrackets')}' : ''}', - overflow: TextOverflow.fade, - textAlign: TextAlign.end, + sortedApps[index].app.releaseDate == + null + ? tr('changes') + : DateFormat('yyyy-MM-dd').format( + sortedApps[index] + .app + .releaseDate!), + style: const TextStyle( + fontStyle: FontStyle.italic, + decoration: + TextDecoration.underline), )), sortedApps[index].app.installedVersion != null && @@ -304,29 +328,47 @@ class AppsPageState extends State { sortedApps[index] .app .latestVersion - ? GestureDetector( - onTap: changesUrl == null - ? null - : () { - launchUrlString(changesUrl, - mode: LaunchMode - .externalApplication); - }, - child: appsProvider - .areDownloadsRunning() - ? Text(tr('pleaseWait')) - : Text( - '${tr('updateAvailable')}${sortedApps[index].app.additionalSettings['trackOnly'] == true ? ' ${tr('estimateInBracketsShort')}' : ''}', - style: TextStyle( - fontStyle: - FontStyle.italic, - decoration: changesUrl == - null - ? TextDecoration.none - : TextDecoration - .underline), - )) - : const SizedBox(), + ? appsProvider.areDownloadsRunning() + ? Text(tr('pleaseWait')) + : Row( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: + MainAxisAlignment.end, + children: [ + GestureDetector( + onTap: () { + appsProvider + .downloadAndInstallLatestApps( + [ + sortedApps[index] + .app + .id + ], + globalNavigatorKey + .currentContext).catchError( + (e) { + showError(e, context); + }); + }, + child: Text( + sortedApps[index] + .app + .additionalSettings[ + 'trackOnly'] == + true + ? tr('markUpdated') + : tr('update'), + style: TextStyle( + color: + Theme.of(context) + .colorScheme + .primary, + fontWeight: + FontWeight.bold), + )), + ], + ) + : const SizedBox.shrink(), ], ))), onTap: () { diff --git a/lib/providers/source_provider.dart b/lib/providers/source_provider.dart index 955a82d..357081a 100644 --- a/lib/providers/source_provider.dart +++ b/lib/providers/source_provider.dart @@ -33,8 +33,9 @@ class APKDetails { late String version; late List apkUrls; late AppNames names; + late DateTime? releaseDate; - APKDetails(this.version, this.apkUrls, this.names); + APKDetails(this.version, this.apkUrls, this.names, {this.releaseDate}); } class App { @@ -50,6 +51,7 @@ class App { late DateTime? lastUpdateCheck; bool pinned = false; List categories; + late DateTime? releaseDate; App( this.id, this.url, @@ -62,7 +64,8 @@ class App { this.additionalSettings, this.lastUpdateCheck, this.pinned, - {this.categories = const []}); + {this.categories = const [], + this.releaseDate}); @override String toString() { @@ -111,30 +114,34 @@ class App { preferredApkIndex = 0; } return App( - json['id'] as String, - json['url'] as String, - json['author'] as String, - json['name'] as String, - json['installedVersion'] == null - ? null - : json['installedVersion'] as String, - json['latestVersion'] as String, - json['apkUrls'] == null - ? [] - : List.from(jsonDecode(json['apkUrls'])), - preferredApkIndex, - additionalSettings, - json['lastUpdateCheck'] == null - ? null - : DateTime.fromMicrosecondsSinceEpoch(json['lastUpdateCheck']), - json['pinned'] ?? false, - categories: json['categories'] != null - ? (json['categories'] as List) - .map((e) => e.toString()) - .toList() - : json['category'] != null - ? [json['category'] as String] - : []); + json['id'] as String, + json['url'] as String, + json['author'] as String, + json['name'] as String, + json['installedVersion'] == null + ? null + : json['installedVersion'] as String, + json['latestVersion'] as String, + json['apkUrls'] == null + ? [] + : List.from(jsonDecode(json['apkUrls'])), + preferredApkIndex, + additionalSettings, + json['lastUpdateCheck'] == null + ? null + : DateTime.fromMicrosecondsSinceEpoch(json['lastUpdateCheck']), + json['pinned'] ?? false, + categories: json['categories'] != null + ? (json['categories'] as List) + .map((e) => e.toString()) + .toList() + : json['category'] != null + ? [json['category'] as String] + : [], + releaseDate: json['releaseDate'] == null + ? null + : DateTime.fromMicrosecondsSinceEpoch(json['releaseDate']), + ); } Map toJson() => { @@ -149,7 +156,8 @@ class App { 'additionalSettings': jsonEncode(additionalSettings), 'lastUpdateCheck': lastUpdateCheck?.microsecondsSinceEpoch, 'pinned': pinned, - 'categories': categories + 'categories': categories, + 'releaseDate': releaseDate?.microsecondsSinceEpoch }; } @@ -225,6 +233,10 @@ class AppSource { label: tr('trackOnly'), ) ], + [ + GeneratedFormSwitch('releaseDateAsVersion', + label: tr('useReleaseDateAsVersion')) + ], [ GeneratedFormSwitch('noVersionDetection', label: tr('noVersionDetection')) ], @@ -359,16 +371,19 @@ class SourceProvider { } Future getApp( - AppSource source, - String url, - Map additionalSettings, { - App? currentApp, - bool trackOnlyOverride = false, - noVersionDetectionOverride = false, - }) async { + AppSource source, String url, Map additionalSettings, + {App? currentApp, + bool trackOnlyOverride = false, + bool noVersionDetectionOverride = false, + bool releaseDateAsVersionOverride = false}) async { if (trackOnlyOverride || source.enforceTrackOnly) { additionalSettings['trackOnly'] = true; } + if (releaseDateAsVersionOverride) { + additionalSettings['releaseDateAsVersion'] = true; + noVersionDetectionOverride = + true; // Rel. date as version means no ver. det. + } if (noVersionDetectionOverride) { additionalSettings['noVersionDetection'] = true; } @@ -376,6 +391,10 @@ class SourceProvider { String standardUrl = source.standardizeURL(preStandardizeUrl(url)); APKDetails apk = await source.getLatestAPKDetails(standardUrl, additionalSettings); + if (additionalSettings['releaseDateAsVersion'] == true && + apk.releaseDate != null) { + apk.version = apk.releaseDate!.microsecondsSinceEpoch.toString(); + } if (additionalSettings['apkFilterRegEx'] != null) { var reg = RegExp(additionalSettings['apkFilterRegEx']); apk.apkUrls = @@ -404,7 +423,8 @@ class SourceProvider { additionalSettings, DateTime.now(), currentApp?.pinned ?? false, - categories: currentApp?.categories ?? const []); + categories: currentApp?.categories ?? const [], + releaseDate: apk.releaseDate); } // Returns errors in [results, errors] instead of throwing them From 5f7e342e6bc8b5a10eaf9842a4dde5e0edc4bb43 Mon Sep 17 00:00:00 2001 From: Imran Remtulla Date: Sat, 18 Feb 2023 20:47:29 -0500 Subject: [PATCH 3/9] Added rel. date sort --- assets/translations/de.json | 1 + assets/translations/en.json | 1 + assets/translations/fa.json | 1 + assets/translations/hu.json | 1 + assets/translations/it.json | 1 + assets/translations/ja.json | 1 + assets/translations/zh.json | 1 + lib/pages/apps.dart | 91 +++++++++++++++------------- lib/pages/settings.dart | 4 ++ lib/providers/settings_provider.dart | 3 +- 10 files changed, 60 insertions(+), 45 deletions(-) diff --git a/assets/translations/de.json b/assets/translations/de.json index 6cff611..3aa50e4 100644 --- a/assets/translations/de.json +++ b/assets/translations/de.json @@ -216,6 +216,7 @@ "useReleaseDateAsVersion": "Use Release Date as Version", "releaseDateAsVersionExplanation": "This option should only be used for Apps where version detection does not work correctly, but a release date is available.", "changes": "Changes", + "releaseDate": "Release Date", "removeAppQuestion": { "one": "App entfernen?", "other": "App entfernen?" diff --git a/assets/translations/en.json b/assets/translations/en.json index eeed384..c6c91bd 100644 --- a/assets/translations/en.json +++ b/assets/translations/en.json @@ -216,6 +216,7 @@ "useReleaseDateAsVersion": "Use Release Date as Version", "releaseDateAsVersionExplanation": "This option should only be used for Apps where version detection does not work correctly, but a release date is available.", "changes": "Changes", + "releaseDate": "Release Date", "removeAppQuestion": { "one": "Remove App?", "other": "Remove Apps?" diff --git a/assets/translations/fa.json b/assets/translations/fa.json index fcafb95..999162b 100644 --- a/assets/translations/fa.json +++ b/assets/translations/fa.json @@ -216,6 +216,7 @@ "useReleaseDateAsVersion": "Use Release Date as Version", "releaseDateAsVersionExplanation": "This option should only be used for Apps where version detection does not work correctly, but a release date is available.", "changes": "Changes", + "releaseDate": "Release Date", "removeAppQuestion": { "one": "برنامه حذف شود؟", "other": "برنامه ها حذف شوند؟" diff --git a/assets/translations/hu.json b/assets/translations/hu.json index 6982f24..45ee74d 100644 --- a/assets/translations/hu.json +++ b/assets/translations/hu.json @@ -215,6 +215,7 @@ "useReleaseDateAsVersion": "Use Release Date as Version", "releaseDateAsVersionExplanation": "This option should only be used for Apps where version detection does not work correctly, but a release date is available.", "changes": "Changes", + "releaseDate": "Release Date", "removeAppQuestion": { "one": "Eltávolítja az alkalmazást?", "other": "Eltávolítja az alkalmazást?" diff --git a/assets/translations/it.json b/assets/translations/it.json index ad67d63..39d8054 100644 --- a/assets/translations/it.json +++ b/assets/translations/it.json @@ -216,6 +216,7 @@ "useReleaseDateAsVersion": "Use Release Date as Version", "releaseDateAsVersionExplanation": "This option should only be used for Apps where version detection does not work correctly, but a release date is available.", "changes": "Changes", + "releaseDate": "Release Date", "removeAppQuestion": { "one": "Rimuovere l'App?", "other": "Rimuovere le App?" diff --git a/assets/translations/ja.json b/assets/translations/ja.json index ca15558..fce19ec 100644 --- a/assets/translations/ja.json +++ b/assets/translations/ja.json @@ -216,6 +216,7 @@ "useReleaseDateAsVersion": "Use Release Date as Version", "releaseDateAsVersionExplanation": "This option should only be used for Apps where version detection does not work correctly, but a release date is available.", "changes": "Changes", + "releaseDate": "Release Date", "removeAppQuestion": { "one": "アプリを削除しますか?", "other": "アプリを削除しますか?" diff --git a/assets/translations/zh.json b/assets/translations/zh.json index ba97af8..001a3ef 100644 --- a/assets/translations/zh.json +++ b/assets/translations/zh.json @@ -216,6 +216,7 @@ "useReleaseDateAsVersion": "Use Release Date as Version", "releaseDateAsVersionExplanation": "This option should only be used for Apps where version detection does not work correctly, but a release date is available.", "changes": "Changes", + "releaseDate": "Release Date", "removeAppQuestion": { "one": "删除应用?", "other": "删除应用?" diff --git a/lib/pages/apps.dart b/lib/pages/apps.dart index 0b49c0d..3cf8040 100644 --- a/lib/pages/apps.dart +++ b/lib/pages/apps.dart @@ -54,12 +54,12 @@ class AppsPageState extends State { Widget build(BuildContext context) { var appsProvider = context.watch(); var settingsProvider = context.watch(); - var sortedApps = appsProvider.apps.values.toList(); + var listedApps = appsProvider.apps.values.toList(); var currentFilterIsUpdatesOnly = filter.isIdenticalTo(updatesOnlyFilter, settingsProvider); selectedApps = selectedApps - .where((element) => sortedApps.map((e) => e.app).contains(element)) + .where((element) => listedApps.map((e) => e.app).contains(element)) .toSet(); toggleAppSelected(App app) { @@ -72,7 +72,7 @@ class AppsPageState extends State { }); } - sortedApps = sortedApps.where((app) { + listedApps = listedApps.where((app) { if (app.app.installedVersion == app.app.latestVersion && !(filter.includeUptodate)) { return false; @@ -111,7 +111,7 @@ class AppsPageState extends State { return true; }).toList(); - sortedApps.sort((a, b) { + listedApps.sort((a, b) { var nameA = a.installedInfo?.name ?? a.app.name; var nameB = b.installedInfo?.name ?? b.app.name; int result = 0; @@ -119,25 +119,30 @@ class AppsPageState extends State { result = (a.app.author + nameA).compareTo(b.app.author + nameB); } else if (settingsProvider.sortColumn == SortColumnSettings.nameAuthor) { result = (nameA + a.app.author).compareTo(nameB + b.app.author); + } else if (settingsProvider.sortColumn == + SortColumnSettings.releaseDate) { + result = (a.app.releaseDate) + ?.compareTo(b.app.releaseDate ?? DateTime.now()) ?? + 0; } return result; }); if (settingsProvider.sortOrder == SortOrderSettings.descending) { - sortedApps = sortedApps.reversed.toList(); + listedApps = listedApps.reversed.toList(); } var existingUpdates = appsProvider.findExistingUpdates(installedOnly: true); var existingUpdateIdsAllOrSelected = existingUpdates .where((element) => selectedApps.isEmpty - ? sortedApps.where((a) => a.app.id == element).isNotEmpty + ? listedApps.where((a) => a.app.id == element).isNotEmpty : selectedApps.map((e) => e.id).contains(element)) .toList(); var newInstallIdsAllOrSelected = appsProvider .findExistingUpdates(nonInstalledOnly: true) .where((element) => selectedApps.isEmpty - ? sortedApps.where((a) => a.app.id == element).isNotEmpty + ? listedApps.where((a) => a.app.id == element).isNotEmpty : selectedApps.map((e) => e.id).contains(element)) .toList(); @@ -159,26 +164,26 @@ class AppsPageState extends State { if (settingsProvider.pinUpdates) { var temp = []; - sortedApps = sortedApps.where((sa) { + listedApps = listedApps.where((sa) { if (existingUpdates.contains(sa.app.id)) { temp.add(sa); return false; } return true; }).toList(); - sortedApps = [...temp, ...sortedApps]; + listedApps = [...temp, ...listedApps]; } var tempPinned = []; var tempNotPinned = []; - for (var a in sortedApps) { + for (var a in listedApps) { if (a.app.pinned) { tempPinned.add(a); } else { tempNotPinned.add(a); } } - sortedApps = [...tempPinned, ...tempNotPinned]; + listedApps = [...tempPinned, ...tempNotPinned]; return Scaffold( backgroundColor: Theme.of(context).colorScheme.surface, @@ -198,7 +203,7 @@ class AppsPageState extends State { }, child: CustomScrollView(slivers: [ CustomAppBar(title: tr('appsString')), - if (appsProvider.loadingApps || sortedApps.isEmpty) + if (appsProvider.loadingApps || listedApps.isEmpty) SliverFillRemaining( child: Center( child: appsProvider.loadingApps @@ -225,8 +230,8 @@ class AppsPageState extends State { delegate: SliverChildBuilderDelegate( (BuildContext context, int index) { String? changesUrl = SourceProvider() - .getSource(sortedApps[index].app.url) - .changeLogPageFromStandardUrl(sortedApps[index].app.url); + .getSource(listedApps[index].app.url) + .changeLogPageFromStandardUrl(listedApps[index].app.url); var transparent = const Color.fromARGB(0, 0, 0, 0).value; return Container( decoration: BoxDecoration( @@ -234,53 +239,53 @@ class AppsPageState extends State { vertical: BorderSide( width: 4, color: Color( - sortedApps[index].app.categories.isNotEmpty + listedApps[index].app.categories.isNotEmpty ? settingsProvider.categories[ - sortedApps[index] + listedApps[index] .app .categories .first] ?? transparent : transparent)))), child: ListTile( - tileColor: sortedApps[index].app.pinned + tileColor: listedApps[index].app.pinned ? Colors.grey.withOpacity(0.1) : Colors.transparent, selectedTileColor: Theme.of(context) .colorScheme .primary - .withOpacity(sortedApps[index].app.pinned ? 0.2 : 0.1), - selected: selectedApps.contains(sortedApps[index].app), + .withOpacity(listedApps[index].app.pinned ? 0.2 : 0.1), + selected: selectedApps.contains(listedApps[index].app), onLongPress: () { - toggleAppSelected(sortedApps[index].app); + toggleAppSelected(listedApps[index].app); }, - leading: sortedApps[index].installedInfo != null + leading: listedApps[index].installedInfo != null ? Image.memory( - sortedApps[index].installedInfo!.icon!, + listedApps[index].installedInfo!.icon!, gaplessPlayback: true, ) : null, title: Text( - sortedApps[index].installedInfo?.name ?? - sortedApps[index].app.name, + listedApps[index].installedInfo?.name ?? + listedApps[index].app.name, style: TextStyle( overflow: TextOverflow.ellipsis, - fontWeight: sortedApps[index].app.pinned + fontWeight: listedApps[index].app.pinned ? FontWeight.bold : FontWeight.normal, ), ), subtitle: Text( - tr('byX', args: [sortedApps[index].app.author]), + tr('byX', args: [listedApps[index].app.author]), style: TextStyle( - fontWeight: sortedApps[index].app.pinned + fontWeight: listedApps[index].app.pinned ? FontWeight.bold : FontWeight.normal)), trailing: SingleChildScrollView( reverse: true, - child: sortedApps[index].downloadProgress != null + child: listedApps[index].downloadProgress != null ? Text(tr('percentProgress', args: [ - sortedApps[index] + listedApps[index] .downloadProgress ?.toInt() .toString() ?? @@ -294,7 +299,7 @@ class AppsPageState extends State { mainAxisSize: MainAxisSize.min, children: [ Text( - '${sortedApps[index].app.installedVersion ?? tr('notInstalled')}${sortedApps[index].app.additionalSettings['trackOnly'] == true ? ' ${tr('estimateInBrackets')}' : ''}', + '${listedApps[index].app.installedVersion ?? tr('notInstalled')}${listedApps[index].app.additionalSettings['trackOnly'] == true ? ' ${tr('estimateInBrackets')}' : ''}', overflow: TextOverflow.ellipsis, textAlign: TextAlign.end, ) @@ -308,11 +313,11 @@ class AppsPageState extends State { .externalApplication); }, child: Text( - sortedApps[index].app.releaseDate == + listedApps[index].app.releaseDate == null ? tr('changes') : DateFormat('yyyy-MM-dd').format( - sortedApps[index] + listedApps[index] .app .releaseDate!), style: const TextStyle( @@ -320,12 +325,12 @@ class AppsPageState extends State { decoration: TextDecoration.underline), )), - sortedApps[index].app.installedVersion != + listedApps[index].app.installedVersion != null && - sortedApps[index] + listedApps[index] .app .installedVersion != - sortedApps[index] + listedApps[index] .app .latestVersion ? appsProvider.areDownloadsRunning() @@ -340,7 +345,7 @@ class AppsPageState extends State { appsProvider .downloadAndInstallLatestApps( [ - sortedApps[index] + listedApps[index] .app .id ], @@ -351,7 +356,7 @@ class AppsPageState extends State { }); }, child: Text( - sortedApps[index] + listedApps[index] .app .additionalSettings[ 'trackOnly'] == @@ -373,18 +378,18 @@ class AppsPageState extends State { ))), onTap: () { if (selectedApps.isNotEmpty) { - toggleAppSelected(sortedApps[index].app); + toggleAppSelected(listedApps[index].app); } else { Navigator.push( context, MaterialPageRoute( builder: (context) => - AppPage(appId: sortedApps[index].app.id)), + AppPage(appId: listedApps[index].app.id)), ); } }, )); - }, childCount: sortedApps.length)) + }, childCount: listedApps.length)) ])), persistentFooterButtons: appsProvider.apps.isEmpty ? null @@ -396,20 +401,20 @@ class AppsPageState extends State { style: const ButtonStyle( visualDensity: VisualDensity.compact), onPressed: () { - selectThese(sortedApps.map((e) => e.app).toList()); + selectThese(listedApps.map((e) => e.app).toList()); }, icon: Icon( Icons.select_all_outlined, color: Theme.of(context).colorScheme.primary, ), - label: Text(sortedApps.length.toString())) + label: Text(listedApps.length.toString())) : TextButton.icon( style: const ButtonStyle( visualDensity: VisualDensity.compact), onPressed: () { selectedApps.isEmpty ? selectThese( - sortedApps.map((e) => e.app).toList()) + listedApps.map((e) => e.app).toList()) : clearSelected(); }, icon: Icon( diff --git a/lib/pages/settings.dart b/lib/pages/settings.dart index e43aca4..e2ba218 100644 --- a/lib/pages/settings.dart +++ b/lib/pages/settings.dart @@ -101,6 +101,10 @@ class _SettingsPageState extends State { DropdownMenuItem( value: SortColumnSettings.added, child: Text(tr('asAdded')), + ), + DropdownMenuItem( + value: SortColumnSettings.releaseDate, + child: Text(tr('releaseDate')), ) ], onChanged: (value) { diff --git a/lib/providers/settings_provider.dart b/lib/providers/settings_provider.dart index 068ebf8..ea6985a 100644 --- a/lib/providers/settings_provider.dart +++ b/lib/providers/settings_provider.dart @@ -6,7 +6,6 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:fluttertoast/fluttertoast.dart'; import 'package:obtainium/app_sources/github.dart'; -import 'package:obtainium/components/generated_form.dart'; import 'package:obtainium/main.dart'; import 'package:permission_handler/permission_handler.dart'; import 'package:shared_preferences/shared_preferences.dart'; @@ -18,7 +17,7 @@ enum ThemeSettings { system, light, dark } enum ColourSettings { basic, materialYou } -enum SortColumnSettings { added, nameAuthor, authorName } +enum SortColumnSettings { added, nameAuthor, authorName, releaseDate } enum SortOrderSettings { ascending, descending } From 2b4f94b407fb1657a2cc51436eacd35c7942fa4a Mon Sep 17 00:00:00 2001 From: Imran Remtulla Date: Sat, 18 Feb 2023 20:49:45 -0500 Subject: [PATCH 4/9] Date sort bugfix --- lib/pages/apps.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/pages/apps.dart b/lib/pages/apps.dart index 3cf8040..3ee67c6 100644 --- a/lib/pages/apps.dart +++ b/lib/pages/apps.dart @@ -121,8 +121,8 @@ class AppsPageState extends State { result = (nameA + a.app.author).compareTo(nameB + b.app.author); } else if (settingsProvider.sortColumn == SortColumnSettings.releaseDate) { - result = (a.app.releaseDate) - ?.compareTo(b.app.releaseDate ?? DateTime.now()) ?? + result = (a.app.releaseDate)?.compareTo( + b.app.releaseDate ?? DateTime.fromMicrosecondsSinceEpoch(0)) ?? 0; } return result; From f06d245e205b875c58ef6157026af25f5ea40ecb Mon Sep 17 00:00:00 2001 From: Imran Remtulla Date: Sat, 18 Feb 2023 20:55:23 -0500 Subject: [PATCH 5/9] Added release date support to GitLab --- lib/app_sources/gitlab.dart | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/app_sources/gitlab.dart b/lib/app_sources/gitlab.dart index b9bef9b..428568e 100644 --- a/lib/app_sources/gitlab.dart +++ b/lib/app_sources/gitlab.dart @@ -54,10 +54,14 @@ class GitLab extends AppSource { var entryId = entry?.querySelector('id')?.innerHtml; var version = entryId == null ? null : Uri.parse(entryId).pathSegments.last; + var releaseDateString = entry?.querySelector('updated')?.innerHtml; + DateTime? releaseDate = + releaseDateString != null ? DateTime.parse(releaseDateString) : null; if (version == null) { throw NoVersionError(); } - return APKDetails(version, apkUrls, GitHub().getAppNames(standardUrl)); + return APKDetails(version, apkUrls, GitHub().getAppNames(standardUrl), + releaseDate: releaseDate); } else { throw getObtainiumHttpError(res); } From d5fdf28a98d259788a59c2837533080150140745 Mon Sep 17 00:00:00 2001 From: Imran Remtulla Date: Sat, 18 Feb 2023 20:58:08 -0500 Subject: [PATCH 6/9] Added release date support to Codeberg --- lib/app_sources/codeberg.dart | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/app_sources/codeberg.dart b/lib/app_sources/codeberg.dart index e2fa5ab..aa21171 100644 --- a/lib/app_sources/codeberg.dart +++ b/lib/app_sources/codeberg.dart @@ -112,11 +112,15 @@ class Codeberg extends AppSource { throw NoReleasesError(); } String? version = targetRelease['tag_name']; + DateTime? releaseDate = targetRelease['published_at'] != null + ? DateTime.parse(targetRelease['published_at']) + : null; if (version == null) { throw NoVersionError(); } return APKDetails(version, targetRelease['apkUrls'] as List, - getAppNames(standardUrl)); + getAppNames(standardUrl), + releaseDate: releaseDate); } else { throw getObtainiumHttpError(res); } From fe0126095a2779a3c339dee6992d62356719f02d Mon Sep 17 00:00:00 2001 From: Imran Remtulla Date: Sat, 18 Feb 2023 21:03:22 -0500 Subject: [PATCH 7/9] Added release date support to third part f-droid repos --- lib/app_sources/fdroidrepo.dart | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/app_sources/fdroidrepo.dart b/lib/app_sources/fdroidrepo.dart index cffe307..11e8e01 100644 --- a/lib/app_sources/fdroidrepo.dart +++ b/lib/app_sources/fdroidrepo.dart @@ -69,6 +69,8 @@ class FDroidRepo extends AppSource { foundApps[0].querySelector('name')?.innerHtml ?? appIdOrName; var releases = foundApps[0].querySelectorAll('package'); String? latestVersion = releases[0].querySelector('version')?.innerHtml; + String? added = releases[0].querySelector('added')?.innerHtml; + DateTime? releaseDate = added != null ? DateTime.parse(added) : null; if (latestVersion == null) { throw NoVersionError(); } @@ -78,7 +80,8 @@ class FDroidRepo extends AppSource { element.querySelector('apkname') != null) .map((e) => '$standardUrl/${e.querySelector('apkname')!.innerHtml}') .toList(); - return APKDetails(latestVersion, apkUrls, AppNames(authorName, appName)); + return APKDetails(latestVersion, apkUrls, AppNames(authorName, appName), + releaseDate: releaseDate); } else { throw getObtainiumHttpError(res); } From 4be3478b9776d7682d42795e31d5b01c1de2be6f Mon Sep 17 00:00:00 2001 From: Imran Remtulla Date: Sat, 18 Feb 2023 21:16:28 -0500 Subject: [PATCH 8/9] Added release date support to APKMirror --- lib/app_sources/apkmirror.dart | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/lib/app_sources/apkmirror.dart b/lib/app_sources/apkmirror.dart index d583bbb..cf4721a 100644 --- a/lib/app_sources/apkmirror.dart +++ b/lib/app_sources/apkmirror.dart @@ -1,3 +1,5 @@ +import 'dart:io'; + import 'package:html/parser.dart'; import 'package:http/http.dart'; import 'package:obtainium/custom_errors.dart'; @@ -30,10 +32,16 @@ class APKMirror extends AppSource { ) async { Response res = await get(Uri.parse('$standardUrl/feed')); if (res.statusCode == 200) { - String? titleString = parse(res.body) - .querySelector('item') - ?.querySelector('title') - ?.innerHtml; + var item = parse(res.body).querySelector('item'); + String? titleString = item?.querySelector('title')?.innerHtml; + String? dateString = item + ?.querySelector('pubDate') + ?.innerHtml + .split(' ') + .sublist(0, 5) + .join(' '); + DateTime? releaseDate = + dateString != null ? HttpDate.parse('$dateString GMT') : null; String? version = titleString ?.substring(RegExp('[0-9]').firstMatch(titleString)?.start ?? 0, RegExp(' by ').firstMatch(titleString)?.start ?? 0) @@ -44,7 +52,8 @@ class APKMirror extends AppSource { if (version == null || version.isEmpty) { throw NoVersionError(); } - return APKDetails(version, [], getAppNames(standardUrl)); + return APKDetails(version, [], getAppNames(standardUrl), + releaseDate: releaseDate); } else { throw getObtainiumHttpError(res); } From a788d9d7cd5ecd6a5d2454dc400063b02f83aedb Mon Sep 17 00:00:00 2001 From: Imran Remtulla Date: Sat, 18 Feb 2023 21:22:36 -0500 Subject: [PATCH 9/9] Increment version --- lib/main.dart | 2 +- pubspec.lock | 4 ++-- pubspec.yaml | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/main.dart b/lib/main.dart index c5da1bb..ff32826 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -21,7 +21,7 @@ import 'package:easy_localization/src/easy_localization_controller.dart'; // ignore: implementation_imports import 'package:easy_localization/src/localization.dart'; -const String currentVersion = '0.10.12'; +const String currentVersion = '0.11.0'; const String currentReleaseTag = 'v$currentVersion-beta'; // KEEP THIS IN SYNC WITH GITHUB RELEASES diff --git a/pubspec.lock b/pubspec.lock index 15e4730..cab96e0 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -258,10 +258,10 @@ packages: dependency: transitive description: name: flutter_local_notifications_linux - sha256: "8f6c1611e0c4a88a382691a97bb3c3feb24cc0c0b54152b8b5fb7ffb837f7fbf" + sha256: ccb08b93703aeedb58856e5637450bf3ffec899adb66dc325630b68994734b89 url: "https://pub.dev" source: hosted - version: "3.0.0" + version: "3.0.0+1" flutter_local_notifications_platform_interface: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index a9d897e..bdceeff 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -17,7 +17,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html # In Windows, build-name is used as the major, minor, and patch parts # of the product and file versions while build-number is used as the build suffix. -version: 0.10.12+118 # When changing this, update the tag in main() accordingly +version: 0.11.0+119 # When changing this, update the tag in main() accordingly environment: sdk: '>=2.18.2 <3.0.0'