From 4cc64dc2339ebaed082155dd2c264b48991ef0ea Mon Sep 17 00:00:00 2001 From: iDazai <50296346+iDazai@users.noreply.github.com> Date: Mon, 25 Dec 2023 10:30:44 +0100 Subject: [PATCH 01/20] Update de.json translated three new strings, fixed on with hyphen missing --- assets/translations/de.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/assets/translations/de.json b/assets/translations/de.json index fae8d4d..99c1376 100644 --- a/assets/translations/de.json +++ b/assets/translations/de.json @@ -247,7 +247,7 @@ "backgroundUpdateLimitsExplanation": "Der Erfolg einer Hintergrundinstallation kann nur festgestellt werden, wenn Obtainium geöffnet wird.", "verifyLatestTag": "Überprüfe das „latest“ Tag", "intermediateLinkRegex": "Filter für einen „Zwischen“-Link, der zuerst besucht werden soll", - "intermediateLinkNotFound": "„Zwischen“link nicht gefunden", + "intermediateLinkNotFound": "„Zwischen“-Link nicht gefunden", "exemptFromBackgroundUpdates": "Ausschluss von Hintergrundaktualisierungen (falls aktiviert)", "bgUpdatesOnWiFiOnly": "Hintergrundaktualisierungen deaktivieren, wenn kein WLAN vorhanden ist", "autoSelectHighestVersionCode": "Automatisch höchste APK-Version auswählen", @@ -256,13 +256,13 @@ "highlightTouchTargets": "Weniger offensichtliche Touch-Ziele hervorheben", "pickExportDir": "Export-Verzeichnis wählen", "autoExportOnChanges": "Automatischer Export bei Änderung(en)", - "includeSettings": "Include settings", + "includeSettings": "Einstellungen einbeziehen", "filterVersionsByRegEx": "Versionen nach regulären Ausdrücken filtern", "trySelectingSuggestedVersionCode": "Versuchen, den vorgeschlagenen APK-Versionscode auszuwählen", "dontSortReleasesList": "Freigaberelease von der API ordern", "reverseSort": "Umgekehrtes Sortieren", - "takeFirstLink": "Take first link", - "skipSort": "Skip sorting", + "takeFirstLink": "Verwende den ersten Link", + "skipSort": "Überspringe Sortieren", "debugMenu": "Debug-Menü", "bgTaskStarted": "Hintergrundaufgabe gestartet – Logs prüfen.", "runBgCheckNow": "Hintergrundaktualisierungsprüfung jetzt durchführen", @@ -280,7 +280,7 @@ "onlyCheckInstalledOrTrackOnlyApps": "Überprüfe nur installierte und mit „nur Nachverfolgen“ markierte Apps auf Aktualisierungen", "supportFixedAPKURL": "neuere Version anhand der ersten dreißig Zahlen der Checksumme der APK URL erraten, wenn anderweitig nicht unterstützt", "selectX": "Wähle {}", - "parallelDownloads": "Allow parallel downloads", + "parallelDownloads": "Erlaube parallele Downloads", "removeAppQuestion": { "one": "App entfernen?", "other": "Apps entfernen?" From bc574097e28d61bad64ada05714c35fedb0e6c32 Mon Sep 17 00:00:00 2001 From: unbranched <39440265+unbranched@users.noreply.github.com> Date: Mon, 25 Dec 2023 09:40:29 +0000 Subject: [PATCH 02/20] Update it.json --- assets/translations/it.json | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/assets/translations/it.json b/assets/translations/it.json index 34a7ea2..465aedf 100644 --- a/assets/translations/it.json +++ b/assets/translations/it.json @@ -256,13 +256,13 @@ "highlightTouchTargets": "Evidenzia elementi toccabili meno ovvi", "pickExportDir": "Scegli cartella esp.", "autoExportOnChanges": "Auto-esporta dopo modifiche", - "includeSettings": "Include settings", + "includeSettings": "Includi impostazioni", "filterVersionsByRegEx": "Filtra versioni con espressione regolare", "trySelectingSuggestedVersionCode": "Prova a selezionare APK con versionCode suggerito", "dontSortReleasesList": "Conserva l'ordine di release da API", "reverseSort": "Ordine inverso", - "takeFirstLink": "Take first link", - "skipSort": "Skip sorting", + "takeFirstLink": "Prendi il primo link", + "skipSort": "Salta ordinamento", "debugMenu": "Menu di debug", "bgTaskStarted": "Attività in secondo piano iniziata - controllo log.", "runBgCheckNow": "Inizia aggiornamento in secondo piano ora", @@ -278,9 +278,9 @@ "completeAppInstallationNotifChannel": "Completa l'installazione dell'app", "checkingForUpdatesNotifChannel": "Controllo degli aggiornamenti in corso", "onlyCheckInstalledOrTrackOnlyApps": "Cerca aggiornamenti solo per app installate e app in Solo-Monitoraggio", - "supportFixedAPKURL": "Support fixed APK URLs", - "selectX": "Select {}", - "parallelDownloads": "Allow parallel downloads", + "supportFixedAPKURL": "Supporta URL fissi di APK", + "selectX": "Seleziona {}", + "parallelDownloads": "Permetti download paralleli", "removeAppQuestion": { "one": "Rimuovere l'app?", "other": "Rimuovere le app?" From 617ab9efab328d10cb59912be50e0679326dc269 Mon Sep 17 00:00:00 2001 From: LucasTavaresA Date: Mon, 25 Dec 2023 10:45:12 -0300 Subject: [PATCH 03/20] Update pt.json --- assets/translations/pt.json | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/assets/translations/pt.json b/assets/translations/pt.json index eed4690..8051934 100644 --- a/assets/translations/pt.json +++ b/assets/translations/pt.json @@ -277,10 +277,15 @@ "downloadingXNotifChannel": "Baixando {}", "completeAppInstallationNotifChannel": "Instalação completa do App", "checkingForUpdatesNotifChannel": "Checando por Atualizações", - "onlyCheckInstalledOrTrackOnlyApps": "Only check installed and Track-Only apps for updates", - "supportFixedAPKURL": "Support fixed APK URLs", - "selectX": "Select {}", - "parallelDownloads": "Allow parallel downloads", + "onlyCheckInstalledOrTrackOnlyApps": "Apenas checar apps instalados e 'Apenas Seguir' por updates", + "supportFixedAPKURL": "Suporte APK com URLs fixas", + "selectX": "Selecionar {}", + "parallelDownloads": "Permitir downloads paralelos", + "installMethod": "Método de instalação", + "normal": "Normal", + "shizuku": "Shizuku", + "root": "Root", + "shizukuBinderNotFound": "Shizuku não esta rodando", "removeAppQuestion": { "one": "Remover App?", "other": "Remover Apps?" From 3c9bb63d3245f07bb573636489a35e113e31d701 Mon Sep 17 00:00:00 2001 From: CertainBot <53102558+CertainBot@users.noreply.github.com> Date: Mon, 25 Dec 2023 16:04:51 +0100 Subject: [PATCH 04/20] Update es.json MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - English: updated new strings, few minor corrections. - Español: actualizacion de frases nuevas, correcciones menores. --- assets/translations/es.json | 46 ++++++++++++++++++------------------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/assets/translations/es.json b/assets/translations/es.json index 58a5c62..cd5b02f 100644 --- a/assets/translations/es.json +++ b/assets/translations/es.json @@ -9,12 +9,12 @@ "placeholder": "Espacio reservado", "someErrors": "Han ocurrido algunos errores", "unexpectedError": "Error Inesperado", - "ok": "Correcto", + "ok": "OK", "and": "y", "githubPATLabel": "Token Github de Acceso Personal\n(Reduce tiempos de espera)", "includePrereleases": "Incluir versiones preliminares", "fallbackToOlderReleases": "Retroceder a versiones previas", - "filterReleaseTitlesByRegEx": "Filtrar Títulos de Versiones", + "filterReleaseTitlesByRegEx": "Filtrar por título de version", "invalidRegEx": "Expresión inválida", "noDescription": "Sin descripción", "cancel": "Cancelar", @@ -29,7 +29,7 @@ "source": "Origen", "app": "Aplicación", "appsFromSourceAreTrackOnly": "Las aplicaciones de este origen son de 'Solo Seguimiento'.", - "youPickedTrackOnly": "Debes seleccionar la opción de 'Solo Seguimiento'.", + "youPickedTrackOnly": "Debe seleccionar la opción de 'Solo Seguimiento'.", "trackOnlyAppDescription": "Se hará el seguimiento de actualizaciones para la aplicación, pero Obtainium no será capaz de descargarla o actalizarla.", "cancelled": "Cancelado", "appAlreadyAdded": "Aplicación ya añadida", @@ -46,8 +46,8 @@ "searchableInBrackets": "(soporta búsqueda)", "appsString": "Aplicaciones", "noApps": "Sin Aplicaciones", - "noAppsForFilter": "Sin Aplicaciones para Filtrar", - "byX": "Por {}", + "noAppsForFilter": "Sin aplicaciones para filtrar", + "byX": "por: {}", "percentProgress": "Progreso: {}%", "pleaseWait": "Por favor, espere", "updateAvailable": "Actualización Disponible", @@ -102,8 +102,8 @@ "importedAppsIdDisclaimer": "Las aplicaciones importadas podrían mostrarse incorrectamente como \"No Instalada\".\nPara solucionarlo, reinstálalas a través de Obtainium.\nEsto no debería afectar a los datos de las aplicaciones.\n\nSolo afecta a las URLs y a los métodos de importación mediante terceros.", "importErrors": "Errores de Importación", "importedXOfYApps": "{} de {} Aplicaciones importadas.", - "followingURLsHadErrors": "Las siguientes URLs tuvieron problemas:", - "okay": "Correcto", + "followingURLsHadErrors": "Las siguientes URLs han tenido problemas:", + "okay": "Aceptar", "selectURL": "Seleccionar URL", "selectURLs": "Seleccionar URLs", "pick": "Escoger", @@ -113,7 +113,7 @@ "followSystem": "Seguir al Sistema", "obtainium": "Obtainium", "materialYou": "Material You", - "useBlackTheme": "Usar negros puros en tema oscuro", + "useBlackTheme": "Negro puro en tema Oscuro", "appSortBy": "Ordenar Apps Por", "authorName": "Autor/Nombre", "nameAuthor": "Nombre/Autor", @@ -135,10 +135,10 @@ "share": "Compartir", "appNotFound": "Aplicación no encontrada", "obtainiumExportHyphenatedLowercase": "obtainium-export", - "pickAnAPK": "Selecciona una APK", + "pickAnAPK": "Seleccione una APK", "appHasMoreThanOnePackage": "{} tiene más de un paquete:", - "deviceSupportsXArch": "Tu dispositivo soporta las siguientes arquitecturas de procesador: {}.", - "deviceSupportsFollowingArchs": "Tu dispositivo soporta las siguientes arquitecturas de procesador:", + "deviceSupportsXArch": "Su dispositivo soporta las siguientes arquitecturas de procesador: {}.", + "deviceSupportsFollowingArchs": "Su dispositivo soporta las siguientes arquitecturas de procesador:", "warning": "Aviso", "sourceIsXButPackageFromYPrompt": "La fuente de la aplicación es '{}' pero el paquete de la actualización viene de '{}'. ¿Desea continuar?", "updatesAvailable": "Actualizaciones Disponibles", @@ -158,7 +158,7 @@ "completeAppInstallationNotifDescription": "Pide al usuario volver a Obtainium para terminar de instalar una aplicación", "checkingForUpdates": "Buscando Actualizaciones", "checkingForUpdatesNotifDescription": "Notificación temporal que aparece al buscar actualizaciones", - "pleaseAllowInstallPerm": "Por favor, permite a Obtainium instalar aplicaciones", + "pleaseAllowInstallPerm": "Por favor, permita que Obtainium instale aplicaciones", "trackOnly": "Solo Seguimiento", "errorWithHttpStatusCode": "Error {}", "versionCorrectionDisabled": "Corrección de versiones desactivada (el plugin parece no funcionar)", @@ -207,15 +207,15 @@ "removeFromObtainium": "Eliminar de Obtainium", "uninstallFromDevice": "Desinstalar del Dispositivo", "onlyWorksWithNonVersionDetectApps": "Solo funciona para aplicaciones con la detección de versiones desactivada.", - "releaseDateAsVersion": "Usar Fecha de Publicación como Versión", + "releaseDateAsVersion": "Por fecha de publicación", "releaseDateAsVersionExplanation": "Esta opción solo se debería usar con aplicaciones en las que la detección de versiones no funciona pero hay disponible una fecha de publicación.", "changes": "Cambios", "releaseDate": "Fecha de Publicación", "importFromURLsInFile": "Importar URLs desde archivo (como OPML)", "versionDetection": "Detección de Versiones", - "standardVersionDetection": "Detección de versiones estándar", + "standardVersionDetection": "Por versión", "groupByCategory": "Agrupar por Categoría", - "autoApkFilterByArch": "Filtrar las APKs mediante arquitecturas de procesador, si es posible", + "autoApkFilterByArch": "Filtrar APKs por arquitectura del procesador, si es posible", "overrideSource": "Sobrescribir Fuente", "dontShowAgain": "No mostrar de nuevo", "dontShowTrackOnlyWarnings": "No mostrar avisos de 'Solo Seguimiento'", @@ -233,11 +233,11 @@ "reversePageTransitions": "Invertir animaciones de transición de la página", "minStarCount": "Número Mínimo de Estrellas", "addInfoBelow": "Añadir esta información debajo.", - "addInfoInSettings": "Añadir esta información en Ajustes.", + "addInfoInSettings": "Puede añadir esta información en Ajustes.", "githubSourceNote": "La limitación de velocidad de GitHub puede evitarse con una clave API.", "gitlabSourceNote": "La extracción de APK de GitLab podría no funcionar sin una clave API.", "sortByFileNamesNotLinks": "Ordenar por nombres de fichero en vez de por enlaces completos", - "filterReleaseNotesByRegEx": "Filtrar por Notas de Versión (Release Notes)", + "filterReleaseNotesByRegEx": "Filtrar por notas de nersión (release notes)", "customLinkFilterRegex": "Filtro personalizado de Enlace APK (por defecto '.apk$')", "appsPossiblyUpdated": "Actualización de Apps intentada", "appsPossiblyUpdatedNotifDescription": "Notifica al usuario que las actualizaciones en segundo plano podrían haberse realizado para una o más aplicaciones", @@ -245,10 +245,10 @@ "enableBackgroundUpdates": "Habilitar actualizaciones en segundo plano", "backgroundUpdateReqsExplanation": "Las actualizaciones en segundo plano pueden no estar disponibles para todas las aplicaciones.", "backgroundUpdateLimitsExplanation": "El éxito de las instalaciones en segundo plano solo se puede comprobar con Obtainium abierto.", - "verifyLatestTag": "Comprueba la etiqueta 'latest'", - "intermediateLinkRegex": "Filtrar por Enlace 'Intermedio' para Visitar Primero", - "intermediateLinkNotFound": "Enlace Intermedio no encontrado", - "exemptFromBackgroundUpdates": "Exento de actualizciones en segundo plano (si están habilitadas)", + "verifyLatestTag": "Comprueba la etiqueta 'Latest'", + "intermediateLinkRegex": "Filtrar por enlace 'intermedio' para visitar primero", + "intermediateLinkNotFound": "Enlace intermedio no encontrado", + "exemptFromBackgroundUpdates": "Exenta de actualizciones en segundo plano (si están habilitadas)", "bgUpdatesOnWiFiOnly": "Deshabilitar las actualizaciones en segundo plano sin WiFi", "autoSelectHighestVersionCode": "Auto Selección de la versionCode APK superior", "versionExtractionRegEx": "Versión de Extracción de RegEx", @@ -258,7 +258,7 @@ "autoExportOnChanges": "Auto Exportar cuando haya cambios", "includeSettings": "Incluir ajustes", "filterVersionsByRegEx": "Filtrar por Versiones", - "trySelectingSuggestedVersionCode": "Prueba seleccionando la versionCode APK sugerida", + "trySelectingSuggestedVersionCode": "Pruebe seleccionando la versionCode APK sugerida", "dontSortReleasesList": "Mantener el order de publicación desde API", "reverseSort": "Orden inverso", "takeFirstLink": "Usar primer enlace", @@ -268,7 +268,7 @@ "runBgCheckNow": "Ejecutar verficiación de actualizaciones en segundo plano", "versionExtractWholePage": "Aplicar la Versión de Extracción Regex a la Página Entera", "installing": "Instalando", - "skipUpdateNotifications": "Omitir notificaciones sobre actualizaciones", + "skipUpdateNotifications": "Omitir de notificaciones sobre actualizaciones", "updatesAvailableNotifChannel": "Actualizaciones Disponibles", "appsUpdatedNotifChannel": "Aplicaciones Actualizadas", "appsPossiblyUpdatedNotifChannel": "Se ha Intentado Actualizar la Aplicación", From 3e54e80eb615ad98a61ec0a7e725a31f876f1572 Mon Sep 17 00:00:00 2001 From: unbranched <39440265+unbranched@users.noreply.github.com> Date: Wed, 27 Dec 2023 09:15:22 +0000 Subject: [PATCH 05/20] Update it.json Added missing strings. --- assets/translations/it.json | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/assets/translations/it.json b/assets/translations/it.json index 465aedf..71b1fc4 100644 --- a/assets/translations/it.json +++ b/assets/translations/it.json @@ -281,6 +281,11 @@ "supportFixedAPKURL": "Supporta URL fissi di APK", "selectX": "Seleziona {}", "parallelDownloads": "Permetti download paralleli", + "installMethod": "Metodo d'installazione", + "normal": "Normale", + "shizuku": "Shizuku", + "root": "Root", + "shizukuBinderNotFound": "Shizuku non è in esecuzione", "removeAppQuestion": { "one": "Rimuovere l'app?", "other": "Rimuovere le app?" From 77d81716ed34afea12fb6df6955847833bcd873e Mon Sep 17 00:00:00 2001 From: Imran <30463115+ImranR98@users.noreply.github.com> Date: Thu, 28 Dec 2023 16:06:11 -0600 Subject: [PATCH 06/20] Switch to a New Background Task Plugin (#608), Add Link Text Filter for HTML Links (#1182), Add Support for Multiple Intermediate Links to HTML Source (#1204) - Switch to a New Background Task Plugin (#608) - Add Link Text Filter for HTML Links (#1182) - Add Support for Multiple Intermediate Links to HTML Source --- android/app/build.gradle | 4 +- android/app/src/main/AndroidManifest.xml | 1 + android/build.gradle | 9 + assets/translations/bs.json | 6 +- assets/translations/cs.json | 6 +- assets/translations/de.json | 6 +- assets/translations/en.json | 6 +- assets/translations/es.json | 6 +- assets/translations/fa.json | 6 +- assets/translations/fr.json | 6 +- assets/translations/hu.json | 6 +- assets/translations/it.json | 6 +- assets/translations/ja.json | 6 +- assets/translations/nl.json | 6 +- assets/translations/pl.json | 6 +- assets/translations/pt.json | 6 +- assets/translations/ru.json | 6 +- assets/translations/sv.json | 6 +- assets/translations/tr.json | 6 +- assets/translations/vi.json | 6 +- assets/translations/zh.json | 6 +- lib/app_sources/aptoide.dart | 2 +- lib/app_sources/html.dart | 320 ++++++++++++----------- lib/components/generated_form.dart | 179 ++++++++++--- lib/main.dart | 70 +++-- lib/pages/settings.dart | 62 ++--- lib/providers/apps_provider.dart | 222 +++++++--------- lib/providers/settings_provider.dart | 12 +- lib/providers/source_provider.dart | 26 +- pubspec.lock | 24 +- pubspec.yaml | 4 +- 31 files changed, 606 insertions(+), 437 deletions(-) diff --git a/android/app/build.gradle b/android/app/build.gradle index 2559d75..77c8d88 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -33,7 +33,7 @@ if (keystorePropertiesFile.exists()) { } android { - compileSdkVersion 34 + compileSdkVersion rootProject.ext.compileSdkVersion ndkVersion flutter.ndkVersion compileOptions { @@ -54,7 +54,7 @@ android { // You can update the following values to match your application needs. // For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-build-configuration. minSdkVersion 24 - targetSdkVersion 34 + targetSdkVersion rootProject.ext.targetSdkVersion versionCode flutterVersionCode.toInteger() versionName flutterVersionName } diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 503cd67..3e13a38 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -1,4 +1,5 @@ regExValidator(value)]), + ], + [ + GeneratedFormTextField('matchGroupToUse', + label: tr('matchGroupToUse'), + required: false, + hint: '0', + textInputType: const TextInputType.numberWithOptions(), + additionalValidators: [ + (value) { + if (value?.isEmpty == true) { + value = null; + } + value ??= '0'; + return intValidator(value); + } + ]) + ], + [ + GeneratedFormSwitch('versionExtractWholePage', + label: tr('versionExtractWholePage')) + ], + [ + GeneratedFormSwitch('supportFixedAPKURL', + defaultValue: true, label: tr('supportFixedAPKURL')), + ], + ]; + var commonFormItems = [ + [GeneratedFormSwitch('filterByLinkText', label: tr('filterByLinkText'))], + [GeneratedFormSwitch('skipSort', label: tr('skipSort'))], + [GeneratedFormSwitch('reverseSort', label: tr('takeFirstLink'))], + [ + GeneratedFormSwitch('sortByLastLinkSegment', + label: tr('sortByLastLinkSegment')) + ], + ]; + var intermediateFormItems = [ + [ + GeneratedFormTextField('customLinkFilterRegex', + label: tr('intermediateLinkRegex'), + hint: '([0-9]+.)*[0-9]+/\$', + required: true, + additionalValidators: [(value) => regExValidator(value)]) + ], + ]; HTML() { additionalSourceAppSpecificSettingFormItems = [ [ - GeneratedFormSwitch('sortByFileNamesNotLinks', - label: tr('sortByFileNamesNotLinks')) + GeneratedFormSubForm( + 'intermediateLink', [...intermediateFormItems, ...commonFormItems], + label: tr('intermediateLink')) ], - [GeneratedFormSwitch('skipSort', label: tr('skipSort'))], - [GeneratedFormSwitch('reverseSort', label: tr('takeFirstLink'))], - [ - GeneratedFormSwitch('supportFixedAPKURL', - defaultValue: true, label: tr('supportFixedAPKURL')), - ], - [ - GeneratedFormTextField('customLinkFilterRegex', - label: tr('customLinkFilterRegex'), - hint: 'download/(.*/)?(android|apk|mobile)', - required: false, - additionalValidators: [ - (value) { - return regExValidator(value); - } - ]) - ], - [ - GeneratedFormTextField('intermediateLinkRegex', - label: tr('intermediateLinkRegex'), - hint: '([0-9]+.)*[0-9]+/\$', - required: false, - additionalValidators: [(value) => regExValidator(value)]) - ], - [ - GeneratedFormTextField('versionExtractionRegEx', - label: tr('versionExtractionRegEx'), - required: false, - additionalValidators: [(value) => regExValidator(value)]), - ], - [ - GeneratedFormTextField('matchGroupToUse', - label: tr('matchGroupToUse'), - required: false, - hint: '0', - textInputType: const TextInputType.numberWithOptions(), - additionalValidators: [ - (value) { - if (value?.isEmpty == true) { - value = null; - } - value ??= '0'; - return intValidator(value); - } - ]) - ], - [ - GeneratedFormSwitch('versionExtractWholePage', - label: tr('versionExtractWholePage')) - ] + finalStepFormitems[0], + ...commonFormItems, + ...finalStepFormitems.sublist(1) ]; overrideVersionDetectionFormDefault('noVersionDetection', disableStandard: false, disableRelDate: true); @@ -164,107 +179,120 @@ class HTML extends AppSource { return url; } + // Given an HTTP response, grab some links according to the common additional settings + // (those that apply to intermediate and final steps) + Future>> grabLinksCommon( + Response res, Map additionalSettings) async { + if (res.statusCode != 200) { + throw getObtainiumHttpError(res); + } + var html = parse(res.body); + List> allLinks = html + .querySelectorAll('a') + .map((element) => MapEntry( + element.attributes['href'] ?? '', + element.text.isNotEmpty + ? element.text + : (element.attributes['href'] ?? '').split('/').last)) + .where((element) => element.key.isNotEmpty) + .toList(); + if (allLinks.isEmpty) { + allLinks = RegExp( + r'(http|ftp|https)://([\w_-]+(?:(?:\.[\w_-]+)+))([\w.,@?^=%&:/~+#-]*[\w@?^=%&/~+#-])?') + .allMatches(res.body) + .map((match) => + MapEntry(match.group(0)!, match.group(0)?.split('/').last ?? '')) + .toList(); + } + List> links = []; + bool skipSort = additionalSettings['skipSort'] == true; + bool filterLinkByText = additionalSettings['filterByLinkText'] == true; + if ((additionalSettings['customLinkFilterRegex'] as String?)?.isNotEmpty == + true) { + var reg = RegExp(additionalSettings['customLinkFilterRegex']); + links = allLinks + .where((element) => + reg.hasMatch(filterLinkByText ? element.value : element.key)) + .toList(); + } else { + links = allLinks + .where((element) => + Uri.parse(filterLinkByText ? element.value : element.key) + .path + .toLowerCase() + .endsWith('.apk')) + .toList(); + } + if (!skipSort) { + links.sort((a, b) => additionalSettings['sortByLastLinkSegment'] == true + ? compareAlphaNumeric( + a.key.split('/').where((e) => e.isNotEmpty).last, + b.key.split('/').where((e) => e.isNotEmpty).last) + : compareAlphaNumeric(a.key, b.key)); + } + if (additionalSettings['reverseSort'] == true) { + links = links.reversed.toList(); + } + return links; + } + @override Future getLatestAPKDetails( String standardUrl, Map additionalSettings, ) async { - var uri = Uri.parse(standardUrl); - Response res = await sourceRequest(standardUrl); - if (res.statusCode == 200) { - var html = parse(res.body); - List allLinks = html - .querySelectorAll('a') - .map((element) => element.attributes['href'] ?? '') - .where((element) => element.isNotEmpty) - .toList(); - if (allLinks.isEmpty) { - allLinks = RegExp( - r'(http|ftp|https)://([\w_-]+(?:(?:\.[\w_-]+)+))([\w.,@?^=%&:/~+#-]*[\w@?^=%&/~+#-])?') - .allMatches(res.body) - .map((match) => match.group(0)!) - .toList(); - } - List links = []; - bool skipSort = additionalSettings['skipSort'] == true; - if ((additionalSettings['intermediateLinkRegex'] as String?) - ?.isNotEmpty == - true) { - var reg = RegExp(additionalSettings['intermediateLinkRegex']); - links = allLinks.where((element) => reg.hasMatch(element)).toList(); - if (!skipSort) { - links.sort((a, b) => compareAlphaNumeric(a, b)); - } - if (links.isEmpty) { - throw ObtainiumError(tr('intermediateLinkNotFound')); - } - Map additionalSettingsTemp = - Map.from(additionalSettings); - additionalSettingsTemp['intermediateLinkRegex'] = null; - return getLatestAPKDetails( - ensureAbsoluteUrl(links.last, uri), additionalSettingsTemp); - } - if ((additionalSettings['customLinkFilterRegex'] as String?) - ?.isNotEmpty == - true) { - var reg = RegExp(additionalSettings['customLinkFilterRegex']); - links = allLinks.where((element) => reg.hasMatch(element)).toList(); - } else { - links = allLinks - .where((element) => - Uri.parse(element).path.toLowerCase().endsWith('.apk')) - .toList(); - } - if (!skipSort) { - links.sort((a, b) => - additionalSettings['sortByFileNamesNotLinks'] == true - ? compareAlphaNumeric( - a.split('/').where((e) => e.isNotEmpty).last, - b.split('/').where((e) => e.isNotEmpty).last) - : compareAlphaNumeric(a, b)); - } - if (additionalSettings['reverseSort'] == true) { - links = links.reversed.toList(); - } - if ((additionalSettings['apkFilterRegEx'] as String?)?.isNotEmpty == - true) { - var reg = RegExp(additionalSettings['apkFilterRegEx']); - links = links.where((element) => reg.hasMatch(element)).toList(); - } - if (links.isEmpty) { + var currentUrl = standardUrl; + for (int i = 0; + i < (additionalSettings['intermediateLink']?.length ?? 0); + i++) { + var intLinks = await grabLinksCommon(await sourceRequest(currentUrl), + additionalSettings['intermediateLink'][i]); + if (intLinks.isEmpty) { throw NoReleasesError(); + } else { + currentUrl = intLinks.last.key; } - var rel = links.last; - String? version; - if (additionalSettings['supportFixedAPKURL'] != true) { - version = rel.hashCode.toString(); - } - var versionExtractionRegEx = - additionalSettings['versionExtractionRegEx'] as String?; - if (versionExtractionRegEx?.isNotEmpty == true) { - var match = RegExp(versionExtractionRegEx!).allMatches( - additionalSettings['versionExtractWholePage'] == true - ? res.body.split('\r\n').join('\n').split('\n').join('\\n') - : rel); - if (match.isEmpty) { - throw NoVersionError(); - } - String matchGroupString = - (additionalSettings['matchGroupToUse'] as String).trim(); - if (matchGroupString.isEmpty) { - matchGroupString = "0"; - } - version = match.last.group(int.parse(matchGroupString)); - if (version?.isEmpty == true) { - throw NoVersionError(); - } - } - rel = ensureAbsoluteUrl(rel, uri); - version ??= (await checkDownloadHash(rel)).toString(); - return APKDetails(version, [rel].map((e) => MapEntry(e, e)).toList(), - AppNames(uri.host, tr('app'))); - } else { - throw getObtainiumHttpError(res); } + + var uri = Uri.parse(currentUrl); + Response res = await sourceRequest(currentUrl); + var links = await grabLinksCommon(res, additionalSettings); + + if ((additionalSettings['apkFilterRegEx'] as String?)?.isNotEmpty == true) { + var reg = RegExp(additionalSettings['apkFilterRegEx']); + links = links.where((element) => reg.hasMatch(element.key)).toList(); + } + if (links.isEmpty) { + throw NoReleasesError(); + } + var rel = links.last.key; + String? version; + if (additionalSettings['supportFixedAPKURL'] != true) { + version = rel.hashCode.toString(); + } + var versionExtractionRegEx = + additionalSettings['versionExtractionRegEx'] as String?; + if (versionExtractionRegEx?.isNotEmpty == true) { + var match = RegExp(versionExtractionRegEx!).allMatches( + additionalSettings['versionExtractWholePage'] == true + ? res.body.split('\r\n').join('\n').split('\n').join('\\n') + : rel); + if (match.isEmpty) { + throw NoVersionError(); + } + String matchGroupString = + (additionalSettings['matchGroupToUse'] as String).trim(); + if (matchGroupString.isEmpty) { + matchGroupString = "0"; + } + version = match.last.group(int.parse(matchGroupString)); + if (version?.isEmpty == true) { + throw NoVersionError(); + } + } + rel = ensureAbsoluteUrl(rel, uri); + version ??= (await checkDownloadHash(rel)).toString(); + return APKDetails(version, [rel].map((e) => MapEntry(e, e)).toList(), + AppNames(uri.host, tr('app'))); } } diff --git a/lib/components/generated_form.dart b/lib/components/generated_form.dart index af9a929..0958cc5 100644 --- a/lib/components/generated_form.dart +++ b/lib/components/generated_form.dart @@ -4,6 +4,7 @@ import 'package:hsluv/hsluv.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:obtainium/components/generated_form_modal.dart'; +import 'package:obtainium/providers/source_provider.dart'; abstract class GeneratedFormItem { late String key; @@ -31,7 +32,8 @@ class GeneratedFormTextField extends GeneratedFormItem { {super.label, super.belowWidgets, String super.defaultValue = '', - List super.additionalValidators = const [], + List super.additionalValidators = + const [], this.required = true, this.max = 1, this.hint, @@ -117,6 +119,18 @@ class GeneratedForm extends StatefulWidget { State createState() => _GeneratedFormState(); } +class GeneratedFormSubForm extends GeneratedFormItem { + final List> items; + + GeneratedFormSubForm(super.key, this.items, + {super.label, super.belowWidgets, super.defaultValue}); + + @override + ensureType(val) { + return val; // Not easy to validate List> + } +} + // Generates a color in the HSLuv (Pastel) color space // https://pub.dev/documentation/hsluv/latest/hsluv/Hsluv/hpluvToRgb.html Color generateRandomLightColor() { @@ -133,6 +147,9 @@ Color generateRandomLightColor() { return Color.fromARGB(255, rgbValues[0], rgbValues[1], rgbValues[2]); } +bool validateTextField(TextFormField tf) => + (tf.key as GlobalKey).currentState?.isValid == true; + class _GeneratedFormState extends State { final _formKey = GlobalKey(); Map values = {}; @@ -141,20 +158,19 @@ class _GeneratedFormState extends State { String? initKey; // If any value changes, call this to update the parent with value and validity - void someValueChanged({bool isBuilding = false}) { + void someValueChanged({bool isBuilding = false, bool forceInvalid = false}) { Map returnValues = values; var valid = true; for (int r = 0; r < widget.items.length; r++) { for (int i = 0; i < widget.items[r].length; i++) { if (formInputs[r][i] is TextFormField) { - var fieldState = - (formInputs[r][i].key as GlobalKey).currentState; - if (fieldState != null) { - valid = valid && fieldState.isValid; - } + valid = valid && validateTextField(formInputs[r][i] as TextFormField); } } } + if (forceInvalid) { + valid = false; + } widget.onValueChanges(returnValues, valid, isBuilding); } @@ -229,6 +245,17 @@ class _GeneratedFormState extends State { someValueChanged(); }); }); + } else if (formItem is GeneratedFormSubForm) { + values[formItem.key] = []; + for (Map v + in ((formItem.defaultValue ?? []) as List)) { + var fullDefaults = getDefaultValuesFromFormItems(formItem.items); + for (var element in v.entries) { + fullDefaults[element.key] = element.value; + } + values[formItem.key].add(fullDefaults); + } + return Container(); } else { return Container(); // Some input types added in build } @@ -250,6 +277,7 @@ class _GeneratedFormState extends State { } for (var r = 0; r < formInputs.length; r++) { for (var e = 0; e < formInputs[r].length; e++) { + String fieldKey = widget.items[r][e].key; if (widget.items[r][e] is GeneratedFormSwitch) { formInputs[r][e] = Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, @@ -259,10 +287,10 @@ class _GeneratedFormState extends State { width: 8, ), Switch( - value: values[widget.items[r][e].key], + value: values[fieldKey], onChanged: (value) { setState(() { - values[widget.items[r][e].key] = value; + values[fieldKey] = value; someValueChanged(); }); }) @@ -271,8 +299,7 @@ class _GeneratedFormState extends State { } else if (widget.items[r][e] is GeneratedFormTagInput) { formInputs[r][e] = Column(crossAxisAlignment: CrossAxisAlignment.stretch, children: [ - if ((values[widget.items[r][e].key] - as Map>?) + if ((values[fieldKey] as Map>?) ?.isNotEmpty == true && (widget.items[r][e] as GeneratedFormTagInput) @@ -295,8 +322,7 @@ class _GeneratedFormState extends State { (widget.items[r][e] as GeneratedFormTagInput).alignment, crossAxisAlignment: WrapCrossAlignment.center, children: [ - (values[widget.items[r][e].key] - as Map>?) + (values[fieldKey] as Map>?) ?.isEmpty == true ? Text( @@ -304,8 +330,7 @@ class _GeneratedFormState extends State { .emptyMessage, ) : const SizedBox.shrink(), - ...(values[widget.items[r][e].key] - as Map>?) + ...(values[fieldKey] as Map>?) ?.entries .map((e2) { return Padding( @@ -318,11 +343,10 @@ class _GeneratedFormState extends State { selected: e2.value.value, onSelected: (value) { setState(() { - (values[widget.items[r][e].key] as Map>)[e2.key] = MapEntry( - (values[widget.items[r][e].key] as Map< - String, + (values[fieldKey] as Map>)[e2.key]! .key, value); @@ -330,22 +354,18 @@ class _GeneratedFormState extends State { as GeneratedFormTagInput) .singleSelect && value == true) { - for (var key in (values[ - widget.items[r][e].key] + for (var key in (values[fieldKey] as Map>) .keys) { if (key != e2.key) { - (values[widget.items[r][e].key] as Map< - String, - MapEntry>)[key] = - MapEntry( - (values[widget.items[r][e].key] - as Map< - String, - MapEntry>)[key]! - .key, - false); + (values[fieldKey] as Map< + String, + MapEntry>)[key] = MapEntry( + (values[fieldKey] as Map>)[key]! + .key, + false); } } } @@ -355,8 +375,7 @@ class _GeneratedFormState extends State { )); }) ?? [const SizedBox.shrink()], - (values[widget.items[r][e].key] - as Map>?) + (values[fieldKey] as Map>?) ?.values .where((e) => e.value) .length == @@ -366,7 +385,7 @@ class _GeneratedFormState extends State { child: IconButton( onPressed: () { setState(() { - var temp = values[widget.items[r][e].key] + var temp = values[fieldKey] as Map>; // get selected category str where bool is true final oldEntry = temp.entries @@ -379,7 +398,7 @@ class _GeneratedFormState extends State { // Update entry with new color, remain selected temp.update(oldEntry.key, (old) => MapEntry(newColor, old.value)); - values[widget.items[r][e].key] = temp; + values[fieldKey] = temp; someValueChanged(); }); }, @@ -388,8 +407,7 @@ class _GeneratedFormState extends State { tooltip: tr('colour'), )) : const SizedBox.shrink(), - (values[widget.items[r][e].key] - as Map>?) + (values[fieldKey] as Map>?) ?.values .where((e) => e.value) .isNotEmpty == @@ -400,10 +418,10 @@ class _GeneratedFormState extends State { onPressed: () { fn() { setState(() { - var temp = values[widget.items[r][e].key] + var temp = values[fieldKey] as Map>; temp.removeWhere((key, value) => value.value); - values[widget.items[r][e].key] = temp; + values[fieldKey] = temp; someValueChanged(); }); } @@ -454,7 +472,7 @@ class _GeneratedFormState extends State { String? label = value?['label']; if (label != null) { setState(() { - var temp = values[widget.items[r][e].key] + var temp = values[fieldKey] as Map>?; temp ??= {}; if (temp[label] == null) { @@ -467,7 +485,7 @@ class _GeneratedFormState extends State { temp[label] = MapEntry( generateRandomLightColor().value, !(someSelected && singleSelect)); - values[widget.items[r][e].key] = temp; + values[fieldKey] = temp; someValueChanged(); } }); @@ -481,6 +499,85 @@ class _GeneratedFormState extends State { ], ) ]); + } else if (widget.items[r][e] is GeneratedFormSubForm) { + List subformColumn = []; + for (int i = 0; i < values[fieldKey].length; i++) { + subformColumn.add(Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Divider(), + const SizedBox( + height: 16, + ), + Text( + '${(widget.items[r][e] as GeneratedFormSubForm).label} (${i + 1})', + style: const TextStyle(fontWeight: FontWeight.bold), + ), + GeneratedForm( + items: (widget.items[r][e] as GeneratedFormSubForm) + .items + .map((x) => x.map((y) { + y.defaultValue = values[fieldKey]?[i]?[y.key]; + return y; + }).toList()) + .toList(), + onValueChanges: (values, valid, isBuilding) { + if (valid) { + this.values[fieldKey]?[i] = values; + } + someValueChanged( + isBuilding: isBuilding, forceInvalid: !valid); + }, + ), + Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + TextButton.icon( + style: TextButton.styleFrom( + foregroundColor: + Theme.of(context).colorScheme.error), + onPressed: (values[fieldKey].length > 0) + ? () { + var temp = List.from(values[fieldKey]); + temp.removeAt(i); + values[fieldKey] = List.from(temp); + someValueChanged(); + } + : null, + label: Text( + '${(widget.items[r][e] as GeneratedFormSubForm).label} (${i + 1})', + ), + icon: const Icon( + Icons.delete_outline_rounded, + )) + ], + ), + ], + )); + } + subformColumn.add(Padding( + padding: EdgeInsets.only( + bottom: values[fieldKey].length > 0 ? 24 : 0, top: 8), + child: Row( + children: [ + Expanded( + child: ElevatedButton.icon( + onPressed: () { + values[fieldKey].add(getDefaultValuesFromFormItems( + (widget.items[r][e] as GeneratedFormSubForm) + .items)); + someValueChanged(); + }, + icon: const Icon(Icons.add), + label: Text((widget.items[r][e] as GeneratedFormSubForm) + .label))), + ], + ), + )); + if (values[fieldKey].length > 0) { + subformColumn.add(const Divider()); + } + formInputs[r][e] = Column(children: subformColumn); } } } diff --git a/lib/main.dart b/lib/main.dart index 21be97b..de57a6c 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -12,14 +12,14 @@ import 'package:permission_handler/permission_handler.dart'; import 'package:provider/provider.dart'; import 'package:dynamic_color/dynamic_color.dart'; import 'package:device_info_plus/device_info_plus.dart'; -import 'package:android_alarm_manager_plus/android_alarm_manager_plus.dart'; +import 'package:background_fetch/background_fetch.dart'; import 'package:easy_localization/easy_localization.dart'; // ignore: implementation_imports import 'package:easy_localization/src/easy_localization_controller.dart'; // ignore: implementation_imports import 'package:easy_localization/src/localization.dart'; -const String currentVersion = '0.14.41'; +const String currentVersion = '0.15.0'; const String currentReleaseTag = 'v$currentVersion-beta'; // KEEP THIS IN SYNC WITH GITHUB RELEASES @@ -76,6 +76,19 @@ Future loadTranslations() async { fallbackTranslations: controller.fallbackTranslations); } +@pragma('vm:entry-point') +void backgroundFetchHeadlessTask(HeadlessTask task) async { + String taskId = task.taskId; + bool isTimeout = task.timeout; + if (isTimeout) { + print('BG update task timed out.'); + BackgroundFetch.finish(taskId); + return; + } + await bgUpdateCheck(taskId, null); + BackgroundFetch.finish(taskId); +} + void main() async { WidgetsFlutterBinding.ensureInitialized(); try { @@ -93,7 +106,6 @@ void main() async { ); SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge); } - await AndroidAlarmManager.initialize(); runApp(MultiProvider( providers: [ ChangeNotifierProvider(create: (context) => AppsProvider()), @@ -108,6 +120,7 @@ void main() async { useOnlyLangCode: true, child: const Obtainium()), )); + BackgroundFetch.registerHeadlessTask(backgroundFetchHeadlessTask); } var defaultThemeColour = Colors.deepPurple; @@ -122,6 +135,33 @@ class Obtainium extends StatefulWidget { class _ObtainiumState extends State { var existingUpdateInterval = -1; + @override + void initState() { + super.initState(); + initPlatformState(); + } + + Future initPlatformState() async { + await BackgroundFetch.configure( + BackgroundFetchConfig( + minimumFetchInterval: 15, + stopOnTerminate: false, + enableHeadless: true, + requiresBatteryNotLow: false, + requiresCharging: false, + requiresStorageNotLow: false, + requiresDeviceIdle: false, + requiredNetworkType: NetworkType.ANY), (String taskId) async { + // We don't want periodic tasks in the foreground - ignore + await bgUpdateCheck(taskId, null); + BackgroundFetch.finish(taskId); + }, (String taskId) async { + context.read().add('BG update task timed out.'); + BackgroundFetch.finish(taskId); + }); + if (!mounted) return; + } + @override Widget build(BuildContext context) { SettingsProvider settingsProvider = context.watch(); @@ -161,30 +201,6 @@ class _ObtainiumState extends State { context.locale.languageCode)) { settingsProvider.resetLocaleSafe(context); } - // Register the background update task according to the user's setting - var actualUpdateInterval = settingsProvider.updateInterval; - if (existingUpdateInterval != actualUpdateInterval) { - if (actualUpdateInterval == 0) { - AndroidAlarmManager.cancel(bgUpdateCheckAlarmId); - } else { - var settingChanged = existingUpdateInterval != -1; - var lastCheckWasTooLongAgo = actualUpdateInterval != 0 && - settingsProvider.lastBGCheckTime - .add(Duration(minutes: actualUpdateInterval + 60)) - .isBefore(DateTime.now()); - if (settingChanged || lastCheckWasTooLongAgo) { - logs.add( - 'Update interval was set to ${actualUpdateInterval.toString()} (reason: ${settingChanged ? 'setting changed' : 'last check was ${settingsProvider.lastBGCheckTime.toLocal().toString()}'}).'); - AndroidAlarmManager.periodic( - Duration(minutes: actualUpdateInterval), - bgUpdateCheckAlarmId, - bgUpdateCheck, - rescheduleOnReboot: true, - wakeup: true); - } - } - existingUpdateInterval = actualUpdateInterval; - } } return DynamicColorBuilder( diff --git a/lib/pages/settings.dart b/lib/pages/settings.dart index e83ddf3..d889de4 100644 --- a/lib/pages/settings.dart +++ b/lib/pages/settings.dart @@ -1,4 +1,3 @@ -import 'package:android_alarm_manager_plus/android_alarm_manager_plus.dart'; import 'package:device_info_plus/device_info_plus.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; @@ -608,38 +607,35 @@ class _SettingsPageState extends State { const Divider( height: 32, ), - Padding( - padding: const EdgeInsets.fromLTRB(16, 0, 16, 16), - child: Column(children: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Flexible(child: Text(tr('debugMenu'))), - Switch( - value: settingsProvider.showDebugOpts, - onChanged: (value) { - settingsProvider.showDebugOpts = value; - }) - ], - ), - if (settingsProvider.showDebugOpts) - Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - height16, - TextButton( - onPressed: () { - AndroidAlarmManager.oneShot( - const Duration(seconds: 0), - bgUpdateCheckAlarmId + 200, - bgUpdateCheck); - showMessage(tr('bgTaskStarted'), context); - }, - child: Text(tr('runBgCheckNow'))) - ], - ), - ]), - ), + // Padding( + // padding: const EdgeInsets.fromLTRB(16, 0, 16, 16), + // child: Column(children: [ + // Row( + // mainAxisAlignment: MainAxisAlignment.spaceBetween, + // children: [ + // Flexible(child: Text(tr('debugMenu'))), + // Switch( + // value: settingsProvider.showDebugOpts, + // onChanged: (value) { + // settingsProvider.showDebugOpts = value; + // }) + // ], + // ), + // if (settingsProvider.showDebugOpts) + // Column( + // crossAxisAlignment: CrossAxisAlignment.stretch, + // children: [ + // height16, + // TextButton( + // onPressed: () { + // bgUpdateCheck('taskId', null); + // showMessage(tr('bgTaskStarted'), context); + // }, + // child: Text(tr('runBgCheckNow'))) + // ], + // ), + // ]), + // ), ], ), ) diff --git a/lib/providers/apps_provider.dart b/lib/providers/apps_provider.dart index 97e6118..99503f2 100644 --- a/lib/providers/apps_provider.dart +++ b/lib/providers/apps_provider.dart @@ -8,7 +8,6 @@ import 'dart:math'; import 'package:http/http.dart' as http; import 'package:crypto/crypto.dart'; -import 'package:android_alarm_manager_plus/android_alarm_manager_plus.dart'; import 'package:android_intent_plus/flag.dart'; import 'package:android_package_installer/android_package_installer.dart'; import 'package:android_package_manager/android_package_manager.dart'; @@ -621,7 +620,8 @@ class AppsProvider with ChangeNotifier { // Returns an array of Ids for Apps that were successfully downloaded, regardless of installation result Future> downloadAndInstallLatestApps( List appIds, BuildContext? context, - {NotificationsProvider? notificationsProvider}) async { + {NotificationsProvider? notificationsProvider, + bool forceParallelDownloads = false}) async { notificationsProvider = notificationsProvider ?? context?.read(); List appsToInstall = []; @@ -742,7 +742,7 @@ class AppsProvider with ChangeNotifier { } } - if (!settingsProvider.parallelDownloads) { + if (forceParallelDownloads || !settingsProvider.parallelDownloads) { for (var id in appsToInstall) { await updateFn(id); } @@ -1448,19 +1448,17 @@ class _APKOriginWarningDialogState extends State { /// When toCheck is empty, the function is in "install mode" (else it is in "update mode"). /// In update mode, all apps in toCheck are checked for updates (in parallel). /// If an update is available and it cannot be installed silently, the user is notified of the available update. -/// If there are any errors, the task is run again for the remaining apps after a few minutes (based on the error with the longest retry interval). -/// Any app that has reached it's retry limit, the user is notified that it could not be checked. +/// If there are any errors, we recursively call the same function with retry count for the relevant apps decremented (if zero, the user is notified). /// /// Once all update checks are complete, the task is run again in install mode. -/// In this mode, all pending silent updates are downloaded and installed in the background (serially - one at a time). -/// If there is an error, the offending app is moved to the back of the line of remaining apps, and the task is retried. -/// If an app repeatedly fails to install up to its retry limit, the user is notified. +/// In this mode, all pending silent updates are downloaded (in parallel) and installed in the background. +/// If there is an error, the user is notified. /// -@pragma('vm:entry-point') -Future bgUpdateCheck(int taskId, Map? params) async { +Future bgUpdateCheck(String taskId, Map? params) async { + // ignore: avoid_print + print('Started $taskId: ${params.toString()}'); WidgetsFlutterBinding.ensureInitialized(); await EasyLocalization.ensureInitialized(); - await AndroidAlarmManager.initialize(); await loadTranslations(); LogsProvider logs = LogsProvider(); @@ -1469,11 +1467,20 @@ Future bgUpdateCheck(int taskId, Map? params) async { await appsProvider.loadApps(); int maxAttempts = 4; + int maxRetryWaitSeconds = 5; + + var netResult = await (Connectivity().checkConnectivity()); + if (netResult == ConnectivityResult.none) { + logs.add('BG update task: No network.'); + return; + } params ??= {}; - if (params['toCheck'] == null) { - appsProvider.settingsProvider.lastBGCheckTime = DateTime.now(); - } + + bool firstEverUpdateTask = DateTime.fromMillisecondsSinceEpoch(0) + .compareTo(appsProvider.settingsProvider.lastCompletedBGCheckTime) == + 0; + List> toCheck = >[ ...(params['toCheck'] ?.map((entry) => MapEntry( @@ -1481,6 +1488,11 @@ Future bgUpdateCheck(int taskId, Map? params) async { .toList() ?? appsProvider .getAppsSortedByUpdateCheckTime( + ignoreAppsCheckedAfter: params['toCheck'] == null + ? firstEverUpdateTask + ? null + : appsProvider.settingsProvider.lastCompletedBGCheckTime + : null, onlyCheckInstalledOrTrackOnlyApps: appsProvider .settingsProvider.onlyCheckInstalledOrTrackOnlyApps) .map((e) => MapEntry(e, 0))) @@ -1493,51 +1505,34 @@ Future bgUpdateCheck(int taskId, Map? params) async { (>>[])) ]; - var netResult = await (Connectivity().checkConnectivity()); - - if (netResult == ConnectivityResult.none) { - var networkBasedRetryInterval = 15; - var nextRegularCheck = appsProvider.settingsProvider.lastBGCheckTime - .add(Duration(minutes: appsProvider.settingsProvider.updateInterval)); - var potentialNetworkRetryCheck = - DateTime.now().add(Duration(minutes: networkBasedRetryInterval)); - var shouldRetry = potentialNetworkRetryCheck.isBefore(nextRegularCheck); - logs.add( - 'BG update task $taskId: No network. Will ${shouldRetry ? 'retry in $networkBasedRetryInterval minutes' : 'not retry'}.'); - AndroidAlarmManager.oneShot( - const Duration(minutes: 15), taskId + 1, bgUpdateCheck, - params: { - 'toCheck': toCheck - .map((entry) => {'key': entry.key, 'value': entry.value}) - .toList(), - 'toInstall': toInstall - .map((entry) => {'key': entry.key, 'value': entry.value}) - .toList(), - }); - return; - } - var networkRestricted = false; if (appsProvider.settingsProvider.bgUpdatesOnWiFiOnly) { networkRestricted = (netResult != ConnectivityResult.wifi) && (netResult != ConnectivityResult.ethernet); } - bool installMode = - toCheck.isEmpty; // Task is either in update mode or install mode - - logs.add( - 'BG ${installMode ? 'install' : 'update'} task $taskId: Started (${installMode ? toInstall.length : toCheck.length}).'); - - if (!installMode) { + if (toCheck.isNotEmpty) { + // Task is either in update mode or install mode // If in update mode, we check for updates. // We divide the results into 4 groups: // - toNotify - Apps with updates that the user will be notified about (can't be silently installed) - // - toRetry - Apps with update check errors that will be retried in a while // - toThrow - Apps with update check errors that the user will be notified about (no retry) // After grouping the updates, we take care of toNotify and toThrow first - // Then if toRetry is not empty, we schedule another update task to run in a while - // If toRetry is empty, we take care of schedule another task that will run in install mode (toCheck is empty) + // Then we run the function again in install mode (toCheck is empty) + + var enoughTimePassed = appsProvider.settingsProvider.updateInterval != 0 && + appsProvider.settingsProvider.lastCompletedBGCheckTime + .add( + Duration(minutes: appsProvider.settingsProvider.updateInterval)) + .isBefore(DateTime.now()); + if (!enoughTimePassed) { + // ignore: avoid_print + print( + 'BG update task: Too early for another check (last check was ${appsProvider.settingsProvider.lastCompletedBGCheckTime.toIso8601String()}, interval is ${appsProvider.settingsProvider.updateInterval}).'); + return; + } + + logs.add('BG update task: Started (${toCheck.length}).'); // Init. vars. List updates = []; // All updates found (silent and non-silent) @@ -1545,8 +1540,7 @@ Future bgUpdateCheck(int taskId, Map? params) async { []; // All non-silent updates that the user will be notified about List> toRetry = []; // All apps that got errors while checking - var retryAfterXSeconds = - 0; // How long to wait until the next attempt (if there are errors) + var retryAfterXSeconds = 0; MultiAppMultiError? errors; // All errors including those that will lead to a retry MultiAppMultiError toThrow = @@ -1569,27 +1563,32 @@ Future bgUpdateCheck(int taskId, Map? params) async { specificIds: toCheck.map((e) => e.key).toList(), sp: appsProvider.settingsProvider); } catch (e) { - // If there were errors, group them into toRetry and toThrow based on max retry count per app if (e is Map) { updates = e['updates']; errors = e['errors']; errors!.rawErrors.forEach((key, err) { logs.add( - 'BG update task $taskId: Got error on checking for $key \'${err.toString()}\'.'); + 'BG update task: Got error on checking for $key \'${err.toString()}\'.'); + var toCheckApp = toCheck.where((element) => element.key == key).first; if (toCheckApp.value < maxAttempts) { toRetry.add(MapEntry(toCheckApp.key, toCheckApp.value + 1)); // Next task interval is based on the error with the longest retry time - var minRetryIntervalForThisApp = err is RateLimitError + int minRetryIntervalForThisApp = err is RateLimitError ? (err.remainingMinutes * 60) : e is ClientException ? (15 * 60) - : pow(toCheckApp.value + 1, 2).toInt(); + : (toCheckApp.value + 1); + if (minRetryIntervalForThisApp > maxRetryWaitSeconds) { + minRetryIntervalForThisApp = maxRetryWaitSeconds; + } if (minRetryIntervalForThisApp > retryAfterXSeconds) { retryAfterXSeconds = minRetryIntervalForThisApp; } } else { - toThrow.add(key, err, appName: errors?.appIdNames[key]); + if (err is! RateLimitError) { + toThrow.add(key, err, appName: errors?.appIdNames[key]); + } } }); } else { @@ -1624,37 +1623,32 @@ Future bgUpdateCheck(int taskId, Map? params) async { id: Random().nextInt(10000))); } } - // if there are update checks to retry, schedule a retry task + logs.add('BG update task: Done checking for updates.'); if (toRetry.isNotEmpty) { logs.add( 'BG update task $taskId: Will retry in $retryAfterXSeconds seconds.'); - AndroidAlarmManager.oneShot( - Duration(seconds: retryAfterXSeconds), taskId + 1, bgUpdateCheck, - params: { - 'toCheck': toRetry - .map((entry) => {'key': entry.key, 'value': entry.value}) - .toList(), - 'toInstall': toInstall - .map((entry) => {'key': entry.key, 'value': entry.value}) - .toList(), - }); + return await bgUpdateCheck(taskId, { + 'toCheck': toRetry + .map((entry) => {'key': entry.key, 'value': entry.value}) + .toList(), + 'toInstall': toInstall + .map((entry) => {'key': entry.key, 'value': entry.value}) + .toList(), + }); } else { - // If there are no more update checks, schedule an install task - logs.add( - 'BG update task $taskId: Done. Scheduling install task to run immediately.'); - AndroidAlarmManager.oneShot( - const Duration(minutes: 0), taskId + 1, bgUpdateCheck, - params: { - 'toCheck': [], - 'toInstall': toInstall - .map((entry) => {'key': entry.key, 'value': entry.value}) - .toList() - }); + // If there are no more update checks, call the function in install mode + logs.add('BG update task: Done checking for updates.'); + return await bgUpdateCheck(taskId, { + 'toCheck': [], + 'toInstall': toInstall + .map((entry) => {'key': entry.key, 'value': entry.value}) + .toList() + }); } } else { // In install mode... - // If you haven't explicitly been given updates to install (which is the case for new tasks), grab all available silent updates + // If you haven't explicitly been given updates to install, grab all available silent updates if (toInstall.isEmpty && !networkRestricted) { var temp = appsProvider.findExistingUpdates(installedOnly: true); for (var i = 0; i < temp.length; i++) { @@ -1664,60 +1658,34 @@ Future bgUpdateCheck(int taskId, Map? params) async { } } } - var didCompleteInstalling = false; - var tempObtArr = toInstall.where((element) => element.key == obtainiumId); - if (tempObtArr.isNotEmpty) { - // Move obtainium to the end of the list as it must always install last - var obt = tempObtArr.first; - toInstall = moveStrToEndMapEntryWithCount(toInstall, obt); - } - // Loop through all updates and install each - for (var i = 0; i < toInstall.length; i++) { - var appId = toInstall[i].key; - var retryCount = toInstall[i].value; + if (toInstall.isNotEmpty) { + logs.add('BG install task: Started (${toInstall.length}).'); + var tempObtArr = toInstall.where((element) => element.key == obtainiumId); + if (tempObtArr.isNotEmpty) { + // Move obtainium to the end of the list as it must always install last + var obt = tempObtArr.first; + toInstall = moveStrToEndMapEntryWithCount(toInstall, obt); + } + // Loop through all updates and install each try { - logs.add( - 'BG install task $taskId: Attempting to update $appId in the background.'); - await appsProvider.downloadAndInstallLatestApps([appId], null, - notificationsProvider: notificationsProvider); - await Future.delayed(const Duration( - seconds: - 5)); // Just in case task ending causes install fail (not clear) - if (i == (toCheck.length - 1)) { - didCompleteInstalling = true; - } + await appsProvider.downloadAndInstallLatestApps( + toInstall.map((e) => e.key).toList(), null, + notificationsProvider: notificationsProvider, + forceParallelDownloads: true); } catch (e) { - // If you got an error, move the offender to the back of the line (increment their fail count) and schedule another task to continue installing shortly - logs.add( - 'BG install task $taskId: Got error on updating $appId \'${e.toString()}\'.'); - if (retryCount < maxAttempts) { - var remainingSeconds = retryCount; - logs.add( - 'BG install task $taskId: Will continue in $remainingSeconds seconds (with $appId moved to the end of the line).'); - var remainingToInstall = moveStrToEndMapEntryWithCount( - toInstall.sublist(i), MapEntry(appId, retryCount + 1)); - AndroidAlarmManager.oneShot( - Duration(seconds: remainingSeconds), taskId + 1, bgUpdateCheck, - params: { - 'toCheck': toCheck - .map((entry) => {'key': entry.key, 'value': entry.value}) - .toList(), - 'toInstall': remainingToInstall - .map((entry) => {'key': entry.key, 'value': entry.value}) - .toList(), - }); - break; + if (e is MultiAppMultiError) { + e.idsByErrorString.forEach((key, value) { + notificationsProvider.notify(ErrorCheckingUpdatesNotification( + e.errorsAppsString(key, value))); + }); } else { - // If the offender has reached its fail limit, notify the user and remove it from the list (task can continue) - toInstall.removeAt(i); - i--; - notificationsProvider - .notify(ErrorCheckingUpdatesNotification(e.toString())); + // We don't expect to ever get here in any situation so no need to catch (but log it in case) + logs.add('Fatal error in BG install task: ${e.toString()}'); + rethrow; } } - } - if (didCompleteInstalling || toInstall.isEmpty) { - logs.add('BG install task $taskId: Done.'); + logs.add('BG install task: Done installing updates.'); } } + appsProvider.settingsProvider.lastCompletedBGCheckTime = DateTime.now(); } diff --git a/lib/providers/settings_provider.dart b/lib/providers/settings_provider.dart index 266f244..0fb6d2a 100644 --- a/lib/providers/settings_provider.dart +++ b/lib/providers/settings_provider.dart @@ -52,8 +52,8 @@ class SettingsProvider with ChangeNotifier { } InstallMethodSettings get installMethod { - return InstallMethodSettings - .values[prefs?.getInt('installMethod') ?? InstallMethodSettings.normal.index]; + return InstallMethodSettings.values[ + prefs?.getInt('installMethod') ?? InstallMethodSettings.normal.index]; } set installMethod(InstallMethodSettings t) { @@ -345,15 +345,15 @@ class SettingsProvider with ChangeNotifier { notifyListeners(); } - DateTime get lastBGCheckTime { - int? temp = prefs?.getInt('lastBGCheckTime'); + DateTime get lastCompletedBGCheckTime { + int? temp = prefs?.getInt('lastCompletedBGCheckTime'); return temp != null ? DateTime.fromMillisecondsSinceEpoch(temp) : DateTime.fromMillisecondsSinceEpoch(0); } - set lastBGCheckTime(DateTime val) { - prefs?.setInt('lastBGCheckTime', val.millisecondsSinceEpoch); + set lastCompletedBGCheckTime(DateTime val) { + prefs?.setInt('lastCompletedBGCheckTime', val.millisecondsSinceEpoch); notifyListeners(); } diff --git a/lib/providers/source_provider.dart b/lib/providers/source_provider.dart index 86ac8db..564770a 100644 --- a/lib/providers/source_provider.dart +++ b/lib/providers/source_provider.dart @@ -135,10 +135,28 @@ appJSONCompatibilityModifiers(Map json) { if (additionalSettings['autoApkFilterByArch'] == null) { additionalSettings['autoApkFilterByArch'] = false; } - // HTML 'fixed URL' support should be disabled if it previously did not exist - if (source.runtimeType == HTML().runtimeType && - originalAdditionalSettings['supportFixedAPKURL'] == null) { - additionalSettings['supportFixedAPKURL'] = false; + if (source.runtimeType == HTML().runtimeType) { + // HTML 'fixed URL' support should be disabled if it previously did not exist + if (originalAdditionalSettings['supportFixedAPKURL'] == null) { + additionalSettings['supportFixedAPKURL'] = false; + } + // HTML key rename + if (originalAdditionalSettings['sortByFileNamesNotLinks'] != null) { + additionalSettings['sortByLastLinkSegment'] = + originalAdditionalSettings['sortByFileNamesNotLinks']; + } + // HTML single 'intermediate link' should be converted to multi-support version + if (originalAdditionalSettings['intermediateLinkRegex'] != null && + additionalSettings['intermediateLink']?.isNotEmpty != true) { + additionalSettings['intermediateLink'] = [ + { + 'customLinkFilterRegex': + originalAdditionalSettings['intermediateLinkRegex'], + 'filterByLinkText': + originalAdditionalSettings['intermediateLinkByText'] + } + ]; + } } json['additionalSettings'] = jsonEncode(additionalSettings); // F-Droid no longer needs cloudflare exception since override can be used - migrate apps appropriately diff --git a/pubspec.lock b/pubspec.lock index 6bc1ea8..5a31918 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -1,14 +1,6 @@ # Generated by pub # See https://dart.dev/tools/pub/glossary#lockfile packages: - android_alarm_manager_plus: - dependency: "direct main" - description: - name: android_alarm_manager_plus - sha256: "84720c8ad2758aabfbeafd24a8c355d8c8dd3aa52b01eaf3bb827c7210f61a91" - url: "https://pub.dev" - source: hosted - version: "3.0.4" android_intent_plus: dependency: "direct main" description: @@ -74,6 +66,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.11.0" + background_fetch: + dependency: "direct main" + description: + name: background_fetch + sha256: f70b28a0f7a3156195e9742229696f004ea3bf10f74039b7bf4c78a74fbda8a4 + url: "https://pub.dev" + source: hosted + version: "1.2.1" boolean_selector: dependency: transitive description: @@ -299,10 +299,10 @@ packages: dependency: "direct main" description: name: flutter_local_notifications - sha256: bb5cd63ff7c91d6efe452e41d0d0ae6348925c82eafd10ce170ef585ea04776e + sha256: "892ada16046d641263f30c72e7432397088810a84f34479f6677494802a2b535" url: "https://pub.dev" source: hosted - version: "16.2.0" + version: "16.3.0" flutter_local_notifications_linux: dependency: transitive description: @@ -514,10 +514,10 @@ packages: dependency: transitive description: name: path_provider_android - sha256: e595b98692943b4881b219f0a9e3945118d3c16bd7e2813f98ec6e532d905f72 + sha256: "477184d672607c0a3bf68fbbf601805f92ef79c82b64b4d6eb318cbca4c48668" url: "https://pub.dev" source: hosted - version: "2.2.1" + version: "2.2.2" path_provider_foundation: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 7b1bd76..511b9d0 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.14.41+235 # When changing this, update the tag in main() accordingly +version: 0.15.0+236 # When changing this, update the tag in main() accordingly environment: sdk: '>=3.0.0 <4.0.0' @@ -57,7 +57,6 @@ dependencies: ref: main android_package_manager: ^0.6.0 share_plus: ^7.0.0 - android_alarm_manager_plus: ^3.0.0 sqflite: ^2.2.0+3 easy_localization: ^3.0.1 android_intent_plus: ^4.0.0 @@ -68,6 +67,7 @@ dependencies: shared_storage: ^0.8.0 crypto: ^3.0.3 app_links: ^3.5.0 + background_fetch: ^1.2.1 dev_dependencies: flutter_test: From 85d103f3f6dd1fbf817ecabc0219a295d99148ad Mon Sep 17 00:00:00 2001 From: Imran Remtulla Date: Thu, 28 Dec 2023 19:16:16 -0500 Subject: [PATCH 07/20] Bugfix in HTML intermediate link UI (incomplete) --- lib/components/generated_form.dart | 31 +++++++++++++++++++++++------- 1 file changed, 24 insertions(+), 7 deletions(-) diff --git a/lib/components/generated_form.dart b/lib/components/generated_form.dart index 0958cc5..3eb2865 100644 --- a/lib/components/generated_form.dart +++ b/lib/components/generated_form.dart @@ -147,6 +147,14 @@ Color generateRandomLightColor() { return Color.fromARGB(255, rgbValues[0], rgbValues[1], rgbValues[2]); } +int generateRandomNumber(int seed1, + {int seed2 = 0, int seed3 = 0, max = 10000}) { + int combinedSeed = seed1.hashCode ^ seed2.hashCode ^ seed3.hashCode; + Random random = Random(combinedSeed); + int randomNumber = random.nextInt(max); + return randomNumber; +} + bool validateTextField(TextFormField tf) => (tf.key as GlobalKey).currentState?.isValid == true; @@ -156,6 +164,7 @@ class _GeneratedFormState extends State { late List> formInputs; List> rows = []; String? initKey; + int forceUpdateKeyCount = 0; // If any value changes, call this to update the parent with value and validity void someValueChanged({bool isBuilding = false, bool forceInvalid = false}) { @@ -502,6 +511,17 @@ class _GeneratedFormState extends State { } else if (widget.items[r][e] is GeneratedFormSubForm) { List subformColumn = []; for (int i = 0; i < values[fieldKey].length; i++) { + var items = (widget.items[r][e] as GeneratedFormSubForm) + .items + .map((x) => x.map((y) { + y.defaultValue = values[fieldKey]?[i]?[y.key]; + return y; + }).toList()) + .toList(); + var internalFormKey = ValueKey(generateRandomNumber( + values[fieldKey].length, + seed2: i, + seed3: forceUpdateKeyCount)); subformColumn.add(Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ @@ -514,13 +534,8 @@ class _GeneratedFormState extends State { style: const TextStyle(fontWeight: FontWeight.bold), ), GeneratedForm( - items: (widget.items[r][e] as GeneratedFormSubForm) - .items - .map((x) => x.map((y) { - y.defaultValue = values[fieldKey]?[i]?[y.key]; - return y; - }).toList()) - .toList(), + key: internalFormKey, + items: items, onValueChanges: (values, valid, isBuilding) { if (valid) { this.values[fieldKey]?[i] = values; @@ -541,6 +556,7 @@ class _GeneratedFormState extends State { var temp = List.from(values[fieldKey]); temp.removeAt(i); values[fieldKey] = List.from(temp); + forceUpdateKeyCount++; someValueChanged(); } : null, @@ -566,6 +582,7 @@ class _GeneratedFormState extends State { values[fieldKey].add(getDefaultValuesFromFormItems( (widget.items[r][e] as GeneratedFormSubForm) .items)); + forceUpdateKeyCount++; someValueChanged(); }, icon: const Icon(Icons.add), From d225650e15334cfdc542d11683d6c1f2001ec438 Mon Sep 17 00:00:00 2001 From: gidano Date: Fri, 29 Dec 2023 10:12:13 +0100 Subject: [PATCH 08/20] Update hu.json --- assets/translations/hu.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assets/translations/hu.json b/assets/translations/hu.json index 63155b3..7d2d9dc 100644 --- a/assets/translations/hu.json +++ b/assets/translations/hu.json @@ -236,7 +236,7 @@ "addInfoInSettings": "Adja hozzá ezt az infót a Beállításokban.", "githubSourceNote": "A GitHub sebességkorlátozás elkerülhető API-kulcs használatával.", "gitlabSourceNote": "Előfordulhat, hogy a GitLab APK kibontása nem működik API-kulcs nélkül.", - "sortByLastLinkSegment": "Sort by only the last segment of the link", + "sortByLastLinkSegment": "Rendezés csak a link utolsó szegmense szerint", "filterReleaseNotesByRegEx": "Kiadási megjegyzések szűrése reguláris kifejezéssel", "customLinkFilterRegex": "Egyéni APK hivatkozásszűrő reguláris kifejezéssel (Alapérték '.apk$')", "appsPossiblyUpdated": "App frissítési kísérlet", From 0d6e7181cf1db3e08e526c78d2b498ed656140d5 Mon Sep 17 00:00:00 2001 From: iDazai <50296346+iDazai@users.noreply.github.com> Date: Fri, 29 Dec 2023 17:35:25 +0100 Subject: [PATCH 09/20] Update de.json --- assets/translations/de.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/assets/translations/de.json b/assets/translations/de.json index 1e771a4..23a70b6 100644 --- a/assets/translations/de.json +++ b/assets/translations/de.json @@ -236,7 +236,7 @@ "addInfoInSettings": "Fügen Sie diese Info in den Einstellungen hinzu.", "githubSourceNote": "Die GitHub-Ratenbegrenzung kann mit einem API-Schlüssel umgangen werden.", "gitlabSourceNote": "GitLab APK-Extraktion funktioniert möglicherweise nicht ohne API-Schlüssel", - "sortByLastLinkSegment": "Sort by only the last segment of the link", + "sortByLastLinkSegment": "Sortiere nur nach dem letzten Teil des Links", "filterReleaseNotesByRegEx": "Versionshinweise nach regulärem Ausdruck filtern", "customLinkFilterRegex": "Benutzerdefinierter APK Link Filter nach Regulärem Ausdruck (Standard '.apk$')", "appsPossiblyUpdated": "App Aktualisierungen wurden versucht", @@ -247,9 +247,9 @@ "backgroundUpdateLimitsExplanation": "Der Erfolg einer Hintergrundinstallation kann nur festgestellt werden, wenn Obtainium geöffnet wird.", "verifyLatestTag": "Überprüfe das „latest“ Tag", "intermediateLinkRegex": "Filter für einen „Zwischen“-Link, der zuerst besucht werden soll", - "filterByLinkText": "Filter links by link text", + "filterByLinkText": "Filtere Links durch Linktext", "intermediateLinkNotFound": "„Zwischen“-Link nicht gefunden", - "intermediateLink": "Intermediate link", + "intermediateLink": "„Zwischen“-Link", "exemptFromBackgroundUpdates": "Ausschluss von Hintergrundaktualisierungen (falls aktiviert)", "bgUpdatesOnWiFiOnly": "Hintergrundaktualisierungen deaktivieren, wenn kein WLAN vorhanden ist", "autoSelectHighestVersionCode": "Automatisch höchste APK-Version auswählen", From 9935cb482e034030134a8817b55ee8a97936ae17 Mon Sep 17 00:00:00 2001 From: Imran Remtulla Date: Fri, 29 Dec 2023 13:39:56 -0500 Subject: [PATCH 10/20] HTML intermediate link bugfix (#1211) --- lib/providers/source_provider.dart | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/lib/providers/source_provider.dart b/lib/providers/source_provider.dart index 564770a..39d87bc 100644 --- a/lib/providers/source_provider.dart +++ b/lib/providers/source_provider.dart @@ -147,7 +147,7 @@ appJSONCompatibilityModifiers(Map json) { } // HTML single 'intermediate link' should be converted to multi-support version if (originalAdditionalSettings['intermediateLinkRegex'] != null && - additionalSettings['intermediateLink']?.isNotEmpty != true) { + additionalSettings['intermediateLinkRegex']?.isNotEmpty != true) { additionalSettings['intermediateLink'] = [ { 'customLinkFilterRegex': @@ -157,6 +157,12 @@ appJSONCompatibilityModifiers(Map json) { } ]; } + if ((additionalSettings['intermediateLink']?.length ?? 0) > 0) { + additionalSettings['intermediateLink'] = + additionalSettings['intermediateLink'].where((e) { + return e['intermediateLinkRegex']?.isNotEmpty == true; + }).toList(); + } } json['additionalSettings'] = jsonEncode(additionalSettings); // F-Droid no longer needs cloudflare exception since override can be used - migrate apps appropriately From eb29b908c237b21b2d1e338b590b2035328adf28 Mon Sep 17 00:00:00 2001 From: Imran Remtulla Date: Fri, 29 Dec 2023 13:43:06 -0500 Subject: [PATCH 11/20] Increment version --- lib/main.dart | 2 +- pubspec.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/main.dart b/lib/main.dart index de57a6c..3466492 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -19,7 +19,7 @@ import 'package:easy_localization/src/easy_localization_controller.dart'; // ignore: implementation_imports import 'package:easy_localization/src/localization.dart'; -const String currentVersion = '0.15.0'; +const String currentVersion = '0.15.1'; const String currentReleaseTag = 'v$currentVersion-beta'; // KEEP THIS IN SYNC WITH GITHUB RELEASES diff --git a/pubspec.yaml b/pubspec.yaml index 511b9d0..943295b 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.15.0+236 # When changing this, update the tag in main() accordingly +version: 0.15.1+237 # When changing this, update the tag in main() accordingly environment: sdk: '>=3.0.0 <4.0.0' From dd7217ca547dc105da7082321f46a17bde5675ff Mon Sep 17 00:00:00 2001 From: zenobit Date: Sat, 30 Dec 2023 04:30:28 +0100 Subject: [PATCH 12/20] Update cs.json --- assets/translations/cs.json | 283 ++++++++++++++++++------------------ 1 file changed, 144 insertions(+), 139 deletions(-) diff --git a/assets/translations/cs.json b/assets/translations/cs.json index 4d50530..fa04d52 100644 --- a/assets/translations/cs.json +++ b/assets/translations/cs.json @@ -9,36 +9,36 @@ "placeholder": "Zástupce", "someErrors": "Vyskytly se nějaké chyby", "unexpectedError": "Neočekávaná chyba", - "ok": "Okay", + "ok": "Ok", "and": "a", - "githubPATLabel": "GitHub Personal Access Token (Raises Rate Limit)", - "includePrereleases": "includepreleases", - "fallbackToOlderReleases": "Fallback to older releases", - "filterReleaseTitlesByRegEx": "Názvy vydání podle regulárního výrazu\filtr", + "githubPATLabel": "GitHub Personal Access Token (zvyšuje limit rychlosti)", + "includePrereleases": "Zahrnout předběžné verze", + "fallbackToOlderReleases": "Přechod na starší verze", + "filterReleaseTitlesByRegEx": "Filtrovat názvy verzí podle regulárního výrazu", "invalidRegEx": "Neplatný regulární výraz", "noDescription": "Žádný popis", "cancel": "Zrušit", "continue": "Pokračovat", "requiredInBracets": "(Required)", "dropdownNoOptsError": "ERROR: DROPDOWN MUSÍ MÍT AŽ JEDNU MOŽNOST", - "color": "barva", + "colour": "Barva", "githubStarredRepos": "GitHub Starred Repos", - "uname": "username", - "wrongArgNum": "Špatný počet předložených argumentů", - "xIsTrackOnly": "{} je určeno pouze pro sledování", - "source": "zdroj", + "uname": "Uživatelské jméno", + "wrongArgNum": "Nesprávný počet zadaných argumentů", + "xIsTrackOnly":"{} je určeno pouze pro sledování", + "source": "Zdroj", "app": "App", - "appsFromSourceAreTrackOnly": "Aplikace z tohoto zdroje jsou 'Jen sledovány'.", - "youPickedTrackOnly": "Vybrali jste možnost 'Jen sledovat'.", + "appsFromSourceAreTrackOnly": "Aplikace z tohoto zdroje jsou Jen sledovány.", + "youPickedTrackOnly": "Vybrali jste možnost Jen sledovat.", "trackOnlyAppDescription": "Aplikace je sledována kvůli aktualizacím, ale Obtainium ji nebude stahovat ani instalovat.", "cancelled": "Zrušeno", "appAlreadyAdded": "Aplikace již přidána", "alreadyUpToDateQuestion": "App already up to date?", "addApp": "Přidat aplikaci", - "appSourceURL": "zdrojová adresa URL aplikace", + "appSourceURL": "Zdrojová adresa URL aplikace", "error": "Chyba", "add": "Přidat", - "searchSomeSourcesLabel": "Vyhledávání (pouze konkrétní zdroje)", + "searchSomeSourcesLabel": "Vyhledávání (pouze pro určité zdroje)", "search": "Hledat", "additionalOptsFor": "Další možnosti pro {}", "supportedSources": "Podporované zdroje", @@ -46,45 +46,45 @@ "searchableInBrackets": "(s možností vyhledávání)", "appsString": "Apky", "noApps": "Žádné aplikace", - "noAppsForFilter": "žádné aplikace pro vybraný filtr", - "byX": "By {}", + "noAppsForFilter": "Žádné aplikace pro vybraný filtr", + "byX": "Od {}", "percentProgress": "Pokrok: {}%", "pleaseWait": "Počkejte prosím", "updateAvailable": "Aktualizace je k dispozici", "estimateInBracketsShort": "(approx.)", "notInstalled": "Není nainstalováno", "estimateInBrackets": "(přibližně)", - "selectAll": "Vybrat Vše", + "selectAll": "Vybrat vše", "deselectX": "{} deselected", "xWillBeRemovedButRemainInstalled": "{} bude odstraněn z Obtainium, ale zůstane nainstalován v zařízení.", "removeSelectedAppsQuestion": "Odebrat vybrané aplikace?", "removeSelectedApps": "Odebrat vybrané aplikace", "updateX": "Aktualizovat {}", "installX": "Instalovat {}", - "markXTrackOnlyAsUpdated": "Označit {}\n(Track-Only)\njako aktualizované", + "markXTrackOnlyAsUpdated": "Označit {}\n(Jen sledované)\njako aktualizované", "changeX": "Změnit {}", "installUpdateApps": "Instalovat/aktualizovat aplikace", "installUpdateSelectedApps": "Instalovat/aktualizovat vybrané aplikace", - "markXSelectedAppsAsUpdated": "označit {} vybrané aplikace jako aktuální?", + "markXSelectedAppsAsUpdated": "Označit {} vybrané aplikace jako aktuální?", "no": "Ne", - "yes": "ano", - "markSelectedAppsUpdated": "označit vybrané aplikace jako aktuální", + "yes": "Ano", + "markSelectedAppsUpdated": "Označit vybrané aplikace jako aktuální", "pinToTop": "Připnout nahoru", - "unpinFromTop": "'Unpin Top'", + "unpinFromTop": "Odepnout shora", "resetInstallStatusForSelectedAppsQuestion": "Obnovit stav instalace vybraných aplikací?", "installStatusOfXWillBeResetExplanation": "Stav instalace vybraných aplikací bude resetován. To může být užitečné, pokud je verze aplikace zobrazená v Obtainium nesprávná z důvodu neúspěšných aktualizací nebo jiných problémů.", "shareSelectedAppURLs": "Sdílet adresy URL vybraných aplikací", - "resetInstallStatus": "Obnovení stavu instalace", - "more": "more", - "removeOutdatedFilter": "Odstranit filtr aplikace 'Not Current'", - "showOutdatedOnly": "Zobrazit pouze aplikace, které nejsou aktuální", + "resetInstallStatus": "Obnovit stav instalace", + "more": "Více", + "removeOutdatedFilter": "Odstranit filtr Neaktuální", + "showOutdatedOnly": "Zobrazovat pouze zastaralé aplikace", "filter": "Filtr", "filterActive": "Filtr *", "filterApps": "Filtrovat aplikace", - "appName": "název aplikace", + "appName": "Název aplikace", "author": "Autor", - "upToDateApps": "Apps with current version", - "nonInstalledApps": "Apps not installed", + "upToDateApps": "Aktuální apky", + "nonInstalledApps": "Neinstalované apky", "importExport": "Import/Export", "settings": "Nastavení", "exportedTo": "Exportováno do {}", @@ -93,76 +93,76 @@ "importedX": "Importováno {}", "obtainiumImport": "Obtainium Import", "importFromURLList": "Import ze seznamu URL", - "searchQuery": "Search Query", - "appURLList": "App URL List", - "line": "line", + "searchQuery": "Vyhledávací dotaz", + "appURLList": "Seznam adres aplikací", + "line": "Linka", "searchX": "Search {}", "noResults": "Nebyly nalezeny žádné výsledky", "importX": "Import {}", - "importedAppsIdDisclaimer": "Importované aplikace mohou být nesprávně zobrazeny jako \"Neinstalované\". Chcete-li to opravit, nainstalujte je znovu prostřednictvím Obtainium. To nemá vliv na data aplikací. Ovlivňuje pouze metody importu URL a třetích stran.", - "importErrors": "Import Errors", - "importedXOfYApps": "{}importováno {}aplikací.", - "followingURLsHadErrors": "U následujících adres URL došlo k chybám:", + "importedAppsIdDisclaimer": "Importované aplikace mohou být nesprávně zobrazeny jako \"Neinstalovány\". Chcete-li to opravit, nainstalujte je znovu prostřednictvím Obtainium. To nemá vliv na data aplikací. Ovlivňuje pouze metody importu URL a třetích stran.", + "importErrors": "Chyba importu", + "importedXOfYApps": "{}importováno z {} aplikací.", + "followingURLsHadErrors": "U následujících adres došlo k chybám:", "okay": "Okay", - "selectURL": "Select URL", - "selectURLs": "Select URLs", + "selectURL": "Vybrat adresu", + "selectURLs": "Select adresy", "pick": "Vybrat", "theme": "Téma", "dark": "Tmavé", "light": "Světlé", - "followSystem": "Follow System", + "followSystem": "Jako systém", "obtainium": "Obtainium", "materialYou": "Material You", "useBlackTheme": "Použít čistě černé tmavé téma", - "appSortBy": "Seřadit aplikaci podle", - "authorName": "autor/jméno", - "nameAuthor": "jméno/autor", - "asAdded": "AsAdded", - "appSortOrder": "Sort App By", + "appSortBy": "Seřadit podle", + "authorName": "Autor/Jméno", + "nameAuthor": "Jméno/Autor", + "asAdded": "Přidáno", + "appSortOrder": "Seřadit", "ascending": "Vzestupně", "descending": "Sestupně", - "bgUpdateCheckInterval": "Background Update Check Interval", + "bgUpdateCheckInterval": "Interval kontroly aktualizace na pozadí", "neverManualOnly": "Nikdy - pouze ručně", "appearance": "Vzhled", "showWebInAppView": "Zobrazit zdrojové webové stránky v zobrazení aplikace", - "pinUpdates": "Připnout aplikace s aktualizacemi nahoře", + "pinUpdates": "Připnout aplikace s aktualizacemi nahoru", "updates": "Updates", - "sourceSpecific": "source specific", - "appSource": "zdroj aplikace", + "sourceSpecific": "Specifické pro zdroj", + "appSource": "Zdroj aplikace", "noLogs": "Žádné protokoly", - "appLogs": "App Logs", + "appLogs": "Záznamy apky", "close": "Zavřít", "share": "Sdílet", - "appNotFound": "App not found", + "appNotFound": "Aplikace nenalezena", "obtainiumExportHyphenatedLowercase": "obtainium-export", "pickAnAPK": "Vybrat APK", "appHasMoreThanOnePackage": "{} má více než jeden balíček:", "deviceSupportsXArch": "Vaše zařízení podporuje architekturu CPU {}.", "deviceSupportsFollowingArchs": "Vaše zařízení podporuje následující architektury CPU:", "warning": "Varování", - "sourceIsXButPackageFromYPrompt": "The app source is '{}' but the release package is from '{}'. Pokračovat?", - "updatesAvailable": "dostupné aktualizace", + "sourceIsXButPackageFromYPrompt": "Zdroj aplikace je '{}', ale balíček pro vydání je z '{}'. Pokračovat?", + "updatesAvailable": "Dostupné aktualizace", "updatesAvailableNotifDescription": "Upozorňuje uživatele, že jsou k dispozici aktualizace pro jednu nebo více aplikací sledovaných Obtainium", "noNewUpdates": "Žádné nové aktualizace.", "xHasAnUpdate": "{} má aktualizaci.", "appsUpdated": "Aplikace aktualizovány", - "appsUpdatedNotifDescription": "Upozorňuje uživatele, že byly provedeny aktualizace jedné nebo více aplikací na pozadí", - "xWasUpdatedToY": "{} byl aktualizován na {}", - "errorCheckingUpdates": "Chybová kontrola aktualizací", - "errorCheckingUpdatesNotifDescription": "Oznámení zobrazené při neúspěšné kontrole aktualizací na pozadí", + "appsUpdatedNotifDescription": "Upozornit, že byly provedeny aktualizace jedné nebo více aplikací na pozadí", + "xWasUpdatedToY": "{} byla aktualizována na {}", + "errorCheckingUpdates": "Chyba kontroly aktualizací", + "errorCheckingUpdatesNotifDescription": "Zobrazit oznámení při neúspěšné kontrole aktualizací na pozadí", "appsRemoved": "Odstraněné aplikace", - "appsRemovedNotifDescription": "Oznámení uživateli, že jedna nebo více aplikací byly odstraněny z důvodu chyb při načítání", + "appsRemovedNotifDescription": "Oznámit, že jedna nebo více aplikací bylo odstraněno z důvodu chyb při načítání", "xWasRemovedDueToErrorY": "{} byla odstraněna z důvodu následující chyby: {}", "completeAppInstallation": "Dokončit instalaci aplikace", "obtainiumMustBeOpenToInstallApps": "Obtainium musí být otevřeno, aby bylo možné instalovat aplikace", - "completeAppInstallationNotifDescription": "Vyzvat uživatele k návratu do Obtainium pro dokončení instalace aplikací", + "completeAppInstallationNotifDescription": "Vyzvat k návratu do Obtainium pro dokončení instalace aplikací", "checkingForUpdates": "Zkontrolovat aktualizace", "checkingForUpdatesNotifDescription": "Dočasné oznámení zobrazené při kontrole aktualizací", "pleaseAllowInstallPerm": "Povolte prosím Obtainium instalovat aplikace", "trackOnly": "Jen sledovat", - "errorWithHttpStatusCode": "error {}", + "errorWithHttpStatusCode": "Chyba {}", "versionCorrectionDisabled": "Oprava verze zakázána (zásuvný modul zřejmě nefunguje)", - "unknown": "Unknown", + "unknown": "Neznám", "none": "None", "never": "Nikdy", "latestVersionX": "Nejnovější verze: {}", @@ -170,12 +170,12 @@ "lastUpdateCheckX": "Poslední kontrola aktualizace: {}", "remove": "Odebrat", "yesMarkUpdated": "Ano, označit jako aktualizované", - "fdroid": "F-Droid Official", - "appIdOrName": "App ID or Name", + "fdroid": "Oficiální repozitář F-Droid", + "appIdOrName": "ID nebo název apky", "appId": "App ID", "appWithIdOrNameNotFound": "Žádná aplikace s tímto ID nebo názvem nebyla nalezena", "reposHaveMultipleApps": "Repozitáře mohou obsahovat více aplikací", - "fdroidThirdPartyRepo": "F-Droid Third-Party Repo", + "fdroidThirdPartyRepo": "F-Droid repozitář třetí strany", "steam": "Steam", "steamMobile": "Steam Mobile", "steamChat": "Steam Chat", @@ -183,106 +183,111 @@ "markInstalled": "Označit jako nainstalovaný", "update": "Aktualizovat", "markUpdated": "Označit jako aktuální", - "additionalOptions": "Additional Options", - "disableVersionDetection": "Zakázat detekci verze", - "noVersionDetectionExplanation": "Tato volba by měla být použita pouze u aplikací, kde detekce verzí nefunguje správně.", - "downloadingX": "download {}", + "additionalOptions": "Další možnosti", + "disableVersionDetection": "Deaktivovat detekci verze", + "noVersionDetectionExplanation": "Tato možnost by měla být použita pouze u aplikace, kde detekce verzí nefunguje správně.", + "downloadingX": "Stáhnout {}", "downloadNotifDescription": "Informuje uživatele o průběhu stahování aplikace", "noAPKFound": "Žádná APK nebyla nalezena", "noVersionDetection": "Žádná detekce verze", "categorize": "Kategorizovat", "categories": "Kategorie", - "category": "kategorie", + "category": "Kategorie", "noCategory": "Žádná kategorie", "noCategories": "Žádné kategorie", "deleteCategoriesQuestion": "Smazat kategorie?", "categoryDeleteWarning": "Všechny aplikace v odstraněných kategoriích budou nastaveny na nekategorizované.", - "addCategory": "přidat kategorii", - "label": "štítek", + "addCategory": "Přidat kategorii", + "label": "Štítek", "language": "Jazyk", - "copiedToClipboard": "zkopírováno do schránky", - "storagePermissionDenied": "povolení k ukládání odepřeno", + "copiedToClipboard": "Zkopírováno do schránky", + "storagePermissionDenied": "Oprávnění k ukládání odepřeno", "selectedCategorizeWarning": "Toto nahradí všechna stávající nastavení kategorií pro vybrané aplikace.", "filterAPKsByRegEx": "Filtrovat APK podle regulárního výrazu", "removeFromObtainium": "Odebrat z Obtainium", "uninstallFromDevice": "Odinstalovat ze zařízení", "onlyWorksWithNonVersionDetectApps": "Funguje pouze pro aplikace s vypnutou detekcí verze.", "releaseDateAsVersion": "Použít datum vydání jako verzi", - "releaseDateAsVersionExplanation": "Tato možnost by měla být použita pouze u aplikací, u kterých detekce verze nefunguje správně, ale je k dispozici datum vydání.", + "releaseDateAsVersionExplanation": "Tato možnost by měla být použita pouze u aplikace, kde detekce verzí nefunguje správně, ale je k dispozici datum vydání.", "changes": "Změny", - "releaseDate": "datum vydání", + "releaseDate": "Datum vydání", "importFromURLsInFile": "Importovat adresy URL ze souboru (např. OPML)", - "versionDetection": "detekce verze", - "standardVersionDetection": "standardní detekce verze", + "versionDetection": "Detekce verze", + "standardVersionDetection": "Standardní detekce verze", "groupByCategory": "Seskupit podle kategorie", "autoApkFilterByArch": "Pokud je to možné, pokuste se filtrovat soubory APK podle architektury procesoru", "overrideSource": "Přepsat zdroj", "dontShowAgain": "Nezobrazovat znovu", - "dontShowTrackOnlyWarnings": "Nezobrazovat varování pro 'Track Only'", + "dontShowTrackOnlyWarnings": "Nezobrazovat varování pro 'Jen sledované'", "dontShowAPKOriginWarnings": "Nezobrazovat varování pro původ APK", "moveNonInstalledAppsToBottom": "Přesunout nenainstalované aplikace na konec zobrazení Aplikace", "gitlabPATLabel": "GitLab Personal Access Token\n(Umožňuje vyhledávání a lepší zjišťování APK)", - "about": "About", + "about": "O", "requiresCredentialsInSettings": "{}: Vyžaduje další pověření (v nastavení)", "checkOnStart": "Zkontrolovat jednou při spuštění", "tryInferAppIdFromCode": "Pokusit se určit ID aplikace ze zdrojového kódu", "removeOnExternalUninstall": "Automaticky odstranit externě odinstalované aplikace", - "pickHighestVersionCode": "Automaticky vybrat APK s kódem nejvyšší verze", - "checkUpdateOnDetailPage": "Zkontrolovat aktualizace při otevření stránky s podrobnostmi aplikace", + "pickHighestVersionCode": "Automaticky vybrat nejvyšší verzi APK", + "checkUpdateOnDetailPage": "Zkontrolovat aktualizaci při otevření stránky s podrobnostmi aplikace", "disablePageTransitions": "Zakázat animace pro přechody stránek", "reversePageTransitions": "Obrátit animace pro přechody stránek", "minStarCount": "Minimální počet hvězdiček", - "addInfoBelow": "Přidat tuto informaci na konec stránky", + "addInfoBelow": "Přidat tuto informaci na konec stránky.", "addInfoInSettings": "Přidat tuto informaci do nastavení.", "githubSourceNote": "Omezení rychlosti GitHub lze obejít pomocí klíče API.", "gitlabSourceNote": "Extrakce GitLab APK nemusí fungovat bez klíče API", - "sortByLastLinkSegment": "Sort by only the last segment of the link", + "sortByLastLinkSegment": "Seřadit pouze podle poslední části odkazu", "filterReleaseNotesByRegEx": "Filtrovat poznámky k vydání podle regulárního výrazu", "customLinkFilterRegex": "Vlastní filtr odkazů APK podle regulárního výrazu (výchozí '.apk$')", "appsPossiblyUpdated": "Byly provedeny pokusy o aktualizaci aplikací", "appsPossiblyUpdatedNotifDescription": "Upozorňuje uživatele, že na pozadí mohly být provedeny aktualizace jedné nebo více aplikací", - "xWasPossiblyUpdatedToY": "{} mohlo být aktualizováno na {}.", + "xWasPossiblyUpdatedToY":"{} mohlo být aktualizováno na {}.", "enableBackgroundUpdates": "Povolit aktualizace na pozadí", - "backgroundUpdateReqsExplanation": "Aktualizace na pozadí nemusí být možné pro všechny aplikace.", - "backgroundUpdateLimitsExplanation": "Úspěšnost instalace na pozadí lze určit pouze v případě, že je otevřen Obtainium.", - "verifyLatestTag": "Ověřit značku 'latest'", - "intermediateLinkRegex": "Filter for an 'Intermediate' Link to Visit", - "filterByLinkText": "Filter links by link text", - "intermediateLinkNotFound": "Intermediate link not found", - "intermediateLink": "Intermediate link", - "exemptFromBackgroundUpdates": "Vyloučit aktualizace na pozadí (pokud jsou povoleny)", - "bgUpdatesOnWiFiOnly": "Zakázat aktualizace na pozadí, pokud není přítomna Wi-Fi", - "autoSelectHighestVersionCode": "Automatický výběr nejvyššího kódu verze APK", - "versionExtractionRegEx": "Version Extraction RegEx", - "matchGroupToUse": "Match Group to Use", + "backgroundUpdateReqsExplanation": "Aktualizace na pozadí nemusí být možná pro všechny aplikace.", + "backgroundUpdateLimitsExplanation": "Úspěšnost instalace na pozadí lze určit pouze v případě, že je otevřeno Obtainium.", + "verifyLatestTag": "Zkontrolovat značku latest", + "intermediateLinkRegex": "Filtrovat mezipropojení, které by mělo být navštíveno jako první", + "filterByLinkText": "Filtrovat odkazy podle textu odkazu", + "intermediateLinkNotFound": "Připojený odkaz nenalezen", + "intermediateLink": "Připojený odkaz", + "exemptFromBackgroundUpdates": "Vyloučit z aktualizací na pozadí (je-li povoleno)", + "bgUpdatesOnWiFiOnly": "Deaktivovat aktualizace na pozadí, pokud není k dispozici Wi-Fi", + "autoSelectHighestVersionCode": "Automaticky vybrat nejvyšší verzi APK", + "versionExtractionRegEx": "Extrakce verze pomocí RegEx", + "matchGroupToUse": "Odpovídá použité skupině", "highlightTouchTargets": "Zvýraznit méně zjevné cíle dotyku", "pickExportDir": "Vybrat adresář pro export", - "autoExportOnChanges": "Automatický export při změnách", - "includeSettings": "Include settings", - "filterVersionsByRegEx": "Filtrovat verze podle regulárního výrazu", - "trySelectingSuggestedVersionCode": "Zkusit vybrat navrhovaný kód verze APK", - "dontSortReleasesList": "Retain release order from API", - "reverseSort": "Reverse sorting", - "takeFirstLink": "Take first link", - "skipSort": "Skip sorting", - "debugMenu": "Debug Menu", - "bgTaskStarted": "Background task started - check logs.", - "runBgCheckNow": "Run Background Update Check Now", - "versionExtractWholePage": "Apply Version Extraction Regex to Entire Page", - "installing": "Installing", - "skipUpdateNotifications": "Skip update notifications", - "updatesAvailableNotifChannel": "dostupné aktualizace", - "appsUpdatedNotifChannel": "Aplikace aktualizovány", - "appsPossiblyUpdatedNotifChannel": "Byly provedeny pokusy o aktualizaci aplikací", - "errorCheckingUpdatesNotifChannel": "Chybová kontrola aktualizací", - "appsRemovedNotifChannel": "Odstraněné aplikace", - "downloadingXNotifChannel": "download {}", + "autoExportOnChanges": "Automatický export při změně", + "includeSettings": "Zahrnout nastavení", + "filterVersionsByRegEx": "Filtrovat verze podle regulárních výrazů", + "trySelectingSuggestedVersionCode": "Zkusit vybrat navrhovanou verzi APK", + "dontSortReleasesList": "Seřadit vydání z rozhraní API", + "reverseSort": "Obrácené třídění", + "takeFirstLink": "Použít první odkaz", + "skipSort": "Přeskočit třídění", + "debugMenu": "Nabídka ladění", + "bgTaskStarted": "Spuštěna úloha na pozadí - zkontrolujte protokoly.", + "runBgCheckNow": "Spustit kontrolu aktualizací na pozadí nyní", + "versionExtractWholePage": "Použít extrakci verze pomocí RegEx na celou stránku", + "installing": "Instaluji", + "skipUpdateNotifications": "Neposkytovat oznámení o aktualizaci", + "updatesAvailableNotifChannel": "Dostupné aktualizace", + "appsUpdatedNotifChannel": "Apky aktualizovány", + "appsPossiblyUpdatedNotifChannel": "Byly provedeny pokusy o aktualizace aplikací", + "errorCheckingUpdatesNotifChannel": "Chyba při kontrole aktualizací", + "appsRemovedNotifChannel": "Odstraněné apky", + "downloadingXNotifChannel": "Stáhnout {}", "completeAppInstallationNotifChannel": "Dokončit instalaci aplikace", "checkingForUpdatesNotifChannel": "Zkontrolovat aktualizace", - "onlyCheckInstalledOrTrackOnlyApps": "Only check installed and Track-Only apps for updates", - "supportFixedAPKURL": "Support fixed APK URLs", - "selectX": "Select {}", - "parallelDownloads": "Allow parallel downloads", + "onlyCheckInstalledOrTrackOnlyApps": "Na aktualizace kontrolovat pouze nainstalované aplikace a aplikace označené Track only", + "supportFixedAPKURL": "Odhadnout novější verzi na základě prvních třiceti číslic kontrolního součtu adresy URL APK, pokud není podporována jinak", + "selectX": "Vybrat {}", + "parallelDownloads": "Povolit souběžné stahování", + "installMethod": "Metoda instalace", + "normal": "Normální", + "shizuku": "Shizuku", + "root": "Správce", + "shizukuBinderNotFound": "Shizuku neběží", "removeAppQuestion": { "one": "Odstranit Apku?", "other": "Odstranit Apky?" @@ -292,47 +297,47 @@ "other": "Příliš mnoho požadavků (omezená rychlost) - zkuste to znovu za {} minut" }, "bgUpdateGotErrorRetryInMinutes": { - "one": "Při kontrole aktualizace na pozadí byla zjištěna chyba {}, opakování pokusu bude naplánováno za {} minut", - "other": "Během kontroly aktualizace na pozadí byla zjištěna chyba {}, opakování bude naplánováno za {} minut" + "one": "Při kontrole aktualizace na pozadí byla zjištěna chyba {}, opakování bude naplánováno za {} minut", + "other": "Při kontrole aktualizací na pozadí byla zjištěna chyba {}, opakování bude naplánováno za {} minut" }, "bgCheckFoundUpdatesWillNotifyIfNeeded": { "one": "Při kontrole aktualizací na pozadí nalezena {}aktualizace - v případě potřeby upozorní uživatele", - "other": "Kontrola aktualizací na pozadí našla {} aktualizací - v případě potřeby upozorní uživatele" + "other": "Kontrola aktualizací na pozadí nalezla {} aktualizací - v případě potřeby upozorní uživatele" }, "apps": { - "one": "{} App", - "other": "{} apps" + "one": "{} Apka", + "other": "{} Apky" }, "url": { - "jedna": "{} URL", - "other": "{} URLs" + "one": "{} Adresa", + "other": "{} Adres" }, "minute": { - "one": "{} minute", - "other": "{} minutes" + "one": "{} Minuta", + "other": "{} Minut" }, "hour": { - "jedna": "{} hodina", - "other": "{} hours" + "one": "{} Hodina", + "other": "{} Hodin" }, "day": { - "jedna": "{} den", - "other": "{} dny" + "one": "{} Den", + "other": "{} Dnů" }, "clearedNLogsBeforeXAfterY": { - "one": "{n} log vymazán (před = {před}, po = {po})", - "other": "{n} logů vymazáno (před = {před}, po = {po})" + "one": "{n} Záznam vymazán (před = {before}, po = {after})", + "other": "{n} Záznamů vymazáno (před = {before}, po = {after})" }, "xAndNMoreUpdatesAvailable": { "one": "{} a 1 další aplikace mají aktualizace.", "other": "{} a {} další aplikace mají aktualizace." }, "xAndNMoreUpdatesInstalled": { - "one": "{} a {} další aplikace mají aktualizace.", - "další": "{} a {} další aplikace byly aktualizovány." + "one": "{} a 1 další aplikace mají aktualizace.", + "other": "{} a {} další aplikace byly aktualizovány." }, "xAndNMoreUpdatesPossiblyInstalled": { - "one": "{} a {} další aplikace byly možná aktualizovány", - "other": "{} a {} další aplikace mohly být aktualizovány." + "one": "{} a 1 další aplikace možno aktualizovat", + "other": "{} a {} další aplikace mohou být aktualizovány." } -} \ No newline at end of file +} From f6faa19e5f89435d91fa60a12ab865d5c35b5f3d Mon Sep 17 00:00:00 2001 From: gidano Date: Sat, 30 Dec 2023 09:45:46 +0100 Subject: [PATCH 13/20] Update hu.json --- assets/translations/hu.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/assets/translations/hu.json b/assets/translations/hu.json index 7d2d9dc..3a8643d 100644 --- a/assets/translations/hu.json +++ b/assets/translations/hu.json @@ -282,7 +282,7 @@ "onlyCheckInstalledOrTrackOnlyApps": "Csak a telepített és a csak követhető appokat ellenőrizze frissítésekért", "supportFixedAPKURL": "Támogatja a rögzített APK URL-eket", "selectX": "Kiválaszt {}", - "parallelDownloads": "Allow parallel downloads", + "parallelDownloads": "Párhuzamos letöltéseket enged", "removeAppQuestion": { "one": "Eltávolítja az alkalmazást?", "other": "Eltávolítja az alkalmazást?" From 51970abce7b6ea3b980f6c429886aff720beb687 Mon Sep 17 00:00:00 2001 From: Matsuri Date: Sat, 30 Dec 2023 16:55:01 +0800 Subject: [PATCH 14/20] Update zh.json - Add & translate new strings - Minor improvements Signed-off-by: Matsuri --- assets/translations/zh.json | 37 +++++++++++++++++++++---------------- 1 file changed, 21 insertions(+), 16 deletions(-) diff --git a/assets/translations/zh.json b/assets/translations/zh.json index 62b52b1..bd2eeb6 100644 --- a/assets/translations/zh.json +++ b/assets/translations/zh.json @@ -223,7 +223,7 @@ "moveNonInstalledAppsToBottom": "将未安装应用置底", "gitlabPATLabel": "GitLab 个人访问令牌(启用搜索功能并增强 APK 发现)", "about": "相关文档", - "requiresCredentialsInSettings": "{}: 此功能需要额外的凭据(在“设置”中添加)", + "requiresCredentialsInSettings": "{}:此功能需要额外的凭据(在“设置”中添加)", "checkOnStart": "启动时进行一次检查", "tryInferAppIdFromCode": "尝试从源代码推断应用 ID", "removeOnExternalUninstall": "自动删除已卸载的外部应用", @@ -236,9 +236,9 @@ "addInfoInSettings": "在“设置”中添加此凭据。", "githubSourceNote": "使用访问令牌可避免触发 GitHub 的 API 请求限制。", "gitlabSourceNote": "未使用访问令牌时可能无法从 GitLab 获取 APK 文件。", - "sortByLastLinkSegment": "Sort by only the last segment of the link", + "sortByLastLinkSegment": "仅根据链接的末尾部分进行筛选", "filterReleaseNotesByRegEx": "筛选发行说明(正则表达式)", - "customLinkFilterRegex": "筛选自定义来源 APK 文件链接\n(正则表达式,默认匹配模式为“.apk$”)", + "customLinkFilterRegex": "筛选自定义来源的 APK 文件链接\n(正则表达式,默认匹配模式为“.apk$”)", "appsPossiblyUpdated": "已尝试更新应用", "appsPossiblyUpdatedNotifDescription": "当应用已尝试在后台更新时发送通知", "xWasPossiblyUpdatedToY": "已尝试将“{}”更新至 {}。", @@ -246,29 +246,29 @@ "backgroundUpdateReqsExplanation": "后台更新未必适用于所有的应用。", "backgroundUpdateLimitsExplanation": "只有在启动 Obtainium 时才能确认安装是否成功。", "verifyLatestTag": "验证“Latest”标签", - "intermediateLinkRegex": "Filter for an 'Intermediate' Link to Visit", - "filterByLinkText": "Filter links by link text", - "intermediateLinkNotFound": "未找到“中转”链接", - "intermediateLink": "Intermediate link", - "exemptFromBackgroundUpdates": "禁用后台更新\n(如果已经全局启用)", + "intermediateLinkRegex": "筛选中转链接(正则表达式)", + "filterByLinkText": "根据链接文本进行筛选", + "intermediateLinkNotFound": "未找到中转链接", + "intermediateLink": "中转链接", + "exemptFromBackgroundUpdates": "禁用后台更新(如果已经全局启用)", "bgUpdatesOnWiFiOnly": "未连接 Wi-Fi 时禁用后台更新", "autoSelectHighestVersionCode": "自动选择版本号最高的 APK 文件", - "versionExtractionRegEx": "提取版本号(正则表达式)", + "versionExtractionRegEx": "版本号提取规则(正则表达式)", "matchGroupToUse": "引用的捕获组", "highlightTouchTargets": "突出展示不明显的触摸区域", "pickExportDir": "选择导出文件夹", "autoExportOnChanges": "数据变更时自动导出", - "includeSettings": "Include settings", + "includeSettings": "同时导出应用设置", "filterVersionsByRegEx": "筛选版本号(正则表达式)", "trySelectingSuggestedVersionCode": "尝试选择推荐版本的 APK 文件", "dontSortReleasesList": "保持来自 API 的发行顺序", "reverseSort": "反转排序", - "takeFirstLink": "Take first link", - "skipSort": "Skip sorting", + "takeFirstLink": "选取第一个链接", + "skipSort": "不进行排序", "debugMenu": "调试选项", "bgTaskStarted": "后台任务已启动 - 详见日志", "runBgCheckNow": "立即进行后台更新检查", - "versionExtractWholePage": "将提取版本号的正则表达式应用于整个页面", + "versionExtractWholePage": "将版本号提取规则应用于完整页面", "installing": "正在安装", "skipUpdateNotifications": "忽略更新通知", "updatesAvailableNotifChannel": "更新可用", @@ -280,9 +280,14 @@ "completeAppInstallationNotifChannel": "完成应用安装", "checkingForUpdatesNotifChannel": "正在检查更新", "onlyCheckInstalledOrTrackOnlyApps": "只对已安装和“仅追踪”的应用进行更新检查", - "supportFixedAPKURL": "Support fixed APK URLs", - "selectX": "Select {}", - "parallelDownloads": "Allow parallel downloads", + "supportFixedAPKURL": "支持固定的 APK 文件链接", + "selectX": "选择 {}", + "parallelDownloads": "启用并行下载", + "installMethod": "安装方式", + "normal": "常规", + "shizuku": "Shizuku", + "root": "Root", + "shizukuBinderNotFound": "Shizuku 服务未运行", "removeAppQuestion": { "one": "是否删除应用?", "other": "是否删除应用?" From 029b9ef498e1995c934d6c716efd0a185d71e6af Mon Sep 17 00:00:00 2001 From: Imran Remtulla Date: Sat, 30 Dec 2023 10:49:05 -0500 Subject: [PATCH 15/20] Fix intermediate link bug (#1219) --- lib/main.dart | 2 +- lib/providers/source_provider.dart | 2 +- pubspec.yaml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/main.dart b/lib/main.dart index 3466492..84d32d3 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -19,7 +19,7 @@ import 'package:easy_localization/src/easy_localization_controller.dart'; // ignore: implementation_imports import 'package:easy_localization/src/localization.dart'; -const String currentVersion = '0.15.1'; +const String currentVersion = '0.15.2'; const String currentReleaseTag = 'v$currentVersion-beta'; // KEEP THIS IN SYNC WITH GITHUB RELEASES diff --git a/lib/providers/source_provider.dart b/lib/providers/source_provider.dart index 39d87bc..ef0aa8d 100644 --- a/lib/providers/source_provider.dart +++ b/lib/providers/source_provider.dart @@ -160,7 +160,7 @@ appJSONCompatibilityModifiers(Map json) { if ((additionalSettings['intermediateLink']?.length ?? 0) > 0) { additionalSettings['intermediateLink'] = additionalSettings['intermediateLink'].where((e) { - return e['intermediateLinkRegex']?.isNotEmpty == true; + return e['customLinkFilterRegex']?.isNotEmpty == true; }).toList(); } } diff --git a/pubspec.yaml b/pubspec.yaml index 943295b..d7524e7 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.15.1+237 # When changing this, update the tag in main() accordingly +version: 0.15.2+238 # When changing this, update the tag in main() accordingly environment: sdk: '>=3.0.0 <4.0.0' From 436a6310d770ba374599053ba0fa0120612a8a97 Mon Sep 17 00:00:00 2001 From: Imran Remtulla Date: Sun, 31 Dec 2023 15:59:44 -0500 Subject: [PATCH 16/20] Add www subdomain support for various sources (#1222) --- lib/app_sources/apkcombo.dart | 2 +- lib/app_sources/apkpure.dart | 2 +- lib/app_sources/codeberg.dart | 2 +- lib/app_sources/fdroid.dart | 5 +++-- lib/app_sources/github.dart | 2 +- lib/app_sources/gitlab.dart | 2 +- lib/app_sources/huaweiappgallery.dart | 2 +- lib/app_sources/mullvad.dart | 2 +- lib/app_sources/neutroncode.dart | 3 ++- lib/app_sources/sourceforge.dart | 5 +++-- lib/app_sources/sourcehut.dart | 2 +- 11 files changed, 16 insertions(+), 13 deletions(-) diff --git a/lib/app_sources/apkcombo.dart b/lib/app_sources/apkcombo.dart index 73c144b..802373c 100644 --- a/lib/app_sources/apkcombo.dart +++ b/lib/app_sources/apkcombo.dart @@ -10,7 +10,7 @@ class APKCombo extends AppSource { @override String sourceSpecificStandardizeURL(String url) { - RegExp standardUrlRegEx = RegExp('^https?://$host/+[^/]+/+[^/]+'); + RegExp standardUrlRegEx = RegExp('^https?://(www\\.)?$host/+[^/]+/+[^/]+'); var match = standardUrlRegEx.firstMatch(url.toLowerCase()); if (match == null) { throw InvalidURLError(name); diff --git a/lib/app_sources/apkpure.dart b/lib/app_sources/apkpure.dart index a86fa0d..0fe3548 100644 --- a/lib/app_sources/apkpure.dart +++ b/lib/app_sources/apkpure.dart @@ -34,7 +34,7 @@ class APKPure extends AppSource { url = 'https://$host${Uri.parse(url).path}'; } RegExp standardUrlRegExA = - RegExp('^https?://$host/+[^/]+/+[^/]+(/+[^/]+)?'); + RegExp('^https?://(www\\.)?$host/+[^/]+/+[^/]+(/+[^/]+)?'); match = standardUrlRegExA.firstMatch(url.toLowerCase()); if (match == null) { throw InvalidURLError(name); diff --git a/lib/app_sources/codeberg.dart b/lib/app_sources/codeberg.dart index df06b9f..44a7bdf 100644 --- a/lib/app_sources/codeberg.dart +++ b/lib/app_sources/codeberg.dart @@ -16,7 +16,7 @@ class Codeberg extends AppSource { @override String sourceSpecificStandardizeURL(String url) { - RegExp standardUrlRegEx = RegExp('^https?://$host/[^/]+/[^/]+'); + RegExp standardUrlRegEx = RegExp('^https?://(www\\.)?$host/[^/]+/[^/]+'); RegExpMatch? match = standardUrlRegEx.firstMatch(url.toLowerCase()); if (match == null) { throw InvalidURLError(name); diff --git a/lib/app_sources/fdroid.dart b/lib/app_sources/fdroid.dart index 2d7d7a1..fdb7ddc 100644 --- a/lib/app_sources/fdroid.dart +++ b/lib/app_sources/fdroid.dart @@ -38,13 +38,14 @@ class FDroid extends AppSource { @override String sourceSpecificStandardizeURL(String url) { RegExp standardUrlRegExB = - RegExp('^https?://$host/+[^/]+/+packages/+[^/]+'); + RegExp('^https?://(www\\.)?$host/+[^/]+/+packages/+[^/]+'); RegExpMatch? match = standardUrlRegExB.firstMatch(url.toLowerCase()); if (match != null) { url = 'https://${Uri.parse(url.substring(0, match.end)).host}/packages/${Uri.parse(url).pathSegments.last}'; } - RegExp standardUrlRegExA = RegExp('^https?://$host/+packages/+[^/]+'); + RegExp standardUrlRegExA = + RegExp('^https?://(www\\.)?$host/+packages/+[^/]+'); match = standardUrlRegExA.firstMatch(url.toLowerCase()); if (match == null) { throw InvalidURLError(name); diff --git a/lib/app_sources/github.dart b/lib/app_sources/github.dart index 6ed69c8..5bc2801 100644 --- a/lib/app_sources/github.dart +++ b/lib/app_sources/github.dart @@ -149,7 +149,7 @@ class GitHub extends AppSource { @override String sourceSpecificStandardizeURL(String url) { - RegExp standardUrlRegEx = RegExp('^https?://$host/[^/]+/[^/]+'); + RegExp standardUrlRegEx = RegExp('^https?://(www\\.)?$host/[^/]+/[^/]+'); RegExpMatch? match = standardUrlRegEx.firstMatch(url.toLowerCase()); if (match == null) { throw InvalidURLError(name); diff --git a/lib/app_sources/gitlab.dart b/lib/app_sources/gitlab.dart index ae191f1..a0e8f14 100644 --- a/lib/app_sources/gitlab.dart +++ b/lib/app_sources/gitlab.dart @@ -52,7 +52,7 @@ class GitLab extends AppSource { @override String sourceSpecificStandardizeURL(String url) { - RegExp standardUrlRegEx = RegExp('^https?://$host/[^/]+/[^/]+'); + RegExp standardUrlRegEx = RegExp('^https?://(www\\.)?$host/[^/]+/[^/]+'); RegExpMatch? match = standardUrlRegEx.firstMatch(url.toLowerCase()); if (match == null) { throw InvalidURLError(name); diff --git a/lib/app_sources/huaweiappgallery.dart b/lib/app_sources/huaweiappgallery.dart index 76388e9..a073d2a 100644 --- a/lib/app_sources/huaweiappgallery.dart +++ b/lib/app_sources/huaweiappgallery.dart @@ -13,7 +13,7 @@ class HuaweiAppGallery extends AppSource { @override String sourceSpecificStandardizeURL(String url) { - RegExp standardUrlRegEx = RegExp('^https?://$host/app/[^/]+'); + RegExp standardUrlRegEx = RegExp('^https?://(www\\.)?$host/app/[^/]+'); RegExpMatch? match = standardUrlRegEx.firstMatch(url.toLowerCase()); if (match == null) { throw InvalidURLError(name); diff --git a/lib/app_sources/mullvad.dart b/lib/app_sources/mullvad.dart index 4e15d2f..ff9cefb 100644 --- a/lib/app_sources/mullvad.dart +++ b/lib/app_sources/mullvad.dart @@ -11,7 +11,7 @@ class Mullvad extends AppSource { @override String sourceSpecificStandardizeURL(String url) { - RegExp standardUrlRegEx = RegExp('^https?://$host'); + RegExp standardUrlRegEx = RegExp('^https?://(www\\.)?$host'); RegExpMatch? match = standardUrlRegEx.firstMatch(url.toLowerCase()); if (match == null) { throw InvalidURLError(name); diff --git a/lib/app_sources/neutroncode.dart b/lib/app_sources/neutroncode.dart index 4fbec3c..9ce0fdd 100644 --- a/lib/app_sources/neutroncode.dart +++ b/lib/app_sources/neutroncode.dart @@ -10,7 +10,8 @@ class NeutronCode extends AppSource { @override String sourceSpecificStandardizeURL(String url) { - RegExp standardUrlRegEx = RegExp('^https?://$host/downloads/file/[^/]+'); + RegExp standardUrlRegEx = + RegExp('^https?://(www\\.)?$host/downloads/file/[^/]+'); RegExpMatch? match = standardUrlRegEx.firstMatch(url.toLowerCase()); if (match == null) { throw InvalidURLError(name); diff --git a/lib/app_sources/sourceforge.dart b/lib/app_sources/sourceforge.dart index 3253926..885a9cc 100644 --- a/lib/app_sources/sourceforge.dart +++ b/lib/app_sources/sourceforge.dart @@ -10,13 +10,14 @@ class SourceForge extends AppSource { @override String sourceSpecificStandardizeURL(String url) { - RegExp standardUrlRegExB = RegExp('^https?://$host/p/[^/]+'); + RegExp standardUrlRegExB = RegExp('^https?://(www\\.)?$host/p/[^/]+'); RegExpMatch? match = standardUrlRegExB.firstMatch(url.toLowerCase()); if (match != null) { url = 'https://${Uri.parse(url.substring(0, match.end)).host}/projects/${url.substring(Uri.parse(url.substring(0, match.end)).host.length + '/projects/'.length + 1)}'; } - RegExp standardUrlRegExA = RegExp('^https?://$host/projects/[^/]+'); + RegExp standardUrlRegExA = + RegExp('^https?://(www\\.)?$host/projects/[^/]+'); match = standardUrlRegExA.firstMatch(url.toLowerCase()); if (match == null) { throw InvalidURLError(name); diff --git a/lib/app_sources/sourcehut.dart b/lib/app_sources/sourcehut.dart index d74fd7c..05b9959 100644 --- a/lib/app_sources/sourcehut.dart +++ b/lib/app_sources/sourcehut.dart @@ -20,7 +20,7 @@ class SourceHut extends AppSource { @override String sourceSpecificStandardizeURL(String url) { - RegExp standardUrlRegEx = RegExp('^https?://$host/[^/]+/[^/]+'); + RegExp standardUrlRegEx = RegExp('^https?://(www\\.)?$host/[^/]+/[^/]+'); RegExpMatch? match = standardUrlRegEx.firstMatch(url.toLowerCase()); if (match == null) { throw InvalidURLError(name); From 6317f0162a63d6e3b19b8f245425bbfdc1653c92 Mon Sep 17 00:00:00 2001 From: Imran Remtulla Date: Sun, 31 Dec 2023 22:05:50 -0500 Subject: [PATCH 17/20] Disable standard version detection for WhatsApp (should never have been enabled) --- lib/app_sources/whatsapp.dart | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/app_sources/whatsapp.dart b/lib/app_sources/whatsapp.dart index ff295b4..702b52a 100644 --- a/lib/app_sources/whatsapp.dart +++ b/lib/app_sources/whatsapp.dart @@ -6,6 +6,8 @@ import 'package:obtainium/providers/source_provider.dart'; class WhatsApp extends AppSource { WhatsApp() { host = 'whatsapp.com'; + overrideVersionDetectionFormDefault('noVersionDetection', + disableStandard: true, disableRelDate: true); } @override From e2f7d52beec83a6ec84349ec2890f2b276d6494e Mon Sep 17 00:00:00 2001 From: Imran Remtulla Date: Sun, 31 Dec 2023 23:27:23 -0500 Subject: [PATCH 18/20] Increment version --- lib/main.dart | 3 +-- pubspec.yaml | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/lib/main.dart b/lib/main.dart index 84d32d3..71cf818 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -19,7 +19,7 @@ import 'package:easy_localization/src/easy_localization_controller.dart'; // ignore: implementation_imports import 'package:easy_localization/src/localization.dart'; -const String currentVersion = '0.15.2'; +const String currentVersion = '0.15.3'; const String currentReleaseTag = 'v$currentVersion-beta'; // KEEP THIS IN SYNC WITH GITHUB RELEASES @@ -152,7 +152,6 @@ class _ObtainiumState extends State { requiresStorageNotLow: false, requiresDeviceIdle: false, requiredNetworkType: NetworkType.ANY), (String taskId) async { - // We don't want periodic tasks in the foreground - ignore await bgUpdateCheck(taskId, null); BackgroundFetch.finish(taskId); }, (String taskId) async { diff --git a/pubspec.yaml b/pubspec.yaml index d7524e7..da63a01 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.15.2+238 # When changing this, update the tag in main() accordingly +version: 0.15.3+239 # When changing this, update the tag in main() accordingly environment: sdk: '>=3.0.0 <4.0.0' From 5d9645eaffc808b16a3878b1490de65723db6b05 Mon Sep 17 00:00:00 2001 From: Imran Remtulla Date: Sun, 31 Dec 2023 23:41:46 -0500 Subject: [PATCH 19/20] Save Search Preferences (#1226) --- lib/pages/add_app.dart | 5 ++++- lib/pages/import_export.dart | 14 ++++++++++---- lib/providers/settings_provider.dart | 9 +++++++++ 3 files changed, 23 insertions(+), 5 deletions(-) diff --git a/lib/pages/add_app.dart b/lib/pages/add_app.dart index fe141f8..6c4b9a3 100644 --- a/lib/pages/add_app.dart +++ b/lib/pages/add_app.dart @@ -286,10 +286,14 @@ class AddAppPageState extends State { selectedByDefault: true, onlyOneSelectionAllowed: false, titlesAreLinks: false, + deselectThese: settingsProvider.searchDeselected, ); }) ?? []; if (searchSources.isNotEmpty) { + settingsProvider.searchDeselected = sourceStrings.keys + .where((s) => !searchSources.contains(s)) + .toList(); var results = await Future.wait(sourceProvider.sources .where((e) => searchSources.contains(e.name)) .map((e) async { @@ -306,7 +310,6 @@ class AddAppPageState extends State { } })); - // .then((results) async { // Interleave results instead of simple reduce Map> res = {}; var si = 0; diff --git a/lib/pages/import_export.dart b/lib/pages/import_export.dart index 635c720..4672d66 100644 --- a/lib/pages/import_export.dart +++ b/lib/pages/import_export.dart @@ -604,11 +604,13 @@ class SelectionModal extends StatefulWidget { this.selectedByDefault = true, this.onlyOneSelectionAllowed = false, this.titlesAreLinks = true, - this.title}); + this.title, + this.deselectThese = const []}); String? title; Map> entries; bool selectedByDefault; + List deselectThese; bool onlyOneSelectionAllowed; bool titlesAreLinks; @@ -622,9 +624,13 @@ class _SelectionModalState extends State { @override void initState() { super.initState(); - for (var url in widget.entries.entries) { - entrySelections.putIfAbsent(url, - () => widget.selectedByDefault && !widget.onlyOneSelectionAllowed); + for (var entry in widget.entries.entries) { + entrySelections.putIfAbsent( + entry, + () => + widget.selectedByDefault && + !widget.onlyOneSelectionAllowed && + !widget.deselectThese.contains(entry.key)); } if (widget.selectedByDefault && widget.onlyOneSelectionAllowed) { selectOnlyOne(widget.entries.entries.first.key); diff --git a/lib/providers/settings_provider.dart b/lib/providers/settings_provider.dart index 0fb6d2a..2da2366 100644 --- a/lib/providers/settings_provider.dart +++ b/lib/providers/settings_provider.dart @@ -446,4 +446,13 @@ class SettingsProvider with ChangeNotifier { prefs?.setBool('parallelDownloads', val); notifyListeners(); } + + List get searchDeselected { + return prefs?.getStringList('searchDeselected') ?? []; + } + + set searchDeselected(List list) { + prefs?.setStringList('searchDeselected', list); + notifyListeners(); + } } From f9b3169b6a99228fbba7a97743675dd1ad933fbb Mon Sep 17 00:00:00 2001 From: Imran <30463115+ImranR98@users.noreply.github.com> Date: Mon, 1 Jan 2024 07:55:00 -0500 Subject: [PATCH 20/20] Update README.md --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 9f82043..07980e3 100644 --- a/README.md +++ b/README.md @@ -30,6 +30,7 @@ Currently supported App sources: - [Signal](https://signal.org/) - [VLC](https://videolan.org/) - Other - App-Specific: + - [WhatsApp](https://whatsapp.com) - [Telegram App](https://telegram.org) - [Steam Mobile Apps](https://store.steampowered.com/mobile) - [Neutron Code](https://neutroncode.com)