mirror of
				https://github.com/ImranR98/Obtainium.git
				synced 2025-10-30 21:13:28 +01:00 
			
		
		
		
	Compare commits
	
		
			23 Commits
		
	
	
		
			v0.8.21-be
			...
			v0.8.23-be
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|  | e2f99c5e71 | ||
|  | 1f582d239b | ||
|  | 5e6b00718e | ||
|  | 56594e6b19 | ||
|  | bbcc3ff9b3 | ||
|  | 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", | ||||
|     "includePrereleases": "Vorabversionen einbeziehen", | ||||
|     "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", | ||||
|     "noDescription": "Keine Beschreibung", | ||||
|     "cancel": "Abbrechen", | ||||
| @@ -43,7 +43,7 @@ | ||||
|     "cancelled": "Abgebrochen", | ||||
|     "appAlreadyAdded": "App bereits hinzugefügt", | ||||
|     "alreadyUpToDateQuestion": "App bereits auf dem neuesten Stand?", | ||||
|     "addApp": "App hinzugefügt", | ||||
|     "addApp": "App hinzufügen", | ||||
|     "appSourceURL": "Quell-URL der App", | ||||
|     "error": "Fehler", | ||||
|     "add": "Hinzufügen", | ||||
| @@ -92,7 +92,7 @@ | ||||
|     "filterActive": "Filter *", | ||||
|     "filterApps": "Apps filtern", | ||||
|     "appName": "App Name", | ||||
|     "author": "Ersteller", | ||||
|     "author": "Autor", | ||||
|     "upToDateApps": "Apps mit aktueller Version", | ||||
|     "nonInstalledApps": "Nicht installierte Apps", | ||||
|     "importExport": "Import/Export", | ||||
| @@ -134,7 +134,7 @@ | ||||
|     "neverManualOnly": "Nie - nur manuell", | ||||
|     "appearance": "Aussehen", | ||||
|     "showWebInAppView": "Quellwebseite in der App-Ansicht anzeigen", | ||||
|     "pinUpdates": "Aktualisierungen in der App-Ansicht oben anheften", | ||||
|     "pinUpdates": "Apps mit Aktualisierungen oben anheften", | ||||
|     "updates": "Aktualisiert", | ||||
|     "sourceSpecific": "Quellenspezifisch", | ||||
|     "appSource": "App-Quelle", | ||||
| @@ -188,6 +188,17 @@ | ||||
|     "steam": "Steam", | ||||
|     "steamMobile": "Steam Mobile", | ||||
|     "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": { | ||||
|         "one": "Zu viele Anfragen (Rate begrenzt) - versuchen Sie es in {} Minute erneut", | ||||
|         "other": "Zu viele Anfragen (Rate begrenzt) - versuchen Sie es in {} Minuten erneut" | ||||
|   | ||||
| @@ -37,7 +37,7 @@ | ||||
|     "xIsTrackOnly": "{} is Track-Only", | ||||
|     "source": "Source", | ||||
|     "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.", | ||||
|     "trackOnlyAppDescription": "The App will be tracked for updates, but Obtainium will not be able to download or install it.", | ||||
|     "cancelled": "Cancelled", | ||||
| @@ -188,6 +188,17 @@ | ||||
|     "steam": "Steam", | ||||
|     "steamMobile": "Steam Mobile", | ||||
|     "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": { | ||||
|         "one": "Too many requests (rate limited) - try again in {} minute", | ||||
|         "other": "Too many requests (rate limited) - try again in {} minutes" | ||||
|   | ||||
| @@ -37,7 +37,7 @@ | ||||
|     "xIsTrackOnly": "{} csak nyomon követhető", | ||||
|     "source": "Forrás", | ||||
|     "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.", | ||||
|     "trackOnlyAppDescription": "Az alkalmazás frissítéseit nyomon követi, de az Obtainium nem tudja letölteni vagy telepíteni.", | ||||
|     "cancelled": "Törölve", | ||||
| @@ -188,6 +188,17 @@ | ||||
|     "steam": "Steam", | ||||
|     "steamMobile": "Steam Mobile", | ||||
|     "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": { | ||||
|         "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" | ||||
| @@ -232,4 +243,4 @@ | ||||
|         "one": "A(z) {} és 1 további alkalmazás frissítve.", | ||||
|         "other": "{} és további {} alkalmazás frissítve." | ||||
|     } | ||||
| } | ||||
| } | ||||
| @@ -22,13 +22,13 @@ | ||||
|     "githubPATFormat": "username:token", | ||||
|     "githubPATLinkText": "Informazioni su GitHub PAT", | ||||
|     "includePrereleases": "Includi prerelease", | ||||
|     "fallbackToOlderReleases": "Ripiega su release datate", | ||||
|     "filterReleaseTitlesByRegEx": "Filtra le release con le espressioni regolari", | ||||
|     "invalidRegEx": "Espressione regolare invalida", | ||||
|     "fallbackToOlderReleases": "Ripiega su release precedenti", | ||||
|     "filterReleaseTitlesByRegEx": "Filtra release con espressioni regolari", | ||||
|     "invalidRegEx": "Espressione regolare non valida", | ||||
|     "noDescription": "Descrizione assente", | ||||
|     "cancel": "Annulla", | ||||
|     "continue": "Continua", | ||||
|     "requiredInBrackets": "(Richiesto)", | ||||
|     "requiredInBrackets": "(richiesto)", | ||||
|     "dropdownNoOptsError": "ERRORE: LA TENDINA DEVE AVERE ALMENO UN'OPZIONE", | ||||
|     "colour": "Colore", | ||||
|     "githubStarredRepos": "i repository stellati da GitHub", | ||||
| @@ -47,7 +47,7 @@ | ||||
|     "appSourceURL": "URL della fonte dell'App", | ||||
|     "error": "Errore", | ||||
|     "add": "Aggiungi", | ||||
|     "searchSomeSourcesLabel": "Cerca (disponibile solo per alcune fonti)", | ||||
|     "searchSomeSourcesLabel": "Cerca (solo per alcune fonti)", | ||||
|     "search": "Cerca", | ||||
|     "additionalOptsFor": "Opzioni aggiuntive per {}", | ||||
|     "supportedSourcesBelow": "Fonti supportate:", | ||||
| @@ -86,8 +86,8 @@ | ||||
|     "shareSelectedAppURLs": "Condividi gli URL delle App selezionate", | ||||
|     "resetInstallStatus": "Ripristina lo stato d'installazione", | ||||
|     "more": "Di più", | ||||
|     "removeOutdatedFilter": "Rimuovi il filtro per le App datate", | ||||
|     "showOutdatedOnly": "Mostra solo le App datate", | ||||
|     "removeOutdatedFilter": "Rimuovi il filtro per le App non aggiornate", | ||||
|     "showOutdatedOnly": "Mostra solo le App non aggiornate", | ||||
|     "filter": "Filtri", | ||||
|     "filterActive": "Filtri *", | ||||
|     "filterApps": "Filtra App", | ||||
| @@ -106,7 +106,7 @@ | ||||
|     "searchQuery": "Stringa di ricerca", | ||||
|     "appURLList": "Lista di URL delle App", | ||||
|     "line": "Linea", | ||||
|     "searchX": "Cerca {}", | ||||
|     "searchX": "Cerca su {}", | ||||
|     "noResults": "Nessun risultato trovato", | ||||
|     "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.", | ||||
| @@ -120,7 +120,7 @@ | ||||
|     "theme": "Tema", | ||||
|     "dark": "Scuro", | ||||
|     "light": "Chiaro", | ||||
|     "followSystem": "Segui il sistema", | ||||
|     "followSystem": "Segui sistema", | ||||
|     "obtainium": "Obtainium", | ||||
|     "materialYou": "Material You", | ||||
|     "appSortBy": "App ordinate per", | ||||
| @@ -133,10 +133,10 @@ | ||||
|     "bgUpdateCheckInterval": "Intervallo di controllo degli aggiornamenti in background", | ||||
|     "neverManualOnly": "Mai - Solo manuale", | ||||
|     "appearance": "Aspetto", | ||||
|     "showWebInAppView": "Mostra la pagina web dell'App se selezionata", | ||||
|     "pinUpdates": "Fissa in alto gli aggiornamenti disponibili nella pagina delle App", | ||||
|     "showWebInAppView": "Mostra pagina web dell'App se selezionata", | ||||
|     "pinUpdates": "Fissa in alto gli aggiornamenti disponibili", | ||||
|     "updates": "Aggiornato", | ||||
|     "sourceSpecific": "Specifico per la fonte", | ||||
|     "sourceSpecific": "Specifiche per la fonte", | ||||
|     "appSource": "Sorgente dell'App", | ||||
|     "noLogs": "Nessun log", | ||||
|     "appLogs": "Log dell'App", | ||||
| @@ -184,10 +184,21 @@ | ||||
|     "appIdOrName": "ID o nome dell'App", | ||||
|     "appWithIdOrNameNotFound": "Non è stata trovata alcuna App con quell'ID o nome", | ||||
|     "reposHaveMultipleApps": "I repository possono contenere più App", | ||||
|     "fdroidThirdPartyRepo": "Repository di terze parti di F-Droid", | ||||
|     "fdroidThirdPartyRepo": "Repository F-Droid di terze parti", | ||||
|     "steam": "Steam", | ||||
|     "steamMobile": "Steam Mobile", | ||||
|     "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": { | ||||
|         "one": "Troppe richieste (traffico limitato) - riprova tra {} minuto", | ||||
|         "other": "Troppe richieste (traffico limitato) - riprova tra {} minuti" | ||||
| @@ -232,4 +243,4 @@ | ||||
|         "one": "{} e un'altra App sono state aggiornate.", | ||||
|         "other": "{} e altre {} App sono state aggiornate." | ||||
|     } | ||||
| } | ||||
| } | ||||
| @@ -29,16 +29,16 @@ | ||||
|     "cancel": "キャンセル", | ||||
|     "continue": "続ける", | ||||
|     "requiredInBrackets": "(必須)", | ||||
|     "dropdownNoOptsError": "エラー: ドロップダウンには、少なくとも1つのoptが必要です。", | ||||
|     "dropdownNoOptsError": "エラー: ドロップダウンには、少なくとも1つのオプションが必要です", | ||||
|     "colour": "カラー", | ||||
|     "githubStarredRepos": "Githubでスターしたリポジトリ", | ||||
|     "uname": "ユーザー名", | ||||
|     "wrongArgNum": "提供する引数の数が間違っています", | ||||
|     "xIsTrackOnly": "{} は'追跡のみ'です", | ||||
|     "xIsTrackOnly": "{} は「追跡のみ」です", | ||||
|     "source": "ソース", | ||||
|     "app": "アプリ", | ||||
|     "appsFromSourceAreTrackOnly": "このソースからのアプリは'追跡のみ'です'。", | ||||
|     "youPickedTrackOnly": "'追跡のみ'を選択しています", | ||||
|     "appsFromSourceAreTrackOnly": "このソースからのアプリは「追跡のみ」です。", | ||||
|     "youPickedTrackOnly": "「追跡のみ」を選択しています", | ||||
|     "trackOnlyAppDescription": "アプリのアップデートは追跡されますが、Obtainiumはアプリのダウンロードやインストールをすることはできません。", | ||||
|     "cancelled": "キャンセルしました", | ||||
|     "appAlreadyAdded": "アプリはすでに追加されています", | ||||
| @@ -152,11 +152,11 @@ | ||||
|     "sourceIsXButPackageFromYPrompt": "アプリのソースは'{}'ですが、リリースパッケージは'{}'から来ています。続行しますか?", | ||||
|     "updatesAvailable": "アップデートが利用可能", | ||||
|     "updatesAvailableNotifDescription": "Obtainiumが追跡している1つまたは複数のアプリのアップデートが利用可能であることをユーザーに通知する", | ||||
|     "noNewUpdates": "新しいアップデートはありません。", | ||||
|     "noNewUpdates": "新しいアップデートはありません", | ||||
|     "xHasAnUpdate": "{}のアップデートが利用可能です", | ||||
|     "appsUpdated": "アプリをアップデートしました", | ||||
|     "appsUpdatedNotifDescription": "1つまたは複数のAppのアップデートがバックグラウンドで適用されたことをユーザーに通知する", | ||||
|     "xWasUpdatedToY": "{}が{}にアップデートされました。", | ||||
|     "xWasUpdatedToY": "{}が{}にアップデートされました", | ||||
|     "errorCheckingUpdates": "アップデート確認中のエラー", | ||||
|     "errorCheckingUpdatesNotifDescription": "バックグラウンドでのアップデート確認に失敗した際に表示される通知", | ||||
|     "appsRemoved": "削除されたアプリ", | ||||
| @@ -173,7 +173,7 @@ | ||||
|     "versionCorrectionDisabled": "バージョン補正無効 (プラグインが動作していません)", | ||||
|     "unknown": "不明", | ||||
|     "none": "なし", | ||||
|     "never": "Never", | ||||
|     "never": "しない", | ||||
|     "latestVersionX": "最新のバージョン: {}", | ||||
|     "installedVersionX": "インストールされたバージョン: {}", | ||||
|     "lastUpdateCheckX": "最終アップデート確認: {}", | ||||
| @@ -181,13 +181,24 @@ | ||||
|     "removeAppQuestion": "アプリを削除しますか?", | ||||
|     "yesMarkUpdated": "はい、アップデート済みとしてマークします", | ||||
|     "fdroid": "F-Droid", | ||||
|     "appIdOrName": "アプリIDまたは名前", | ||||
|     "appIdOrName": "アプリのIDまたは名前", | ||||
|     "appWithIdOrNameNotFound": "そのIDや名前を持つアプリは見つかりませんでした", | ||||
|     "reposHaveMultipleApps": "リポジトリには複数のアプリが含まれることがあります", | ||||
|     "fdroidThirdPartyRepo": "F-Droid Third-Party Repo", | ||||
|     "steam": "Steam", | ||||
|     "steamMobile": "Steam Mobile", | ||||
|     "steamChat": "Steam Chat", | ||||
|     "install": "インストール", | ||||
|     "markInstalled": "インストール済みとしてマークする", | ||||
|     "update": "アップデート", | ||||
|     "markUpdated": "アップデート済みとしてマークする", | ||||
|     "additionalOptions": "追加のオプション", | ||||
|     "disableVersionDetection": "バージョン検出を無効にする", | ||||
|     "noVersionDetectionExplanation": "このオプションは、バージョン検出が正しく機能しないアプリにのみ使用する必要があります。", | ||||
|     "downloadingX": "{} をダウンロード中", | ||||
|     "downloadNotifDescription": "アプリのダウンロード状況を通知する", | ||||
|     "noAPKFound": "APKが見つかりません", | ||||
|     "noVersionDetection": "バージョン検出を行わない", | ||||
|     "tooManyRequestsTryAgainInMinutes": { | ||||
|         "one": "リクエストが多すぎます(レート制限)- {}分後に再試行してください", | ||||
|         "other": "リクエストが多すぎます(レート制限)- {}分後に再試行してください" | ||||
| @@ -225,11 +236,11 @@ | ||||
|         "other": "{n}個のログをクリアしました (前 = {before}, 後 = {after})" | ||||
|     }, | ||||
|     "xAndNMoreUpdatesAvailable": { | ||||
|         "one": "{}とさらに{}個のアプリのアップデートが利用可能です。", | ||||
|         "other": "{}とさらに{}個のアプリのアップデートが利用可能です。" | ||||
|         "one": "{}とさらに{}個のアプリのアップデートが利用可能です", | ||||
|         "other": "{}とさらに{}個のアプリのアップデートが利用可能です" | ||||
|     }, | ||||
|     "xAndNMoreUpdatesInstalled": { | ||||
|         "one": "{}とさらに{}個のアプリがアップデートされました。", | ||||
|         "other": "{}とさらに{}個のアプリがアップデートされました。" | ||||
|         "one": "{}とさらに{}個のアプリがアップデートされました", | ||||
|         "other": "{}とさらに{}個のアプリがアップデートされました" | ||||
|     } | ||||
| } | ||||
| } | ||||
| @@ -22,9 +22,9 @@ | ||||
|     "githubPATFormat": "username:token", | ||||
|     "githubPATLinkText": "关于 GitHub 个人访问令牌", | ||||
|     "includePrereleases": "包含预发布版", | ||||
|     "fallbackToOlderReleases": "回落到旧版", | ||||
|     "filterReleaseTitlesByRegEx": "通过正则表达式过滤发布标题", | ||||
|     "invalidRegEx": "无效的正则表达式", | ||||
|     "fallbackToOlderReleases": "回退到旧版", | ||||
|     "filterReleaseTitlesByRegEx": "使用正则以过滤发布标题", | ||||
|     "invalidRegEx": "表达式无效", | ||||
|     "noDescription": "无描述", | ||||
|     "cancel": "取消", | ||||
|     "continue": "继续", | ||||
| @@ -109,7 +109,7 @@ | ||||
|     "searchX": "搜索 {}", | ||||
|     "noResults": "无结果", | ||||
|     "importX": "导入 {}", | ||||
|     "importedAppsIdDisclaimer": "导入的应用程序可能不正确地显示为未安装。要解决这个问题,请通过 Obtainium 重新安装它们。", | ||||
|     "importedAppsIdDisclaimer": "导入的应用程序可能显示为未安装。要解决这个问题,请通过 Obtainium 重新安装它们。", | ||||
|     "importErrors": "导入错误", | ||||
|     "importedXOfYApps": "{} 中的 {} 个应用已导入", | ||||
|     "followingURLsHadErrors": "以下 URL 有错误:", | ||||
| @@ -134,7 +134,7 @@ | ||||
|     "neverManualOnly": "手动", | ||||
|     "appearance": "外观", | ||||
|     "showWebInAppView": "在应用来源页显示网页", | ||||
|     "pinUpdates": "将需要更新的应用固定到顶部", | ||||
|     "pinUpdates": "需更新的应用置顶", | ||||
|     "updates": "检查间隔", | ||||
|     "sourceSpecific": "Github 访问令牌", | ||||
|     "appSource": "源代码", | ||||
| @@ -145,9 +145,9 @@ | ||||
|     "appNotFound": "未找到应用", | ||||
|     "obtainiumExportHyphenatedLowercase": "obtainium-导出", | ||||
|     "pickAnAPK": "选择一个安装包", | ||||
|     "appHasMoreThanOnePackage": "{} 有多于一个安装包:", | ||||
|     "deviceSupportsXArch": "你的设备支持 {} CPU 架构", | ||||
|     "deviceSupportsFollowingArchs": "你的设备支持以下 CPU 架构:", | ||||
|     "appHasMoreThanOnePackage": "{} 有多个架构可用:", | ||||
|     "deviceSupportsXArch": "你的设备支持 {} 架构", | ||||
|     "deviceSupportsFollowingArchs": "你的设备支持以下架构:", | ||||
|     "warning": "警告", | ||||
|     "sourceIsXButPackageFromYPrompt": "此应用来源是 '{}' 但更新包来自 '{}'。 继续吗?", | ||||
|     "updatesAvailable": "更新可用", | ||||
| @@ -188,6 +188,17 @@ | ||||
|     "steam": "Steam", | ||||
|     "steamMobile": "Steam Mobile", | ||||
|     "steamChat": "Steam Chat", | ||||
|     "install": "安装", | ||||
|     "markInstalled": "标记为已安装", | ||||
|     "update": "更新", | ||||
|     "markUpdated": "标记为已更新", | ||||
|     "additionalOptions": "附加选项", | ||||
|     "disableVersionDetection": "关闭版本检测", | ||||
|     "noVersionDetectionExplanation": "此选项应只用于版本检测不能工作的应用程序", | ||||
|     "downloadingX": "下载中 {}", | ||||
|     "downloadNotifDescription": "通知用户下载进度", | ||||
|     "noAPKFound": "未找到安装包", | ||||
|     "noVersionDetection": "无版本检测", | ||||
|     "tooManyRequestsTryAgainInMinutes": { | ||||
|         "one": "请求过多 (API 限制) - 在 {} 分钟后重试", | ||||
|         "other": "请求过多 (API 限制) - 在 {} 分钟后重试" | ||||
|   | ||||
| @@ -25,8 +25,9 @@ class APKMirror extends AppSource { | ||||
|  | ||||
|   @override | ||||
|   Future<APKDetails> getLatestAPKDetails( | ||||
|       String standardUrl, List<String> additionalData, | ||||
|       {bool trackOnly = false}) async { | ||||
|     String standardUrl, | ||||
|     Map<String, String> additionalSettings, | ||||
|   ) async { | ||||
|     Response res = await get(Uri.parse('$standardUrl/feed')); | ||||
|     if (res.statusCode == 200) { | ||||
|       String? titleString = parse(res.body) | ||||
|   | ||||
| @@ -32,7 +32,7 @@ class FDroid extends AppSource { | ||||
|  | ||||
|   @override | ||||
|   String? tryInferringAppId(String standardUrl, | ||||
|       {List<String> additionalData = const []}) { | ||||
|       {Map<String, String> additionalSettings = const {}}) { | ||||
|     return Uri.parse(standardUrl).pathSegments.last; | ||||
|   } | ||||
|  | ||||
| @@ -60,8 +60,9 @@ class FDroid extends AppSource { | ||||
|  | ||||
|   @override | ||||
|   Future<APKDetails> getLatestAPKDetails( | ||||
|       String standardUrl, List<String> additionalData, | ||||
|       {bool trackOnly = false}) async { | ||||
|     String standardUrl, | ||||
|     Map<String, String> additionalSettings, | ||||
|   ) async { | ||||
|     String? appId = tryInferringAppId(standardUrl); | ||||
|     return getAPKUrlsFromFDroidPackagesAPIResponse( | ||||
|         await get(Uri.parse('https://f-droid.org/api/v1/packages/$appId')), | ||||
|   | ||||
| @@ -9,13 +9,12 @@ class FDroidRepo extends AppSource { | ||||
|   FDroidRepo() { | ||||
|     name = tr('fdroidThirdPartyRepo'); | ||||
|  | ||||
|     additionalSourceAppSpecificFormItems = [ | ||||
|     additionalSourceAppSpecificSettingFormItems = [ | ||||
|       [ | ||||
|         GeneratedFormItem( | ||||
|         GeneratedFormItem('appIdOrName', | ||||
|             label: tr('appIdOrName'), | ||||
|             hint: tr('reposHaveMultipleApps'), | ||||
|             required: true, | ||||
|             key: 'appIdOrName') | ||||
|             required: true) | ||||
|       ] | ||||
|     ]; | ||||
|   } | ||||
| @@ -33,13 +32,10 @@ class FDroidRepo extends AppSource { | ||||
|  | ||||
|   @override | ||||
|   Future<APKDetails> getLatestAPKDetails( | ||||
|       String standardUrl, List<String> additionalData, | ||||
|       {bool trackOnly = false}) async { | ||||
|     String? appIdOrName = findGeneratedFormValueByKey( | ||||
|         additionalSourceAppSpecificFormItems | ||||
|             .reduce((value, element) => [...value, ...element]), | ||||
|         additionalData, | ||||
|         'appIdOrName'); | ||||
|     String standardUrl, | ||||
|     Map<String, String> additionalSettings, | ||||
|   ) async { | ||||
|     String? appIdOrName = additionalSettings['appIdOrName']; | ||||
|     if (appIdOrName == null) { | ||||
|       throw NoReleasesError(); | ||||
|     } | ||||
|   | ||||
| @@ -12,12 +12,9 @@ class GitHub extends AppSource { | ||||
|   GitHub() { | ||||
|     host = 'github.com'; | ||||
|  | ||||
|     additionalSourceAppSpecificDefaults = ['true', 'true', '']; | ||||
|  | ||||
|     additionalSourceSpecificSettingFormItems = [ | ||||
|       GeneratedFormItem( | ||||
|       GeneratedFormItem('github-creds', | ||||
|           label: tr('githubPATLabel'), | ||||
|           id: 'github-creds', | ||||
|           required: false, | ||||
|           additionalValidators: [ | ||||
|             (value) { | ||||
| @@ -52,17 +49,21 @@ class GitHub extends AppSource { | ||||
|           ]) | ||||
|     ]; | ||||
|  | ||||
|     additionalSourceAppSpecificFormItems = [ | ||||
|     additionalSourceAppSpecificSettingFormItems = [ | ||||
|       [ | ||||
|         GeneratedFormItem( | ||||
|             label: tr('includePrereleases'), type: FormItemType.bool) | ||||
|         GeneratedFormItem('includePrereleases', | ||||
|             label: tr('includePrereleases'), | ||||
|             type: FormItemType.bool, | ||||
|             defaultValue: '') | ||||
|       ], | ||||
|       [ | ||||
|         GeneratedFormItem( | ||||
|             label: tr('fallbackToOlderReleases'), type: FormItemType.bool) | ||||
|         GeneratedFormItem('fallbackToOlderReleases', | ||||
|             label: tr('fallbackToOlderReleases'), | ||||
|             type: FormItemType.bool, | ||||
|             defaultValue: 'true') | ||||
|       ], | ||||
|       [ | ||||
|         GeneratedFormItem( | ||||
|         GeneratedFormItem('filterReleaseTitlesByRegEx', | ||||
|             label: tr('filterReleaseTitlesByRegEx'), | ||||
|             type: FormItemType.string, | ||||
|             required: false, | ||||
| @@ -99,7 +100,7 @@ class GitHub extends AppSource { | ||||
|     SettingsProvider settingsProvider = SettingsProvider(); | ||||
|     await settingsProvider.initializeSettings(); | ||||
|     String? creds = settingsProvider | ||||
|         .getSettingString(additionalSourceSpecificSettingFormItems[0].id); | ||||
|         .getSettingString(additionalSourceSpecificSettingFormItems[0].key); | ||||
|     return creds != null && creds.isNotEmpty ? '$creds@' : ''; | ||||
|   } | ||||
|  | ||||
| @@ -109,15 +110,16 @@ class GitHub extends AppSource { | ||||
|  | ||||
|   @override | ||||
|   Future<APKDetails> getLatestAPKDetails( | ||||
|       String standardUrl, List<String> additionalData, | ||||
|       {bool trackOnly = false}) async { | ||||
|     var includePrereleases = | ||||
|         additionalData.isNotEmpty && additionalData[0] == 'true'; | ||||
|     String standardUrl, | ||||
|     Map<String, String> additionalSettings, | ||||
|   ) async { | ||||
|     var includePrereleases = additionalSettings['includePrereleases'] == 'true'; | ||||
|     var fallbackToOlderReleases = | ||||
|         additionalData.length >= 2 && additionalData[1] == 'true'; | ||||
|     var regexFilter = additionalData.length >= 3 && additionalData[2].isNotEmpty | ||||
|         ? additionalData[2] | ||||
|         : null; | ||||
|         additionalSettings['fallbackToOlderReleases'] == 'true'; | ||||
|     var regexFilter = | ||||
|         additionalSettings['filterReleaseTitlesByRegEx']?.isNotEmpty == true | ||||
|             ? additionalSettings['filterReleaseTitlesByRegEx'] | ||||
|             : null; | ||||
|     Response res = await get(Uri.parse( | ||||
|         'https://${await getCredentialPrefixIfAny()}api.$host/repos${standardUrl.substring('https://$host'.length)}/releases')); | ||||
|     if (res.statusCode == 200) { | ||||
| @@ -148,7 +150,7 @@ class GitHub extends AppSource { | ||||
|           continue; | ||||
|         } | ||||
|         var apkUrls = getReleaseAPKUrls(releases[i]); | ||||
|         if (apkUrls.isEmpty && !trackOnly) { | ||||
|         if (apkUrls.isEmpty && additionalSettings['trackOnly'] != 'true') { | ||||
|           continue; | ||||
|         } | ||||
|         targetRelease = releases[i]; | ||||
|   | ||||
| @@ -25,8 +25,9 @@ class GitLab extends AppSource { | ||||
|  | ||||
|   @override | ||||
|   Future<APKDetails> getLatestAPKDetails( | ||||
|       String standardUrl, List<String> additionalData, | ||||
|       {bool trackOnly = false}) async { | ||||
|     String standardUrl, | ||||
|     Map<String, String> additionalSettings, | ||||
|   ) async { | ||||
|     Response res = await get(Uri.parse('$standardUrl/-/tags?format=atom')); | ||||
|     if (res.statusCode == 200) { | ||||
|       var standardUri = Uri.parse(standardUrl); | ||||
|   | ||||
| @@ -23,14 +23,15 @@ class IzzyOnDroid extends AppSource { | ||||
|  | ||||
|   @override | ||||
|   String? tryInferringAppId(String standardUrl, | ||||
|       {List<String> additionalData = const []}) { | ||||
|       {Map<String, String> additionalSettings = const {}}) { | ||||
|     return FDroid().tryInferringAppId(standardUrl); | ||||
|   } | ||||
|  | ||||
|   @override | ||||
|   Future<APKDetails> getLatestAPKDetails( | ||||
|       String standardUrl, List<String> additionalData, | ||||
|       {bool trackOnly = false}) async { | ||||
|     String standardUrl, | ||||
|     Map<String, String> additionalSettings, | ||||
|   ) async { | ||||
|     String? appId = tryInferringAppId(standardUrl); | ||||
|     return FDroid().getAPKUrlsFromFDroidPackagesAPIResponse( | ||||
|         await get( | ||||
|   | ||||
| @@ -24,8 +24,9 @@ class Mullvad extends AppSource { | ||||
|  | ||||
|   @override | ||||
|   Future<APKDetails> getLatestAPKDetails( | ||||
|       String standardUrl, List<String> additionalData, | ||||
|       {bool trackOnly = false}) async { | ||||
|     String standardUrl, | ||||
|     Map<String, String> additionalSettings, | ||||
|   ) async { | ||||
|     Response res = await get(Uri.parse('$standardUrl/en/download/android')); | ||||
|     if (res.statusCode == 200) { | ||||
|       var version = parse(res.body) | ||||
|   | ||||
| @@ -18,8 +18,9 @@ class Signal extends AppSource { | ||||
|  | ||||
|   @override | ||||
|   Future<APKDetails> getLatestAPKDetails( | ||||
|       String standardUrl, List<String> additionalData, | ||||
|       {bool trackOnly = false}) async { | ||||
|     String standardUrl, | ||||
|     Map<String, String> additionalSettings, | ||||
|   ) async { | ||||
|     Response res = | ||||
|         await get(Uri.parse('https://updates.$host/android/latest.json')); | ||||
|     if (res.statusCode == 200) { | ||||
|   | ||||
| @@ -23,8 +23,9 @@ class SourceForge extends AppSource { | ||||
|  | ||||
|   @override | ||||
|   Future<APKDetails> getLatestAPKDetails( | ||||
|       String standardUrl, List<String> additionalData, | ||||
|       {bool trackOnly = false}) async { | ||||
|     String standardUrl, | ||||
|     Map<String, String> additionalSettings, | ||||
|   ) async { | ||||
|     Response res = await get(Uri.parse('$standardUrl/rss?path=/')); | ||||
|     if (res.statusCode == 200) { | ||||
|       var parsedHtml = parse(res.body); | ||||
|   | ||||
| @@ -9,13 +9,10 @@ class SteamMobile extends AppSource { | ||||
|   SteamMobile() { | ||||
|     host = 'store.steampowered.com'; | ||||
|     name = tr('steam'); | ||||
|     additionalSourceAppSpecificFormItems = [ | ||||
|     additionalSourceAppSpecificSettingFormItems = [ | ||||
|       [ | ||||
|         GeneratedFormItem( | ||||
|             label: tr('app'), | ||||
|             key: 'app', | ||||
|             required: true, | ||||
|             opts: apks.entries.toList()) | ||||
|         GeneratedFormItem('app', | ||||
|             label: tr('app'), required: true, opts: apks.entries.toList()) | ||||
|       ] | ||||
|     ]; | ||||
|   } | ||||
| @@ -32,15 +29,12 @@ class SteamMobile extends AppSource { | ||||
|  | ||||
|   @override | ||||
|   Future<APKDetails> getLatestAPKDetails( | ||||
|       String standardUrl, List<String> additionalData, | ||||
|       {bool trackOnly = false}) async { | ||||
|     String standardUrl, | ||||
|     Map<String, String> additionalSettings, | ||||
|   ) async { | ||||
|     Response res = await get(Uri.parse('https://$host/mobile')); | ||||
|     if (res.statusCode == 200) { | ||||
|       var apkNamePrefix = findGeneratedFormValueByKey( | ||||
|           additionalSourceAppSpecificFormItems | ||||
|               .reduce((value, element) => [...value, ...element]), | ||||
|           additionalData, | ||||
|           'app'); | ||||
|       var apkNamePrefix = additionalSettings['app']; | ||||
|       if (apkNamePrefix == null) { | ||||
|         throw NoReleasesError(); | ||||
|       } | ||||
|   | ||||
| @@ -4,7 +4,7 @@ import 'package:flutter/material.dart'; | ||||
| enum FormItemType { string, bool } | ||||
|  | ||||
| typedef OnValueChanges = void Function( | ||||
|     List<String> values, bool valid, bool isBuilding); | ||||
|     Map<String, String> values, bool valid, bool isBuilding); | ||||
|  | ||||
| class GeneratedFormItem { | ||||
|   late String key; | ||||
| @@ -13,22 +13,21 @@ class GeneratedFormItem { | ||||
|   late bool required; | ||||
|   late int max; | ||||
|   late List<String? Function(String? value)> additionalValidators; | ||||
|   late String id; | ||||
|   late List<Widget> belowWidgets; | ||||
|   late String? hint; | ||||
|   late List<MapEntry<String, String>>? opts; | ||||
|   late String? defaultValue; | ||||
|  | ||||
|   GeneratedFormItem( | ||||
|   GeneratedFormItem(this.key, | ||||
|       {this.label = 'Input', | ||||
|       this.type = FormItemType.string, | ||||
|       this.required = true, | ||||
|       this.max = 1, | ||||
|       this.additionalValidators = const [], | ||||
|       this.id = 'input', | ||||
|       this.belowWidgets = const [], | ||||
|       this.hint, | ||||
|       this.opts, | ||||
|       this.key = 'default'}) { | ||||
|       this.defaultValue}) { | ||||
|     if (type != FormItemType.string) { | ||||
|       required = false; | ||||
|     } | ||||
| @@ -37,14 +36,10 @@ class GeneratedFormItem { | ||||
|  | ||||
| class GeneratedForm extends StatefulWidget { | ||||
|   const GeneratedForm( | ||||
|       {super.key, | ||||
|       required this.items, | ||||
|       required this.onValueChanges, | ||||
|       required this.defaultValues}); | ||||
|       {super.key, required this.items, required this.onValueChanges}); | ||||
|  | ||||
|   final List<List<GeneratedFormItem>> items; | ||||
|   final OnValueChanges onValueChanges; | ||||
|   final List<String> defaultValues; | ||||
|  | ||||
|   @override | ||||
|   State<GeneratedForm> createState() => _GeneratedFormState(); | ||||
| @@ -52,17 +47,18 @@ class GeneratedForm extends StatefulWidget { | ||||
|  | ||||
| class _GeneratedFormState extends State<GeneratedForm> { | ||||
|   final _formKey = GlobalKey<FormState>(); | ||||
|   late List<List<String>> values; | ||||
|   Map<String, String> values = {}; | ||||
|   late List<List<Widget>> formInputs; | ||||
|   List<List<Widget>> rows = []; | ||||
|  | ||||
|   // If any value changes, call this to update the parent with value and validity | ||||
|   void someValueChanged({bool isBuilding = false}) { | ||||
|     List<String> returnValues = []; | ||||
|     Map<String, String> returnValues = {}; | ||||
|     var valid = true; | ||||
|     for (int r = 0; r < values.length; r++) { | ||||
|       for (int i = 0; i < values[r].length; i++) { | ||||
|         returnValues.add(values[r][i]); | ||||
|     for (int r = 0; r < widget.items.length; r++) { | ||||
|       for (int i = 0; i < widget.items[r].length; i++) { | ||||
|         returnValues[widget.items[r][i].key] = | ||||
|             values[widget.items[r][i].key] ?? ''; | ||||
|         if (formInputs[r][i] is TextFormField) { | ||||
|           valid = valid && | ||||
|               ((formInputs[r][i].key as GlobalKey<FormFieldState>) | ||||
| @@ -80,16 +76,13 @@ class _GeneratedFormState extends State<GeneratedForm> { | ||||
|     super.initState(); | ||||
|  | ||||
|     // Initialize form values as all empty | ||||
|     values.clear(); | ||||
|     int j = 0; | ||||
|     values = widget.items | ||||
|         .map((row) => row.map((e) { | ||||
|               return j < widget.defaultValues.length | ||||
|                   ? widget.defaultValues[j++] | ||||
|                   : e.opts != null | ||||
|                       ? e.opts!.first.key | ||||
|                       : ''; | ||||
|             }).toList()) | ||||
|         .toList(); | ||||
|     for (var row in widget.items) { | ||||
|       for (var e in row) { | ||||
|         values[e.key] = e.defaultValue ?? e.opts?.first.key ?? ''; | ||||
|       } | ||||
|     } | ||||
|  | ||||
|     // Dynamically create form inputs | ||||
|     formInputs = widget.items.asMap().entries.map((row) { | ||||
| @@ -98,11 +91,11 @@ class _GeneratedFormState extends State<GeneratedForm> { | ||||
|           final formFieldKey = GlobalKey<FormFieldState>(); | ||||
|           return TextFormField( | ||||
|             key: formFieldKey, | ||||
|             initialValue: values[row.key][e.key], | ||||
|             initialValue: values[e.value.key], | ||||
|             autovalidateMode: AutovalidateMode.onUserInteraction, | ||||
|             onChanged: (value) { | ||||
|               setState(() { | ||||
|                 values[row.key][e.key] = value; | ||||
|                 values[e.value.key] = value; | ||||
|                 someValueChanged(); | ||||
|               }); | ||||
|             }, | ||||
| @@ -131,14 +124,14 @@ class _GeneratedFormState extends State<GeneratedForm> { | ||||
|           } | ||||
|           return DropdownButtonFormField( | ||||
|               decoration: InputDecoration(labelText: e.value.label), | ||||
|               value: values[row.key][e.key], | ||||
|               value: values[e.value.key], | ||||
|               items: e.value.opts! | ||||
|                   .map((e) => | ||||
|                       DropdownMenuItem(value: e.key, child: Text(e.value))) | ||||
|                   .toList(), | ||||
|               onChanged: (value) { | ||||
|                 setState(() { | ||||
|                   values[row.key][e.key] = value ?? e.value.opts!.first.key; | ||||
|                   values[e.value.key] = value ?? e.value.opts!.first.key; | ||||
|                   someValueChanged(); | ||||
|                 }); | ||||
|               }); | ||||
| @@ -160,10 +153,10 @@ class _GeneratedFormState extends State<GeneratedForm> { | ||||
|             children: [ | ||||
|               Text(widget.items[r][e].label), | ||||
|               Switch( | ||||
|                   value: values[r][e] == 'true', | ||||
|                   value: values[widget.items[r][e].key] == 'true', | ||||
|                   onChanged: (value) { | ||||
|                     setState(() { | ||||
|                       values[r][e] = value ? 'true' : ''; | ||||
|                       values[widget.items[r][e].key] = value ? 'true' : ''; | ||||
|                       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, | ||||
|       required this.title, | ||||
|       required this.items, | ||||
|       required this.defaultValues, | ||||
|       this.initValid = false, | ||||
|       this.message = ''}); | ||||
|  | ||||
|   final String title; | ||||
|   final String message; | ||||
|   final List<List<GeneratedFormItem>> items; | ||||
|   final List<String> defaultValues; | ||||
|   final bool initValid; | ||||
|  | ||||
|   @override | ||||
| @@ -23,13 +21,12 @@ class GeneratedFormModal extends StatefulWidget { | ||||
| } | ||||
|  | ||||
| class _GeneratedFormModalState extends State<GeneratedFormModal> { | ||||
|   List<String> values = []; | ||||
|   Map<String, String> values = {}; | ||||
|   bool valid = false; | ||||
|  | ||||
|   @override | ||||
|   void initState() { | ||||
|     super.initState(); | ||||
|     values = widget.defaultValues; | ||||
|     valid = widget.initValid || widget.items.isEmpty; | ||||
|   } | ||||
|  | ||||
| @@ -57,8 +54,7 @@ class _GeneratedFormModalState extends State<GeneratedFormModal> { | ||||
|                   this.valid = valid; | ||||
|                 }); | ||||
|               } | ||||
|             }, | ||||
|             defaultValues: widget.defaultValues) | ||||
|             }) | ||||
|       ]), | ||||
|       actions: [ | ||||
|         TextButton( | ||||
|   | ||||
| @@ -21,7 +21,7 @@ import 'package:easy_localization/src/easy_localization_controller.dart'; | ||||
| // ignore: implementation_imports | ||||
| import 'package:easy_localization/src/localization.dart'; | ||||
|  | ||||
| const String currentVersion = '0.8.21'; | ||||
| const String currentVersion = '0.8.23'; | ||||
| const String currentReleaseTag = | ||||
|     'v$currentVersion-beta'; // KEEP THIS IN SYNC WITH GITHUB RELEASES | ||||
|  | ||||
| @@ -200,9 +200,8 @@ class _ObtainiumState extends State<Obtainium> { | ||||
|               currentReleaseTag, | ||||
|               [], | ||||
|               0, | ||||
|               ['true'], | ||||
|               {'includePrereleases': 'true'}, | ||||
|               null, | ||||
|               false, | ||||
|               false) | ||||
|         ]); | ||||
|       } | ||||
|   | ||||
| @@ -27,10 +27,8 @@ class _AddAppPageState extends State<AddAppPage> { | ||||
|   String userInput = ''; | ||||
|   String searchQuery = ''; | ||||
|   AppSource? pickedSource; | ||||
|   List<String> sourceSpecificAdditionalData = []; | ||||
|   bool sourceSpecificDataIsValid = true; | ||||
|   List<String> otherAdditionalData = []; | ||||
|   bool otherAdditionalDataIsValid = true; | ||||
|   Map<String, String> additionalSettings = {}; | ||||
|   bool additionalSettingsValid = true; | ||||
|  | ||||
|   @override | ||||
|   Widget build(BuildContext context) { | ||||
| @@ -43,10 +41,12 @@ class _AddAppPageState extends State<AddAppPage> { | ||||
|         var source = valid ? sourceProvider.getSource(userInput) : null; | ||||
|         if (pickedSource.runtimeType != source.runtimeType) { | ||||
|           pickedSource = source; | ||||
|           sourceSpecificAdditionalData = | ||||
|               source != null ? source.additionalSourceAppSpecificDefaults : []; | ||||
|           sourceSpecificDataIsValid = source != null | ||||
|               ? !sourceProvider.ifSourceAppsRequireAdditionalData(source) | ||||
|           additionalSettings = source != null | ||||
|               ? getDefaultValuesFromFormItems( | ||||
|                   source.combinedAppSpecificSettingFormItems) | ||||
|               : {}; | ||||
|           additionalSettingsValid = source != null | ||||
|               ? !sourceProvider.ifRequiredAppSpecificSettingsExist(source) | ||||
|               : true; | ||||
|         } | ||||
|       } | ||||
| @@ -66,11 +66,9 @@ class _AddAppPageState extends State<AddAppPage> { | ||||
|       }); | ||||
|       var settingsProvider = context.read<SettingsProvider>(); | ||||
|       () async { | ||||
|         var userPickedTrackOnly = findGeneratedFormValueByKey( | ||||
|                 pickedSource!.additionalAppSpecificSourceAgnosticFormItems, | ||||
|                 otherAdditionalData, | ||||
|                 'trackOnlyFormItemKey') == | ||||
|             'true'; | ||||
|         var userPickedTrackOnly = additionalSettings['trackOnly'] == 'true'; | ||||
|         var userPickedNoVersionDetection = | ||||
|             additionalSettings['noVersionDetection'] == 'true'; | ||||
|         var cont = true; | ||||
|         if ((userPickedTrackOnly || pickedSource!.enforceTrackOnly) && | ||||
|             await showDialog( | ||||
| @@ -83,7 +81,6 @@ class _AddAppPageState extends State<AddAppPage> { | ||||
|                               : tr('app') | ||||
|                         ]), | ||||
|                         items: const [], | ||||
|                         defaultValues: const [], | ||||
|                         message: | ||||
|                             '${pickedSource!.enforceTrackOnly ? tr('appsFromSourceAreTrackOnly') : tr('youPickedTrackOnly')}\n\n${tr('trackOnlyAppDescription')}', | ||||
|                       ); | ||||
| @@ -91,17 +88,32 @@ class _AddAppPageState extends State<AddAppPage> { | ||||
|                 null) { | ||||
|           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) { | ||||
|           HapticFeedback.selectionClick(); | ||||
|           var trackOnly = pickedSource!.enforceTrackOnly || userPickedTrackOnly; | ||||
|           App app = await sourceProvider.getApp( | ||||
|               pickedSource!, userInput, sourceSpecificAdditionalData, | ||||
|               trackOnly: trackOnly); | ||||
|               pickedSource!, userInput, additionalSettings, | ||||
|               trackOnlyOverride: trackOnly, | ||||
|               noVersionDetectionOverride: userPickedNoVersionDetection); | ||||
|           if (!trackOnly) { | ||||
|             await settingsProvider.getInstallPermission(); | ||||
|           } | ||||
|           // 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 | ||||
|             var apkUrl = await appsProvider.confirmApkUrl(app, context); | ||||
|             if (apkUrl == null) { | ||||
| @@ -116,7 +128,7 @@ class _AddAppPageState extends State<AddAppPage> { | ||||
|           if (appsProvider.apps.containsKey(app.id)) { | ||||
|             throw ObtainiumError(tr('appAlreadyAdded')); | ||||
|           } | ||||
|           if (app.trackOnly) { | ||||
|           if (app.additionalSettings['trackOnly'] == 'true') { | ||||
|             app.installedVersion = app.latestVersion; | ||||
|           } | ||||
|           await appsProvider.saveApps([app]); | ||||
| @@ -156,34 +168,33 @@ class _AddAppPageState extends State<AddAppPage> { | ||||
|                           Expanded( | ||||
|                               child: GeneratedForm( | ||||
|                                   items: [ | ||||
|                                     [ | ||||
|                                       GeneratedFormItem( | ||||
|                                           label: tr('appSourceURL'), | ||||
|                                           additionalValidators: [ | ||||
|                                             (value) { | ||||
|                                               try { | ||||
|                                                 sourceProvider | ||||
|                                                     .getSource(value ?? '') | ||||
|                                                     .standardizeURL( | ||||
|                                                         preStandardizeUrl( | ||||
|                                                             value ?? '')); | ||||
|                                               } catch (e) { | ||||
|                                                 return e is String | ||||
|                                                     ? e | ||||
|                                                     : e is ObtainiumError | ||||
|                                                         ? e.toString() | ||||
|                                                         : tr('error'); | ||||
|                                               } | ||||
|                                               return null; | ||||
|                                             } | ||||
|                                           ]) | ||||
|                                     ] | ||||
|                                   ], | ||||
|                                 [ | ||||
|                                   GeneratedFormItem('appSourceURL', | ||||
|                                       label: tr('appSourceURL'), | ||||
|                                       additionalValidators: [ | ||||
|                                         (value) { | ||||
|                                           try { | ||||
|                                             sourceProvider | ||||
|                                                 .getSource(value ?? '') | ||||
|                                                 .standardizeURL( | ||||
|                                                     preStandardizeUrl( | ||||
|                                                         value ?? '')); | ||||
|                                           } catch (e) { | ||||
|                                             return e is String | ||||
|                                                 ? e | ||||
|                                                 : e is ObtainiumError | ||||
|                                                     ? e.toString() | ||||
|                                                     : tr('error'); | ||||
|                                           } | ||||
|                                           return null; | ||||
|                                         } | ||||
|                                       ]) | ||||
|                                 ] | ||||
|                               ], | ||||
|                                   onValueChanges: (values, valid, isBuilding) { | ||||
|                                     changeUserInput( | ||||
|                                         values[0], valid, isBuilding); | ||||
|                                   }, | ||||
|                                   defaultValues: const [])), | ||||
|                                     changeUserInput(values['appSourceURL']!, | ||||
|                                         valid, isBuilding); | ||||
|                                   })), | ||||
|                           const SizedBox( | ||||
|                             width: 16, | ||||
|                           ), | ||||
| @@ -193,13 +204,9 @@ class _AddAppPageState extends State<AddAppPage> { | ||||
|                                   onPressed: gettingAppInfo || | ||||
|                                           pickedSource == null || | ||||
|                                           (pickedSource! | ||||
|                                                   .additionalSourceAppSpecificFormItems | ||||
|                                                   .combinedAppSpecificSettingFormItems | ||||
|                                                   .isNotEmpty && | ||||
|                                               !sourceSpecificDataIsValid) || | ||||
|                                           (pickedSource! | ||||
|                                                   .additionalAppSpecificSourceAgnosticDefaults | ||||
|                                                   .isNotEmpty && | ||||
|                                               !otherAdditionalDataIsValid) | ||||
|                                               !additionalSettingsValid) | ||||
|                                       ? null | ||||
|                                       : addApp, | ||||
|                                   child: Text(tr('add'))) | ||||
| @@ -224,7 +231,7 @@ class _AddAppPageState extends State<AddAppPage> { | ||||
|                               child: GeneratedForm( | ||||
|                                   items: [ | ||||
|                                     [ | ||||
|                                       GeneratedFormItem( | ||||
|                                       GeneratedFormItem('searchSomeSources', | ||||
|                                           label: tr('searchSomeSourcesLabel'), | ||||
|                                           required: false), | ||||
|                                     ] | ||||
| @@ -232,11 +239,11 @@ class _AddAppPageState extends State<AddAppPage> { | ||||
|                                   onValueChanges: (values, valid, isBuilding) { | ||||
|                                     if (values.isNotEmpty && valid) { | ||||
|                                       setState(() { | ||||
|                                         searchQuery = values[0].trim(); | ||||
|                                         searchQuery = | ||||
|                                             values['searchSomeSources']!.trim(); | ||||
|                                       }); | ||||
|                                     } | ||||
|                                   }, | ||||
|                                   defaultValues: const ['']), | ||||
|                                   }), | ||||
|                             ), | ||||
|                             const SizedBox( | ||||
|                               width: 16, | ||||
| @@ -292,15 +299,8 @@ class _AddAppPageState extends State<AddAppPage> { | ||||
|                           ], | ||||
|                         ), | ||||
|                       if (pickedSource != null && | ||||
|                           (pickedSource!.additionalSourceAppSpecificDefaults | ||||
|                                   .isNotEmpty || | ||||
|                               pickedSource! | ||||
|                                   .additionalAppSpecificSourceAgnosticFormItems | ||||
|                                   .where((e) => pickedSource!.enforceTrackOnly | ||||
|                                       ? e.key != 'trackOnlyFormItemKey' | ||||
|                                       : true) | ||||
|                                   .map((e) => [e]) | ||||
|                                   .isNotEmpty)) | ||||
|                           (pickedSource! | ||||
|                               .combinedAppSpecificSettingFormItems.isNotEmpty)) | ||||
|                         Column( | ||||
|                           crossAxisAlignment: CrossAxisAlignment.stretch, | ||||
|                           children: [ | ||||
| @@ -316,46 +316,17 @@ class _AddAppPageState extends State<AddAppPage> { | ||||
|                             const SizedBox( | ||||
|                               height: 16, | ||||
|                             ), | ||||
|                             if (pickedSource! | ||||
|                                 .additionalSourceAppSpecificFormItems | ||||
|                                 .isNotEmpty) | ||||
|                               GeneratedForm( | ||||
|                                   items: pickedSource! | ||||
|                                       .additionalSourceAppSpecificFormItems, | ||||
|                                   onValueChanges: (values, valid, isBuilding) { | ||||
|                                     if (!isBuilding) { | ||||
|                                       setState(() { | ||||
|                                         sourceSpecificAdditionalData = values; | ||||
|                                         sourceSpecificDataIsValid = 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(), | ||||
|                                     .combinedAppSpecificSettingFormItems, | ||||
|                                 onValueChanges: (values, valid, isBuilding) { | ||||
|                                   if (!isBuilding) { | ||||
|                                     setState(() { | ||||
|                                       otherAdditionalData = values; | ||||
|                                       otherAdditionalDataIsValid = valid; | ||||
|                                       additionalSettings = values; | ||||
|                                       additionalSettingsValid = valid; | ||||
|                                     }); | ||||
|                                   } | ||||
|                                 }, | ||||
|                                 defaultValues: pickedSource! | ||||
|                                     .additionalAppSpecificSourceAgnosticDefaults), | ||||
|                                 }), | ||||
|                           ], | ||||
|                         ) | ||||
|                       else | ||||
|   | ||||
| @@ -40,16 +40,33 @@ class _AppPageState extends State<AppPage> { | ||||
|       prevApp = app; | ||||
|       getUpdate(app.app.id); | ||||
|     } | ||||
|     var trackOnly = app?.app.additionalSettings['trackOnly'] == 'true'; | ||||
|     return Scaffold( | ||||
|       appBar: settingsProvider.showAppWebpage ? AppBar() : null, | ||||
|       backgroundColor: Theme.of(context).colorScheme.surface, | ||||
|       body: RefreshIndicator( | ||||
|           child: settingsProvider.showAppWebpage | ||||
|               ? WebView( | ||||
|                   backgroundColor: Theme.of(context).colorScheme.background, | ||||
|                   initialUrl: app?.app.url, | ||||
|                   javascriptMode: JavascriptMode.unrestricted, | ||||
|                 ) | ||||
|               ? app != null | ||||
|                   ? WebViewWidget( | ||||
|                       controller: WebViewController() | ||||
|                         ..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( | ||||
|                   slivers: [ | ||||
|                     SliverFillRemaining( | ||||
| @@ -72,7 +89,9 @@ class _AppPageState extends State<AppPage> { | ||||
|                           height: 25, | ||||
|                         ), | ||||
|                         Text( | ||||
|                           app?.installedInfo?.name ?? app?.app.name ?? 'App', | ||||
|                           app?.installedInfo?.name ?? | ||||
|                               app?.app.name ?? | ||||
|                               tr('app'), | ||||
|                           textAlign: TextAlign.center, | ||||
|                           style: Theme.of(context).textTheme.displayLarge, | ||||
|                         ), | ||||
| @@ -111,7 +130,7 @@ class _AppPageState extends State<AppPage> { | ||||
|                         Text( | ||||
|                           '${tr('installedVersionX', args: [ | ||||
|                                 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') | ||||
|                                 ])}' : ''}', | ||||
|                           textAlign: TextAlign.center, | ||||
| @@ -151,7 +170,7 @@ class _AppPageState extends State<AppPage> { | ||||
|                       mainAxisAlignment: MainAxisAlignment.spaceEvenly, | ||||
|                       children: [ | ||||
|                         if (app?.app.installedVersion != null && | ||||
|                             app?.app.trackOnly == false && | ||||
|                             !trackOnly && | ||||
|                             app?.app.installedVersion != app?.app.latestVersion) | ||||
|                           IconButton( | ||||
|                               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)), | ||||
|                         if (source != null && | ||||
|                             source.additionalSourceAppSpecificFormItems | ||||
|                                 .isNotEmpty) | ||||
|                             source | ||||
|                                 .combinedAppSpecificSettingFormItems.isNotEmpty) | ||||
|                           IconButton( | ||||
|                               onPressed: app?.downloadProgress != null | ||||
|                                   ? null | ||||
|                                   : () { | ||||
|                                       showDialog<List<String>>( | ||||
|                                       showDialog<Map<String, String>>( | ||||
|                                           context: context, | ||||
|                                           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( | ||||
|                                                 title: 'Additional Options', | ||||
|                                                 items: source | ||||
|                                                     .additionalSourceAppSpecificFormItems, | ||||
|                                                 defaultValues: app != null | ||||
|                                                     ? app.app.additionalData | ||||
|                                                     : source | ||||
|                                                         .additionalSourceAppSpecificDefaults); | ||||
|                                                 title: tr('additionalOptions'), | ||||
|                                                 items: items); | ||||
|                                           }).then((values) { | ||||
|                                         if (app != null && values != null) { | ||||
|                                           var changedApp = app.app; | ||||
|                                           changedApp.additionalData = values; | ||||
|                                           changedApp.additionalSettings = | ||||
|                                               values; | ||||
|                                           if (source.enforceTrackOnly) { | ||||
|                                             changedApp.additionalSettings[ | ||||
|                                                 'trackOnly'] = 'true'; | ||||
|                                             showError( | ||||
|                                                 tr('appsFromSourceAreTrackOnly'), | ||||
|                                                 context); | ||||
|                                           } | ||||
|                                           appsProvider.saveApps( | ||||
|                                               [changedApp]).then((value) { | ||||
|                                             getUpdate(changedApp.id); | ||||
| @@ -230,7 +267,7 @@ class _AppPageState extends State<AppPage> { | ||||
|                                         } | ||||
|                                       }); | ||||
|                                     }, | ||||
|                               tooltip: 'Additional Options', | ||||
|                               tooltip: tr('additionalOptions'), | ||||
|                               icon: const Icon(Icons.settings)), | ||||
|                         const SizedBox(width: 16.0), | ||||
|                         Expanded( | ||||
| @@ -242,7 +279,9 @@ class _AppPageState extends State<AppPage> { | ||||
|                                     ? () { | ||||
|                                         HapticFeedback.heavyImpact(); | ||||
|                                         () async { | ||||
|                                           if (app?.app.trackOnly != true) { | ||||
|                                           if (app?.app.additionalSettings[ | ||||
|                                                   'trackOnly'] != | ||||
|                                               'true') { | ||||
|                                             await settingsProvider | ||||
|                                                 .getInstallPermission(); | ||||
|                                           } | ||||
| @@ -264,12 +303,12 @@ class _AppPageState extends State<AppPage> { | ||||
|                                       } | ||||
|                                     : null, | ||||
|                                 child: Text(app?.app.installedVersion == null | ||||
|                                     ? app?.app.trackOnly == false | ||||
|                                         ? 'Install' | ||||
|                                         : 'Mark Installed' | ||||
|                                     : app?.app.trackOnly == false | ||||
|                                         ? 'Update' | ||||
|                                         : 'Mark Updated'))), | ||||
|                                     ? !trackOnly | ||||
|                                         ? tr('install') | ||||
|                                         : tr('markInstalled') | ||||
|                                     : !trackOnly | ||||
|                                         ? tr('update') | ||||
|                                         : tr('markUpdated')))), | ||||
|                         const SizedBox(width: 16.0), | ||||
|                         ElevatedButton( | ||||
|                           onPressed: app?.downloadProgress != null | ||||
|   | ||||
| @@ -139,14 +139,16 @@ class AppsPageState extends State<AppsPage> { | ||||
|  | ||||
|     List<String> trackOnlyUpdateIdsAllOrSelected = []; | ||||
|     existingUpdateIdsAllOrSelected = existingUpdateIdsAllOrSelected.where((id) { | ||||
|       if (appsProvider.apps[id]!.app.trackOnly) { | ||||
|       if (appsProvider.apps[id]!.app.additionalSettings['trackOnly'] == | ||||
|           'true') { | ||||
|         trackOnlyUpdateIdsAllOrSelected.add(id); | ||||
|         return false; | ||||
|       } | ||||
|       return true; | ||||
|     }).toList(); | ||||
|     newInstallIdsAllOrSelected = newInstallIdsAllOrSelected.where((id) { | ||||
|       if (appsProvider.apps[id]!.app.trackOnly) { | ||||
|       if (appsProvider.apps[id]!.app.additionalSettings['trackOnly'] == | ||||
|           'true') { | ||||
|         trackOnlyUpdateIdsAllOrSelected.add(id); | ||||
|         return false; | ||||
|       } | ||||
| @@ -271,7 +273,7 @@ class AppsPageState extends State<AppsPage> { | ||||
|                               SizedBox( | ||||
|                                   width: 100, | ||||
|                                   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, | ||||
|                                     textAlign: TextAlign.end, | ||||
|                                   )), | ||||
| @@ -289,7 +291,7 @@ class AppsPageState extends State<AppsPage> { | ||||
|                                       child: appsProvider.areDownloadsRunning() | ||||
|                                           ? Text(tr('pleaseWait')) | ||||
|                                           : Text( | ||||
|                                               '${tr('updateAvailable')}${sortedApps[index].app.trackOnly ? ' ${tr('estimateInBracketsShort')}' : ''}', | ||||
|                                               '${tr('updateAvailable')}${sortedApps[index].app.additionalSettings['trackOnly'] == 'true' ? ' ${tr('estimateInBracketsShort')}' : ''}', | ||||
|                                               style: TextStyle( | ||||
|                                                   fontStyle: FontStyle.italic, | ||||
|                                                   decoration: changesUrl == null | ||||
| @@ -343,13 +345,12 @@ class AppsPageState extends State<AppsPage> { | ||||
|                     : IconButton( | ||||
|                         visualDensity: VisualDensity.compact, | ||||
|                         onPressed: () { | ||||
|                           showDialog<List<String>?>( | ||||
|                           showDialog<Map<String, String>?>( | ||||
|                               context: context, | ||||
|                               builder: (BuildContext ctx) { | ||||
|                                 return GeneratedFormModal( | ||||
|                                   title: tr('removeSelectedAppsQuestion'), | ||||
|                                   items: const [], | ||||
|                                   defaultValues: const [], | ||||
|                                   initValid: true, | ||||
|                                   message: tr( | ||||
|                                       'xWillBeRemovedButRemainInstalled', | ||||
| @@ -376,41 +377,42 @@ class AppsPageState extends State<AppsPage> { | ||||
|                         ? null | ||||
|                         : () { | ||||
|                             HapticFeedback.heavyImpact(); | ||||
|                             List<GeneratedFormItem> formInputs = []; | ||||
|                             List<String> defaultValues = []; | ||||
|                             List<GeneratedFormItem> formItems = []; | ||||
|                             if (existingUpdateIdsAllOrSelected.isNotEmpty) { | ||||
|                               formInputs.add(GeneratedFormItem( | ||||
|                               formItems.add(GeneratedFormItem('updates', | ||||
|                                   label: tr('updateX', args: [ | ||||
|                                     plural('apps', | ||||
|                                         existingUpdateIdsAllOrSelected.length) | ||||
|                                   ]), | ||||
|                                   type: FormItemType.bool, | ||||
|                                   key: 'updates')); | ||||
|                               defaultValues.add('true'); | ||||
|                                   defaultValue: 'true')); | ||||
|                             } | ||||
|                             if (newInstallIdsAllOrSelected.isNotEmpty) { | ||||
|                               formInputs.add(GeneratedFormItem( | ||||
|                               formItems.add(GeneratedFormItem('installs', | ||||
|                                   label: tr('installX', args: [ | ||||
|                                     plural('apps', | ||||
|                                         newInstallIdsAllOrSelected.length) | ||||
|                                   ]), | ||||
|                                   type: FormItemType.bool, | ||||
|                                   key: 'installs')); | ||||
|                               defaultValues | ||||
|                                   .add(defaultValues.isEmpty ? 'true' : ''); | ||||
|                                   defaultValue: | ||||
|                                       existingUpdateIdsAllOrSelected.isNotEmpty | ||||
|                                           ? 'true' | ||||
|                                           : '')); | ||||
|                             } | ||||
|                             if (trackOnlyUpdateIdsAllOrSelected.isNotEmpty) { | ||||
|                               formInputs.add(GeneratedFormItem( | ||||
|                               formItems.add(GeneratedFormItem('trackonlies', | ||||
|                                   label: tr('markXTrackOnlyAsUpdated', args: [ | ||||
|                                     plural('apps', | ||||
|                                         trackOnlyUpdateIdsAllOrSelected.length) | ||||
|                                   ]), | ||||
|                                   type: FormItemType.bool, | ||||
|                                   key: 'trackonlies')); | ||||
|                               defaultValues | ||||
|                                   .add(defaultValues.isEmpty ? 'true' : ''); | ||||
|                                   defaultValue: existingUpdateIdsAllOrSelected | ||||
|                                               .isNotEmpty || | ||||
|                                           newInstallIdsAllOrSelected.isNotEmpty | ||||
|                                       ? 'true' | ||||
|                                       : '')); | ||||
|                             } | ||||
|                             showDialog<List<String>?>( | ||||
|                             showDialog<Map<String, String>?>( | ||||
|                                 context: context, | ||||
|                                 builder: (BuildContext ctx) { | ||||
|                                   var totalApps = existingUpdateIdsAllOrSelected | ||||
| @@ -420,27 +422,21 @@ class AppsPageState extends State<AppsPage> { | ||||
|                                   return GeneratedFormModal( | ||||
|                                     title: tr('changeX', | ||||
|                                         args: [plural('apps', totalApps)]), | ||||
|                                     items: formInputs.map((e) => [e]).toList(), | ||||
|                                     defaultValues: defaultValues, | ||||
|                                     items: formItems.map((e) => [e]).toList(), | ||||
|                                     initValid: true, | ||||
|                                   ); | ||||
|                                 }).then((values) { | ||||
|                               if (values != null) { | ||||
|                                 if (values.isEmpty) { | ||||
|                                   values = defaultValues; | ||||
|                                   values = getDefaultValuesFromFormItems( | ||||
|                                       [formItems]); | ||||
|                                 } | ||||
|                                 bool shouldInstallUpdates = | ||||
|                                     findGeneratedFormValueByKey( | ||||
|                                             formInputs, values, 'updates') == | ||||
|                                         'true'; | ||||
|                                     values['updates'] == 'true'; | ||||
|                                 bool shouldInstallNew = | ||||
|                                     findGeneratedFormValueByKey( | ||||
|                                             formInputs, values, 'installs') == | ||||
|                                         'true'; | ||||
|                                     values['installs'] == 'true'; | ||||
|                                 bool shouldMarkTrackOnlies = | ||||
|                                     findGeneratedFormValueByKey(formInputs, | ||||
|                                             values, 'trackonlies') == | ||||
|                                         'true'; | ||||
|                                     values['trackonlies'] == 'true'; | ||||
|                                 (() async { | ||||
|                                   if (shouldInstallNew || | ||||
|                                       shouldInstallUpdates) { | ||||
| @@ -613,7 +609,6 @@ class AppsPageState extends State<AppsPage> { | ||||
|                                                       title: tr( | ||||
|                                                           'resetInstallStatusForSelectedAppsQuestion'), | ||||
|                                                       items: const [], | ||||
|                                                       defaultValues: const [], | ||||
|                                                       initValid: true, | ||||
|                                                       message: tr( | ||||
|                                                           'installStatusOfXWillBeResetExplanation', | ||||
| @@ -683,36 +678,42 @@ class AppsPageState extends State<AppsPage> { | ||||
|                               : FontWeight.bold), | ||||
|                     ), | ||||
|                     onPressed: () { | ||||
|                       showDialog<List<String>?>( | ||||
|                       showDialog<Map<String, String>?>( | ||||
|                           context: context, | ||||
|                           builder: (BuildContext ctx) { | ||||
|                             var vals = filter == null | ||||
|                                 ? AppsFilter().toValuesMap() | ||||
|                                 : filter!.toValuesMap(); | ||||
|                             return GeneratedFormModal( | ||||
|                                 title: tr('filterApps'), | ||||
|                                 items: [ | ||||
|                                   [ | ||||
|                                     GeneratedFormItem( | ||||
|                                         label: tr('appName'), required: false), | ||||
|                                     GeneratedFormItem( | ||||
|                                         label: tr('author'), required: false) | ||||
|                                     GeneratedFormItem('appName', | ||||
|                                         label: tr('appName'), | ||||
|                                         required: false, | ||||
|                                         defaultValue: vals['appName']), | ||||
|                                     GeneratedFormItem('author', | ||||
|                                         label: tr('author'), | ||||
|                                         required: false, | ||||
|                                         defaultValue: vals['author']) | ||||
|                                   ], | ||||
|                                   [ | ||||
|                                     GeneratedFormItem( | ||||
|                                     GeneratedFormItem('upToDateApps', | ||||
|                                         label: tr('upToDateApps'), | ||||
|                                         type: FormItemType.bool) | ||||
|                                         type: FormItemType.bool, | ||||
|                                         defaultValue: vals['upToDateApps']) | ||||
|                                   ], | ||||
|                                   [ | ||||
|                                     GeneratedFormItem( | ||||
|                                     GeneratedFormItem('nonInstalledApps', | ||||
|                                         label: tr('nonInstalledApps'), | ||||
|                                         type: FormItemType.bool) | ||||
|                                         type: FormItemType.bool, | ||||
|                                         defaultValue: vals['nonInstalledApps']) | ||||
|                                   ] | ||||
|                                 ], | ||||
|                                 defaultValues: filter == null | ||||
|                                     ? AppsFilter().toValuesArray() | ||||
|                                     : filter!.toValuesArray()); | ||||
|                                 ]); | ||||
|                           }).then((values) { | ||||
|                         if (values != null) { | ||||
|                           setState(() { | ||||
|                             filter = AppsFilter.fromValuesArray(values); | ||||
|                             filter = AppsFilter.fromValuesMap(values); | ||||
|                             if (AppsFilter().isIdenticalTo(filter!)) { | ||||
|                               filter = null; | ||||
|                             } | ||||
| @@ -740,20 +741,20 @@ class AppsFilter { | ||||
|       this.includeUptodate = true, | ||||
|       this.includeNonInstalled = true}); | ||||
|  | ||||
|   List<String> toValuesArray() { | ||||
|     return [ | ||||
|       nameFilter, | ||||
|       authorFilter, | ||||
|       includeUptodate ? 'true' : '', | ||||
|       includeNonInstalled ? 'true' : '' | ||||
|     ]; | ||||
|   Map<String, String> toValuesMap() { | ||||
|     return { | ||||
|       'appName': nameFilter, | ||||
|       'author': authorFilter, | ||||
|       'upToDateApps': includeUptodate ? 'true' : '', | ||||
|       'nonInstalledApps': includeNonInstalled ? 'true' : '' | ||||
|     }; | ||||
|   } | ||||
|  | ||||
|   AppsFilter.fromValuesArray(List<String> values) { | ||||
|     nameFilter = values[0]; | ||||
|     authorFilter = values[1]; | ||||
|     includeUptodate = values[2] == 'true'; | ||||
|     includeNonInstalled = values[3] == 'true'; | ||||
|   AppsFilter.fromValuesMap(Map<String, String> values) { | ||||
|     nameFilter = values['appName']!; | ||||
|     authorFilter = values['author']!; | ||||
|     includeUptodate = values['upToDateApps'] == 'true'; | ||||
|     includeNonInstalled = values['nonInstalledApps'] == 'true'; | ||||
|   } | ||||
|  | ||||
|   bool isIdenticalTo(AppsFilter other) => | ||||
|   | ||||
| @@ -145,7 +145,7 @@ class _ImportExportPageState extends State<ImportExportPage> { | ||||
|                                           title: tr('importFromURLList'), | ||||
|                                           items: [ | ||||
|                                             [ | ||||
|                                               GeneratedFormItem( | ||||
|                                               GeneratedFormItem('appURLList', | ||||
|                                                   label: tr('appURLList'), | ||||
|                                                   max: 7, | ||||
|                                                   additionalValidators: [ | ||||
| @@ -172,7 +172,6 @@ class _ImportExportPageState extends State<ImportExportPage> { | ||||
|                                                   ]) | ||||
|                                             ] | ||||
|                                           ], | ||||
|                                           defaultValues: const [], | ||||
|                                         ); | ||||
|                                       }).then((values) { | ||||
|                                     if (values != null) { | ||||
| @@ -237,11 +236,11 @@ class _ImportExportPageState extends State<ImportExportPage> { | ||||
|                                                           items: [ | ||||
|                                                             [ | ||||
|                                                               GeneratedFormItem( | ||||
|                                                                   'searchQuery', | ||||
|                                                                   label: tr( | ||||
|                                                                       'searchQuery')) | ||||
|                                                             ] | ||||
|                                                           ], | ||||
|                                                           defaultValues: const [], | ||||
|                                                         ); | ||||
|                                                       }); | ||||
|                                                   if (values != null && | ||||
| @@ -346,10 +345,10 @@ class _ImportExportPageState extends State<ImportExportPage> { | ||||
|                                                                   .requiredArgs | ||||
|                                                                   .map( | ||||
|                                                                       (e) => [ | ||||
|                                                                             GeneratedFormItem(label: e) | ||||
|                                                                             GeneratedFormItem(e, | ||||
|                                                                                 label: e) | ||||
|                                                                           ]) | ||||
|                                                                   .toList(), | ||||
|                                                           defaultValues: const [], | ||||
|                                                         ); | ||||
|                                                       }); | ||||
|                                                   if (values != null) { | ||||
|   | ||||
| @@ -143,16 +143,11 @@ class _SettingsPageState extends State<SettingsPage> { | ||||
|                 .toList(), | ||||
|             onValueChanges: (values, valid, isBuilding) { | ||||
|               if (valid) { | ||||
|                 for (var i = 0; i < values.length; i++) { | ||||
|                   settingsProvider.setSettingString( | ||||
|                       e.additionalSourceSpecificSettingFormItems[i].id, | ||||
|                       values[i]); | ||||
|                 } | ||||
|                 values.forEach((key, value) { | ||||
|                   settingsProvider.setSettingString(key, value); | ||||
|                 }); | ||||
|               } | ||||
|             }, | ||||
|             defaultValues: e.additionalSourceSpecificSettingFormItems.map((e) { | ||||
|               return settingsProvider.getSettingString(e.id) ?? ''; | ||||
|             }).toList()); | ||||
|             }); | ||||
|       } else { | ||||
|         return Container(); | ||||
|       } | ||||
|   | ||||
| @@ -313,7 +313,8 @@ class AppsProvider with ChangeNotifier { | ||||
|         throw ObtainiumError(tr('appNotFound')); | ||||
|       } | ||||
|       String? apkUrl; | ||||
|       if (!apps[id]!.app.trackOnly) { | ||||
|       var trackOnly = apps[id]!.app.additionalSettings['trackOnly'] == 'true'; | ||||
|       if (!trackOnly) { | ||||
|         apkUrl = await confirmApkUrl(apps[id]!.app, context); | ||||
|       } | ||||
|       if (apkUrl != null) { | ||||
| @@ -326,7 +327,7 @@ class AppsProvider with ChangeNotifier { | ||||
|           appsToInstall.add(id); | ||||
|         } | ||||
|       } | ||||
|       if (apps[id]!.app.trackOnly) { | ||||
|       if (trackOnly) { | ||||
|         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) | ||||
|   App? getCorrectedInstallStatusAppIfPossible(App app, AppInfo? installedInfo) { | ||||
|     var modded = false; | ||||
|     if (installedInfo == null && | ||||
|         app.installedVersion != null && | ||||
|         !app.trackOnly) { | ||||
|     var trackOnly = app.additionalSettings['trackOnly'] == 'true'; | ||||
|     var noVersionDetection = | ||||
|         app.additionalSettings['noVersionDetection'] == 'true'; | ||||
|     if (installedInfo == null && app.installedVersion != null && !trackOnly) { | ||||
|       app.installedVersion = null; | ||||
|       modded = true; | ||||
|     } else if (installedInfo?.versionName != null && | ||||
| @@ -461,7 +463,8 @@ class AppsProvider with ChangeNotifier { | ||||
|       app.installedVersion = installedInfo!.versionName; | ||||
|       modded = true; | ||||
|     } else if (installedInfo?.versionName != null && | ||||
|         installedInfo!.versionName != app.installedVersion) { | ||||
|         installedInfo!.versionName != app.installedVersion && | ||||
|         !noVersionDetection) { | ||||
|       String? correctedInstalledVersion = reconcileRealAndInternalVersions( | ||||
|           installedInfo.versionName!, app.installedVersion!); | ||||
|       if (correctedInstalledVersion != null) { | ||||
| @@ -470,7 +473,8 @@ class AppsProvider with ChangeNotifier { | ||||
|       } | ||||
|     } | ||||
|     if (app.installedVersion != null && | ||||
|         app.installedVersion != app.latestVersion) { | ||||
|         app.installedVersion != app.latestVersion && | ||||
|         !noVersionDetection) { | ||||
|       app.installedVersion = reconcileRealAndInternalVersions( | ||||
|               app.installedVersion!, app.latestVersion, | ||||
|               matchMode: true) ?? | ||||
| @@ -623,12 +627,8 @@ class AppsProvider with ChangeNotifier { | ||||
|     App newApp = await sourceProvider.getApp( | ||||
|         sourceProvider.getSource(currentApp.url), | ||||
|         currentApp.url, | ||||
|         currentApp.additionalData, | ||||
|         name: currentApp.name, | ||||
|         id: currentApp.id, | ||||
|         pinned: currentApp.pinned, | ||||
|         trackOnly: currentApp.trackOnly, | ||||
|         installedVersion: currentApp.installedVersion); | ||||
|         currentApp.additionalSettings, | ||||
|         currentApp: currentApp); | ||||
|     if (currentApp.preferredApkIndex < newApp.apkUrls.length) { | ||||
|       newApp.preferredApkIndex = currentApp.preferredApkIndex; | ||||
|     } | ||||
|   | ||||
| @@ -80,11 +80,11 @@ class DownloadNotification extends ObtainiumNotification { | ||||
|   DownloadNotification(String appName, int progPercent) | ||||
|       : super( | ||||
|             appName.hashCode, | ||||
|             'Downloading $appName', | ||||
|             tr('downloadingX', args: [appName]), | ||||
|             '', | ||||
|             'APP_DOWNLOADING', | ||||
|             'Downloading App', | ||||
|             'Notifies the user of the progress in downloading an App', | ||||
|             tr('downloadingX', args: [tr('app')]), | ||||
|             tr('downloadNotifDescription'), | ||||
|             Importance.low, | ||||
|             onlyAlertOnce: true, | ||||
|             progPercent: progPercent); | ||||
|   | ||||
| @@ -44,10 +44,9 @@ class App { | ||||
|   late String latestVersion; | ||||
|   List<String> apkUrls = []; | ||||
|   late int preferredApkIndex; | ||||
|   late List<String> additionalData; | ||||
|   late Map<String, String> additionalSettings; | ||||
|   late DateTime? lastUpdateCheck; | ||||
|   bool pinned = false; | ||||
|   bool trackOnly = false; | ||||
|   App( | ||||
|       this.id, | ||||
|       this.url, | ||||
| @@ -57,39 +56,59 @@ class App { | ||||
|       this.latestVersion, | ||||
|       this.apkUrls, | ||||
|       this.preferredApkIndex, | ||||
|       this.additionalData, | ||||
|       this.additionalSettings, | ||||
|       this.lastUpdateCheck, | ||||
|       this.pinned, | ||||
|       this.trackOnly); | ||||
|       this.pinned); | ||||
|  | ||||
|   @override | ||||
|   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( | ||||
|       json['id'] as String, | ||||
|       json['url'] as String, | ||||
|       json['author'] as String, | ||||
|       json['name'] as String, | ||||
|       json['installedVersion'] == null | ||||
|           ? null | ||||
|           : json['installedVersion'] as String, | ||||
|       json['latestVersion'] as String, | ||||
|       json['apkUrls'] == null | ||||
|           ? [] | ||||
|           : List<String>.from(jsonDecode(json['apkUrls'])), | ||||
|       json['preferredApkIndex'] == null ? 0 : json['preferredApkIndex'] as int, | ||||
|       json['additionalData'] == null | ||||
|           ? SourceProvider() | ||||
|               .getSource(json['url']) | ||||
|               .additionalSourceAppSpecificDefaults | ||||
|           : List<String>.from(jsonDecode(json['additionalData'])), | ||||
|       json['lastUpdateCheck'] == null | ||||
|           ? null | ||||
|           : DateTime.fromMicrosecondsSinceEpoch(json['lastUpdateCheck']), | ||||
|       json['pinned'] ?? false, | ||||
|       json['trackOnly'] ?? false); | ||||
|   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['url'] as String, | ||||
|         json['author'] as String, | ||||
|         json['name'] as String, | ||||
|         json['installedVersion'] == null | ||||
|             ? null | ||||
|             : json['installedVersion'] as String, | ||||
|         json['latestVersion'] as String, | ||||
|         json['apkUrls'] == null | ||||
|             ? [] | ||||
|             : List<String>.from(jsonDecode(json['apkUrls'])), | ||||
|         json['preferredApkIndex'] == null | ||||
|             ? 0 | ||||
|             : json['preferredApkIndex'] as int, | ||||
|         additionalSettings, | ||||
|         json['lastUpdateCheck'] == null | ||||
|             ? null | ||||
|             : DateTime.fromMicrosecondsSinceEpoch(json['lastUpdateCheck']), | ||||
|         json['pinned'] ?? false); | ||||
|   } | ||||
|  | ||||
|   Map<String, dynamic> toJson() => { | ||||
|         'id': id, | ||||
| @@ -100,10 +119,9 @@ class App { | ||||
|         'latestVersion': latestVersion, | ||||
|         'apkUrls': jsonEncode(apkUrls), | ||||
|         'preferredApkIndex': preferredApkIndex, | ||||
|         'additionalData': jsonEncode(additionalData), | ||||
|         'additionalSettings': jsonEncode(additionalSettings), | ||||
|         'lastUpdateCheck': lastUpdateCheck?.microsecondsSinceEpoch, | ||||
|         'pinned': pinned, | ||||
|         'trackOnly': trackOnly | ||||
|         'pinned': pinned | ||||
|       }; | ||||
| } | ||||
|  | ||||
| @@ -124,7 +142,7 @@ preStandardizeUrl(String url) { | ||||
|   return url; | ||||
| } | ||||
|  | ||||
| const String noAPKFound = 'No APK found'; | ||||
| String noAPKFound = tr('noAPKFound'); | ||||
|  | ||||
| List<String> getLinksFromParsedHTML( | ||||
|         Document dom, RegExp hrefPattern, String prependToLinks) => | ||||
| @@ -137,6 +155,13 @@ List<String> getLinksFromParsedHTML( | ||||
|         .map((e) => '$prependToLinks${e.attributes['href']!}') | ||||
|         .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 { | ||||
|   String? host; | ||||
|   late String name; | ||||
| @@ -151,23 +176,37 @@ class AppSource { | ||||
|   } | ||||
|  | ||||
|   Future<APKDetails> getLatestAPKDetails( | ||||
|       String standardUrl, List<String> additionalData, | ||||
|       {bool trackOnly = false}) { | ||||
|       String standardUrl, Map<String, String> additionalSettings) { | ||||
|     throw NotImplementedError(); | ||||
|   } | ||||
|  | ||||
|   // Different Sources may need different kinds of additional data for Apps | ||||
|   List<List<GeneratedFormItem>> additionalSourceAppSpecificFormItems = []; | ||||
|   List<String> additionalSourceAppSpecificDefaults = []; | ||||
|   List<List<GeneratedFormItem>> additionalSourceAppSpecificSettingFormItems = | ||||
|       []; | ||||
|  | ||||
|   // Some additional data may be needed for Apps regardless of Source | ||||
|   final List<GeneratedFormItem> additionalAppSpecificSourceAgnosticFormItems = [ | ||||
|     GeneratedFormItem( | ||||
|   final List<List<GeneratedFormItem>> | ||||
|       additionalAppSpecificSourceAgnosticSettingFormItems = [ | ||||
|     [ | ||||
|       GeneratedFormItem( | ||||
|         'trackOnly', | ||||
|         label: tr('trackOnly'), | ||||
|         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 | ||||
|   List<GeneratedFormItem> additionalSourceSpecificSettingFormItems = []; | ||||
| @@ -186,7 +225,7 @@ class AppSource { | ||||
|   } | ||||
|  | ||||
|   String? tryInferringAppId(String standardUrl, | ||||
|       {List<String> additionalData = const []}) { | ||||
|       {Map<String, String> additionalSettings = const {}}) { | ||||
|     return null; | ||||
|   } | ||||
| } | ||||
| @@ -246,8 +285,8 @@ class SourceProvider { | ||||
|     return source; | ||||
|   } | ||||
|  | ||||
|   bool ifSourceAppsRequireAdditionalData(AppSource source) { | ||||
|     for (var row in source.additionalSourceAppSpecificFormItems) { | ||||
|   bool ifRequiredAppSpecificSettingsExist(AppSource source) { | ||||
|     for (var row in source.combinedAppSpecificSettingFormItems) { | ||||
|       for (var element in row) { | ||||
|         if (element.required && element.opts == null) { | ||||
|           return true; | ||||
| @@ -274,37 +313,44 @@ class SourceProvider { | ||||
|     return true; | ||||
|   } | ||||
|  | ||||
|   Future<App> getApp(AppSource source, String url, List<String> additionalData, | ||||
|       {String name = '', | ||||
|       String? id, | ||||
|       bool pinned = false, | ||||
|       bool trackOnly = false, | ||||
|       String? installedVersion}) async { | ||||
|   Future<App> getApp( | ||||
|       AppSource source, String url, Map<String, String> additionalSettings, | ||||
|       {App? currentApp, | ||||
|       bool trackOnlyOverride = false, | ||||
|       noVersionDetectionOverride = false}) async { | ||||
|     if (trackOnlyOverride) { | ||||
|       additionalSettings['trackOnly'] = 'true'; | ||||
|     } | ||||
|     if (noVersionDetectionOverride) { | ||||
|       additionalSettings['noVersionDetection'] = 'true'; | ||||
|     } | ||||
|     var trackOnly = currentApp?.additionalSettings['trackOnly'] == 'true'; | ||||
|     String standardUrl = source.standardizeURL(preStandardizeUrl(url)); | ||||
|     APKDetails apk = await source | ||||
|         .getLatestAPKDetails(standardUrl, additionalData, trackOnly: trackOnly); | ||||
|     APKDetails apk = | ||||
|         await source.getLatestAPKDetails(standardUrl, additionalSettings); | ||||
|     if (apk.apkUrls.isEmpty && !trackOnly) { | ||||
|       throw NoAPKError(); | ||||
|     } | ||||
|     String apkVersion = apk.version.replaceAll('/', '-'); | ||||
|     var name = currentApp?.name.trim() ?? | ||||
|         apk.names.name[0].toUpperCase() + apk.names.name.substring(1); | ||||
|     return App( | ||||
|         id ?? | ||||
|         currentApp?.id ?? | ||||
|             source.tryInferringAppId(standardUrl, | ||||
|                 additionalData: additionalData) ?? | ||||
|                 additionalSettings: additionalSettings) ?? | ||||
|             generateTempID(apk.names, source), | ||||
|         standardUrl, | ||||
|         apk.names.author[0].toUpperCase() + apk.names.author.substring(1), | ||||
|         name.trim().isNotEmpty | ||||
|             ? name | ||||
|             : apk.names.name[0].toUpperCase() + apk.names.name.substring(1), | ||||
|         installedVersion, | ||||
|         currentApp?.installedVersion, | ||||
|         apkVersion, | ||||
|         apk.apkUrls, | ||||
|         apk.apkUrls.length - 1, | ||||
|         additionalData, | ||||
|         additionalSettings, | ||||
|         DateTime.now(), | ||||
|         pinned, | ||||
|         trackOnly); | ||||
|         currentApp?.pinned ?? false); | ||||
|   } | ||||
|  | ||||
|   // Returns errors in [results, errors] instead of throwing them | ||||
| @@ -316,7 +362,10 @@ class SourceProvider { | ||||
|       try { | ||||
|         var source = getSource(url); | ||||
|         apps.add(await getApp( | ||||
|             source, url, source.additionalSourceAppSpecificDefaults)); | ||||
|             source, | ||||
|             url, | ||||
|             getDefaultValuesFromFormItems( | ||||
|                 source.combinedAppSpecificSettingFormItems))); | ||||
|       } catch (e) { | ||||
|         errors.addAll(<String, dynamic>{url: e}); | ||||
|       } | ||||
|   | ||||
							
								
								
									
										26
									
								
								pubspec.lock
									
									
									
									
									
								
							
							
						
						
									
										26
									
								
								pubspec.lock
									
									
									
									
									
								
							| @@ -182,7 +182,7 @@ packages: | ||||
|       name: file_picker | ||||
|       url: "https://pub.dartlang.org" | ||||
|     source: hosted | ||||
|     version: "5.2.3" | ||||
|     version: "5.2.4" | ||||
|   flutter: | ||||
|     dependency: "direct main" | ||||
|     description: flutter | ||||
| @@ -215,14 +215,14 @@ packages: | ||||
|       name: flutter_local_notifications | ||||
|       url: "https://pub.dartlang.org" | ||||
|     source: hosted | ||||
|     version: "12.0.4" | ||||
|     version: "13.0.0" | ||||
|   flutter_local_notifications_linux: | ||||
|     dependency: transitive | ||||
|     description: | ||||
|       name: flutter_local_notifications_linux | ||||
|       url: "https://pub.dartlang.org" | ||||
|     source: hosted | ||||
|     version: "2.0.0" | ||||
|     version: "3.0.0" | ||||
|   flutter_local_notifications_platform_interface: | ||||
|     dependency: transitive | ||||
|     description: | ||||
| @@ -258,7 +258,7 @@ packages: | ||||
|       name: fluttertoast | ||||
|       url: "https://pub.dartlang.org" | ||||
|     source: hosted | ||||
|     version: "8.1.1" | ||||
|     version: "8.1.2" | ||||
|   html: | ||||
|     dependency: "direct main" | ||||
|     description: | ||||
| @@ -510,7 +510,7 @@ packages: | ||||
|       name: provider | ||||
|       url: "https://pub.dartlang.org" | ||||
|     source: hosted | ||||
|     version: "6.0.4" | ||||
|     version: "6.0.5" | ||||
|   share_plus: | ||||
|     dependency: "direct main" | ||||
|     description: | ||||
| @@ -552,7 +552,7 @@ packages: | ||||
|       name: shared_preferences_linux | ||||
|       url: "https://pub.dartlang.org" | ||||
|     source: hosted | ||||
|     version: "2.1.1" | ||||
|     version: "2.1.2" | ||||
|   shared_preferences_macos: | ||||
|     dependency: transitive | ||||
|     description: | ||||
| @@ -580,7 +580,7 @@ packages: | ||||
|       name: shared_preferences_windows | ||||
|       url: "https://pub.dartlang.org" | ||||
|     source: hosted | ||||
|     version: "2.1.1" | ||||
|     version: "2.1.2" | ||||
|   sky_engine: | ||||
|     dependency: transitive | ||||
|     description: flutter | ||||
| @@ -599,7 +599,7 @@ packages: | ||||
|       name: sqflite | ||||
|       url: "https://pub.dartlang.org" | ||||
|     source: hosted | ||||
|     version: "2.2.1" | ||||
|     version: "2.2.2" | ||||
|   sqflite_common: | ||||
|     dependency: transitive | ||||
|     description: | ||||
| @@ -739,35 +739,35 @@ packages: | ||||
|       name: webview_flutter | ||||
|       url: "https://pub.dartlang.org" | ||||
|     source: hosted | ||||
|     version: "3.0.4" | ||||
|     version: "4.0.0" | ||||
|   webview_flutter_android: | ||||
|     dependency: transitive | ||||
|     description: | ||||
|       name: webview_flutter_android | ||||
|       url: "https://pub.dartlang.org" | ||||
|     source: hosted | ||||
|     version: "2.10.4" | ||||
|     version: "3.0.0" | ||||
|   webview_flutter_platform_interface: | ||||
|     dependency: transitive | ||||
|     description: | ||||
|       name: webview_flutter_platform_interface | ||||
|       url: "https://pub.dartlang.org" | ||||
|     source: hosted | ||||
|     version: "1.9.5" | ||||
|     version: "2.0.0" | ||||
|   webview_flutter_wkwebview: | ||||
|     dependency: transitive | ||||
|     description: | ||||
|       name: webview_flutter_wkwebview | ||||
|       url: "https://pub.dartlang.org" | ||||
|     source: hosted | ||||
|     version: "2.9.5" | ||||
|     version: "3.0.0" | ||||
|   win32: | ||||
|     dependency: transitive | ||||
|     description: | ||||
|       name: win32 | ||||
|       url: "https://pub.dartlang.org" | ||||
|     source: hosted | ||||
|     version: "3.1.2" | ||||
|     version: "3.1.3" | ||||
|   xdg_directories: | ||||
|     dependency: transitive | ||||
|     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 | ||||
| # In Windows, build-name is used as the major, minor, and patch parts | ||||
| # of the product and file versions while build-number is used as the build suffix. | ||||
| version: 0.8.21+85 # When changing this, update the tag in main() accordingly | ||||
| version: 0.8.23+87 # When changing this, update the tag in main() accordingly | ||||
|  | ||||
| environment: | ||||
|   sdk: '>=2.18.2 <3.0.0' | ||||
| @@ -38,10 +38,10 @@ dependencies: | ||||
|   cupertino_icons: ^1.0.5 | ||||
|   path_provider: ^2.0.11 | ||||
|   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 | ||||
|   http: ^0.13.5 | ||||
|   webview_flutter: ^3.0.4 | ||||
|   webview_flutter: ^4.0.0 | ||||
|   dynamic_color: ^1.5.4 | ||||
|   html: ^0.15.0 | ||||
|   shared_preferences: ^2.0.15 | ||||
|   | ||||
		Reference in New Issue
	
	Block a user