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