mirror of
				https://github.com/ImranR98/Obtainium.git
				synced 2025-10-31 05:23:28 +01:00 
			
		
		
		
	Compare commits
	
		
			18 Commits
		
	
	
		
			v0.8.21-be
			...
			v0.8.22-be
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|  | ee66c53320 | ||
|  | b7d581f8b0 | ||
|  | ead63ba21d | ||
|  | c69404363f | ||
|  | 99d0bd2461 | ||
|  | 54efda3eea | ||
|  | d76d68329c | ||
|  | b151eb27e1 | ||
|  | 6a21045e5b | ||
|  | 6aedd9ce37 | ||
|  | f319639a99 | ||
|  | 92e6798809 | ||
|  | 9a129d41df | ||
|  | 0c2654a226 | ||
|  | afc8e41171 | ||
|  | 1fe9e4f91e | ||
|  | dbd6dec0a6 | ||
|  | 63034dd3f9 | 
| @@ -23,7 +23,7 @@ | |||||||
|     "githubPATLinkText": "Über GitHub PATs", |     "githubPATLinkText": "Über GitHub PATs", | ||||||
|     "includePrereleases": "Vorabversionen einbeziehen", |     "includePrereleases": "Vorabversionen einbeziehen", | ||||||
|     "fallbackToOlderReleases": "Fallback auf ältere Versionen", |     "fallbackToOlderReleases": "Fallback auf ältere Versionen", | ||||||
|     "filterReleaseTitlesByRegEx": "Release-Titel nach regulärem Ausdruck filtern", |     "filterReleaseTitlesByRegEx": "Release-Titel nach regulärem Ausdruck\nfiltern", | ||||||
|     "invalidRegEx": "Ungültiger regulärer Ausdruck", |     "invalidRegEx": "Ungültiger regulärer Ausdruck", | ||||||
|     "noDescription": "Keine Beschreibung", |     "noDescription": "Keine Beschreibung", | ||||||
|     "cancel": "Abbrechen", |     "cancel": "Abbrechen", | ||||||
| @@ -43,7 +43,7 @@ | |||||||
|     "cancelled": "Abgebrochen", |     "cancelled": "Abgebrochen", | ||||||
|     "appAlreadyAdded": "App bereits hinzugefügt", |     "appAlreadyAdded": "App bereits hinzugefügt", | ||||||
|     "alreadyUpToDateQuestion": "App bereits auf dem neuesten Stand?", |     "alreadyUpToDateQuestion": "App bereits auf dem neuesten Stand?", | ||||||
|     "addApp": "App hinzugefügt", |     "addApp": "App hinzufügen", | ||||||
|     "appSourceURL": "Quell-URL der App", |     "appSourceURL": "Quell-URL der App", | ||||||
|     "error": "Fehler", |     "error": "Fehler", | ||||||
|     "add": "Hinzufügen", |     "add": "Hinzufügen", | ||||||
| @@ -92,7 +92,7 @@ | |||||||
|     "filterActive": "Filter *", |     "filterActive": "Filter *", | ||||||
|     "filterApps": "Apps filtern", |     "filterApps": "Apps filtern", | ||||||
|     "appName": "App Name", |     "appName": "App Name", | ||||||
|     "author": "Ersteller", |     "author": "Autor", | ||||||
|     "upToDateApps": "Apps mit aktueller Version", |     "upToDateApps": "Apps mit aktueller Version", | ||||||
|     "nonInstalledApps": "Nicht installierte Apps", |     "nonInstalledApps": "Nicht installierte Apps", | ||||||
|     "importExport": "Import/Export", |     "importExport": "Import/Export", | ||||||
| @@ -134,7 +134,7 @@ | |||||||
|     "neverManualOnly": "Nie - nur manuell", |     "neverManualOnly": "Nie - nur manuell", | ||||||
|     "appearance": "Aussehen", |     "appearance": "Aussehen", | ||||||
|     "showWebInAppView": "Quellwebseite in der App-Ansicht anzeigen", |     "showWebInAppView": "Quellwebseite in der App-Ansicht anzeigen", | ||||||
|     "pinUpdates": "Aktualisierungen in der App-Ansicht oben anheften", |     "pinUpdates": "Apps mit Aktualisierungen oben anheften", | ||||||
|     "updates": "Aktualisiert", |     "updates": "Aktualisiert", | ||||||
|     "sourceSpecific": "Quellenspezifisch", |     "sourceSpecific": "Quellenspezifisch", | ||||||
|     "appSource": "App-Quelle", |     "appSource": "App-Quelle", | ||||||
| @@ -188,6 +188,17 @@ | |||||||
|     "steam": "Steam", |     "steam": "Steam", | ||||||
|     "steamMobile": "Steam Mobile", |     "steamMobile": "Steam Mobile", | ||||||
|     "steamChat": "Steam Chat", |     "steamChat": "Steam Chat", | ||||||
|  |     "install": "Install", | ||||||
|  |     "markInstalled": "Mark Installed", | ||||||
|  |     "update": "Update", | ||||||
|  |     "markUpdated": "Mark Updated", | ||||||
|  |     "additionalOptions": "Additional Options", | ||||||
|  |     "disableVersionDetection": "Disable Version Detection", | ||||||
|  |     "noVersionDetectionExplanation": "This option should only be used for Apps where version detection does not work correctly.", | ||||||
|  |     "downloadingX": "Downloading {}", | ||||||
|  |     "downloadNotifDescription": "Notifies the user of the progress in downloading an App", | ||||||
|  |     "noAPKFound": "No APK found", | ||||||
|  |     "noVersionDetection": "No version detection", | ||||||
|     "tooManyRequestsTryAgainInMinutes": { |     "tooManyRequestsTryAgainInMinutes": { | ||||||
|         "one": "Zu viele Anfragen (Rate begrenzt) - versuchen Sie es in {} Minute erneut", |         "one": "Zu viele Anfragen (Rate begrenzt) - versuchen Sie es in {} Minute erneut", | ||||||
|         "other": "Zu viele Anfragen (Rate begrenzt) - versuchen Sie es in {} Minuten erneut" |         "other": "Zu viele Anfragen (Rate begrenzt) - versuchen Sie es in {} Minuten erneut" | ||||||
|   | |||||||
| @@ -37,7 +37,7 @@ | |||||||
|     "xIsTrackOnly": "{} is Track-Only", |     "xIsTrackOnly": "{} is Track-Only", | ||||||
|     "source": "Source", |     "source": "Source", | ||||||
|     "app": "App", |     "app": "App", | ||||||
|     "appsFromSourceAreTrackOnly": "Apps from this source are 'Track-Only'.' ", |     "appsFromSourceAreTrackOnly": "Apps from this source are 'Track-Only'.", | ||||||
|     "youPickedTrackOnly": "You have selected the 'Track-Only' option.", |     "youPickedTrackOnly": "You have selected the 'Track-Only' option.", | ||||||
|     "trackOnlyAppDescription": "The App will be tracked for updates, but Obtainium will not be able to download or install it.", |     "trackOnlyAppDescription": "The App will be tracked for updates, but Obtainium will not be able to download or install it.", | ||||||
|     "cancelled": "Cancelled", |     "cancelled": "Cancelled", | ||||||
| @@ -188,6 +188,17 @@ | |||||||
|     "steam": "Steam", |     "steam": "Steam", | ||||||
|     "steamMobile": "Steam Mobile", |     "steamMobile": "Steam Mobile", | ||||||
|     "steamChat": "Steam Chat", |     "steamChat": "Steam Chat", | ||||||
|  |     "install": "Install", | ||||||
|  |     "markInstalled": "Mark Installed", | ||||||
|  |     "update": "Update", | ||||||
|  |     "markUpdated": "Mark Updated", | ||||||
|  |     "additionalOptions": "Additional Options", | ||||||
|  |     "disableVersionDetection": "Disable Version Detection", | ||||||
|  |     "noVersionDetectionExplanation": "This option should only be used for Apps where version detection does not work correctly.", | ||||||
|  |     "downloadingX": "Downloading {}", | ||||||
|  |     "downloadNotifDescription": "Notifies the user of the progress in downloading an App", | ||||||
|  |     "noAPKFound": "No APK found", | ||||||
|  |     "noVersionDetection": "No version detection", | ||||||
|     "tooManyRequestsTryAgainInMinutes": { |     "tooManyRequestsTryAgainInMinutes": { | ||||||
|         "one": "Too many requests (rate limited) - try again in {} minute", |         "one": "Too many requests (rate limited) - try again in {} minute", | ||||||
|         "other": "Too many requests (rate limited) - try again in {} minutes" |         "other": "Too many requests (rate limited) - try again in {} minutes" | ||||||
|   | |||||||
| @@ -37,7 +37,7 @@ | |||||||
|     "xIsTrackOnly": "{} csak nyomon követhető", |     "xIsTrackOnly": "{} csak nyomon követhető", | ||||||
|     "source": "Forrás", |     "source": "Forrás", | ||||||
|     "app": "App", |     "app": "App", | ||||||
|     "appsFromSourceAreTrackOnly": "Az ebből a forrásból származó alkalmazások 'Csak nyomon követhetőek'.' ", |     "appsFromSourceAreTrackOnly": "Az ebből a forrásból származó alkalmazások 'Csak nyomon követhetőek'.", | ||||||
|     "youPickedTrackOnly": "A 'Csak követés' opciót választotta.", |     "youPickedTrackOnly": "A 'Csak követés' opciót választotta.", | ||||||
|     "trackOnlyAppDescription": "Az alkalmazás frissítéseit nyomon követi, de az Obtainium nem tudja letölteni vagy telepíteni.", |     "trackOnlyAppDescription": "Az alkalmazás frissítéseit nyomon követi, de az Obtainium nem tudja letölteni vagy telepíteni.", | ||||||
|     "cancelled": "Törölve", |     "cancelled": "Törölve", | ||||||
| @@ -188,6 +188,17 @@ | |||||||
|     "steam": "Steam", |     "steam": "Steam", | ||||||
|     "steamMobile": "Steam Mobile", |     "steamMobile": "Steam Mobile", | ||||||
|     "steamChat": "Steam Chat", |     "steamChat": "Steam Chat", | ||||||
|  |     "install": "Install", | ||||||
|  |     "markInstalled": "Mark Installed", | ||||||
|  |     "update": "Update", | ||||||
|  |     "markUpdated": "Mark Updated", | ||||||
|  |     "additionalOptions": "Additional Options", | ||||||
|  |     "disableVersionDetection": "Disable Version Detection", | ||||||
|  |     "noVersionDetectionExplanation": "This option should only be used for Apps where version detection does not work correctly.", | ||||||
|  |     "downloadingX": "Downloading {}", | ||||||
|  |     "downloadNotifDescription": "Notifies the user of the progress in downloading an App", | ||||||
|  |     "noAPKFound": "No APK found", | ||||||
|  |     "noVersionDetection": "No version detection", | ||||||
|     "tooManyRequestsTryAgainInMinutes": { |     "tooManyRequestsTryAgainInMinutes": { | ||||||
|         "one": "Túl sok kérés (korlátozott arány) – próbálja újra {} perc múlva", |         "one": "Túl sok kérés (korlátozott arány) – próbálja újra {} perc múlva", | ||||||
|         "other": "Túl sok kérés (korlátozott arány) – próbálja újra {} perc múlva" |         "other": "Túl sok kérés (korlátozott arány) – próbálja újra {} perc múlva" | ||||||
|   | |||||||
| @@ -22,13 +22,13 @@ | |||||||
|     "githubPATFormat": "username:token", |     "githubPATFormat": "username:token", | ||||||
|     "githubPATLinkText": "Informazioni su GitHub PAT", |     "githubPATLinkText": "Informazioni su GitHub PAT", | ||||||
|     "includePrereleases": "Includi prerelease", |     "includePrereleases": "Includi prerelease", | ||||||
|     "fallbackToOlderReleases": "Ripiega su release datate", |     "fallbackToOlderReleases": "Ripiega su release precedenti", | ||||||
|     "filterReleaseTitlesByRegEx": "Filtra le release con le espressioni regolari", |     "filterReleaseTitlesByRegEx": "Filtra release con espressioni regolari", | ||||||
|     "invalidRegEx": "Espressione regolare invalida", |     "invalidRegEx": "Espressione regolare non valida", | ||||||
|     "noDescription": "Descrizione assente", |     "noDescription": "Descrizione assente", | ||||||
|     "cancel": "Annulla", |     "cancel": "Annulla", | ||||||
|     "continue": "Continua", |     "continue": "Continua", | ||||||
|     "requiredInBrackets": "(Richiesto)", |     "requiredInBrackets": "(richiesto)", | ||||||
|     "dropdownNoOptsError": "ERRORE: LA TENDINA DEVE AVERE ALMENO UN'OPZIONE", |     "dropdownNoOptsError": "ERRORE: LA TENDINA DEVE AVERE ALMENO UN'OPZIONE", | ||||||
|     "colour": "Colore", |     "colour": "Colore", | ||||||
|     "githubStarredRepos": "i repository stellati da GitHub", |     "githubStarredRepos": "i repository stellati da GitHub", | ||||||
| @@ -47,7 +47,7 @@ | |||||||
|     "appSourceURL": "URL della fonte dell'App", |     "appSourceURL": "URL della fonte dell'App", | ||||||
|     "error": "Errore", |     "error": "Errore", | ||||||
|     "add": "Aggiungi", |     "add": "Aggiungi", | ||||||
|     "searchSomeSourcesLabel": "Cerca (disponibile solo per alcune fonti)", |     "searchSomeSourcesLabel": "Cerca (solo per alcune fonti)", | ||||||
|     "search": "Cerca", |     "search": "Cerca", | ||||||
|     "additionalOptsFor": "Opzioni aggiuntive per {}", |     "additionalOptsFor": "Opzioni aggiuntive per {}", | ||||||
|     "supportedSourcesBelow": "Fonti supportate:", |     "supportedSourcesBelow": "Fonti supportate:", | ||||||
| @@ -86,8 +86,8 @@ | |||||||
|     "shareSelectedAppURLs": "Condividi gli URL delle App selezionate", |     "shareSelectedAppURLs": "Condividi gli URL delle App selezionate", | ||||||
|     "resetInstallStatus": "Ripristina lo stato d'installazione", |     "resetInstallStatus": "Ripristina lo stato d'installazione", | ||||||
|     "more": "Di più", |     "more": "Di più", | ||||||
|     "removeOutdatedFilter": "Rimuovi il filtro per le App datate", |     "removeOutdatedFilter": "Rimuovi il filtro per le App non aggiornate", | ||||||
|     "showOutdatedOnly": "Mostra solo le App datate", |     "showOutdatedOnly": "Mostra solo le App non aggiornate", | ||||||
|     "filter": "Filtri", |     "filter": "Filtri", | ||||||
|     "filterActive": "Filtri *", |     "filterActive": "Filtri *", | ||||||
|     "filterApps": "Filtra App", |     "filterApps": "Filtra App", | ||||||
| @@ -106,7 +106,7 @@ | |||||||
|     "searchQuery": "Stringa di ricerca", |     "searchQuery": "Stringa di ricerca", | ||||||
|     "appURLList": "Lista di URL delle App", |     "appURLList": "Lista di URL delle App", | ||||||
|     "line": "Linea", |     "line": "Linea", | ||||||
|     "searchX": "Cerca {}", |     "searchX": "Cerca su {}", | ||||||
|     "noResults": "Nessun risultato trovato", |     "noResults": "Nessun risultato trovato", | ||||||
|     "importX": "Importa {}", |     "importX": "Importa {}", | ||||||
|     "importedAppsIdDisclaimer": "Le App importate potrebbero essere visualizzate erroneamente come \"Non installate\".\nPer risolvere il problema, reinstallale con Obtainium.\nQuesto non dovrebbe influire sui dati delle App.\n\nRiguarda solo l'URL e i metodi di importazione di terze parti.", |     "importedAppsIdDisclaimer": "Le App importate potrebbero essere visualizzate erroneamente come \"Non installate\".\nPer risolvere il problema, reinstallale con Obtainium.\nQuesto non dovrebbe influire sui dati delle App.\n\nRiguarda solo l'URL e i metodi di importazione di terze parti.", | ||||||
| @@ -120,7 +120,7 @@ | |||||||
|     "theme": "Tema", |     "theme": "Tema", | ||||||
|     "dark": "Scuro", |     "dark": "Scuro", | ||||||
|     "light": "Chiaro", |     "light": "Chiaro", | ||||||
|     "followSystem": "Segui il sistema", |     "followSystem": "Segui sistema", | ||||||
|     "obtainium": "Obtainium", |     "obtainium": "Obtainium", | ||||||
|     "materialYou": "Material You", |     "materialYou": "Material You", | ||||||
|     "appSortBy": "App ordinate per", |     "appSortBy": "App ordinate per", | ||||||
| @@ -133,10 +133,10 @@ | |||||||
|     "bgUpdateCheckInterval": "Intervallo di controllo degli aggiornamenti in background", |     "bgUpdateCheckInterval": "Intervallo di controllo degli aggiornamenti in background", | ||||||
|     "neverManualOnly": "Mai - Solo manuale", |     "neverManualOnly": "Mai - Solo manuale", | ||||||
|     "appearance": "Aspetto", |     "appearance": "Aspetto", | ||||||
|     "showWebInAppView": "Mostra la pagina web dell'App se selezionata", |     "showWebInAppView": "Mostra pagina web dell'App se selezionata", | ||||||
|     "pinUpdates": "Fissa in alto gli aggiornamenti disponibili nella pagina delle App", |     "pinUpdates": "Fissa in alto gli aggiornamenti disponibili", | ||||||
|     "updates": "Aggiornato", |     "updates": "Aggiornato", | ||||||
|     "sourceSpecific": "Specifico per la fonte", |     "sourceSpecific": "Specifiche per la fonte", | ||||||
|     "appSource": "Sorgente dell'App", |     "appSource": "Sorgente dell'App", | ||||||
|     "noLogs": "Nessun log", |     "noLogs": "Nessun log", | ||||||
|     "appLogs": "Log dell'App", |     "appLogs": "Log dell'App", | ||||||
| @@ -184,10 +184,21 @@ | |||||||
|     "appIdOrName": "ID o nome dell'App", |     "appIdOrName": "ID o nome dell'App", | ||||||
|     "appWithIdOrNameNotFound": "Non è stata trovata alcuna App con quell'ID o nome", |     "appWithIdOrNameNotFound": "Non è stata trovata alcuna App con quell'ID o nome", | ||||||
|     "reposHaveMultipleApps": "I repository possono contenere più App", |     "reposHaveMultipleApps": "I repository possono contenere più App", | ||||||
|     "fdroidThirdPartyRepo": "Repository di terze parti di F-Droid", |     "fdroidThirdPartyRepo": "Repository F-Droid di terze parti", | ||||||
|     "steam": "Steam", |     "steam": "Steam", | ||||||
|     "steamMobile": "Steam Mobile", |     "steamMobile": "Steam Mobile", | ||||||
|     "steamChat": "Steam Chat", |     "steamChat": "Steam Chat", | ||||||
|  |     "install": "Install", | ||||||
|  |     "markInstalled": "Mark Installed", | ||||||
|  |     "update": "Update", | ||||||
|  |     "markUpdated": "Mark Updated", | ||||||
|  |     "additionalOptions": "Additional Options", | ||||||
|  |     "disableVersionDetection": "Disable Version Detection", | ||||||
|  |     "noVersionDetectionExplanation": "This option should only be used for Apps where version detection does not work correctly.", | ||||||
|  |     "downloadingX": "Downloading {}", | ||||||
|  |     "downloadNotifDescription": "Notifies the user of the progress in downloading an App", | ||||||
|  |     "noAPKFound": "No APK found", | ||||||
|  |     "noVersionDetection": "No version detection", | ||||||
|     "tooManyRequestsTryAgainInMinutes": { |     "tooManyRequestsTryAgainInMinutes": { | ||||||
|         "one": "Troppe richieste (traffico limitato) - riprova tra {} minuto", |         "one": "Troppe richieste (traffico limitato) - riprova tra {} minuto", | ||||||
|         "other": "Troppe richieste (traffico limitato) - riprova tra {} minuti" |         "other": "Troppe richieste (traffico limitato) - riprova tra {} minuti" | ||||||
|   | |||||||
| @@ -188,6 +188,17 @@ | |||||||
|     "steam": "Steam", |     "steam": "Steam", | ||||||
|     "steamMobile": "Steam Mobile", |     "steamMobile": "Steam Mobile", | ||||||
|     "steamChat": "Steam Chat", |     "steamChat": "Steam Chat", | ||||||
|  |     "install": "Install", | ||||||
|  |     "markInstalled": "Mark Installed", | ||||||
|  |     "update": "Update", | ||||||
|  |     "markUpdated": "Mark Updated", | ||||||
|  |     "additionalOptions": "Additional Options", | ||||||
|  |     "disableVersionDetection": "Disable Version Detection", | ||||||
|  |     "noVersionDetectionExplanation": "This option should only be used for Apps where version detection does not work correctly.", | ||||||
|  |     "downloadingX": "Downloading {}", | ||||||
|  |     "downloadNotifDescription": "Notifies the user of the progress in downloading an App", | ||||||
|  |     "noAPKFound": "No APK found", | ||||||
|  |     "noVersionDetection": "No version detection", | ||||||
|     "tooManyRequestsTryAgainInMinutes": { |     "tooManyRequestsTryAgainInMinutes": { | ||||||
|         "one": "リクエストが多すぎます(レート制限)- {}分後に再試行してください", |         "one": "リクエストが多すぎます(レート制限)- {}分後に再試行してください", | ||||||
|         "other": "リクエストが多すぎます(レート制限)- {}分後に再試行してください" |         "other": "リクエストが多すぎます(レート制限)- {}分後に再試行してください" | ||||||
|   | |||||||
| @@ -188,6 +188,17 @@ | |||||||
|     "steam": "Steam", |     "steam": "Steam", | ||||||
|     "steamMobile": "Steam Mobile", |     "steamMobile": "Steam Mobile", | ||||||
|     "steamChat": "Steam Chat", |     "steamChat": "Steam Chat", | ||||||
|  |     "install": "Install", | ||||||
|  |     "markInstalled": "Mark Installed", | ||||||
|  |     "update": "Update", | ||||||
|  |     "markUpdated": "Mark Updated", | ||||||
|  |     "additionalOptions": "Additional Options", | ||||||
|  |     "disableVersionDetection": "Disable Version Detection", | ||||||
|  |     "noVersionDetectionExplanation": "This option should only be used for Apps where version detection does not work correctly.", | ||||||
|  |     "downloadingX": "Downloading {}", | ||||||
|  |     "downloadNotifDescription": "Notifies the user of the progress in downloading an App", | ||||||
|  |     "noAPKFound": "No APK found", | ||||||
|  |     "noVersionDetection": "No version detection", | ||||||
|     "tooManyRequestsTryAgainInMinutes": { |     "tooManyRequestsTryAgainInMinutes": { | ||||||
|         "one": "请求过多 (API 限制) - 在 {} 分钟后重试", |         "one": "请求过多 (API 限制) - 在 {} 分钟后重试", | ||||||
|         "other": "请求过多 (API 限制) - 在 {} 分钟后重试" |         "other": "请求过多 (API 限制) - 在 {} 分钟后重试" | ||||||
|   | |||||||
| @@ -25,8 +25,9 @@ class APKMirror extends AppSource { | |||||||
|  |  | ||||||
|   @override |   @override | ||||||
|   Future<APKDetails> getLatestAPKDetails( |   Future<APKDetails> getLatestAPKDetails( | ||||||
|       String standardUrl, List<String> additionalData, |     String standardUrl, | ||||||
|       {bool trackOnly = false}) async { |     Map<String, String> additionalSettings, | ||||||
|  |   ) async { | ||||||
|     Response res = await get(Uri.parse('$standardUrl/feed')); |     Response res = await get(Uri.parse('$standardUrl/feed')); | ||||||
|     if (res.statusCode == 200) { |     if (res.statusCode == 200) { | ||||||
|       String? titleString = parse(res.body) |       String? titleString = parse(res.body) | ||||||
|   | |||||||
| @@ -32,7 +32,7 @@ class FDroid extends AppSource { | |||||||
|  |  | ||||||
|   @override |   @override | ||||||
|   String? tryInferringAppId(String standardUrl, |   String? tryInferringAppId(String standardUrl, | ||||||
|       {List<String> additionalData = const []}) { |       {Map<String, String> additionalSettings = const {}}) { | ||||||
|     return Uri.parse(standardUrl).pathSegments.last; |     return Uri.parse(standardUrl).pathSegments.last; | ||||||
|   } |   } | ||||||
|  |  | ||||||
| @@ -60,8 +60,9 @@ class FDroid extends AppSource { | |||||||
|  |  | ||||||
|   @override |   @override | ||||||
|   Future<APKDetails> getLatestAPKDetails( |   Future<APKDetails> getLatestAPKDetails( | ||||||
|       String standardUrl, List<String> additionalData, |     String standardUrl, | ||||||
|       {bool trackOnly = false}) async { |     Map<String, String> additionalSettings, | ||||||
|  |   ) async { | ||||||
|     String? appId = tryInferringAppId(standardUrl); |     String? appId = tryInferringAppId(standardUrl); | ||||||
|     return getAPKUrlsFromFDroidPackagesAPIResponse( |     return getAPKUrlsFromFDroidPackagesAPIResponse( | ||||||
|         await get(Uri.parse('https://f-droid.org/api/v1/packages/$appId')), |         await get(Uri.parse('https://f-droid.org/api/v1/packages/$appId')), | ||||||
|   | |||||||
| @@ -9,13 +9,12 @@ class FDroidRepo extends AppSource { | |||||||
|   FDroidRepo() { |   FDroidRepo() { | ||||||
|     name = tr('fdroidThirdPartyRepo'); |     name = tr('fdroidThirdPartyRepo'); | ||||||
|  |  | ||||||
|     additionalSourceAppSpecificFormItems = [ |     additionalSourceAppSpecificSettingFormItems = [ | ||||||
|       [ |       [ | ||||||
|         GeneratedFormItem( |         GeneratedFormItem('appIdOrName', | ||||||
|             label: tr('appIdOrName'), |             label: tr('appIdOrName'), | ||||||
|             hint: tr('reposHaveMultipleApps'), |             hint: tr('reposHaveMultipleApps'), | ||||||
|             required: true, |             required: true) | ||||||
|             key: 'appIdOrName') |  | ||||||
|       ] |       ] | ||||||
|     ]; |     ]; | ||||||
|   } |   } | ||||||
| @@ -33,13 +32,10 @@ class FDroidRepo extends AppSource { | |||||||
|  |  | ||||||
|   @override |   @override | ||||||
|   Future<APKDetails> getLatestAPKDetails( |   Future<APKDetails> getLatestAPKDetails( | ||||||
|       String standardUrl, List<String> additionalData, |     String standardUrl, | ||||||
|       {bool trackOnly = false}) async { |     Map<String, String> additionalSettings, | ||||||
|     String? appIdOrName = findGeneratedFormValueByKey( |   ) async { | ||||||
|         additionalSourceAppSpecificFormItems |     String? appIdOrName = additionalSettings['appIdOrName']; | ||||||
|             .reduce((value, element) => [...value, ...element]), |  | ||||||
|         additionalData, |  | ||||||
|         'appIdOrName'); |  | ||||||
|     if (appIdOrName == null) { |     if (appIdOrName == null) { | ||||||
|       throw NoReleasesError(); |       throw NoReleasesError(); | ||||||
|     } |     } | ||||||
|   | |||||||
| @@ -12,12 +12,9 @@ class GitHub extends AppSource { | |||||||
|   GitHub() { |   GitHub() { | ||||||
|     host = 'github.com'; |     host = 'github.com'; | ||||||
|  |  | ||||||
|     additionalSourceAppSpecificDefaults = ['true', 'true', '']; |  | ||||||
|  |  | ||||||
|     additionalSourceSpecificSettingFormItems = [ |     additionalSourceSpecificSettingFormItems = [ | ||||||
|       GeneratedFormItem( |       GeneratedFormItem('github-creds', | ||||||
|           label: tr('githubPATLabel'), |           label: tr('githubPATLabel'), | ||||||
|           id: 'github-creds', |  | ||||||
|           required: false, |           required: false, | ||||||
|           additionalValidators: [ |           additionalValidators: [ | ||||||
|             (value) { |             (value) { | ||||||
| @@ -52,17 +49,21 @@ class GitHub extends AppSource { | |||||||
|           ]) |           ]) | ||||||
|     ]; |     ]; | ||||||
|  |  | ||||||
|     additionalSourceAppSpecificFormItems = [ |     additionalSourceAppSpecificSettingFormItems = [ | ||||||
|       [ |       [ | ||||||
|         GeneratedFormItem( |         GeneratedFormItem('includePrereleases', | ||||||
|             label: tr('includePrereleases'), type: FormItemType.bool) |             label: tr('includePrereleases'), | ||||||
|  |             type: FormItemType.bool, | ||||||
|  |             defaultValue: '') | ||||||
|       ], |       ], | ||||||
|       [ |       [ | ||||||
|         GeneratedFormItem( |         GeneratedFormItem('fallbackToOlderReleases', | ||||||
|             label: tr('fallbackToOlderReleases'), type: FormItemType.bool) |             label: tr('fallbackToOlderReleases'), | ||||||
|  |             type: FormItemType.bool, | ||||||
|  |             defaultValue: 'true') | ||||||
|       ], |       ], | ||||||
|       [ |       [ | ||||||
|         GeneratedFormItem( |         GeneratedFormItem('filterReleaseTitlesByRegEx', | ||||||
|             label: tr('filterReleaseTitlesByRegEx'), |             label: tr('filterReleaseTitlesByRegEx'), | ||||||
|             type: FormItemType.string, |             type: FormItemType.string, | ||||||
|             required: false, |             required: false, | ||||||
| @@ -99,7 +100,7 @@ class GitHub extends AppSource { | |||||||
|     SettingsProvider settingsProvider = SettingsProvider(); |     SettingsProvider settingsProvider = SettingsProvider(); | ||||||
|     await settingsProvider.initializeSettings(); |     await settingsProvider.initializeSettings(); | ||||||
|     String? creds = settingsProvider |     String? creds = settingsProvider | ||||||
|         .getSettingString(additionalSourceSpecificSettingFormItems[0].id); |         .getSettingString(additionalSourceSpecificSettingFormItems[0].key); | ||||||
|     return creds != null && creds.isNotEmpty ? '$creds@' : ''; |     return creds != null && creds.isNotEmpty ? '$creds@' : ''; | ||||||
|   } |   } | ||||||
|  |  | ||||||
| @@ -109,14 +110,15 @@ class GitHub extends AppSource { | |||||||
|  |  | ||||||
|   @override |   @override | ||||||
|   Future<APKDetails> getLatestAPKDetails( |   Future<APKDetails> getLatestAPKDetails( | ||||||
|       String standardUrl, List<String> additionalData, |     String standardUrl, | ||||||
|       {bool trackOnly = false}) async { |     Map<String, String> additionalSettings, | ||||||
|     var includePrereleases = |   ) async { | ||||||
|         additionalData.isNotEmpty && additionalData[0] == 'true'; |     var includePrereleases = additionalSettings['includePrereleases'] == 'true'; | ||||||
|     var fallbackToOlderReleases = |     var fallbackToOlderReleases = | ||||||
|         additionalData.length >= 2 && additionalData[1] == 'true'; |         additionalSettings['fallbackToOlderReleases'] == 'true'; | ||||||
|     var regexFilter = additionalData.length >= 3 && additionalData[2].isNotEmpty |     var regexFilter = | ||||||
|         ? additionalData[2] |         additionalSettings['filterReleaseTitlesByRegEx']?.isNotEmpty == true | ||||||
|  |             ? additionalSettings['filterReleaseTitlesByRegEx'] | ||||||
|             : null; |             : null; | ||||||
|     Response res = await get(Uri.parse( |     Response res = await get(Uri.parse( | ||||||
|         'https://${await getCredentialPrefixIfAny()}api.$host/repos${standardUrl.substring('https://$host'.length)}/releases')); |         'https://${await getCredentialPrefixIfAny()}api.$host/repos${standardUrl.substring('https://$host'.length)}/releases')); | ||||||
| @@ -148,7 +150,7 @@ class GitHub extends AppSource { | |||||||
|           continue; |           continue; | ||||||
|         } |         } | ||||||
|         var apkUrls = getReleaseAPKUrls(releases[i]); |         var apkUrls = getReleaseAPKUrls(releases[i]); | ||||||
|         if (apkUrls.isEmpty && !trackOnly) { |         if (apkUrls.isEmpty && additionalSettings['trackOnly'] != 'true') { | ||||||
|           continue; |           continue; | ||||||
|         } |         } | ||||||
|         targetRelease = releases[i]; |         targetRelease = releases[i]; | ||||||
|   | |||||||
| @@ -25,8 +25,9 @@ class GitLab extends AppSource { | |||||||
|  |  | ||||||
|   @override |   @override | ||||||
|   Future<APKDetails> getLatestAPKDetails( |   Future<APKDetails> getLatestAPKDetails( | ||||||
|       String standardUrl, List<String> additionalData, |     String standardUrl, | ||||||
|       {bool trackOnly = false}) async { |     Map<String, String> additionalSettings, | ||||||
|  |   ) async { | ||||||
|     Response res = await get(Uri.parse('$standardUrl/-/tags?format=atom')); |     Response res = await get(Uri.parse('$standardUrl/-/tags?format=atom')); | ||||||
|     if (res.statusCode == 200) { |     if (res.statusCode == 200) { | ||||||
|       var standardUri = Uri.parse(standardUrl); |       var standardUri = Uri.parse(standardUrl); | ||||||
|   | |||||||
| @@ -23,14 +23,15 @@ class IzzyOnDroid extends AppSource { | |||||||
|  |  | ||||||
|   @override |   @override | ||||||
|   String? tryInferringAppId(String standardUrl, |   String? tryInferringAppId(String standardUrl, | ||||||
|       {List<String> additionalData = const []}) { |       {Map<String, String> additionalSettings = const {}}) { | ||||||
|     return FDroid().tryInferringAppId(standardUrl); |     return FDroid().tryInferringAppId(standardUrl); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   @override |   @override | ||||||
|   Future<APKDetails> getLatestAPKDetails( |   Future<APKDetails> getLatestAPKDetails( | ||||||
|       String standardUrl, List<String> additionalData, |     String standardUrl, | ||||||
|       {bool trackOnly = false}) async { |     Map<String, String> additionalSettings, | ||||||
|  |   ) async { | ||||||
|     String? appId = tryInferringAppId(standardUrl); |     String? appId = tryInferringAppId(standardUrl); | ||||||
|     return FDroid().getAPKUrlsFromFDroidPackagesAPIResponse( |     return FDroid().getAPKUrlsFromFDroidPackagesAPIResponse( | ||||||
|         await get( |         await get( | ||||||
|   | |||||||
| @@ -24,8 +24,9 @@ class Mullvad extends AppSource { | |||||||
|  |  | ||||||
|   @override |   @override | ||||||
|   Future<APKDetails> getLatestAPKDetails( |   Future<APKDetails> getLatestAPKDetails( | ||||||
|       String standardUrl, List<String> additionalData, |     String standardUrl, | ||||||
|       {bool trackOnly = false}) async { |     Map<String, String> additionalSettings, | ||||||
|  |   ) async { | ||||||
|     Response res = await get(Uri.parse('$standardUrl/en/download/android')); |     Response res = await get(Uri.parse('$standardUrl/en/download/android')); | ||||||
|     if (res.statusCode == 200) { |     if (res.statusCode == 200) { | ||||||
|       var version = parse(res.body) |       var version = parse(res.body) | ||||||
|   | |||||||
| @@ -18,8 +18,9 @@ class Signal extends AppSource { | |||||||
|  |  | ||||||
|   @override |   @override | ||||||
|   Future<APKDetails> getLatestAPKDetails( |   Future<APKDetails> getLatestAPKDetails( | ||||||
|       String standardUrl, List<String> additionalData, |     String standardUrl, | ||||||
|       {bool trackOnly = false}) async { |     Map<String, String> additionalSettings, | ||||||
|  |   ) async { | ||||||
|     Response res = |     Response res = | ||||||
|         await get(Uri.parse('https://updates.$host/android/latest.json')); |         await get(Uri.parse('https://updates.$host/android/latest.json')); | ||||||
|     if (res.statusCode == 200) { |     if (res.statusCode == 200) { | ||||||
|   | |||||||
| @@ -23,8 +23,9 @@ class SourceForge extends AppSource { | |||||||
|  |  | ||||||
|   @override |   @override | ||||||
|   Future<APKDetails> getLatestAPKDetails( |   Future<APKDetails> getLatestAPKDetails( | ||||||
|       String standardUrl, List<String> additionalData, |     String standardUrl, | ||||||
|       {bool trackOnly = false}) async { |     Map<String, String> additionalSettings, | ||||||
|  |   ) async { | ||||||
|     Response res = await get(Uri.parse('$standardUrl/rss?path=/')); |     Response res = await get(Uri.parse('$standardUrl/rss?path=/')); | ||||||
|     if (res.statusCode == 200) { |     if (res.statusCode == 200) { | ||||||
|       var parsedHtml = parse(res.body); |       var parsedHtml = parse(res.body); | ||||||
|   | |||||||
| @@ -9,13 +9,10 @@ class SteamMobile extends AppSource { | |||||||
|   SteamMobile() { |   SteamMobile() { | ||||||
|     host = 'store.steampowered.com'; |     host = 'store.steampowered.com'; | ||||||
|     name = tr('steam'); |     name = tr('steam'); | ||||||
|     additionalSourceAppSpecificFormItems = [ |     additionalSourceAppSpecificSettingFormItems = [ | ||||||
|       [ |       [ | ||||||
|         GeneratedFormItem( |         GeneratedFormItem('app', | ||||||
|             label: tr('app'), |             label: tr('app'), required: true, opts: apks.entries.toList()) | ||||||
|             key: 'app', |  | ||||||
|             required: true, |  | ||||||
|             opts: apks.entries.toList()) |  | ||||||
|       ] |       ] | ||||||
|     ]; |     ]; | ||||||
|   } |   } | ||||||
| @@ -32,15 +29,12 @@ class SteamMobile extends AppSource { | |||||||
|  |  | ||||||
|   @override |   @override | ||||||
|   Future<APKDetails> getLatestAPKDetails( |   Future<APKDetails> getLatestAPKDetails( | ||||||
|       String standardUrl, List<String> additionalData, |     String standardUrl, | ||||||
|       {bool trackOnly = false}) async { |     Map<String, String> additionalSettings, | ||||||
|  |   ) async { | ||||||
|     Response res = await get(Uri.parse('https://$host/mobile')); |     Response res = await get(Uri.parse('https://$host/mobile')); | ||||||
|     if (res.statusCode == 200) { |     if (res.statusCode == 200) { | ||||||
|       var apkNamePrefix = findGeneratedFormValueByKey( |       var apkNamePrefix = additionalSettings['app']; | ||||||
|           additionalSourceAppSpecificFormItems |  | ||||||
|               .reduce((value, element) => [...value, ...element]), |  | ||||||
|           additionalData, |  | ||||||
|           'app'); |  | ||||||
|       if (apkNamePrefix == null) { |       if (apkNamePrefix == null) { | ||||||
|         throw NoReleasesError(); |         throw NoReleasesError(); | ||||||
|       } |       } | ||||||
|   | |||||||
| @@ -4,7 +4,7 @@ import 'package:flutter/material.dart'; | |||||||
| enum FormItemType { string, bool } | enum FormItemType { string, bool } | ||||||
|  |  | ||||||
| typedef OnValueChanges = void Function( | typedef OnValueChanges = void Function( | ||||||
|     List<String> values, bool valid, bool isBuilding); |     Map<String, String> values, bool valid, bool isBuilding); | ||||||
|  |  | ||||||
| class GeneratedFormItem { | class GeneratedFormItem { | ||||||
|   late String key; |   late String key; | ||||||
| @@ -13,22 +13,21 @@ class GeneratedFormItem { | |||||||
|   late bool required; |   late bool required; | ||||||
|   late int max; |   late int max; | ||||||
|   late List<String? Function(String? value)> additionalValidators; |   late List<String? Function(String? value)> additionalValidators; | ||||||
|   late String id; |  | ||||||
|   late List<Widget> belowWidgets; |   late List<Widget> belowWidgets; | ||||||
|   late String? hint; |   late String? hint; | ||||||
|   late List<MapEntry<String, String>>? opts; |   late List<MapEntry<String, String>>? opts; | ||||||
|  |   late String? defaultValue; | ||||||
|  |  | ||||||
|   GeneratedFormItem( |   GeneratedFormItem(this.key, | ||||||
|       {this.label = 'Input', |       {this.label = 'Input', | ||||||
|       this.type = FormItemType.string, |       this.type = FormItemType.string, | ||||||
|       this.required = true, |       this.required = true, | ||||||
|       this.max = 1, |       this.max = 1, | ||||||
|       this.additionalValidators = const [], |       this.additionalValidators = const [], | ||||||
|       this.id = 'input', |  | ||||||
|       this.belowWidgets = const [], |       this.belowWidgets = const [], | ||||||
|       this.hint, |       this.hint, | ||||||
|       this.opts, |       this.opts, | ||||||
|       this.key = 'default'}) { |       this.defaultValue}) { | ||||||
|     if (type != FormItemType.string) { |     if (type != FormItemType.string) { | ||||||
|       required = false; |       required = false; | ||||||
|     } |     } | ||||||
| @@ -37,14 +36,10 @@ class GeneratedFormItem { | |||||||
|  |  | ||||||
| class GeneratedForm extends StatefulWidget { | class GeneratedForm extends StatefulWidget { | ||||||
|   const GeneratedForm( |   const GeneratedForm( | ||||||
|       {super.key, |       {super.key, required this.items, required this.onValueChanges}); | ||||||
|       required this.items, |  | ||||||
|       required this.onValueChanges, |  | ||||||
|       required this.defaultValues}); |  | ||||||
|  |  | ||||||
|   final List<List<GeneratedFormItem>> items; |   final List<List<GeneratedFormItem>> items; | ||||||
|   final OnValueChanges onValueChanges; |   final OnValueChanges onValueChanges; | ||||||
|   final List<String> defaultValues; |  | ||||||
|  |  | ||||||
|   @override |   @override | ||||||
|   State<GeneratedForm> createState() => _GeneratedFormState(); |   State<GeneratedForm> createState() => _GeneratedFormState(); | ||||||
| @@ -52,17 +47,18 @@ class GeneratedForm extends StatefulWidget { | |||||||
|  |  | ||||||
| class _GeneratedFormState extends State<GeneratedForm> { | class _GeneratedFormState extends State<GeneratedForm> { | ||||||
|   final _formKey = GlobalKey<FormState>(); |   final _formKey = GlobalKey<FormState>(); | ||||||
|   late List<List<String>> values; |   Map<String, String> values = {}; | ||||||
|   late List<List<Widget>> formInputs; |   late List<List<Widget>> formInputs; | ||||||
|   List<List<Widget>> rows = []; |   List<List<Widget>> rows = []; | ||||||
|  |  | ||||||
|   // If any value changes, call this to update the parent with value and validity |   // If any value changes, call this to update the parent with value and validity | ||||||
|   void someValueChanged({bool isBuilding = false}) { |   void someValueChanged({bool isBuilding = false}) { | ||||||
|     List<String> returnValues = []; |     Map<String, String> returnValues = {}; | ||||||
|     var valid = true; |     var valid = true; | ||||||
|     for (int r = 0; r < values.length; r++) { |     for (int r = 0; r < widget.items.length; r++) { | ||||||
|       for (int i = 0; i < values[r].length; i++) { |       for (int i = 0; i < widget.items[r].length; i++) { | ||||||
|         returnValues.add(values[r][i]); |         returnValues[widget.items[r][i].key] = | ||||||
|  |             values[widget.items[r][i].key] ?? ''; | ||||||
|         if (formInputs[r][i] is TextFormField) { |         if (formInputs[r][i] is TextFormField) { | ||||||
|           valid = valid && |           valid = valid && | ||||||
|               ((formInputs[r][i].key as GlobalKey<FormFieldState>) |               ((formInputs[r][i].key as GlobalKey<FormFieldState>) | ||||||
| @@ -80,16 +76,13 @@ class _GeneratedFormState extends State<GeneratedForm> { | |||||||
|     super.initState(); |     super.initState(); | ||||||
|  |  | ||||||
|     // Initialize form values as all empty |     // Initialize form values as all empty | ||||||
|  |     values.clear(); | ||||||
|     int j = 0; |     int j = 0; | ||||||
|     values = widget.items |     for (var row in widget.items) { | ||||||
|         .map((row) => row.map((e) { |       for (var e in row) { | ||||||
|               return j < widget.defaultValues.length |         values[e.key] = e.defaultValue ?? e.opts?.first.key ?? ''; | ||||||
|                   ? widget.defaultValues[j++] |       } | ||||||
|                   : e.opts != null |     } | ||||||
|                       ? e.opts!.first.key |  | ||||||
|                       : ''; |  | ||||||
|             }).toList()) |  | ||||||
|         .toList(); |  | ||||||
|  |  | ||||||
|     // Dynamically create form inputs |     // Dynamically create form inputs | ||||||
|     formInputs = widget.items.asMap().entries.map((row) { |     formInputs = widget.items.asMap().entries.map((row) { | ||||||
| @@ -98,11 +91,11 @@ class _GeneratedFormState extends State<GeneratedForm> { | |||||||
|           final formFieldKey = GlobalKey<FormFieldState>(); |           final formFieldKey = GlobalKey<FormFieldState>(); | ||||||
|           return TextFormField( |           return TextFormField( | ||||||
|             key: formFieldKey, |             key: formFieldKey, | ||||||
|             initialValue: values[row.key][e.key], |             initialValue: values[e.value.key], | ||||||
|             autovalidateMode: AutovalidateMode.onUserInteraction, |             autovalidateMode: AutovalidateMode.onUserInteraction, | ||||||
|             onChanged: (value) { |             onChanged: (value) { | ||||||
|               setState(() { |               setState(() { | ||||||
|                 values[row.key][e.key] = value; |                 values[e.value.key] = value; | ||||||
|                 someValueChanged(); |                 someValueChanged(); | ||||||
|               }); |               }); | ||||||
|             }, |             }, | ||||||
| @@ -131,14 +124,14 @@ class _GeneratedFormState extends State<GeneratedForm> { | |||||||
|           } |           } | ||||||
|           return DropdownButtonFormField( |           return DropdownButtonFormField( | ||||||
|               decoration: InputDecoration(labelText: e.value.label), |               decoration: InputDecoration(labelText: e.value.label), | ||||||
|               value: values[row.key][e.key], |               value: values[e.value.key], | ||||||
|               items: e.value.opts! |               items: e.value.opts! | ||||||
|                   .map((e) => |                   .map((e) => | ||||||
|                       DropdownMenuItem(value: e.key, child: Text(e.value))) |                       DropdownMenuItem(value: e.key, child: Text(e.value))) | ||||||
|                   .toList(), |                   .toList(), | ||||||
|               onChanged: (value) { |               onChanged: (value) { | ||||||
|                 setState(() { |                 setState(() { | ||||||
|                   values[row.key][e.key] = value ?? e.value.opts!.first.key; |                   values[e.value.key] = value ?? e.value.opts!.first.key; | ||||||
|                   someValueChanged(); |                   someValueChanged(); | ||||||
|                 }); |                 }); | ||||||
|               }); |               }); | ||||||
| @@ -160,10 +153,10 @@ class _GeneratedFormState extends State<GeneratedForm> { | |||||||
|             children: [ |             children: [ | ||||||
|               Text(widget.items[r][e].label), |               Text(widget.items[r][e].label), | ||||||
|               Switch( |               Switch( | ||||||
|                   value: values[r][e] == 'true', |                   value: values[widget.items[r][e].key] == 'true', | ||||||
|                   onChanged: (value) { |                   onChanged: (value) { | ||||||
|                     setState(() { |                     setState(() { | ||||||
|                       values[r][e] = value ? 'true' : ''; |                       values[widget.items[r][e].key] = value ? 'true' : ''; | ||||||
|                       someValueChanged(); |                       someValueChanged(); | ||||||
|                     }); |                     }); | ||||||
|                   }) |                   }) | ||||||
| @@ -217,18 +210,3 @@ class _GeneratedFormState extends State<GeneratedForm> { | |||||||
|         )); |         )); | ||||||
|   } |   } | ||||||
| } | } | ||||||
|  |  | ||||||
| String? findGeneratedFormValueByKey( |  | ||||||
|     List<GeneratedFormItem> items, List<String> values, String key) { |  | ||||||
|   var foundIndex = -1; |  | ||||||
|   for (var i = 0; i < items.length; i++) { |  | ||||||
|     if (items[i].key == key) { |  | ||||||
|       foundIndex = i; |  | ||||||
|       break; |  | ||||||
|     } |  | ||||||
|   } |  | ||||||
|   if (foundIndex >= 0 && foundIndex < values.length) { |  | ||||||
|     return values[foundIndex]; |  | ||||||
|   } |  | ||||||
|   return null; |  | ||||||
| } |  | ||||||
|   | |||||||
| @@ -8,14 +8,12 @@ class GeneratedFormModal extends StatefulWidget { | |||||||
|       {super.key, |       {super.key, | ||||||
|       required this.title, |       required this.title, | ||||||
|       required this.items, |       required this.items, | ||||||
|       required this.defaultValues, |  | ||||||
|       this.initValid = false, |       this.initValid = false, | ||||||
|       this.message = ''}); |       this.message = ''}); | ||||||
|  |  | ||||||
|   final String title; |   final String title; | ||||||
|   final String message; |   final String message; | ||||||
|   final List<List<GeneratedFormItem>> items; |   final List<List<GeneratedFormItem>> items; | ||||||
|   final List<String> defaultValues; |  | ||||||
|   final bool initValid; |   final bool initValid; | ||||||
|  |  | ||||||
|   @override |   @override | ||||||
| @@ -23,13 +21,12 @@ class GeneratedFormModal extends StatefulWidget { | |||||||
| } | } | ||||||
|  |  | ||||||
| class _GeneratedFormModalState extends State<GeneratedFormModal> { | class _GeneratedFormModalState extends State<GeneratedFormModal> { | ||||||
|   List<String> values = []; |   Map<String, String> values = {}; | ||||||
|   bool valid = false; |   bool valid = false; | ||||||
|  |  | ||||||
|   @override |   @override | ||||||
|   void initState() { |   void initState() { | ||||||
|     super.initState(); |     super.initState(); | ||||||
|     values = widget.defaultValues; |  | ||||||
|     valid = widget.initValid || widget.items.isEmpty; |     valid = widget.initValid || widget.items.isEmpty; | ||||||
|   } |   } | ||||||
|  |  | ||||||
| @@ -57,8 +54,7 @@ class _GeneratedFormModalState extends State<GeneratedFormModal> { | |||||||
|                   this.valid = valid; |                   this.valid = valid; | ||||||
|                 }); |                 }); | ||||||
|               } |               } | ||||||
|             }, |             }) | ||||||
|             defaultValues: widget.defaultValues) |  | ||||||
|       ]), |       ]), | ||||||
|       actions: [ |       actions: [ | ||||||
|         TextButton( |         TextButton( | ||||||
|   | |||||||
| @@ -21,7 +21,7 @@ import 'package:easy_localization/src/easy_localization_controller.dart'; | |||||||
| // ignore: implementation_imports | // ignore: implementation_imports | ||||||
| import 'package:easy_localization/src/localization.dart'; | import 'package:easy_localization/src/localization.dart'; | ||||||
|  |  | ||||||
| const String currentVersion = '0.8.21'; | const String currentVersion = '0.8.22'; | ||||||
| const String currentReleaseTag = | const String currentReleaseTag = | ||||||
|     'v$currentVersion-beta'; // KEEP THIS IN SYNC WITH GITHUB RELEASES |     'v$currentVersion-beta'; // KEEP THIS IN SYNC WITH GITHUB RELEASES | ||||||
|  |  | ||||||
| @@ -200,9 +200,8 @@ class _ObtainiumState extends State<Obtainium> { | |||||||
|               currentReleaseTag, |               currentReleaseTag, | ||||||
|               [], |               [], | ||||||
|               0, |               0, | ||||||
|               ['true'], |               {'includePrereleases': 'true'}, | ||||||
|               null, |               null, | ||||||
|               false, |  | ||||||
|               false) |               false) | ||||||
|         ]); |         ]); | ||||||
|       } |       } | ||||||
|   | |||||||
| @@ -27,10 +27,8 @@ class _AddAppPageState extends State<AddAppPage> { | |||||||
|   String userInput = ''; |   String userInput = ''; | ||||||
|   String searchQuery = ''; |   String searchQuery = ''; | ||||||
|   AppSource? pickedSource; |   AppSource? pickedSource; | ||||||
|   List<String> sourceSpecificAdditionalData = []; |   Map<String, String> additionalSettings = {}; | ||||||
|   bool sourceSpecificDataIsValid = true; |   bool additionalSettingsValid = true; | ||||||
|   List<String> otherAdditionalData = []; |  | ||||||
|   bool otherAdditionalDataIsValid = true; |  | ||||||
|  |  | ||||||
|   @override |   @override | ||||||
|   Widget build(BuildContext context) { |   Widget build(BuildContext context) { | ||||||
| @@ -43,10 +41,12 @@ class _AddAppPageState extends State<AddAppPage> { | |||||||
|         var source = valid ? sourceProvider.getSource(userInput) : null; |         var source = valid ? sourceProvider.getSource(userInput) : null; | ||||||
|         if (pickedSource.runtimeType != source.runtimeType) { |         if (pickedSource.runtimeType != source.runtimeType) { | ||||||
|           pickedSource = source; |           pickedSource = source; | ||||||
|           sourceSpecificAdditionalData = |           additionalSettings = source != null | ||||||
|               source != null ? source.additionalSourceAppSpecificDefaults : []; |               ? getDefaultValuesFromFormItems( | ||||||
|           sourceSpecificDataIsValid = source != null |                   source.combinedAppSpecificSettingFormItems) | ||||||
|               ? !sourceProvider.ifSourceAppsRequireAdditionalData(source) |               : {}; | ||||||
|  |           additionalSettingsValid = source != null | ||||||
|  |               ? !sourceProvider.ifRequiredAppSpecificSettingsExist(source) | ||||||
|               : true; |               : true; | ||||||
|         } |         } | ||||||
|       } |       } | ||||||
| @@ -66,11 +66,9 @@ class _AddAppPageState extends State<AddAppPage> { | |||||||
|       }); |       }); | ||||||
|       var settingsProvider = context.read<SettingsProvider>(); |       var settingsProvider = context.read<SettingsProvider>(); | ||||||
|       () async { |       () async { | ||||||
|         var userPickedTrackOnly = findGeneratedFormValueByKey( |         var userPickedTrackOnly = additionalSettings['trackOnly'] == 'true'; | ||||||
|                 pickedSource!.additionalAppSpecificSourceAgnosticFormItems, |         var userPickedNoVersionDetection = | ||||||
|                 otherAdditionalData, |             additionalSettings['noVersionDetection'] == 'true'; | ||||||
|                 'trackOnlyFormItemKey') == |  | ||||||
|             'true'; |  | ||||||
|         var cont = true; |         var cont = true; | ||||||
|         if ((userPickedTrackOnly || pickedSource!.enforceTrackOnly) && |         if ((userPickedTrackOnly || pickedSource!.enforceTrackOnly) && | ||||||
|             await showDialog( |             await showDialog( | ||||||
| @@ -83,7 +81,6 @@ class _AddAppPageState extends State<AddAppPage> { | |||||||
|                               : tr('app') |                               : tr('app') | ||||||
|                         ]), |                         ]), | ||||||
|                         items: const [], |                         items: const [], | ||||||
|                         defaultValues: const [], |  | ||||||
|                         message: |                         message: | ||||||
|                             '${pickedSource!.enforceTrackOnly ? tr('appsFromSourceAreTrackOnly') : tr('youPickedTrackOnly')}\n\n${tr('trackOnlyAppDescription')}', |                             '${pickedSource!.enforceTrackOnly ? tr('appsFromSourceAreTrackOnly') : tr('youPickedTrackOnly')}\n\n${tr('trackOnlyAppDescription')}', | ||||||
|                       ); |                       ); | ||||||
| @@ -91,17 +88,32 @@ class _AddAppPageState extends State<AddAppPage> { | |||||||
|                 null) { |                 null) { | ||||||
|           cont = false; |           cont = false; | ||||||
|         } |         } | ||||||
|  |         if (userPickedNoVersionDetection && | ||||||
|  |             await showDialog( | ||||||
|  |                     context: context, | ||||||
|  |                     builder: (BuildContext ctx) { | ||||||
|  |                       return GeneratedFormModal( | ||||||
|  |                         title: tr('disableVersionDetection'), | ||||||
|  |                         items: const [], | ||||||
|  |                         message: tr('noVersionDetectionExplanation'), | ||||||
|  |                       ); | ||||||
|  |                     }) == | ||||||
|  |                 null) { | ||||||
|  |           cont = false; | ||||||
|  |         } | ||||||
|         if (cont) { |         if (cont) { | ||||||
|           HapticFeedback.selectionClick(); |           HapticFeedback.selectionClick(); | ||||||
|           var trackOnly = pickedSource!.enforceTrackOnly || userPickedTrackOnly; |           var trackOnly = pickedSource!.enforceTrackOnly || userPickedTrackOnly; | ||||||
|           App app = await sourceProvider.getApp( |           App app = await sourceProvider.getApp( | ||||||
|               pickedSource!, userInput, sourceSpecificAdditionalData, |               pickedSource!, userInput, additionalSettings, | ||||||
|               trackOnly: trackOnly); |               trackOnlyOverride: trackOnly, | ||||||
|  |               noVersionDetectionOverride: userPickedNoVersionDetection); | ||||||
|           if (!trackOnly) { |           if (!trackOnly) { | ||||||
|             await settingsProvider.getInstallPermission(); |             await settingsProvider.getInstallPermission(); | ||||||
|           } |           } | ||||||
|           // Only download the APK here if you need to for the package ID |           // Only download the APK here if you need to for the package ID | ||||||
|           if (sourceProvider.isTempId(app.id) && !app.trackOnly) { |           if (sourceProvider.isTempId(app.id) && | ||||||
|  |               app.additionalSettings['trackOnly'] != 'true') { | ||||||
|             // ignore: use_build_context_synchronously |             // ignore: use_build_context_synchronously | ||||||
|             var apkUrl = await appsProvider.confirmApkUrl(app, context); |             var apkUrl = await appsProvider.confirmApkUrl(app, context); | ||||||
|             if (apkUrl == null) { |             if (apkUrl == null) { | ||||||
| @@ -116,7 +128,7 @@ class _AddAppPageState extends State<AddAppPage> { | |||||||
|           if (appsProvider.apps.containsKey(app.id)) { |           if (appsProvider.apps.containsKey(app.id)) { | ||||||
|             throw ObtainiumError(tr('appAlreadyAdded')); |             throw ObtainiumError(tr('appAlreadyAdded')); | ||||||
|           } |           } | ||||||
|           if (app.trackOnly) { |           if (app.additionalSettings['trackOnly'] == 'true') { | ||||||
|             app.installedVersion = app.latestVersion; |             app.installedVersion = app.latestVersion; | ||||||
|           } |           } | ||||||
|           await appsProvider.saveApps([app]); |           await appsProvider.saveApps([app]); | ||||||
| @@ -157,7 +169,7 @@ class _AddAppPageState extends State<AddAppPage> { | |||||||
|                               child: GeneratedForm( |                               child: GeneratedForm( | ||||||
|                                   items: [ |                                   items: [ | ||||||
|                                 [ |                                 [ | ||||||
|                                       GeneratedFormItem( |                                   GeneratedFormItem('appSourceURL', | ||||||
|                                       label: tr('appSourceURL'), |                                       label: tr('appSourceURL'), | ||||||
|                                       additionalValidators: [ |                                       additionalValidators: [ | ||||||
|                                         (value) { |                                         (value) { | ||||||
| @@ -180,10 +192,9 @@ class _AddAppPageState extends State<AddAppPage> { | |||||||
|                                 ] |                                 ] | ||||||
|                               ], |                               ], | ||||||
|                                   onValueChanges: (values, valid, isBuilding) { |                                   onValueChanges: (values, valid, isBuilding) { | ||||||
|                                     changeUserInput( |                                     changeUserInput(values['appSourceURL']!, | ||||||
|                                         values[0], valid, isBuilding); |                                         valid, isBuilding); | ||||||
|                                   }, |                                   })), | ||||||
|                                   defaultValues: const [])), |  | ||||||
|                           const SizedBox( |                           const SizedBox( | ||||||
|                             width: 16, |                             width: 16, | ||||||
|                           ), |                           ), | ||||||
| @@ -193,13 +204,9 @@ class _AddAppPageState extends State<AddAppPage> { | |||||||
|                                   onPressed: gettingAppInfo || |                                   onPressed: gettingAppInfo || | ||||||
|                                           pickedSource == null || |                                           pickedSource == null || | ||||||
|                                           (pickedSource! |                                           (pickedSource! | ||||||
|                                                   .additionalSourceAppSpecificFormItems |                                                   .combinedAppSpecificSettingFormItems | ||||||
|                                                   .isNotEmpty && |                                                   .isNotEmpty && | ||||||
|                                               !sourceSpecificDataIsValid) || |                                               !additionalSettingsValid) | ||||||
|                                           (pickedSource! |  | ||||||
|                                                   .additionalAppSpecificSourceAgnosticDefaults |  | ||||||
|                                                   .isNotEmpty && |  | ||||||
|                                               !otherAdditionalDataIsValid) |  | ||||||
|                                       ? null |                                       ? null | ||||||
|                                       : addApp, |                                       : addApp, | ||||||
|                                   child: Text(tr('add'))) |                                   child: Text(tr('add'))) | ||||||
| @@ -224,7 +231,7 @@ class _AddAppPageState extends State<AddAppPage> { | |||||||
|                               child: GeneratedForm( |                               child: GeneratedForm( | ||||||
|                                   items: [ |                                   items: [ | ||||||
|                                     [ |                                     [ | ||||||
|                                       GeneratedFormItem( |                                       GeneratedFormItem('searchSomeSources', | ||||||
|                                           label: tr('searchSomeSourcesLabel'), |                                           label: tr('searchSomeSourcesLabel'), | ||||||
|                                           required: false), |                                           required: false), | ||||||
|                                     ] |                                     ] | ||||||
| @@ -232,11 +239,11 @@ class _AddAppPageState extends State<AddAppPage> { | |||||||
|                                   onValueChanges: (values, valid, isBuilding) { |                                   onValueChanges: (values, valid, isBuilding) { | ||||||
|                                     if (values.isNotEmpty && valid) { |                                     if (values.isNotEmpty && valid) { | ||||||
|                                       setState(() { |                                       setState(() { | ||||||
|                                         searchQuery = values[0].trim(); |                                         searchQuery = | ||||||
|  |                                             values['searchSomeSources']!.trim(); | ||||||
|                                       }); |                                       }); | ||||||
|                                     } |                                     } | ||||||
|                                   }, |                                   }), | ||||||
|                                   defaultValues: const ['']), |  | ||||||
|                             ), |                             ), | ||||||
|                             const SizedBox( |                             const SizedBox( | ||||||
|                               width: 16, |                               width: 16, | ||||||
| @@ -292,15 +299,8 @@ class _AddAppPageState extends State<AddAppPage> { | |||||||
|                           ], |                           ], | ||||||
|                         ), |                         ), | ||||||
|                       if (pickedSource != null && |                       if (pickedSource != null && | ||||||
|                           (pickedSource!.additionalSourceAppSpecificDefaults |                           (pickedSource! | ||||||
|                                   .isNotEmpty || |                               .combinedAppSpecificSettingFormItems.isNotEmpty)) | ||||||
|                               pickedSource! |  | ||||||
|                                   .additionalAppSpecificSourceAgnosticFormItems |  | ||||||
|                                   .where((e) => pickedSource!.enforceTrackOnly |  | ||||||
|                                       ? e.key != 'trackOnlyFormItemKey' |  | ||||||
|                                       : true) |  | ||||||
|                                   .map((e) => [e]) |  | ||||||
|                                   .isNotEmpty)) |  | ||||||
|                         Column( |                         Column( | ||||||
|                           crossAxisAlignment: CrossAxisAlignment.stretch, |                           crossAxisAlignment: CrossAxisAlignment.stretch, | ||||||
|                           children: [ |                           children: [ | ||||||
| @@ -316,46 +316,17 @@ class _AddAppPageState extends State<AddAppPage> { | |||||||
|                             const SizedBox( |                             const SizedBox( | ||||||
|                               height: 16, |                               height: 16, | ||||||
|                             ), |                             ), | ||||||
|                             if (pickedSource! |  | ||||||
|                                 .additionalSourceAppSpecificFormItems |  | ||||||
|                                 .isNotEmpty) |  | ||||||
|                             GeneratedForm( |                             GeneratedForm( | ||||||
|                                 items: pickedSource! |                                 items: pickedSource! | ||||||
|                                       .additionalSourceAppSpecificFormItems, |                                     .combinedAppSpecificSettingFormItems, | ||||||
|                                 onValueChanges: (values, valid, isBuilding) { |                                 onValueChanges: (values, valid, isBuilding) { | ||||||
|                                   if (!isBuilding) { |                                   if (!isBuilding) { | ||||||
|                                     setState(() { |                                     setState(() { | ||||||
|                                         sourceSpecificAdditionalData = values; |                                       additionalSettings = values; | ||||||
|                                         sourceSpecificDataIsValid = valid; |                                       additionalSettingsValid = valid; | ||||||
|                                     }); |                                     }); | ||||||
|                                   } |                                   } | ||||||
|                                   }, |                                 }), | ||||||
|                                   defaultValues: pickedSource! |  | ||||||
|                                       .additionalSourceAppSpecificDefaults), |  | ||||||
|                             if (pickedSource! |  | ||||||
|                                 .additionalAppSpecificSourceAgnosticDefaults |  | ||||||
|                                 .isNotEmpty) |  | ||||||
|                               const SizedBox( |  | ||||||
|                                 height: 8, |  | ||||||
|                               ), |  | ||||||
|                             GeneratedForm( |  | ||||||
|                                 items: pickedSource! |  | ||||||
|                                     .additionalAppSpecificSourceAgnosticFormItems |  | ||||||
|                                     .where((e) => pickedSource!.enforceTrackOnly |  | ||||||
|                                         ? e.key != 'trackOnlyFormItemKey' |  | ||||||
|                                         : true) |  | ||||||
|                                     .map((e) => [e]) |  | ||||||
|                                     .toList(), |  | ||||||
|                                 onValueChanges: (values, valid, isBuilding) { |  | ||||||
|                                   if (!isBuilding) { |  | ||||||
|                                     setState(() { |  | ||||||
|                                       otherAdditionalData = values; |  | ||||||
|                                       otherAdditionalDataIsValid = valid; |  | ||||||
|                                     }); |  | ||||||
|                                   } |  | ||||||
|                                 }, |  | ||||||
|                                 defaultValues: pickedSource! |  | ||||||
|                                     .additionalAppSpecificSourceAgnosticDefaults), |  | ||||||
|                           ], |                           ], | ||||||
|                         ) |                         ) | ||||||
|                       else |                       else | ||||||
|   | |||||||
| @@ -40,16 +40,33 @@ class _AppPageState extends State<AppPage> { | |||||||
|       prevApp = app; |       prevApp = app; | ||||||
|       getUpdate(app.app.id); |       getUpdate(app.app.id); | ||||||
|     } |     } | ||||||
|  |     var trackOnly = app?.app.additionalSettings['trackOnly'] == 'true'; | ||||||
|     return Scaffold( |     return Scaffold( | ||||||
|       appBar: settingsProvider.showAppWebpage ? AppBar() : null, |       appBar: settingsProvider.showAppWebpage ? AppBar() : null, | ||||||
|       backgroundColor: Theme.of(context).colorScheme.surface, |       backgroundColor: Theme.of(context).colorScheme.surface, | ||||||
|       body: RefreshIndicator( |       body: RefreshIndicator( | ||||||
|           child: settingsProvider.showAppWebpage |           child: settingsProvider.showAppWebpage | ||||||
|               ? WebView( |               ? app != null | ||||||
|                   backgroundColor: Theme.of(context).colorScheme.background, |                   ? WebViewWidget( | ||||||
|                   initialUrl: app?.app.url, |                       controller: WebViewController() | ||||||
|                   javascriptMode: JavascriptMode.unrestricted, |                         ..setJavaScriptMode(JavaScriptMode.unrestricted) | ||||||
|  |                         ..setBackgroundColor( | ||||||
|  |                             Theme.of(context).colorScheme.background) | ||||||
|  |                         ..setJavaScriptMode(JavaScriptMode.unrestricted) | ||||||
|  |                         ..setNavigationDelegate( | ||||||
|  |                           NavigationDelegate( | ||||||
|  |                             onWebResourceError: (WebResourceError error) { | ||||||
|  |                               if (error.isForMainFrame == true) { | ||||||
|  |                                 showError( | ||||||
|  |                                     ObtainiumError(error.description, | ||||||
|  |                                         unexpected: true), | ||||||
|  |                                     context); | ||||||
|  |                               } | ||||||
|  |                             }, | ||||||
|  |                           ), | ||||||
|                         ) |                         ) | ||||||
|  |                         ..loadRequest(Uri.parse(app.app.url))) | ||||||
|  |                   : Container() | ||||||
|               : CustomScrollView( |               : CustomScrollView( | ||||||
|                   slivers: [ |                   slivers: [ | ||||||
|                     SliverFillRemaining( |                     SliverFillRemaining( | ||||||
| @@ -72,7 +89,9 @@ class _AppPageState extends State<AppPage> { | |||||||
|                           height: 25, |                           height: 25, | ||||||
|                         ), |                         ), | ||||||
|                         Text( |                         Text( | ||||||
|                           app?.installedInfo?.name ?? app?.app.name ?? 'App', |                           app?.installedInfo?.name ?? | ||||||
|  |                               app?.app.name ?? | ||||||
|  |                               tr('app'), | ||||||
|                           textAlign: TextAlign.center, |                           textAlign: TextAlign.center, | ||||||
|                           style: Theme.of(context).textTheme.displayLarge, |                           style: Theme.of(context).textTheme.displayLarge, | ||||||
|                         ), |                         ), | ||||||
| @@ -111,7 +130,7 @@ class _AppPageState extends State<AppPage> { | |||||||
|                         Text( |                         Text( | ||||||
|                           '${tr('installedVersionX', args: [ |                           '${tr('installedVersionX', args: [ | ||||||
|                                 app?.app.installedVersion ?? tr('none') |                                 app?.app.installedVersion ?? tr('none') | ||||||
|                               ])}${app?.app.trackOnly == true ? ' ${tr('estimateInBrackets')}\n\n${tr('xIsTrackOnly', args: [ |                               ])}${trackOnly ? ' ${tr('estimateInBrackets')}\n\n${tr('xIsTrackOnly', args: [ | ||||||
|                                   tr('app') |                                   tr('app') | ||||||
|                                 ])}' : ''}', |                                 ])}' : ''}', | ||||||
|                           textAlign: TextAlign.center, |                           textAlign: TextAlign.center, | ||||||
| @@ -151,7 +170,7 @@ class _AppPageState extends State<AppPage> { | |||||||
|                       mainAxisAlignment: MainAxisAlignment.spaceEvenly, |                       mainAxisAlignment: MainAxisAlignment.spaceEvenly, | ||||||
|                       children: [ |                       children: [ | ||||||
|                         if (app?.app.installedVersion != null && |                         if (app?.app.installedVersion != null && | ||||||
|                             app?.app.trackOnly == false && |                             !trackOnly && | ||||||
|                             app?.app.installedVersion != app?.app.latestVersion) |                             app?.app.installedVersion != app?.app.latestVersion) | ||||||
|                           IconButton( |                           IconButton( | ||||||
|                               onPressed: app?.downloadProgress != null |                               onPressed: app?.downloadProgress != null | ||||||
| @@ -199,30 +218,48 @@ class _AppPageState extends State<AppPage> { | |||||||
|                                             ); |                                             ); | ||||||
|                                           }); |                                           }); | ||||||
|                                     }, |                                     }, | ||||||
|                               tooltip: 'Mark as Updated', |                               tooltip: tr('markUpdated'), | ||||||
|                               icon: const Icon(Icons.done)), |                               icon: const Icon(Icons.done)), | ||||||
|                         if (source != null && |                         if (source != null && | ||||||
|                             source.additionalSourceAppSpecificFormItems |                             source | ||||||
|                                 .isNotEmpty) |                                 .combinedAppSpecificSettingFormItems.isNotEmpty) | ||||||
|                           IconButton( |                           IconButton( | ||||||
|                               onPressed: app?.downloadProgress != null |                               onPressed: app?.downloadProgress != null | ||||||
|                                   ? null |                                   ? null | ||||||
|                                   : () { |                                   : () { | ||||||
|                                       showDialog<List<String>>( |                                       showDialog<Map<String, String>>( | ||||||
|                                           context: context, |                                           context: context, | ||||||
|                                           builder: (BuildContext ctx) { |                                           builder: (BuildContext ctx) { | ||||||
|  |                                             var items = source | ||||||
|  |                                                 .combinedAppSpecificSettingFormItems | ||||||
|  |                                                 .map((row) { | ||||||
|  |                                               row.map((e) { | ||||||
|  |                                                 if (app?.app.additionalSettings[ | ||||||
|  |                                                         e.key] != | ||||||
|  |                                                     null) { | ||||||
|  |                                                   e.defaultValue = app?.app | ||||||
|  |                                                           .additionalSettings[ | ||||||
|  |                                                       e.key]; | ||||||
|  |                                                 } | ||||||
|  |                                                 return e; | ||||||
|  |                                               }).toList(); | ||||||
|  |                                               return row; | ||||||
|  |                                             }).toList(); | ||||||
|                                             return GeneratedFormModal( |                                             return GeneratedFormModal( | ||||||
|                                                 title: 'Additional Options', |                                                 title: tr('additionalOptions'), | ||||||
|                                                 items: source |                                                 items: items); | ||||||
|                                                     .additionalSourceAppSpecificFormItems, |  | ||||||
|                                                 defaultValues: app != null |  | ||||||
|                                                     ? app.app.additionalData |  | ||||||
|                                                     : source |  | ||||||
|                                                         .additionalSourceAppSpecificDefaults); |  | ||||||
|                                           }).then((values) { |                                           }).then((values) { | ||||||
|                                         if (app != null && values != null) { |                                         if (app != null && values != null) { | ||||||
|                                           var changedApp = app.app; |                                           var changedApp = app.app; | ||||||
|                                           changedApp.additionalData = values; |                                           changedApp.additionalSettings = | ||||||
|  |                                               values; | ||||||
|  |                                           if (source.enforceTrackOnly) { | ||||||
|  |                                             changedApp.additionalSettings[ | ||||||
|  |                                                 'trackOnly'] = 'true'; | ||||||
|  |                                             showError( | ||||||
|  |                                                 tr('appsFromSourceAreTrackOnly'), | ||||||
|  |                                                 context); | ||||||
|  |                                           } | ||||||
|                                           appsProvider.saveApps( |                                           appsProvider.saveApps( | ||||||
|                                               [changedApp]).then((value) { |                                               [changedApp]).then((value) { | ||||||
|                                             getUpdate(changedApp.id); |                                             getUpdate(changedApp.id); | ||||||
| @@ -230,7 +267,7 @@ class _AppPageState extends State<AppPage> { | |||||||
|                                         } |                                         } | ||||||
|                                       }); |                                       }); | ||||||
|                                     }, |                                     }, | ||||||
|                               tooltip: 'Additional Options', |                               tooltip: tr('additionalOptions'), | ||||||
|                               icon: const Icon(Icons.settings)), |                               icon: const Icon(Icons.settings)), | ||||||
|                         const SizedBox(width: 16.0), |                         const SizedBox(width: 16.0), | ||||||
|                         Expanded( |                         Expanded( | ||||||
| @@ -242,7 +279,9 @@ class _AppPageState extends State<AppPage> { | |||||||
|                                     ? () { |                                     ? () { | ||||||
|                                         HapticFeedback.heavyImpact(); |                                         HapticFeedback.heavyImpact(); | ||||||
|                                         () async { |                                         () async { | ||||||
|                                           if (app?.app.trackOnly != true) { |                                           if (app?.app.additionalSettings[ | ||||||
|  |                                                   'trackOnly'] != | ||||||
|  |                                               'true') { | ||||||
|                                             await settingsProvider |                                             await settingsProvider | ||||||
|                                                 .getInstallPermission(); |                                                 .getInstallPermission(); | ||||||
|                                           } |                                           } | ||||||
| @@ -264,12 +303,12 @@ class _AppPageState extends State<AppPage> { | |||||||
|                                       } |                                       } | ||||||
|                                     : null, |                                     : null, | ||||||
|                                 child: Text(app?.app.installedVersion == null |                                 child: Text(app?.app.installedVersion == null | ||||||
|                                     ? app?.app.trackOnly == false |                                     ? !trackOnly | ||||||
|                                         ? 'Install' |                                         ? tr('install') | ||||||
|                                         : 'Mark Installed' |                                         : tr('markInstalled') | ||||||
|                                     : app?.app.trackOnly == false |                                     : !trackOnly | ||||||
|                                         ? 'Update' |                                         ? tr('update') | ||||||
|                                         : 'Mark Updated'))), |                                         : tr('markUpdated')))), | ||||||
|                         const SizedBox(width: 16.0), |                         const SizedBox(width: 16.0), | ||||||
|                         ElevatedButton( |                         ElevatedButton( | ||||||
|                           onPressed: app?.downloadProgress != null |                           onPressed: app?.downloadProgress != null | ||||||
|   | |||||||
| @@ -139,14 +139,16 @@ class AppsPageState extends State<AppsPage> { | |||||||
|  |  | ||||||
|     List<String> trackOnlyUpdateIdsAllOrSelected = []; |     List<String> trackOnlyUpdateIdsAllOrSelected = []; | ||||||
|     existingUpdateIdsAllOrSelected = existingUpdateIdsAllOrSelected.where((id) { |     existingUpdateIdsAllOrSelected = existingUpdateIdsAllOrSelected.where((id) { | ||||||
|       if (appsProvider.apps[id]!.app.trackOnly) { |       if (appsProvider.apps[id]!.app.additionalSettings['trackOnly'] == | ||||||
|  |           'true') { | ||||||
|         trackOnlyUpdateIdsAllOrSelected.add(id); |         trackOnlyUpdateIdsAllOrSelected.add(id); | ||||||
|         return false; |         return false; | ||||||
|       } |       } | ||||||
|       return true; |       return true; | ||||||
|     }).toList(); |     }).toList(); | ||||||
|     newInstallIdsAllOrSelected = newInstallIdsAllOrSelected.where((id) { |     newInstallIdsAllOrSelected = newInstallIdsAllOrSelected.where((id) { | ||||||
|       if (appsProvider.apps[id]!.app.trackOnly) { |       if (appsProvider.apps[id]!.app.additionalSettings['trackOnly'] == | ||||||
|  |           'true') { | ||||||
|         trackOnlyUpdateIdsAllOrSelected.add(id); |         trackOnlyUpdateIdsAllOrSelected.add(id); | ||||||
|         return false; |         return false; | ||||||
|       } |       } | ||||||
| @@ -271,7 +273,7 @@ class AppsPageState extends State<AppsPage> { | |||||||
|                               SizedBox( |                               SizedBox( | ||||||
|                                   width: 100, |                                   width: 100, | ||||||
|                                   child: Text( |                                   child: Text( | ||||||
|                                     '${sortedApps[index].app.installedVersion ?? tr('notInstalled')}${sortedApps[index].app.trackOnly == true ? ' ${tr('estimateInBrackets')}' : ''}', |                                     '${sortedApps[index].app.installedVersion ?? tr('notInstalled')}${sortedApps[index].app.additionalSettings['trackOnly'] == 'true' ? ' ${tr('estimateInBrackets')}' : ''}', | ||||||
|                                     overflow: TextOverflow.fade, |                                     overflow: TextOverflow.fade, | ||||||
|                                     textAlign: TextAlign.end, |                                     textAlign: TextAlign.end, | ||||||
|                                   )), |                                   )), | ||||||
| @@ -289,7 +291,7 @@ class AppsPageState extends State<AppsPage> { | |||||||
|                                       child: appsProvider.areDownloadsRunning() |                                       child: appsProvider.areDownloadsRunning() | ||||||
|                                           ? Text(tr('pleaseWait')) |                                           ? Text(tr('pleaseWait')) | ||||||
|                                           : Text( |                                           : Text( | ||||||
|                                               '${tr('updateAvailable')}${sortedApps[index].app.trackOnly ? ' ${tr('estimateInBracketsShort')}' : ''}', |                                               '${tr('updateAvailable')}${sortedApps[index].app.additionalSettings['trackOnly'] == 'true' ? ' ${tr('estimateInBracketsShort')}' : ''}', | ||||||
|                                               style: TextStyle( |                                               style: TextStyle( | ||||||
|                                                   fontStyle: FontStyle.italic, |                                                   fontStyle: FontStyle.italic, | ||||||
|                                                   decoration: changesUrl == null |                                                   decoration: changesUrl == null | ||||||
| @@ -343,13 +345,12 @@ class AppsPageState extends State<AppsPage> { | |||||||
|                     : IconButton( |                     : IconButton( | ||||||
|                         visualDensity: VisualDensity.compact, |                         visualDensity: VisualDensity.compact, | ||||||
|                         onPressed: () { |                         onPressed: () { | ||||||
|                           showDialog<List<String>?>( |                           showDialog<Map<String, String>?>( | ||||||
|                               context: context, |                               context: context, | ||||||
|                               builder: (BuildContext ctx) { |                               builder: (BuildContext ctx) { | ||||||
|                                 return GeneratedFormModal( |                                 return GeneratedFormModal( | ||||||
|                                   title: tr('removeSelectedAppsQuestion'), |                                   title: tr('removeSelectedAppsQuestion'), | ||||||
|                                   items: const [], |                                   items: const [], | ||||||
|                                   defaultValues: const [], |  | ||||||
|                                   initValid: true, |                                   initValid: true, | ||||||
|                                   message: tr( |                                   message: tr( | ||||||
|                                       'xWillBeRemovedButRemainInstalled', |                                       'xWillBeRemovedButRemainInstalled', | ||||||
| @@ -376,41 +377,42 @@ class AppsPageState extends State<AppsPage> { | |||||||
|                         ? null |                         ? null | ||||||
|                         : () { |                         : () { | ||||||
|                             HapticFeedback.heavyImpact(); |                             HapticFeedback.heavyImpact(); | ||||||
|                             List<GeneratedFormItem> formInputs = []; |                             List<GeneratedFormItem> formItems = []; | ||||||
|                             List<String> defaultValues = []; |  | ||||||
|                             if (existingUpdateIdsAllOrSelected.isNotEmpty) { |                             if (existingUpdateIdsAllOrSelected.isNotEmpty) { | ||||||
|                               formInputs.add(GeneratedFormItem( |                               formItems.add(GeneratedFormItem('updates', | ||||||
|                                   label: tr('updateX', args: [ |                                   label: tr('updateX', args: [ | ||||||
|                                     plural('apps', |                                     plural('apps', | ||||||
|                                         existingUpdateIdsAllOrSelected.length) |                                         existingUpdateIdsAllOrSelected.length) | ||||||
|                                   ]), |                                   ]), | ||||||
|                                   type: FormItemType.bool, |                                   type: FormItemType.bool, | ||||||
|                                   key: 'updates')); |                                   defaultValue: 'true')); | ||||||
|                               defaultValues.add('true'); |  | ||||||
|                             } |                             } | ||||||
|                             if (newInstallIdsAllOrSelected.isNotEmpty) { |                             if (newInstallIdsAllOrSelected.isNotEmpty) { | ||||||
|                               formInputs.add(GeneratedFormItem( |                               formItems.add(GeneratedFormItem('installs', | ||||||
|                                   label: tr('installX', args: [ |                                   label: tr('installX', args: [ | ||||||
|                                     plural('apps', |                                     plural('apps', | ||||||
|                                         newInstallIdsAllOrSelected.length) |                                         newInstallIdsAllOrSelected.length) | ||||||
|                                   ]), |                                   ]), | ||||||
|                                   type: FormItemType.bool, |                                   type: FormItemType.bool, | ||||||
|                                   key: 'installs')); |                                   defaultValue: | ||||||
|                               defaultValues |                                       existingUpdateIdsAllOrSelected.isNotEmpty | ||||||
|                                   .add(defaultValues.isEmpty ? 'true' : ''); |                                           ? 'true' | ||||||
|  |                                           : '')); | ||||||
|                             } |                             } | ||||||
|                             if (trackOnlyUpdateIdsAllOrSelected.isNotEmpty) { |                             if (trackOnlyUpdateIdsAllOrSelected.isNotEmpty) { | ||||||
|                               formInputs.add(GeneratedFormItem( |                               formItems.add(GeneratedFormItem('trackonlies', | ||||||
|                                   label: tr('markXTrackOnlyAsUpdated', args: [ |                                   label: tr('markXTrackOnlyAsUpdated', args: [ | ||||||
|                                     plural('apps', |                                     plural('apps', | ||||||
|                                         trackOnlyUpdateIdsAllOrSelected.length) |                                         trackOnlyUpdateIdsAllOrSelected.length) | ||||||
|                                   ]), |                                   ]), | ||||||
|                                   type: FormItemType.bool, |                                   type: FormItemType.bool, | ||||||
|                                   key: 'trackonlies')); |                                   defaultValue: existingUpdateIdsAllOrSelected | ||||||
|                               defaultValues |                                               .isNotEmpty || | ||||||
|                                   .add(defaultValues.isEmpty ? 'true' : ''); |                                           newInstallIdsAllOrSelected.isNotEmpty | ||||||
|  |                                       ? 'true' | ||||||
|  |                                       : '')); | ||||||
|                             } |                             } | ||||||
|                             showDialog<List<String>?>( |                             showDialog<Map<String, String>?>( | ||||||
|                                 context: context, |                                 context: context, | ||||||
|                                 builder: (BuildContext ctx) { |                                 builder: (BuildContext ctx) { | ||||||
|                                   var totalApps = existingUpdateIdsAllOrSelected |                                   var totalApps = existingUpdateIdsAllOrSelected | ||||||
| @@ -420,27 +422,21 @@ class AppsPageState extends State<AppsPage> { | |||||||
|                                   return GeneratedFormModal( |                                   return GeneratedFormModal( | ||||||
|                                     title: tr('changeX', |                                     title: tr('changeX', | ||||||
|                                         args: [plural('apps', totalApps)]), |                                         args: [plural('apps', totalApps)]), | ||||||
|                                     items: formInputs.map((e) => [e]).toList(), |                                     items: formItems.map((e) => [e]).toList(), | ||||||
|                                     defaultValues: defaultValues, |  | ||||||
|                                     initValid: true, |                                     initValid: true, | ||||||
|                                   ); |                                   ); | ||||||
|                                 }).then((values) { |                                 }).then((values) { | ||||||
|                               if (values != null) { |                               if (values != null) { | ||||||
|                                 if (values.isEmpty) { |                                 if (values.isEmpty) { | ||||||
|                                   values = defaultValues; |                                   values = getDefaultValuesFromFormItems( | ||||||
|  |                                       [formItems]); | ||||||
|                                 } |                                 } | ||||||
|                                 bool shouldInstallUpdates = |                                 bool shouldInstallUpdates = | ||||||
|                                     findGeneratedFormValueByKey( |                                     values['updates'] == 'true'; | ||||||
|                                             formInputs, values, 'updates') == |  | ||||||
|                                         'true'; |  | ||||||
|                                 bool shouldInstallNew = |                                 bool shouldInstallNew = | ||||||
|                                     findGeneratedFormValueByKey( |                                     values['installs'] == 'true'; | ||||||
|                                             formInputs, values, 'installs') == |  | ||||||
|                                         'true'; |  | ||||||
|                                 bool shouldMarkTrackOnlies = |                                 bool shouldMarkTrackOnlies = | ||||||
|                                     findGeneratedFormValueByKey(formInputs, |                                     values['trackonlies'] == 'true'; | ||||||
|                                             values, 'trackonlies') == |  | ||||||
|                                         'true'; |  | ||||||
|                                 (() async { |                                 (() async { | ||||||
|                                   if (shouldInstallNew || |                                   if (shouldInstallNew || | ||||||
|                                       shouldInstallUpdates) { |                                       shouldInstallUpdates) { | ||||||
| @@ -613,7 +609,6 @@ class AppsPageState extends State<AppsPage> { | |||||||
|                                                       title: tr( |                                                       title: tr( | ||||||
|                                                           'resetInstallStatusForSelectedAppsQuestion'), |                                                           'resetInstallStatusForSelectedAppsQuestion'), | ||||||
|                                                       items: const [], |                                                       items: const [], | ||||||
|                                                       defaultValues: const [], |  | ||||||
|                                                       initValid: true, |                                                       initValid: true, | ||||||
|                                                       message: tr( |                                                       message: tr( | ||||||
|                                                           'installStatusOfXWillBeResetExplanation', |                                                           'installStatusOfXWillBeResetExplanation', | ||||||
| @@ -683,36 +678,42 @@ class AppsPageState extends State<AppsPage> { | |||||||
|                               : FontWeight.bold), |                               : FontWeight.bold), | ||||||
|                     ), |                     ), | ||||||
|                     onPressed: () { |                     onPressed: () { | ||||||
|                       showDialog<List<String>?>( |                       showDialog<Map<String, String>?>( | ||||||
|                           context: context, |                           context: context, | ||||||
|                           builder: (BuildContext ctx) { |                           builder: (BuildContext ctx) { | ||||||
|  |                             var vals = filter == null | ||||||
|  |                                 ? AppsFilter().toValuesMap() | ||||||
|  |                                 : filter!.toValuesMap(); | ||||||
|                             return GeneratedFormModal( |                             return GeneratedFormModal( | ||||||
|                                 title: tr('filterApps'), |                                 title: tr('filterApps'), | ||||||
|                                 items: [ |                                 items: [ | ||||||
|                                   [ |                                   [ | ||||||
|                                     GeneratedFormItem( |                                     GeneratedFormItem('appName', | ||||||
|                                         label: tr('appName'), required: false), |                                         label: tr('appName'), | ||||||
|                                     GeneratedFormItem( |                                         required: false, | ||||||
|                                         label: tr('author'), required: false) |                                         defaultValue: vals['appName']), | ||||||
|  |                                     GeneratedFormItem('author', | ||||||
|  |                                         label: tr('author'), | ||||||
|  |                                         required: false, | ||||||
|  |                                         defaultValue: vals['author']) | ||||||
|                                   ], |                                   ], | ||||||
|                                   [ |                                   [ | ||||||
|                                     GeneratedFormItem( |                                     GeneratedFormItem('upToDateApps', | ||||||
|                                         label: tr('upToDateApps'), |                                         label: tr('upToDateApps'), | ||||||
|                                         type: FormItemType.bool) |                                         type: FormItemType.bool, | ||||||
|  |                                         defaultValue: vals['upToDateApps']) | ||||||
|                                   ], |                                   ], | ||||||
|                                   [ |                                   [ | ||||||
|                                     GeneratedFormItem( |                                     GeneratedFormItem('nonInstalledApps', | ||||||
|                                         label: tr('nonInstalledApps'), |                                         label: tr('nonInstalledApps'), | ||||||
|                                         type: FormItemType.bool) |                                         type: FormItemType.bool, | ||||||
|  |                                         defaultValue: vals['nonInstalledApps']) | ||||||
|                                   ] |                                   ] | ||||||
|                                 ], |                                 ]); | ||||||
|                                 defaultValues: filter == null |  | ||||||
|                                     ? AppsFilter().toValuesArray() |  | ||||||
|                                     : filter!.toValuesArray()); |  | ||||||
|                           }).then((values) { |                           }).then((values) { | ||||||
|                         if (values != null) { |                         if (values != null) { | ||||||
|                           setState(() { |                           setState(() { | ||||||
|                             filter = AppsFilter.fromValuesArray(values); |                             filter = AppsFilter.fromValuesMap(values); | ||||||
|                             if (AppsFilter().isIdenticalTo(filter!)) { |                             if (AppsFilter().isIdenticalTo(filter!)) { | ||||||
|                               filter = null; |                               filter = null; | ||||||
|                             } |                             } | ||||||
| @@ -740,20 +741,20 @@ class AppsFilter { | |||||||
|       this.includeUptodate = true, |       this.includeUptodate = true, | ||||||
|       this.includeNonInstalled = true}); |       this.includeNonInstalled = true}); | ||||||
|  |  | ||||||
|   List<String> toValuesArray() { |   Map<String, String> toValuesMap() { | ||||||
|     return [ |     return { | ||||||
|       nameFilter, |       'appName': nameFilter, | ||||||
|       authorFilter, |       'author': authorFilter, | ||||||
|       includeUptodate ? 'true' : '', |       'upToDateApps': includeUptodate ? 'true' : '', | ||||||
|       includeNonInstalled ? 'true' : '' |       'nonInstalledApps': includeNonInstalled ? 'true' : '' | ||||||
|     ]; |     }; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   AppsFilter.fromValuesArray(List<String> values) { |   AppsFilter.fromValuesMap(Map<String, String> values) { | ||||||
|     nameFilter = values[0]; |     nameFilter = values['appName']!; | ||||||
|     authorFilter = values[1]; |     authorFilter = values['author']!; | ||||||
|     includeUptodate = values[2] == 'true'; |     includeUptodate = values['upToDateApps'] == 'true'; | ||||||
|     includeNonInstalled = values[3] == 'true'; |     includeNonInstalled = values['nonInstalledApps'] == 'true'; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   bool isIdenticalTo(AppsFilter other) => |   bool isIdenticalTo(AppsFilter other) => | ||||||
|   | |||||||
| @@ -145,7 +145,7 @@ class _ImportExportPageState extends State<ImportExportPage> { | |||||||
|                                           title: tr('importFromURLList'), |                                           title: tr('importFromURLList'), | ||||||
|                                           items: [ |                                           items: [ | ||||||
|                                             [ |                                             [ | ||||||
|                                               GeneratedFormItem( |                                               GeneratedFormItem('appURLList', | ||||||
|                                                   label: tr('appURLList'), |                                                   label: tr('appURLList'), | ||||||
|                                                   max: 7, |                                                   max: 7, | ||||||
|                                                   additionalValidators: [ |                                                   additionalValidators: [ | ||||||
| @@ -172,7 +172,6 @@ class _ImportExportPageState extends State<ImportExportPage> { | |||||||
|                                                   ]) |                                                   ]) | ||||||
|                                             ] |                                             ] | ||||||
|                                           ], |                                           ], | ||||||
|                                           defaultValues: const [], |  | ||||||
|                                         ); |                                         ); | ||||||
|                                       }).then((values) { |                                       }).then((values) { | ||||||
|                                     if (values != null) { |                                     if (values != null) { | ||||||
| @@ -237,11 +236,11 @@ class _ImportExportPageState extends State<ImportExportPage> { | |||||||
|                                                           items: [ |                                                           items: [ | ||||||
|                                                             [ |                                                             [ | ||||||
|                                                               GeneratedFormItem( |                                                               GeneratedFormItem( | ||||||
|  |                                                                   'searchQuery', | ||||||
|                                                                   label: tr( |                                                                   label: tr( | ||||||
|                                                                       'searchQuery')) |                                                                       'searchQuery')) | ||||||
|                                                             ] |                                                             ] | ||||||
|                                                           ], |                                                           ], | ||||||
|                                                           defaultValues: const [], |  | ||||||
|                                                         ); |                                                         ); | ||||||
|                                                       }); |                                                       }); | ||||||
|                                                   if (values != null && |                                                   if (values != null && | ||||||
| @@ -346,10 +345,10 @@ class _ImportExportPageState extends State<ImportExportPage> { | |||||||
|                                                                   .requiredArgs |                                                                   .requiredArgs | ||||||
|                                                                   .map( |                                                                   .map( | ||||||
|                                                                       (e) => [ |                                                                       (e) => [ | ||||||
|                                                                             GeneratedFormItem(label: e) |                                                                             GeneratedFormItem(e, | ||||||
|  |                                                                                 label: e) | ||||||
|                                                                           ]) |                                                                           ]) | ||||||
|                                                                   .toList(), |                                                                   .toList(), | ||||||
|                                                           defaultValues: const [], |  | ||||||
|                                                         ); |                                                         ); | ||||||
|                                                       }); |                                                       }); | ||||||
|                                                   if (values != null) { |                                                   if (values != null) { | ||||||
|   | |||||||
| @@ -143,16 +143,11 @@ class _SettingsPageState extends State<SettingsPage> { | |||||||
|                 .toList(), |                 .toList(), | ||||||
|             onValueChanges: (values, valid, isBuilding) { |             onValueChanges: (values, valid, isBuilding) { | ||||||
|               if (valid) { |               if (valid) { | ||||||
|                 for (var i = 0; i < values.length; i++) { |                 values.forEach((key, value) { | ||||||
|                   settingsProvider.setSettingString( |                   settingsProvider.setSettingString(key, value); | ||||||
|                       e.additionalSourceSpecificSettingFormItems[i].id, |                 }); | ||||||
|                       values[i]); |  | ||||||
|               } |               } | ||||||
|               } |             }); | ||||||
|             }, |  | ||||||
|             defaultValues: e.additionalSourceSpecificSettingFormItems.map((e) { |  | ||||||
|               return settingsProvider.getSettingString(e.id) ?? ''; |  | ||||||
|             }).toList()); |  | ||||||
|       } else { |       } else { | ||||||
|         return Container(); |         return Container(); | ||||||
|       } |       } | ||||||
|   | |||||||
| @@ -313,7 +313,8 @@ class AppsProvider with ChangeNotifier { | |||||||
|         throw ObtainiumError(tr('appNotFound')); |         throw ObtainiumError(tr('appNotFound')); | ||||||
|       } |       } | ||||||
|       String? apkUrl; |       String? apkUrl; | ||||||
|       if (!apps[id]!.app.trackOnly) { |       var trackOnly = apps[id]!.app.additionalSettings['trackOnly'] == 'true'; | ||||||
|  |       if (!trackOnly) { | ||||||
|         apkUrl = await confirmApkUrl(apps[id]!.app, context); |         apkUrl = await confirmApkUrl(apps[id]!.app, context); | ||||||
|       } |       } | ||||||
|       if (apkUrl != null) { |       if (apkUrl != null) { | ||||||
| @@ -326,7 +327,7 @@ class AppsProvider with ChangeNotifier { | |||||||
|           appsToInstall.add(id); |           appsToInstall.add(id); | ||||||
|         } |         } | ||||||
|       } |       } | ||||||
|       if (apps[id]!.app.trackOnly) { |       if (trackOnly) { | ||||||
|         trackOnlyAppsToUpdate.add(id); |         trackOnlyAppsToUpdate.add(id); | ||||||
|       } |       } | ||||||
|     } |     } | ||||||
| @@ -451,9 +452,10 @@ class AppsProvider with ChangeNotifier { | |||||||
|   // Don't save changes, just return the object if changes were made (else null) |   // Don't save changes, just return the object if changes were made (else null) | ||||||
|   App? getCorrectedInstallStatusAppIfPossible(App app, AppInfo? installedInfo) { |   App? getCorrectedInstallStatusAppIfPossible(App app, AppInfo? installedInfo) { | ||||||
|     var modded = false; |     var modded = false; | ||||||
|     if (installedInfo == null && |     var trackOnly = app.additionalSettings['trackOnly'] == 'true'; | ||||||
|         app.installedVersion != null && |     var noVersionDetection = | ||||||
|         !app.trackOnly) { |         app.additionalSettings['noVersionDetection'] == 'true'; | ||||||
|  |     if (installedInfo == null && app.installedVersion != null && !trackOnly) { | ||||||
|       app.installedVersion = null; |       app.installedVersion = null; | ||||||
|       modded = true; |       modded = true; | ||||||
|     } else if (installedInfo?.versionName != null && |     } else if (installedInfo?.versionName != null && | ||||||
| @@ -461,7 +463,8 @@ class AppsProvider with ChangeNotifier { | |||||||
|       app.installedVersion = installedInfo!.versionName; |       app.installedVersion = installedInfo!.versionName; | ||||||
|       modded = true; |       modded = true; | ||||||
|     } else if (installedInfo?.versionName != null && |     } else if (installedInfo?.versionName != null && | ||||||
|         installedInfo!.versionName != app.installedVersion) { |         installedInfo!.versionName != app.installedVersion && | ||||||
|  |         !noVersionDetection) { | ||||||
|       String? correctedInstalledVersion = reconcileRealAndInternalVersions( |       String? correctedInstalledVersion = reconcileRealAndInternalVersions( | ||||||
|           installedInfo.versionName!, app.installedVersion!); |           installedInfo.versionName!, app.installedVersion!); | ||||||
|       if (correctedInstalledVersion != null) { |       if (correctedInstalledVersion != null) { | ||||||
| @@ -470,7 +473,8 @@ class AppsProvider with ChangeNotifier { | |||||||
|       } |       } | ||||||
|     } |     } | ||||||
|     if (app.installedVersion != null && |     if (app.installedVersion != null && | ||||||
|         app.installedVersion != app.latestVersion) { |         app.installedVersion != app.latestVersion && | ||||||
|  |         !noVersionDetection) { | ||||||
|       app.installedVersion = reconcileRealAndInternalVersions( |       app.installedVersion = reconcileRealAndInternalVersions( | ||||||
|               app.installedVersion!, app.latestVersion, |               app.installedVersion!, app.latestVersion, | ||||||
|               matchMode: true) ?? |               matchMode: true) ?? | ||||||
| @@ -623,12 +627,8 @@ class AppsProvider with ChangeNotifier { | |||||||
|     App newApp = await sourceProvider.getApp( |     App newApp = await sourceProvider.getApp( | ||||||
|         sourceProvider.getSource(currentApp.url), |         sourceProvider.getSource(currentApp.url), | ||||||
|         currentApp.url, |         currentApp.url, | ||||||
|         currentApp.additionalData, |         currentApp.additionalSettings, | ||||||
|         name: currentApp.name, |         currentApp: currentApp); | ||||||
|         id: currentApp.id, |  | ||||||
|         pinned: currentApp.pinned, |  | ||||||
|         trackOnly: currentApp.trackOnly, |  | ||||||
|         installedVersion: currentApp.installedVersion); |  | ||||||
|     if (currentApp.preferredApkIndex < newApp.apkUrls.length) { |     if (currentApp.preferredApkIndex < newApp.apkUrls.length) { | ||||||
|       newApp.preferredApkIndex = currentApp.preferredApkIndex; |       newApp.preferredApkIndex = currentApp.preferredApkIndex; | ||||||
|     } |     } | ||||||
|   | |||||||
| @@ -80,11 +80,11 @@ class DownloadNotification extends ObtainiumNotification { | |||||||
|   DownloadNotification(String appName, int progPercent) |   DownloadNotification(String appName, int progPercent) | ||||||
|       : super( |       : super( | ||||||
|             appName.hashCode, |             appName.hashCode, | ||||||
|             'Downloading $appName', |             tr('downloadingX', args: [appName]), | ||||||
|             '', |             '', | ||||||
|             'APP_DOWNLOADING', |             'APP_DOWNLOADING', | ||||||
|             'Downloading App', |             tr('downloadingX', args: [tr('app')]), | ||||||
|             'Notifies the user of the progress in downloading an App', |             tr('downloadNotifDescription'), | ||||||
|             Importance.low, |             Importance.low, | ||||||
|             onlyAlertOnce: true, |             onlyAlertOnce: true, | ||||||
|             progPercent: progPercent); |             progPercent: progPercent); | ||||||
|   | |||||||
| @@ -44,10 +44,9 @@ class App { | |||||||
|   late String latestVersion; |   late String latestVersion; | ||||||
|   List<String> apkUrls = []; |   List<String> apkUrls = []; | ||||||
|   late int preferredApkIndex; |   late int preferredApkIndex; | ||||||
|   late List<String> additionalData; |   late Map<String, String> additionalSettings; | ||||||
|   late DateTime? lastUpdateCheck; |   late DateTime? lastUpdateCheck; | ||||||
|   bool pinned = false; |   bool pinned = false; | ||||||
|   bool trackOnly = false; |  | ||||||
|   App( |   App( | ||||||
|       this.id, |       this.id, | ||||||
|       this.url, |       this.url, | ||||||
| @@ -57,17 +56,39 @@ class App { | |||||||
|       this.latestVersion, |       this.latestVersion, | ||||||
|       this.apkUrls, |       this.apkUrls, | ||||||
|       this.preferredApkIndex, |       this.preferredApkIndex, | ||||||
|       this.additionalData, |       this.additionalSettings, | ||||||
|       this.lastUpdateCheck, |       this.lastUpdateCheck, | ||||||
|       this.pinned, |       this.pinned); | ||||||
|       this.trackOnly); |  | ||||||
|  |  | ||||||
|   @override |   @override | ||||||
|   String toString() { |   String toString() { | ||||||
|     return 'ID: $id URL: $url INSTALLED: $installedVersion LATEST: $latestVersion APK: $apkUrls PREFERREDAPK: $preferredApkIndex ADDITIONALDATA: ${additionalData.toString()} LASTCHECK: ${lastUpdateCheck.toString()} PINNED $pinned'; |     return 'ID: $id URL: $url INSTALLED: $installedVersion LATEST: $latestVersion APK: $apkUrls PREFERREDAPK: $preferredApkIndex ADDITIONALSETTINGS: ${additionalSettings.toString()} LASTCHECK: ${lastUpdateCheck.toString()} PINNED $pinned'; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   factory App.fromJson(Map<String, dynamic> json) => App( |   factory App.fromJson(Map<String, dynamic> json) { | ||||||
|  |     var source = SourceProvider().getSource(json['url']); | ||||||
|  |     var formItems = source.combinedAppSpecificSettingFormItems | ||||||
|  |         .reduce((value, element) => [...value, ...element]); | ||||||
|  |     Map<String, String> additionalSettings = | ||||||
|  |         getDefaultValuesFromFormItems([formItems]); | ||||||
|  |     if (json['additionalSettings'] != null) { | ||||||
|  |       additionalSettings.addEntries( | ||||||
|  |           Map<String, String>.from(jsonDecode(json['additionalSettings'])) | ||||||
|  |               .entries); | ||||||
|  |     } | ||||||
|  |     // If needed, migrate old-style additionalData to new-style additionalSettings | ||||||
|  |     if (json['additionalData'] != null) { | ||||||
|  |       List<String> temp = List<String>.from(jsonDecode(json['additionalData'])); | ||||||
|  |       temp.asMap().forEach((i, value) { | ||||||
|  |         if (i < formItems.length) { | ||||||
|  |           additionalSettings[formItems[i].key] = value; | ||||||
|  |         } | ||||||
|  |       }); | ||||||
|  |       additionalSettings['trackOnly'] = (json['trackOnly'] ?? false).toString(); | ||||||
|  |       additionalSettings['noVersionDetection'] = | ||||||
|  |           (json['noVersionDetection'] ?? false).toString(); | ||||||
|  |     } | ||||||
|  |     return App( | ||||||
|         json['id'] as String, |         json['id'] as String, | ||||||
|         json['url'] as String, |         json['url'] as String, | ||||||
|         json['author'] as String, |         json['author'] as String, | ||||||
| @@ -79,17 +100,15 @@ class App { | |||||||
|         json['apkUrls'] == null |         json['apkUrls'] == null | ||||||
|             ? [] |             ? [] | ||||||
|             : List<String>.from(jsonDecode(json['apkUrls'])), |             : List<String>.from(jsonDecode(json['apkUrls'])), | ||||||
|       json['preferredApkIndex'] == null ? 0 : json['preferredApkIndex'] as int, |         json['preferredApkIndex'] == null | ||||||
|       json['additionalData'] == null |             ? 0 | ||||||
|           ? SourceProvider() |             : json['preferredApkIndex'] as int, | ||||||
|               .getSource(json['url']) |         additionalSettings, | ||||||
|               .additionalSourceAppSpecificDefaults |  | ||||||
|           : List<String>.from(jsonDecode(json['additionalData'])), |  | ||||||
|         json['lastUpdateCheck'] == null |         json['lastUpdateCheck'] == null | ||||||
|             ? null |             ? null | ||||||
|             : DateTime.fromMicrosecondsSinceEpoch(json['lastUpdateCheck']), |             : DateTime.fromMicrosecondsSinceEpoch(json['lastUpdateCheck']), | ||||||
|       json['pinned'] ?? false, |         json['pinned'] ?? false); | ||||||
|       json['trackOnly'] ?? false); |   } | ||||||
|  |  | ||||||
|   Map<String, dynamic> toJson() => { |   Map<String, dynamic> toJson() => { | ||||||
|         'id': id, |         'id': id, | ||||||
| @@ -100,10 +119,9 @@ class App { | |||||||
|         'latestVersion': latestVersion, |         'latestVersion': latestVersion, | ||||||
|         'apkUrls': jsonEncode(apkUrls), |         'apkUrls': jsonEncode(apkUrls), | ||||||
|         'preferredApkIndex': preferredApkIndex, |         'preferredApkIndex': preferredApkIndex, | ||||||
|         'additionalData': jsonEncode(additionalData), |         'additionalSettings': jsonEncode(additionalSettings), | ||||||
|         'lastUpdateCheck': lastUpdateCheck?.microsecondsSinceEpoch, |         'lastUpdateCheck': lastUpdateCheck?.microsecondsSinceEpoch, | ||||||
|         'pinned': pinned, |         'pinned': pinned | ||||||
|         'trackOnly': trackOnly |  | ||||||
|       }; |       }; | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -124,7 +142,7 @@ preStandardizeUrl(String url) { | |||||||
|   return url; |   return url; | ||||||
| } | } | ||||||
|  |  | ||||||
| const String noAPKFound = 'No APK found'; | String noAPKFound = tr('noAPKFound'); | ||||||
|  |  | ||||||
| List<String> getLinksFromParsedHTML( | List<String> getLinksFromParsedHTML( | ||||||
|         Document dom, RegExp hrefPattern, String prependToLinks) => |         Document dom, RegExp hrefPattern, String prependToLinks) => | ||||||
| @@ -137,6 +155,13 @@ List<String> getLinksFromParsedHTML( | |||||||
|         .map((e) => '$prependToLinks${e.attributes['href']!}') |         .map((e) => '$prependToLinks${e.attributes['href']!}') | ||||||
|         .toList(); |         .toList(); | ||||||
|  |  | ||||||
|  | Map<String, String> getDefaultValuesFromFormItems( | ||||||
|  |     List<List<GeneratedFormItem>> items) { | ||||||
|  |   return Map.fromEntries(items | ||||||
|  |       .map((row) => row.map((el) => MapEntry(el.key, el.defaultValue ?? ''))) | ||||||
|  |       .reduce((value, element) => [...value, ...element])); | ||||||
|  | } | ||||||
|  |  | ||||||
| class AppSource { | class AppSource { | ||||||
|   String? host; |   String? host; | ||||||
|   late String name; |   late String name; | ||||||
| @@ -151,23 +176,37 @@ class AppSource { | |||||||
|   } |   } | ||||||
|  |  | ||||||
|   Future<APKDetails> getLatestAPKDetails( |   Future<APKDetails> getLatestAPKDetails( | ||||||
|       String standardUrl, List<String> additionalData, |       String standardUrl, Map<String, String> additionalSettings) { | ||||||
|       {bool trackOnly = false}) { |  | ||||||
|     throw NotImplementedError(); |     throw NotImplementedError(); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   // Different Sources may need different kinds of additional data for Apps |   // Different Sources may need different kinds of additional data for Apps | ||||||
|   List<List<GeneratedFormItem>> additionalSourceAppSpecificFormItems = []; |   List<List<GeneratedFormItem>> additionalSourceAppSpecificSettingFormItems = | ||||||
|   List<String> additionalSourceAppSpecificDefaults = []; |       []; | ||||||
|  |  | ||||||
|   // Some additional data may be needed for Apps regardless of Source |   // Some additional data may be needed for Apps regardless of Source | ||||||
|   final List<GeneratedFormItem> additionalAppSpecificSourceAgnosticFormItems = [ |   final List<List<GeneratedFormItem>> | ||||||
|  |       additionalAppSpecificSourceAgnosticSettingFormItems = [ | ||||||
|  |     [ | ||||||
|       GeneratedFormItem( |       GeneratedFormItem( | ||||||
|  |         'trackOnly', | ||||||
|         label: tr('trackOnly'), |         label: tr('trackOnly'), | ||||||
|         type: FormItemType.bool, |         type: FormItemType.bool, | ||||||
|         key: 'trackOnlyFormItemKey') |       ) | ||||||
|  |     ], | ||||||
|  |     [ | ||||||
|  |       GeneratedFormItem('noVersionDetection', | ||||||
|  |           label: tr('noVersionDetection'), type: FormItemType.bool) | ||||||
|  |     ] | ||||||
|   ]; |   ]; | ||||||
|   final List<String> additionalAppSpecificSourceAgnosticDefaults = ['']; |  | ||||||
|  |   // Previous 2 variables combined into one at runtime for convenient usage | ||||||
|  |   List<List<GeneratedFormItem>> get combinedAppSpecificSettingFormItems { | ||||||
|  |     return [ | ||||||
|  |       ...additionalSourceAppSpecificSettingFormItems, | ||||||
|  |       ...additionalAppSpecificSourceAgnosticSettingFormItems | ||||||
|  |     ]; | ||||||
|  |   } | ||||||
|  |  | ||||||
|   // Some Sources may have additional settings at the Source level (not specific to Apps) - these use SettingsProvider |   // Some Sources may have additional settings at the Source level (not specific to Apps) - these use SettingsProvider | ||||||
|   List<GeneratedFormItem> additionalSourceSpecificSettingFormItems = []; |   List<GeneratedFormItem> additionalSourceSpecificSettingFormItems = []; | ||||||
| @@ -186,7 +225,7 @@ class AppSource { | |||||||
|   } |   } | ||||||
|  |  | ||||||
|   String? tryInferringAppId(String standardUrl, |   String? tryInferringAppId(String standardUrl, | ||||||
|       {List<String> additionalData = const []}) { |       {Map<String, String> additionalSettings = const {}}) { | ||||||
|     return null; |     return null; | ||||||
|   } |   } | ||||||
| } | } | ||||||
| @@ -246,8 +285,8 @@ class SourceProvider { | |||||||
|     return source; |     return source; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   bool ifSourceAppsRequireAdditionalData(AppSource source) { |   bool ifRequiredAppSpecificSettingsExist(AppSource source) { | ||||||
|     for (var row in source.additionalSourceAppSpecificFormItems) { |     for (var row in source.combinedAppSpecificSettingFormItems) { | ||||||
|       for (var element in row) { |       for (var element in row) { | ||||||
|         if (element.required && element.opts == null) { |         if (element.required && element.opts == null) { | ||||||
|           return true; |           return true; | ||||||
| @@ -274,37 +313,44 @@ class SourceProvider { | |||||||
|     return true; |     return true; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   Future<App> getApp(AppSource source, String url, List<String> additionalData, |   Future<App> getApp( | ||||||
|       {String name = '', |       AppSource source, String url, Map<String, String> additionalSettings, | ||||||
|       String? id, |       {App? currentApp, | ||||||
|       bool pinned = false, |       bool trackOnlyOverride = false, | ||||||
|       bool trackOnly = false, |       noVersionDetectionOverride = false}) async { | ||||||
|       String? installedVersion}) async { |     if (trackOnlyOverride) { | ||||||
|  |       additionalSettings['trackOnly'] = 'true'; | ||||||
|  |     } | ||||||
|  |     if (noVersionDetectionOverride) { | ||||||
|  |       additionalSettings['noVersionDetection'] = 'true'; | ||||||
|  |     } | ||||||
|  |     var trackOnly = currentApp?.additionalSettings['trackOnly'] == 'true'; | ||||||
|     String standardUrl = source.standardizeURL(preStandardizeUrl(url)); |     String standardUrl = source.standardizeURL(preStandardizeUrl(url)); | ||||||
|     APKDetails apk = await source |     APKDetails apk = | ||||||
|         .getLatestAPKDetails(standardUrl, additionalData, trackOnly: trackOnly); |         await source.getLatestAPKDetails(standardUrl, additionalSettings); | ||||||
|     if (apk.apkUrls.isEmpty && !trackOnly) { |     if (apk.apkUrls.isEmpty && !trackOnly) { | ||||||
|       throw NoAPKError(); |       throw NoAPKError(); | ||||||
|     } |     } | ||||||
|     String apkVersion = apk.version.replaceAll('/', '-'); |     String apkVersion = apk.version.replaceAll('/', '-'); | ||||||
|  |     var name = currentApp?.name.trim() ?? | ||||||
|  |         apk.names.name[0].toUpperCase() + apk.names.name.substring(1); | ||||||
|     return App( |     return App( | ||||||
|         id ?? |         currentApp?.id ?? | ||||||
|             source.tryInferringAppId(standardUrl, |             source.tryInferringAppId(standardUrl, | ||||||
|                 additionalData: additionalData) ?? |                 additionalSettings: additionalSettings) ?? | ||||||
|             generateTempID(apk.names, source), |             generateTempID(apk.names, source), | ||||||
|         standardUrl, |         standardUrl, | ||||||
|         apk.names.author[0].toUpperCase() + apk.names.author.substring(1), |         apk.names.author[0].toUpperCase() + apk.names.author.substring(1), | ||||||
|         name.trim().isNotEmpty |         name.trim().isNotEmpty | ||||||
|             ? name |             ? name | ||||||
|             : apk.names.name[0].toUpperCase() + apk.names.name.substring(1), |             : apk.names.name[0].toUpperCase() + apk.names.name.substring(1), | ||||||
|         installedVersion, |         currentApp?.installedVersion, | ||||||
|         apkVersion, |         apkVersion, | ||||||
|         apk.apkUrls, |         apk.apkUrls, | ||||||
|         apk.apkUrls.length - 1, |         apk.apkUrls.length - 1, | ||||||
|         additionalData, |         additionalSettings, | ||||||
|         DateTime.now(), |         DateTime.now(), | ||||||
|         pinned, |         currentApp?.pinned ?? false); | ||||||
|         trackOnly); |  | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   // Returns errors in [results, errors] instead of throwing them |   // Returns errors in [results, errors] instead of throwing them | ||||||
| @@ -316,7 +362,10 @@ class SourceProvider { | |||||||
|       try { |       try { | ||||||
|         var source = getSource(url); |         var source = getSource(url); | ||||||
|         apps.add(await getApp( |         apps.add(await getApp( | ||||||
|             source, url, source.additionalSourceAppSpecificDefaults)); |             source, | ||||||
|  |             url, | ||||||
|  |             getDefaultValuesFromFormItems( | ||||||
|  |                 source.combinedAppSpecificSettingFormItems))); | ||||||
|       } catch (e) { |       } catch (e) { | ||||||
|         errors.addAll(<String, dynamic>{url: e}); |         errors.addAll(<String, dynamic>{url: e}); | ||||||
|       } |       } | ||||||
|   | |||||||
							
								
								
									
										26
									
								
								pubspec.lock
									
									
									
									
									
								
							
							
						
						
									
										26
									
								
								pubspec.lock
									
									
									
									
									
								
							| @@ -182,7 +182,7 @@ packages: | |||||||
|       name: file_picker |       name: file_picker | ||||||
|       url: "https://pub.dartlang.org" |       url: "https://pub.dartlang.org" | ||||||
|     source: hosted |     source: hosted | ||||||
|     version: "5.2.3" |     version: "5.2.4" | ||||||
|   flutter: |   flutter: | ||||||
|     dependency: "direct main" |     dependency: "direct main" | ||||||
|     description: flutter |     description: flutter | ||||||
| @@ -215,14 +215,14 @@ packages: | |||||||
|       name: flutter_local_notifications |       name: flutter_local_notifications | ||||||
|       url: "https://pub.dartlang.org" |       url: "https://pub.dartlang.org" | ||||||
|     source: hosted |     source: hosted | ||||||
|     version: "12.0.4" |     version: "13.0.0" | ||||||
|   flutter_local_notifications_linux: |   flutter_local_notifications_linux: | ||||||
|     dependency: transitive |     dependency: transitive | ||||||
|     description: |     description: | ||||||
|       name: flutter_local_notifications_linux |       name: flutter_local_notifications_linux | ||||||
|       url: "https://pub.dartlang.org" |       url: "https://pub.dartlang.org" | ||||||
|     source: hosted |     source: hosted | ||||||
|     version: "2.0.0" |     version: "3.0.0" | ||||||
|   flutter_local_notifications_platform_interface: |   flutter_local_notifications_platform_interface: | ||||||
|     dependency: transitive |     dependency: transitive | ||||||
|     description: |     description: | ||||||
| @@ -258,7 +258,7 @@ packages: | |||||||
|       name: fluttertoast |       name: fluttertoast | ||||||
|       url: "https://pub.dartlang.org" |       url: "https://pub.dartlang.org" | ||||||
|     source: hosted |     source: hosted | ||||||
|     version: "8.1.1" |     version: "8.1.2" | ||||||
|   html: |   html: | ||||||
|     dependency: "direct main" |     dependency: "direct main" | ||||||
|     description: |     description: | ||||||
| @@ -510,7 +510,7 @@ packages: | |||||||
|       name: provider |       name: provider | ||||||
|       url: "https://pub.dartlang.org" |       url: "https://pub.dartlang.org" | ||||||
|     source: hosted |     source: hosted | ||||||
|     version: "6.0.4" |     version: "6.0.5" | ||||||
|   share_plus: |   share_plus: | ||||||
|     dependency: "direct main" |     dependency: "direct main" | ||||||
|     description: |     description: | ||||||
| @@ -552,7 +552,7 @@ packages: | |||||||
|       name: shared_preferences_linux |       name: shared_preferences_linux | ||||||
|       url: "https://pub.dartlang.org" |       url: "https://pub.dartlang.org" | ||||||
|     source: hosted |     source: hosted | ||||||
|     version: "2.1.1" |     version: "2.1.2" | ||||||
|   shared_preferences_macos: |   shared_preferences_macos: | ||||||
|     dependency: transitive |     dependency: transitive | ||||||
|     description: |     description: | ||||||
| @@ -580,7 +580,7 @@ packages: | |||||||
|       name: shared_preferences_windows |       name: shared_preferences_windows | ||||||
|       url: "https://pub.dartlang.org" |       url: "https://pub.dartlang.org" | ||||||
|     source: hosted |     source: hosted | ||||||
|     version: "2.1.1" |     version: "2.1.2" | ||||||
|   sky_engine: |   sky_engine: | ||||||
|     dependency: transitive |     dependency: transitive | ||||||
|     description: flutter |     description: flutter | ||||||
| @@ -599,7 +599,7 @@ packages: | |||||||
|       name: sqflite |       name: sqflite | ||||||
|       url: "https://pub.dartlang.org" |       url: "https://pub.dartlang.org" | ||||||
|     source: hosted |     source: hosted | ||||||
|     version: "2.2.1" |     version: "2.2.2" | ||||||
|   sqflite_common: |   sqflite_common: | ||||||
|     dependency: transitive |     dependency: transitive | ||||||
|     description: |     description: | ||||||
| @@ -739,35 +739,35 @@ packages: | |||||||
|       name: webview_flutter |       name: webview_flutter | ||||||
|       url: "https://pub.dartlang.org" |       url: "https://pub.dartlang.org" | ||||||
|     source: hosted |     source: hosted | ||||||
|     version: "3.0.4" |     version: "4.0.0" | ||||||
|   webview_flutter_android: |   webview_flutter_android: | ||||||
|     dependency: transitive |     dependency: transitive | ||||||
|     description: |     description: | ||||||
|       name: webview_flutter_android |       name: webview_flutter_android | ||||||
|       url: "https://pub.dartlang.org" |       url: "https://pub.dartlang.org" | ||||||
|     source: hosted |     source: hosted | ||||||
|     version: "2.10.4" |     version: "3.0.0" | ||||||
|   webview_flutter_platform_interface: |   webview_flutter_platform_interface: | ||||||
|     dependency: transitive |     dependency: transitive | ||||||
|     description: |     description: | ||||||
|       name: webview_flutter_platform_interface |       name: webview_flutter_platform_interface | ||||||
|       url: "https://pub.dartlang.org" |       url: "https://pub.dartlang.org" | ||||||
|     source: hosted |     source: hosted | ||||||
|     version: "1.9.5" |     version: "2.0.0" | ||||||
|   webview_flutter_wkwebview: |   webview_flutter_wkwebview: | ||||||
|     dependency: transitive |     dependency: transitive | ||||||
|     description: |     description: | ||||||
|       name: webview_flutter_wkwebview |       name: webview_flutter_wkwebview | ||||||
|       url: "https://pub.dartlang.org" |       url: "https://pub.dartlang.org" | ||||||
|     source: hosted |     source: hosted | ||||||
|     version: "2.9.5" |     version: "3.0.0" | ||||||
|   win32: |   win32: | ||||||
|     dependency: transitive |     dependency: transitive | ||||||
|     description: |     description: | ||||||
|       name: win32 |       name: win32 | ||||||
|       url: "https://pub.dartlang.org" |       url: "https://pub.dartlang.org" | ||||||
|     source: hosted |     source: hosted | ||||||
|     version: "3.1.2" |     version: "3.1.3" | ||||||
|   xdg_directories: |   xdg_directories: | ||||||
|     dependency: transitive |     dependency: transitive | ||||||
|     description: |     description: | ||||||
|   | |||||||
| @@ -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 | # 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 | # 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. | # of the product and file versions while build-number is used as the build suffix. | ||||||
| version: 0.8.21+85 # When changing this, update the tag in main() accordingly | version: 0.8.22+86 # When changing this, update the tag in main() accordingly | ||||||
|  |  | ||||||
| environment: | environment: | ||||||
|   sdk: '>=2.18.2 <3.0.0' |   sdk: '>=2.18.2 <3.0.0' | ||||||
| @@ -38,10 +38,10 @@ dependencies: | |||||||
|   cupertino_icons: ^1.0.5 |   cupertino_icons: ^1.0.5 | ||||||
|   path_provider: ^2.0.11 |   path_provider: ^2.0.11 | ||||||
|   flutter_fgbg: ^0.2.0 # Try removing reliance on this |   flutter_fgbg: ^0.2.0 # Try removing reliance on this | ||||||
|   flutter_local_notifications: ^12.0.0 |   flutter_local_notifications: ^13.0.0 | ||||||
|   provider: ^6.0.3 |   provider: ^6.0.3 | ||||||
|   http: ^0.13.5 |   http: ^0.13.5 | ||||||
|   webview_flutter: ^3.0.4 |   webview_flutter: ^4.0.0 | ||||||
|   dynamic_color: ^1.5.4 |   dynamic_color: ^1.5.4 | ||||||
|   html: ^0.15.0 |   html: ^0.15.0 | ||||||
|   shared_preferences: ^2.0.15 |   shared_preferences: ^2.0.15 | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user