Compare commits

...

23 Commits

Author SHA1 Message Date
e2f99c5e71 Increment version 2022-12-20 21:12:50 -05:00
1f582d239b Merge pull request #175 from RanTranslations/main
assests: Update Simplified Chinese
2022-12-20 21:10:48 -05:00
5e6b00718e Merge pull request #176 from bluefly000/main
Update Japanese translation
2022-12-20 21:10:26 -05:00
56594e6b19 Update Japanese translation 2022-12-21 10:44:18 +09:00
bbcc3ff9b3 assests: Update Simplified Chinese 2022-12-21 09:07:31 +08:00
ee66c53320 Updated plugins 2022-12-20 19:06:49 -05:00
b7d581f8b0 GitHub prereleases now not included by default 2022-12-20 18:48:54 -05:00
ead63ba21d Translation typos 2022-12-20 18:41:02 -05:00
c69404363f Increment version 2022-12-20 18:34:47 -05:00
99d0bd2461 Merge pull request #173 from atilluF/main
Fix Italian translation
2022-12-20 18:33:55 -05:00
54efda3eea Merge pull request #171 from markus-gitdev/main
Improving german translation
2022-12-20 18:33:47 -05:00
d76d68329c Merge pull request #174 from ImranR98/dev
"Additional Settings" related code changes for maintainability + other changes
2022-12-20 18:32:46 -05:00
b151eb27e1 Translations + bugfix 2022-12-20 18:19:44 -05:00
6a21045e5b Progress 2022-12-20 18:00:22 -05:00
6aedd9ce37 Update it.json (small fixes) 2022-12-20 18:48:33 +01:00
f319639a99 Merge branch 'ImranR98:main' into main 2022-12-20 08:58:43 +01:00
92e6798809 Update de.json
Abbreviating some texts to provide a better appearance.
2022-12-20 08:56:46 +01:00
9a129d41df Added migration code for additionalData (NOTHING TESTED) 2022-12-19 20:14:54 -05:00
0c2654a226 More fixes to prev commit 2022-12-19 19:58:12 -05:00
afc8e41171 Made defaultvallue part of formitem 2022-12-19 19:48:37 -05:00
1fe9e4f91e Started switching additionaldata to map 2022-12-19 19:34:43 -05:00
dbd6dec0a6 Merge remote-tracking branch 'origin/main' into dev 2022-12-19 16:19:59 -05:00
63034dd3f9 Added 'no version detection' option 2022-12-18 02:46:25 -05:00
29 changed files with 537 additions and 445 deletions

View File

@ -23,7 +23,7 @@
"githubPATLinkText": "Über GitHub PATs", "githubPATLinkText": "Über GitHub PATs",
"includePrereleases": "Vorabversionen einbeziehen", "includePrereleases": "Vorabversionen einbeziehen",
"fallbackToOlderReleases": "Fallback auf ältere Versionen", "fallbackToOlderReleases": "Fallback auf ältere Versionen",
"filterReleaseTitlesByRegEx": "Release-Titel nach regulärem Ausdruck filtern", "filterReleaseTitlesByRegEx": "Release-Titel nach regulärem Ausdruck\nfiltern",
"invalidRegEx": "Ungültiger regulärer Ausdruck", "invalidRegEx": "Ungültiger regulärer Ausdruck",
"noDescription": "Keine Beschreibung", "noDescription": "Keine Beschreibung",
"cancel": "Abbrechen", "cancel": "Abbrechen",
@ -43,7 +43,7 @@
"cancelled": "Abgebrochen", "cancelled": "Abgebrochen",
"appAlreadyAdded": "App bereits hinzugefügt", "appAlreadyAdded": "App bereits hinzugefügt",
"alreadyUpToDateQuestion": "App bereits auf dem neuesten Stand?", "alreadyUpToDateQuestion": "App bereits auf dem neuesten Stand?",
"addApp": "App hinzugefügt", "addApp": "App hinzufügen",
"appSourceURL": "Quell-URL der App", "appSourceURL": "Quell-URL der App",
"error": "Fehler", "error": "Fehler",
"add": "Hinzufügen", "add": "Hinzufügen",
@ -92,7 +92,7 @@
"filterActive": "Filter *", "filterActive": "Filter *",
"filterApps": "Apps filtern", "filterApps": "Apps filtern",
"appName": "App Name", "appName": "App Name",
"author": "Ersteller", "author": "Autor",
"upToDateApps": "Apps mit aktueller Version", "upToDateApps": "Apps mit aktueller Version",
"nonInstalledApps": "Nicht installierte Apps", "nonInstalledApps": "Nicht installierte Apps",
"importExport": "Import/Export", "importExport": "Import/Export",
@ -134,7 +134,7 @@
"neverManualOnly": "Nie - nur manuell", "neverManualOnly": "Nie - nur manuell",
"appearance": "Aussehen", "appearance": "Aussehen",
"showWebInAppView": "Quellwebseite in der App-Ansicht anzeigen", "showWebInAppView": "Quellwebseite in der App-Ansicht anzeigen",
"pinUpdates": "Aktualisierungen in der App-Ansicht oben anheften", "pinUpdates": "Apps mit Aktualisierungen oben anheften",
"updates": "Aktualisiert", "updates": "Aktualisiert",
"sourceSpecific": "Quellenspezifisch", "sourceSpecific": "Quellenspezifisch",
"appSource": "App-Quelle", "appSource": "App-Quelle",
@ -188,6 +188,17 @@
"steam": "Steam", "steam": "Steam",
"steamMobile": "Steam Mobile", "steamMobile": "Steam Mobile",
"steamChat": "Steam Chat", "steamChat": "Steam Chat",
"install": "Install",
"markInstalled": "Mark Installed",
"update": "Update",
"markUpdated": "Mark Updated",
"additionalOptions": "Additional Options",
"disableVersionDetection": "Disable Version Detection",
"noVersionDetectionExplanation": "This option should only be used for Apps where version detection does not work correctly.",
"downloadingX": "Downloading {}",
"downloadNotifDescription": "Notifies the user of the progress in downloading an App",
"noAPKFound": "No APK found",
"noVersionDetection": "No version detection",
"tooManyRequestsTryAgainInMinutes": { "tooManyRequestsTryAgainInMinutes": {
"one": "Zu viele Anfragen (Rate begrenzt) - versuchen Sie es in {} Minute erneut", "one": "Zu viele Anfragen (Rate begrenzt) - versuchen Sie es in {} Minute erneut",
"other": "Zu viele Anfragen (Rate begrenzt) - versuchen Sie es in {} Minuten erneut" "other": "Zu viele Anfragen (Rate begrenzt) - versuchen Sie es in {} Minuten erneut"

View File

@ -37,7 +37,7 @@
"xIsTrackOnly": "{} is Track-Only", "xIsTrackOnly": "{} is Track-Only",
"source": "Source", "source": "Source",
"app": "App", "app": "App",
"appsFromSourceAreTrackOnly": "Apps from this source are 'Track-Only'.' ", "appsFromSourceAreTrackOnly": "Apps from this source are 'Track-Only'.",
"youPickedTrackOnly": "You have selected the 'Track-Only' option.", "youPickedTrackOnly": "You have selected the 'Track-Only' option.",
"trackOnlyAppDescription": "The App will be tracked for updates, but Obtainium will not be able to download or install it.", "trackOnlyAppDescription": "The App will be tracked for updates, but Obtainium will not be able to download or install it.",
"cancelled": "Cancelled", "cancelled": "Cancelled",
@ -188,6 +188,17 @@
"steam": "Steam", "steam": "Steam",
"steamMobile": "Steam Mobile", "steamMobile": "Steam Mobile",
"steamChat": "Steam Chat", "steamChat": "Steam Chat",
"install": "Install",
"markInstalled": "Mark Installed",
"update": "Update",
"markUpdated": "Mark Updated",
"additionalOptions": "Additional Options",
"disableVersionDetection": "Disable Version Detection",
"noVersionDetectionExplanation": "This option should only be used for Apps where version detection does not work correctly.",
"downloadingX": "Downloading {}",
"downloadNotifDescription": "Notifies the user of the progress in downloading an App",
"noAPKFound": "No APK found",
"noVersionDetection": "No version detection",
"tooManyRequestsTryAgainInMinutes": { "tooManyRequestsTryAgainInMinutes": {
"one": "Too many requests (rate limited) - try again in {} minute", "one": "Too many requests (rate limited) - try again in {} minute",
"other": "Too many requests (rate limited) - try again in {} minutes" "other": "Too many requests (rate limited) - try again in {} minutes"

View File

@ -37,7 +37,7 @@
"xIsTrackOnly": "{} csak nyomon követhető", "xIsTrackOnly": "{} csak nyomon követhető",
"source": "Forrás", "source": "Forrás",
"app": "App", "app": "App",
"appsFromSourceAreTrackOnly": "Az ebből a forrásból származó alkalmazások 'Csak nyomon követhetőek'.' ", "appsFromSourceAreTrackOnly": "Az ebből a forrásból származó alkalmazások 'Csak nyomon követhetőek'.",
"youPickedTrackOnly": "A 'Csak követés' opciót választotta.", "youPickedTrackOnly": "A 'Csak követés' opciót választotta.",
"trackOnlyAppDescription": "Az alkalmazás frissítéseit nyomon követi, de az Obtainium nem tudja letölteni vagy telepíteni.", "trackOnlyAppDescription": "Az alkalmazás frissítéseit nyomon követi, de az Obtainium nem tudja letölteni vagy telepíteni.",
"cancelled": "Törölve", "cancelled": "Törölve",
@ -188,6 +188,17 @@
"steam": "Steam", "steam": "Steam",
"steamMobile": "Steam Mobile", "steamMobile": "Steam Mobile",
"steamChat": "Steam Chat", "steamChat": "Steam Chat",
"install": "Install",
"markInstalled": "Mark Installed",
"update": "Update",
"markUpdated": "Mark Updated",
"additionalOptions": "Additional Options",
"disableVersionDetection": "Disable Version Detection",
"noVersionDetectionExplanation": "This option should only be used for Apps where version detection does not work correctly.",
"downloadingX": "Downloading {}",
"downloadNotifDescription": "Notifies the user of the progress in downloading an App",
"noAPKFound": "No APK found",
"noVersionDetection": "No version detection",
"tooManyRequestsTryAgainInMinutes": { "tooManyRequestsTryAgainInMinutes": {
"one": "Túl sok kérés (korlátozott arány) próbálja újra {} perc múlva", "one": "Túl sok kérés (korlátozott arány) próbálja újra {} perc múlva",
"other": "Túl sok kérés (korlátozott arány) próbálja újra {} perc múlva" "other": "Túl sok kérés (korlátozott arány) próbálja újra {} perc múlva"
@ -232,4 +243,4 @@
"one": "A(z) {} és 1 további alkalmazás frissítve.", "one": "A(z) {} és 1 további alkalmazás frissítve.",
"other": "{} és további {} alkalmazás frissítve." "other": "{} és további {} alkalmazás frissítve."
} }
} }

View File

@ -22,13 +22,13 @@
"githubPATFormat": "username:token", "githubPATFormat": "username:token",
"githubPATLinkText": "Informazioni su GitHub PAT", "githubPATLinkText": "Informazioni su GitHub PAT",
"includePrereleases": "Includi prerelease", "includePrereleases": "Includi prerelease",
"fallbackToOlderReleases": "Ripiega su release datate", "fallbackToOlderReleases": "Ripiega su release precedenti",
"filterReleaseTitlesByRegEx": "Filtra le release con le espressioni regolari", "filterReleaseTitlesByRegEx": "Filtra release con espressioni regolari",
"invalidRegEx": "Espressione regolare invalida", "invalidRegEx": "Espressione regolare non valida",
"noDescription": "Descrizione assente", "noDescription": "Descrizione assente",
"cancel": "Annulla", "cancel": "Annulla",
"continue": "Continua", "continue": "Continua",
"requiredInBrackets": "(Richiesto)", "requiredInBrackets": "(richiesto)",
"dropdownNoOptsError": "ERRORE: LA TENDINA DEVE AVERE ALMENO UN'OPZIONE", "dropdownNoOptsError": "ERRORE: LA TENDINA DEVE AVERE ALMENO UN'OPZIONE",
"colour": "Colore", "colour": "Colore",
"githubStarredRepos": "i repository stellati da GitHub", "githubStarredRepos": "i repository stellati da GitHub",
@ -47,7 +47,7 @@
"appSourceURL": "URL della fonte dell'App", "appSourceURL": "URL della fonte dell'App",
"error": "Errore", "error": "Errore",
"add": "Aggiungi", "add": "Aggiungi",
"searchSomeSourcesLabel": "Cerca (disponibile solo per alcune fonti)", "searchSomeSourcesLabel": "Cerca (solo per alcune fonti)",
"search": "Cerca", "search": "Cerca",
"additionalOptsFor": "Opzioni aggiuntive per {}", "additionalOptsFor": "Opzioni aggiuntive per {}",
"supportedSourcesBelow": "Fonti supportate:", "supportedSourcesBelow": "Fonti supportate:",
@ -86,8 +86,8 @@
"shareSelectedAppURLs": "Condividi gli URL delle App selezionate", "shareSelectedAppURLs": "Condividi gli URL delle App selezionate",
"resetInstallStatus": "Ripristina lo stato d'installazione", "resetInstallStatus": "Ripristina lo stato d'installazione",
"more": "Di più", "more": "Di più",
"removeOutdatedFilter": "Rimuovi il filtro per le App datate", "removeOutdatedFilter": "Rimuovi il filtro per le App non aggiornate",
"showOutdatedOnly": "Mostra solo le App datate", "showOutdatedOnly": "Mostra solo le App non aggiornate",
"filter": "Filtri", "filter": "Filtri",
"filterActive": "Filtri *", "filterActive": "Filtri *",
"filterApps": "Filtra App", "filterApps": "Filtra App",
@ -106,7 +106,7 @@
"searchQuery": "Stringa di ricerca", "searchQuery": "Stringa di ricerca",
"appURLList": "Lista di URL delle App", "appURLList": "Lista di URL delle App",
"line": "Linea", "line": "Linea",
"searchX": "Cerca {}", "searchX": "Cerca su {}",
"noResults": "Nessun risultato trovato", "noResults": "Nessun risultato trovato",
"importX": "Importa {}", "importX": "Importa {}",
"importedAppsIdDisclaimer": "Le App importate potrebbero essere visualizzate erroneamente come \"Non installate\".\nPer risolvere il problema, reinstallale con Obtainium.\nQuesto non dovrebbe influire sui dati delle App.\n\nRiguarda solo l'URL e i metodi di importazione di terze parti.", "importedAppsIdDisclaimer": "Le App importate potrebbero essere visualizzate erroneamente come \"Non installate\".\nPer risolvere il problema, reinstallale con Obtainium.\nQuesto non dovrebbe influire sui dati delle App.\n\nRiguarda solo l'URL e i metodi di importazione di terze parti.",
@ -120,7 +120,7 @@
"theme": "Tema", "theme": "Tema",
"dark": "Scuro", "dark": "Scuro",
"light": "Chiaro", "light": "Chiaro",
"followSystem": "Segui il sistema", "followSystem": "Segui sistema",
"obtainium": "Obtainium", "obtainium": "Obtainium",
"materialYou": "Material You", "materialYou": "Material You",
"appSortBy": "App ordinate per", "appSortBy": "App ordinate per",
@ -133,10 +133,10 @@
"bgUpdateCheckInterval": "Intervallo di controllo degli aggiornamenti in background", "bgUpdateCheckInterval": "Intervallo di controllo degli aggiornamenti in background",
"neverManualOnly": "Mai - Solo manuale", "neverManualOnly": "Mai - Solo manuale",
"appearance": "Aspetto", "appearance": "Aspetto",
"showWebInAppView": "Mostra la pagina web dell'App se selezionata", "showWebInAppView": "Mostra pagina web dell'App se selezionata",
"pinUpdates": "Fissa in alto gli aggiornamenti disponibili nella pagina delle App", "pinUpdates": "Fissa in alto gli aggiornamenti disponibili",
"updates": "Aggiornato", "updates": "Aggiornato",
"sourceSpecific": "Specifico per la fonte", "sourceSpecific": "Specifiche per la fonte",
"appSource": "Sorgente dell'App", "appSource": "Sorgente dell'App",
"noLogs": "Nessun log", "noLogs": "Nessun log",
"appLogs": "Log dell'App", "appLogs": "Log dell'App",
@ -184,10 +184,21 @@
"appIdOrName": "ID o nome dell'App", "appIdOrName": "ID o nome dell'App",
"appWithIdOrNameNotFound": "Non è stata trovata alcuna App con quell'ID o nome", "appWithIdOrNameNotFound": "Non è stata trovata alcuna App con quell'ID o nome",
"reposHaveMultipleApps": "I repository possono contenere più App", "reposHaveMultipleApps": "I repository possono contenere più App",
"fdroidThirdPartyRepo": "Repository di terze parti di F-Droid", "fdroidThirdPartyRepo": "Repository F-Droid di terze parti",
"steam": "Steam", "steam": "Steam",
"steamMobile": "Steam Mobile", "steamMobile": "Steam Mobile",
"steamChat": "Steam Chat", "steamChat": "Steam Chat",
"install": "Install",
"markInstalled": "Mark Installed",
"update": "Update",
"markUpdated": "Mark Updated",
"additionalOptions": "Additional Options",
"disableVersionDetection": "Disable Version Detection",
"noVersionDetectionExplanation": "This option should only be used for Apps where version detection does not work correctly.",
"downloadingX": "Downloading {}",
"downloadNotifDescription": "Notifies the user of the progress in downloading an App",
"noAPKFound": "No APK found",
"noVersionDetection": "No version detection",
"tooManyRequestsTryAgainInMinutes": { "tooManyRequestsTryAgainInMinutes": {
"one": "Troppe richieste (traffico limitato) - riprova tra {} minuto", "one": "Troppe richieste (traffico limitato) - riprova tra {} minuto",
"other": "Troppe richieste (traffico limitato) - riprova tra {} minuti" "other": "Troppe richieste (traffico limitato) - riprova tra {} minuti"
@ -232,4 +243,4 @@
"one": "{} e un'altra App sono state aggiornate.", "one": "{} e un'altra App sono state aggiornate.",
"other": "{} e altre {} App sono state aggiornate." "other": "{} e altre {} App sono state aggiornate."
} }
} }

View File

@ -29,16 +29,16 @@
"cancel": "キャンセル", "cancel": "キャンセル",
"continue": "続ける", "continue": "続ける",
"requiredInBrackets": "(必須)", "requiredInBrackets": "(必須)",
"dropdownNoOptsError": "エラー: ドロップダウンには、少なくとも1つのoptが必要です", "dropdownNoOptsError": "エラー: ドロップダウンには、少なくとも1つのオプションが必要です",
"colour": "カラー", "colour": "カラー",
"githubStarredRepos": "Githubでスターしたリポジトリ", "githubStarredRepos": "Githubでスターしたリポジトリ",
"uname": "ユーザー名", "uname": "ユーザー名",
"wrongArgNum": "提供する引数の数が間違っています", "wrongArgNum": "提供する引数の数が間違っています",
"xIsTrackOnly": "{} は'追跡のみ'です", "xIsTrackOnly": "{} は追跡のみです",
"source": "ソース", "source": "ソース",
"app": "アプリ", "app": "アプリ",
"appsFromSourceAreTrackOnly": "このソースからのアプリは'追跡のみ'です'。", "appsFromSourceAreTrackOnly": "このソースからのアプリは追跡のみです。",
"youPickedTrackOnly": "'追跡のみ'を選択しています", "youPickedTrackOnly": "追跡のみを選択しています",
"trackOnlyAppDescription": "アプリのアップデートは追跡されますが、Obtainiumはアプリのダウンロードやインストールをすることはできません。", "trackOnlyAppDescription": "アプリのアップデートは追跡されますが、Obtainiumはアプリのダウンロードやインストールをすることはできません。",
"cancelled": "キャンセルしました", "cancelled": "キャンセルしました",
"appAlreadyAdded": "アプリはすでに追加されています", "appAlreadyAdded": "アプリはすでに追加されています",
@ -152,11 +152,11 @@
"sourceIsXButPackageFromYPrompt": "アプリのソースは'{}'ですが、リリースパッケージは'{}'から来ています。続行しますか?", "sourceIsXButPackageFromYPrompt": "アプリのソースは'{}'ですが、リリースパッケージは'{}'から来ています。続行しますか?",
"updatesAvailable": "アップデートが利用可能", "updatesAvailable": "アップデートが利用可能",
"updatesAvailableNotifDescription": "Obtainiumが追跡している1つまたは複数のアプリのアップデートが利用可能であることをユーザーに通知する", "updatesAvailableNotifDescription": "Obtainiumが追跡している1つまたは複数のアプリのアップデートが利用可能であることをユーザーに通知する",
"noNewUpdates": "新しいアップデートはありません", "noNewUpdates": "新しいアップデートはありません",
"xHasAnUpdate": "{}のアップデートが利用可能です", "xHasAnUpdate": "{}のアップデートが利用可能です",
"appsUpdated": "アプリをアップデートしました", "appsUpdated": "アプリをアップデートしました",
"appsUpdatedNotifDescription": "1つまたは複数のAppのアップデートがバックグラウンドで適用されたことをユーザーに通知する", "appsUpdatedNotifDescription": "1つまたは複数のAppのアップデートがバックグラウンドで適用されたことをユーザーに通知する",
"xWasUpdatedToY": "{}が{}にアップデートされました", "xWasUpdatedToY": "{}が{}にアップデートされました",
"errorCheckingUpdates": "アップデート確認中のエラー", "errorCheckingUpdates": "アップデート確認中のエラー",
"errorCheckingUpdatesNotifDescription": "バックグラウンドでのアップデート確認に失敗した際に表示される通知", "errorCheckingUpdatesNotifDescription": "バックグラウンドでのアップデート確認に失敗した際に表示される通知",
"appsRemoved": "削除されたアプリ", "appsRemoved": "削除されたアプリ",
@ -173,7 +173,7 @@
"versionCorrectionDisabled": "バージョン補正無効 (プラグインが動作していません)", "versionCorrectionDisabled": "バージョン補正無効 (プラグインが動作していません)",
"unknown": "不明", "unknown": "不明",
"none": "なし", "none": "なし",
"never": "Never", "never": "しない",
"latestVersionX": "最新のバージョン: {}", "latestVersionX": "最新のバージョン: {}",
"installedVersionX": "インストールされたバージョン: {}", "installedVersionX": "インストールされたバージョン: {}",
"lastUpdateCheckX": "最終アップデート確認: {}", "lastUpdateCheckX": "最終アップデート確認: {}",
@ -181,13 +181,24 @@
"removeAppQuestion": "アプリを削除しますか?", "removeAppQuestion": "アプリを削除しますか?",
"yesMarkUpdated": "はい、アップデート済みとしてマークします", "yesMarkUpdated": "はい、アップデート済みとしてマークします",
"fdroid": "F-Droid", "fdroid": "F-Droid",
"appIdOrName": "アプリIDまたは名前", "appIdOrName": "アプリIDまたは名前",
"appWithIdOrNameNotFound": "そのIDや名前を持つアプリは見つかりませんでした", "appWithIdOrNameNotFound": "そのIDや名前を持つアプリは見つかりませんでした",
"reposHaveMultipleApps": "リポジトリには複数のアプリが含まれることがあります", "reposHaveMultipleApps": "リポジトリには複数のアプリが含まれることがあります",
"fdroidThirdPartyRepo": "F-Droid Third-Party Repo", "fdroidThirdPartyRepo": "F-Droid Third-Party Repo",
"steam": "Steam", "steam": "Steam",
"steamMobile": "Steam Mobile", "steamMobile": "Steam Mobile",
"steamChat": "Steam Chat", "steamChat": "Steam Chat",
"install": "インストール",
"markInstalled": "インストール済みとしてマークする",
"update": "アップデート",
"markUpdated": "アップデート済みとしてマークする",
"additionalOptions": "追加のオプション",
"disableVersionDetection": "バージョン検出を無効にする",
"noVersionDetectionExplanation": "このオプションは、バージョン検出が正しく機能しないアプリにのみ使用する必要があります。",
"downloadingX": "{} をダウンロード中",
"downloadNotifDescription": "アプリのダウンロード状況を通知する",
"noAPKFound": "APKが見つかりません",
"noVersionDetection": "バージョン検出を行わない",
"tooManyRequestsTryAgainInMinutes": { "tooManyRequestsTryAgainInMinutes": {
"one": "リクエストが多すぎます(レート制限)- {}分後に再試行してください", "one": "リクエストが多すぎます(レート制限)- {}分後に再試行してください",
"other": "リクエストが多すぎます(レート制限)- {}分後に再試行してください" "other": "リクエストが多すぎます(レート制限)- {}分後に再試行してください"
@ -225,11 +236,11 @@
"other": "{n}個のログをクリアしました (前 = {before}, 後 = {after})" "other": "{n}個のログをクリアしました (前 = {before}, 後 = {after})"
}, },
"xAndNMoreUpdatesAvailable": { "xAndNMoreUpdatesAvailable": {
"one": "{}とさらに{}個のアプリのアップデートが利用可能です", "one": "{}とさらに{}個のアプリのアップデートが利用可能です",
"other": "{}とさらに{}個のアプリのアップデートが利用可能です" "other": "{}とさらに{}個のアプリのアップデートが利用可能です"
}, },
"xAndNMoreUpdatesInstalled": { "xAndNMoreUpdatesInstalled": {
"one": "{}とさらに{}個のアプリがアップデートされました", "one": "{}とさらに{}個のアプリがアップデートされました",
"other": "{}とさらに{}個のアプリがアップデートされました" "other": "{}とさらに{}個のアプリがアップデートされました"
} }
} }

View File

@ -22,9 +22,9 @@
"githubPATFormat": "username:token", "githubPATFormat": "username:token",
"githubPATLinkText": "关于 GitHub 个人访问令牌", "githubPATLinkText": "关于 GitHub 个人访问令牌",
"includePrereleases": "包含预发布版", "includePrereleases": "包含预发布版",
"fallbackToOlderReleases": "回到旧版", "fallbackToOlderReleases": "回退到旧版",
"filterReleaseTitlesByRegEx": "通过正则表达式过滤发布标题", "filterReleaseTitlesByRegEx": "使用正则以过滤发布标题",
"invalidRegEx": "无效的正则表达式", "invalidRegEx": "表达式无效",
"noDescription": "无描述", "noDescription": "无描述",
"cancel": "取消", "cancel": "取消",
"continue": "继续", "continue": "继续",
@ -109,7 +109,7 @@
"searchX": "搜索 {}", "searchX": "搜索 {}",
"noResults": "无结果", "noResults": "无结果",
"importX": "导入 {}", "importX": "导入 {}",
"importedAppsIdDisclaimer": "导入的应用程序可能不正确地显示为未安装。要解决这个问题,请通过 Obtainium 重新安装它们。", "importedAppsIdDisclaimer": "导入的应用程序可能显示为未安装。要解决这个问题,请通过 Obtainium 重新安装它们。",
"importErrors": "导入错误", "importErrors": "导入错误",
"importedXOfYApps": "{} 中的 {} 个应用已导入", "importedXOfYApps": "{} 中的 {} 个应用已导入",
"followingURLsHadErrors": "以下 URL 有错误:", "followingURLsHadErrors": "以下 URL 有错误:",
@ -134,7 +134,7 @@
"neverManualOnly": "手动", "neverManualOnly": "手动",
"appearance": "外观", "appearance": "外观",
"showWebInAppView": "在应用来源页显示网页", "showWebInAppView": "在应用来源页显示网页",
"pinUpdates": "将需要更新的应用固定到顶部", "pinUpdates": "更新的应用置顶",
"updates": "检查间隔", "updates": "检查间隔",
"sourceSpecific": "Github 访问令牌", "sourceSpecific": "Github 访问令牌",
"appSource": "源代码", "appSource": "源代码",
@ -145,9 +145,9 @@
"appNotFound": "未找到应用", "appNotFound": "未找到应用",
"obtainiumExportHyphenatedLowercase": "obtainium-导出", "obtainiumExportHyphenatedLowercase": "obtainium-导出",
"pickAnAPK": "选择一个安装包", "pickAnAPK": "选择一个安装包",
"appHasMoreThanOnePackage": "{} 有多于一个安装包", "appHasMoreThanOnePackage": "{} 有多个架构可用",
"deviceSupportsXArch": "你的设备支持 {} CPU 架构", "deviceSupportsXArch": "你的设备支持 {} 架构",
"deviceSupportsFollowingArchs": "你的设备支持以下 CPU 架构:", "deviceSupportsFollowingArchs": "你的设备支持以下架构:",
"warning": "警告", "warning": "警告",
"sourceIsXButPackageFromYPrompt": "此应用来源是 '{}' 但更新包来自 '{}'。 继续吗?", "sourceIsXButPackageFromYPrompt": "此应用来源是 '{}' 但更新包来自 '{}'。 继续吗?",
"updatesAvailable": "更新可用", "updatesAvailable": "更新可用",
@ -188,6 +188,17 @@
"steam": "Steam", "steam": "Steam",
"steamMobile": "Steam Mobile", "steamMobile": "Steam Mobile",
"steamChat": "Steam Chat", "steamChat": "Steam Chat",
"install": "安装",
"markInstalled": "标记为已安装",
"update": "更新",
"markUpdated": "标记为已更新",
"additionalOptions": "附加选项",
"disableVersionDetection": "关闭版本检测",
"noVersionDetectionExplanation": "此选项应只用于版本检测不能工作的应用程序",
"downloadingX": "下载中 {}",
"downloadNotifDescription": "通知用户下载进度",
"noAPKFound": "未找到安装包",
"noVersionDetection": "无版本检测",
"tooManyRequestsTryAgainInMinutes": { "tooManyRequestsTryAgainInMinutes": {
"one": "请求过多 (API 限制) - 在 {} 分钟后重试", "one": "请求过多 (API 限制) - 在 {} 分钟后重试",
"other": "请求过多 (API 限制) - 在 {} 分钟后重试" "other": "请求过多 (API 限制) - 在 {} 分钟后重试"

View File

@ -25,8 +25,9 @@ class APKMirror extends AppSource {
@override @override
Future<APKDetails> getLatestAPKDetails( Future<APKDetails> getLatestAPKDetails(
String standardUrl, List<String> additionalData, String standardUrl,
{bool trackOnly = false}) async { Map<String, String> additionalSettings,
) async {
Response res = await get(Uri.parse('$standardUrl/feed')); Response res = await get(Uri.parse('$standardUrl/feed'));
if (res.statusCode == 200) { if (res.statusCode == 200) {
String? titleString = parse(res.body) String? titleString = parse(res.body)

View File

@ -32,7 +32,7 @@ class FDroid extends AppSource {
@override @override
String? tryInferringAppId(String standardUrl, String? tryInferringAppId(String standardUrl,
{List<String> additionalData = const []}) { {Map<String, String> additionalSettings = const {}}) {
return Uri.parse(standardUrl).pathSegments.last; return Uri.parse(standardUrl).pathSegments.last;
} }
@ -60,8 +60,9 @@ class FDroid extends AppSource {
@override @override
Future<APKDetails> getLatestAPKDetails( Future<APKDetails> getLatestAPKDetails(
String standardUrl, List<String> additionalData, String standardUrl,
{bool trackOnly = false}) async { Map<String, String> additionalSettings,
) async {
String? appId = tryInferringAppId(standardUrl); String? appId = tryInferringAppId(standardUrl);
return getAPKUrlsFromFDroidPackagesAPIResponse( return getAPKUrlsFromFDroidPackagesAPIResponse(
await get(Uri.parse('https://f-droid.org/api/v1/packages/$appId')), await get(Uri.parse('https://f-droid.org/api/v1/packages/$appId')),

View File

@ -9,13 +9,12 @@ class FDroidRepo extends AppSource {
FDroidRepo() { FDroidRepo() {
name = tr('fdroidThirdPartyRepo'); name = tr('fdroidThirdPartyRepo');
additionalSourceAppSpecificFormItems = [ additionalSourceAppSpecificSettingFormItems = [
[ [
GeneratedFormItem( GeneratedFormItem('appIdOrName',
label: tr('appIdOrName'), label: tr('appIdOrName'),
hint: tr('reposHaveMultipleApps'), hint: tr('reposHaveMultipleApps'),
required: true, required: true)
key: 'appIdOrName')
] ]
]; ];
} }
@ -33,13 +32,10 @@ class FDroidRepo extends AppSource {
@override @override
Future<APKDetails> getLatestAPKDetails( Future<APKDetails> getLatestAPKDetails(
String standardUrl, List<String> additionalData, String standardUrl,
{bool trackOnly = false}) async { Map<String, String> additionalSettings,
String? appIdOrName = findGeneratedFormValueByKey( ) async {
additionalSourceAppSpecificFormItems String? appIdOrName = additionalSettings['appIdOrName'];
.reduce((value, element) => [...value, ...element]),
additionalData,
'appIdOrName');
if (appIdOrName == null) { if (appIdOrName == null) {
throw NoReleasesError(); throw NoReleasesError();
} }

View File

@ -12,12 +12,9 @@ class GitHub extends AppSource {
GitHub() { GitHub() {
host = 'github.com'; host = 'github.com';
additionalSourceAppSpecificDefaults = ['true', 'true', ''];
additionalSourceSpecificSettingFormItems = [ additionalSourceSpecificSettingFormItems = [
GeneratedFormItem( GeneratedFormItem('github-creds',
label: tr('githubPATLabel'), label: tr('githubPATLabel'),
id: 'github-creds',
required: false, required: false,
additionalValidators: [ additionalValidators: [
(value) { (value) {
@ -52,17 +49,21 @@ class GitHub extends AppSource {
]) ])
]; ];
additionalSourceAppSpecificFormItems = [ additionalSourceAppSpecificSettingFormItems = [
[ [
GeneratedFormItem( GeneratedFormItem('includePrereleases',
label: tr('includePrereleases'), type: FormItemType.bool) label: tr('includePrereleases'),
type: FormItemType.bool,
defaultValue: '')
], ],
[ [
GeneratedFormItem( GeneratedFormItem('fallbackToOlderReleases',
label: tr('fallbackToOlderReleases'), type: FormItemType.bool) label: tr('fallbackToOlderReleases'),
type: FormItemType.bool,
defaultValue: 'true')
], ],
[ [
GeneratedFormItem( GeneratedFormItem('filterReleaseTitlesByRegEx',
label: tr('filterReleaseTitlesByRegEx'), label: tr('filterReleaseTitlesByRegEx'),
type: FormItemType.string, type: FormItemType.string,
required: false, required: false,
@ -99,7 +100,7 @@ class GitHub extends AppSource {
SettingsProvider settingsProvider = SettingsProvider(); SettingsProvider settingsProvider = SettingsProvider();
await settingsProvider.initializeSettings(); await settingsProvider.initializeSettings();
String? creds = settingsProvider String? creds = settingsProvider
.getSettingString(additionalSourceSpecificSettingFormItems[0].id); .getSettingString(additionalSourceSpecificSettingFormItems[0].key);
return creds != null && creds.isNotEmpty ? '$creds@' : ''; return creds != null && creds.isNotEmpty ? '$creds@' : '';
} }
@ -109,15 +110,16 @@ class GitHub extends AppSource {
@override @override
Future<APKDetails> getLatestAPKDetails( Future<APKDetails> getLatestAPKDetails(
String standardUrl, List<String> additionalData, String standardUrl,
{bool trackOnly = false}) async { Map<String, String> additionalSettings,
var includePrereleases = ) async {
additionalData.isNotEmpty && additionalData[0] == 'true'; var includePrereleases = additionalSettings['includePrereleases'] == 'true';
var fallbackToOlderReleases = var fallbackToOlderReleases =
additionalData.length >= 2 && additionalData[1] == 'true'; additionalSettings['fallbackToOlderReleases'] == 'true';
var regexFilter = additionalData.length >= 3 && additionalData[2].isNotEmpty var regexFilter =
? additionalData[2] additionalSettings['filterReleaseTitlesByRegEx']?.isNotEmpty == true
: null; ? additionalSettings['filterReleaseTitlesByRegEx']
: null;
Response res = await get(Uri.parse( Response res = await get(Uri.parse(
'https://${await getCredentialPrefixIfAny()}api.$host/repos${standardUrl.substring('https://$host'.length)}/releases')); 'https://${await getCredentialPrefixIfAny()}api.$host/repos${standardUrl.substring('https://$host'.length)}/releases'));
if (res.statusCode == 200) { if (res.statusCode == 200) {
@ -148,7 +150,7 @@ class GitHub extends AppSource {
continue; continue;
} }
var apkUrls = getReleaseAPKUrls(releases[i]); var apkUrls = getReleaseAPKUrls(releases[i]);
if (apkUrls.isEmpty && !trackOnly) { if (apkUrls.isEmpty && additionalSettings['trackOnly'] != 'true') {
continue; continue;
} }
targetRelease = releases[i]; targetRelease = releases[i];

View File

@ -25,8 +25,9 @@ class GitLab extends AppSource {
@override @override
Future<APKDetails> getLatestAPKDetails( Future<APKDetails> getLatestAPKDetails(
String standardUrl, List<String> additionalData, String standardUrl,
{bool trackOnly = false}) async { Map<String, String> additionalSettings,
) async {
Response res = await get(Uri.parse('$standardUrl/-/tags?format=atom')); Response res = await get(Uri.parse('$standardUrl/-/tags?format=atom'));
if (res.statusCode == 200) { if (res.statusCode == 200) {
var standardUri = Uri.parse(standardUrl); var standardUri = Uri.parse(standardUrl);

View File

@ -23,14 +23,15 @@ class IzzyOnDroid extends AppSource {
@override @override
String? tryInferringAppId(String standardUrl, String? tryInferringAppId(String standardUrl,
{List<String> additionalData = const []}) { {Map<String, String> additionalSettings = const {}}) {
return FDroid().tryInferringAppId(standardUrl); return FDroid().tryInferringAppId(standardUrl);
} }
@override @override
Future<APKDetails> getLatestAPKDetails( Future<APKDetails> getLatestAPKDetails(
String standardUrl, List<String> additionalData, String standardUrl,
{bool trackOnly = false}) async { Map<String, String> additionalSettings,
) async {
String? appId = tryInferringAppId(standardUrl); String? appId = tryInferringAppId(standardUrl);
return FDroid().getAPKUrlsFromFDroidPackagesAPIResponse( return FDroid().getAPKUrlsFromFDroidPackagesAPIResponse(
await get( await get(

View File

@ -24,8 +24,9 @@ class Mullvad extends AppSource {
@override @override
Future<APKDetails> getLatestAPKDetails( Future<APKDetails> getLatestAPKDetails(
String standardUrl, List<String> additionalData, String standardUrl,
{bool trackOnly = false}) async { Map<String, String> additionalSettings,
) async {
Response res = await get(Uri.parse('$standardUrl/en/download/android')); Response res = await get(Uri.parse('$standardUrl/en/download/android'));
if (res.statusCode == 200) { if (res.statusCode == 200) {
var version = parse(res.body) var version = parse(res.body)

View File

@ -18,8 +18,9 @@ class Signal extends AppSource {
@override @override
Future<APKDetails> getLatestAPKDetails( Future<APKDetails> getLatestAPKDetails(
String standardUrl, List<String> additionalData, String standardUrl,
{bool trackOnly = false}) async { Map<String, String> additionalSettings,
) async {
Response res = Response res =
await get(Uri.parse('https://updates.$host/android/latest.json')); await get(Uri.parse('https://updates.$host/android/latest.json'));
if (res.statusCode == 200) { if (res.statusCode == 200) {

View File

@ -23,8 +23,9 @@ class SourceForge extends AppSource {
@override @override
Future<APKDetails> getLatestAPKDetails( Future<APKDetails> getLatestAPKDetails(
String standardUrl, List<String> additionalData, String standardUrl,
{bool trackOnly = false}) async { Map<String, String> additionalSettings,
) async {
Response res = await get(Uri.parse('$standardUrl/rss?path=/')); Response res = await get(Uri.parse('$standardUrl/rss?path=/'));
if (res.statusCode == 200) { if (res.statusCode == 200) {
var parsedHtml = parse(res.body); var parsedHtml = parse(res.body);

View File

@ -9,13 +9,10 @@ class SteamMobile extends AppSource {
SteamMobile() { SteamMobile() {
host = 'store.steampowered.com'; host = 'store.steampowered.com';
name = tr('steam'); name = tr('steam');
additionalSourceAppSpecificFormItems = [ additionalSourceAppSpecificSettingFormItems = [
[ [
GeneratedFormItem( GeneratedFormItem('app',
label: tr('app'), label: tr('app'), required: true, opts: apks.entries.toList())
key: 'app',
required: true,
opts: apks.entries.toList())
] ]
]; ];
} }
@ -32,15 +29,12 @@ class SteamMobile extends AppSource {
@override @override
Future<APKDetails> getLatestAPKDetails( Future<APKDetails> getLatestAPKDetails(
String standardUrl, List<String> additionalData, String standardUrl,
{bool trackOnly = false}) async { Map<String, String> additionalSettings,
) async {
Response res = await get(Uri.parse('https://$host/mobile')); Response res = await get(Uri.parse('https://$host/mobile'));
if (res.statusCode == 200) { if (res.statusCode == 200) {
var apkNamePrefix = findGeneratedFormValueByKey( var apkNamePrefix = additionalSettings['app'];
additionalSourceAppSpecificFormItems
.reduce((value, element) => [...value, ...element]),
additionalData,
'app');
if (apkNamePrefix == null) { if (apkNamePrefix == null) {
throw NoReleasesError(); throw NoReleasesError();
} }

View File

@ -4,7 +4,7 @@ import 'package:flutter/material.dart';
enum FormItemType { string, bool } enum FormItemType { string, bool }
typedef OnValueChanges = void Function( typedef OnValueChanges = void Function(
List<String> values, bool valid, bool isBuilding); Map<String, String> values, bool valid, bool isBuilding);
class GeneratedFormItem { class GeneratedFormItem {
late String key; late String key;
@ -13,22 +13,21 @@ class GeneratedFormItem {
late bool required; late bool required;
late int max; late int max;
late List<String? Function(String? value)> additionalValidators; late List<String? Function(String? value)> additionalValidators;
late String id;
late List<Widget> belowWidgets; late List<Widget> belowWidgets;
late String? hint; late String? hint;
late List<MapEntry<String, String>>? opts; late List<MapEntry<String, String>>? opts;
late String? defaultValue;
GeneratedFormItem( GeneratedFormItem(this.key,
{this.label = 'Input', {this.label = 'Input',
this.type = FormItemType.string, this.type = FormItemType.string,
this.required = true, this.required = true,
this.max = 1, this.max = 1,
this.additionalValidators = const [], this.additionalValidators = const [],
this.id = 'input',
this.belowWidgets = const [], this.belowWidgets = const [],
this.hint, this.hint,
this.opts, this.opts,
this.key = 'default'}) { this.defaultValue}) {
if (type != FormItemType.string) { if (type != FormItemType.string) {
required = false; required = false;
} }
@ -37,14 +36,10 @@ class GeneratedFormItem {
class GeneratedForm extends StatefulWidget { class GeneratedForm extends StatefulWidget {
const GeneratedForm( const GeneratedForm(
{super.key, {super.key, required this.items, required this.onValueChanges});
required this.items,
required this.onValueChanges,
required this.defaultValues});
final List<List<GeneratedFormItem>> items; final List<List<GeneratedFormItem>> items;
final OnValueChanges onValueChanges; final OnValueChanges onValueChanges;
final List<String> defaultValues;
@override @override
State<GeneratedForm> createState() => _GeneratedFormState(); State<GeneratedForm> createState() => _GeneratedFormState();
@ -52,17 +47,18 @@ class GeneratedForm extends StatefulWidget {
class _GeneratedFormState extends State<GeneratedForm> { class _GeneratedFormState extends State<GeneratedForm> {
final _formKey = GlobalKey<FormState>(); final _formKey = GlobalKey<FormState>();
late List<List<String>> values; Map<String, String> values = {};
late List<List<Widget>> formInputs; late List<List<Widget>> formInputs;
List<List<Widget>> rows = []; List<List<Widget>> rows = [];
// If any value changes, call this to update the parent with value and validity // If any value changes, call this to update the parent with value and validity
void someValueChanged({bool isBuilding = false}) { void someValueChanged({bool isBuilding = false}) {
List<String> returnValues = []; Map<String, String> returnValues = {};
var valid = true; var valid = true;
for (int r = 0; r < values.length; r++) { for (int r = 0; r < widget.items.length; r++) {
for (int i = 0; i < values[r].length; i++) { for (int i = 0; i < widget.items[r].length; i++) {
returnValues.add(values[r][i]); returnValues[widget.items[r][i].key] =
values[widget.items[r][i].key] ?? '';
if (formInputs[r][i] is TextFormField) { if (formInputs[r][i] is TextFormField) {
valid = valid && valid = valid &&
((formInputs[r][i].key as GlobalKey<FormFieldState>) ((formInputs[r][i].key as GlobalKey<FormFieldState>)
@ -80,16 +76,13 @@ class _GeneratedFormState extends State<GeneratedForm> {
super.initState(); super.initState();
// Initialize form values as all empty // Initialize form values as all empty
values.clear();
int j = 0; int j = 0;
values = widget.items for (var row in widget.items) {
.map((row) => row.map((e) { for (var e in row) {
return j < widget.defaultValues.length values[e.key] = e.defaultValue ?? e.opts?.first.key ?? '';
? widget.defaultValues[j++] }
: e.opts != null }
? e.opts!.first.key
: '';
}).toList())
.toList();
// Dynamically create form inputs // Dynamically create form inputs
formInputs = widget.items.asMap().entries.map((row) { formInputs = widget.items.asMap().entries.map((row) {
@ -98,11 +91,11 @@ class _GeneratedFormState extends State<GeneratedForm> {
final formFieldKey = GlobalKey<FormFieldState>(); final formFieldKey = GlobalKey<FormFieldState>();
return TextFormField( return TextFormField(
key: formFieldKey, key: formFieldKey,
initialValue: values[row.key][e.key], initialValue: values[e.value.key],
autovalidateMode: AutovalidateMode.onUserInteraction, autovalidateMode: AutovalidateMode.onUserInteraction,
onChanged: (value) { onChanged: (value) {
setState(() { setState(() {
values[row.key][e.key] = value; values[e.value.key] = value;
someValueChanged(); someValueChanged();
}); });
}, },
@ -131,14 +124,14 @@ class _GeneratedFormState extends State<GeneratedForm> {
} }
return DropdownButtonFormField( return DropdownButtonFormField(
decoration: InputDecoration(labelText: e.value.label), decoration: InputDecoration(labelText: e.value.label),
value: values[row.key][e.key], value: values[e.value.key],
items: e.value.opts! items: e.value.opts!
.map((e) => .map((e) =>
DropdownMenuItem(value: e.key, child: Text(e.value))) DropdownMenuItem(value: e.key, child: Text(e.value)))
.toList(), .toList(),
onChanged: (value) { onChanged: (value) {
setState(() { setState(() {
values[row.key][e.key] = value ?? e.value.opts!.first.key; values[e.value.key] = value ?? e.value.opts!.first.key;
someValueChanged(); someValueChanged();
}); });
}); });
@ -160,10 +153,10 @@ class _GeneratedFormState extends State<GeneratedForm> {
children: [ children: [
Text(widget.items[r][e].label), Text(widget.items[r][e].label),
Switch( Switch(
value: values[r][e] == 'true', value: values[widget.items[r][e].key] == 'true',
onChanged: (value) { onChanged: (value) {
setState(() { setState(() {
values[r][e] = value ? 'true' : ''; values[widget.items[r][e].key] = value ? 'true' : '';
someValueChanged(); someValueChanged();
}); });
}) })
@ -217,18 +210,3 @@ class _GeneratedFormState extends State<GeneratedForm> {
)); ));
} }
} }
String? findGeneratedFormValueByKey(
List<GeneratedFormItem> items, List<String> values, String key) {
var foundIndex = -1;
for (var i = 0; i < items.length; i++) {
if (items[i].key == key) {
foundIndex = i;
break;
}
}
if (foundIndex >= 0 && foundIndex < values.length) {
return values[foundIndex];
}
return null;
}

View File

@ -8,14 +8,12 @@ class GeneratedFormModal extends StatefulWidget {
{super.key, {super.key,
required this.title, required this.title,
required this.items, required this.items,
required this.defaultValues,
this.initValid = false, this.initValid = false,
this.message = ''}); this.message = ''});
final String title; final String title;
final String message; final String message;
final List<List<GeneratedFormItem>> items; final List<List<GeneratedFormItem>> items;
final List<String> defaultValues;
final bool initValid; final bool initValid;
@override @override
@ -23,13 +21,12 @@ class GeneratedFormModal extends StatefulWidget {
} }
class _GeneratedFormModalState extends State<GeneratedFormModal> { class _GeneratedFormModalState extends State<GeneratedFormModal> {
List<String> values = []; Map<String, String> values = {};
bool valid = false; bool valid = false;
@override @override
void initState() { void initState() {
super.initState(); super.initState();
values = widget.defaultValues;
valid = widget.initValid || widget.items.isEmpty; valid = widget.initValid || widget.items.isEmpty;
} }
@ -57,8 +54,7 @@ class _GeneratedFormModalState extends State<GeneratedFormModal> {
this.valid = valid; this.valid = valid;
}); });
} }
}, })
defaultValues: widget.defaultValues)
]), ]),
actions: [ actions: [
TextButton( TextButton(

View File

@ -21,7 +21,7 @@ import 'package:easy_localization/src/easy_localization_controller.dart';
// ignore: implementation_imports // ignore: implementation_imports
import 'package:easy_localization/src/localization.dart'; import 'package:easy_localization/src/localization.dart';
const String currentVersion = '0.8.21'; const String currentVersion = '0.8.23';
const String currentReleaseTag = const String currentReleaseTag =
'v$currentVersion-beta'; // KEEP THIS IN SYNC WITH GITHUB RELEASES 'v$currentVersion-beta'; // KEEP THIS IN SYNC WITH GITHUB RELEASES
@ -200,9 +200,8 @@ class _ObtainiumState extends State<Obtainium> {
currentReleaseTag, currentReleaseTag,
[], [],
0, 0,
['true'], {'includePrereleases': 'true'},
null, null,
false,
false) false)
]); ]);
} }

View File

@ -27,10 +27,8 @@ class _AddAppPageState extends State<AddAppPage> {
String userInput = ''; String userInput = '';
String searchQuery = ''; String searchQuery = '';
AppSource? pickedSource; AppSource? pickedSource;
List<String> sourceSpecificAdditionalData = []; Map<String, String> additionalSettings = {};
bool sourceSpecificDataIsValid = true; bool additionalSettingsValid = true;
List<String> otherAdditionalData = [];
bool otherAdditionalDataIsValid = true;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@ -43,10 +41,12 @@ class _AddAppPageState extends State<AddAppPage> {
var source = valid ? sourceProvider.getSource(userInput) : null; var source = valid ? sourceProvider.getSource(userInput) : null;
if (pickedSource.runtimeType != source.runtimeType) { if (pickedSource.runtimeType != source.runtimeType) {
pickedSource = source; pickedSource = source;
sourceSpecificAdditionalData = additionalSettings = source != null
source != null ? source.additionalSourceAppSpecificDefaults : []; ? getDefaultValuesFromFormItems(
sourceSpecificDataIsValid = source != null source.combinedAppSpecificSettingFormItems)
? !sourceProvider.ifSourceAppsRequireAdditionalData(source) : {};
additionalSettingsValid = source != null
? !sourceProvider.ifRequiredAppSpecificSettingsExist(source)
: true; : true;
} }
} }
@ -66,11 +66,9 @@ class _AddAppPageState extends State<AddAppPage> {
}); });
var settingsProvider = context.read<SettingsProvider>(); var settingsProvider = context.read<SettingsProvider>();
() async { () async {
var userPickedTrackOnly = findGeneratedFormValueByKey( var userPickedTrackOnly = additionalSettings['trackOnly'] == 'true';
pickedSource!.additionalAppSpecificSourceAgnosticFormItems, var userPickedNoVersionDetection =
otherAdditionalData, additionalSettings['noVersionDetection'] == 'true';
'trackOnlyFormItemKey') ==
'true';
var cont = true; var cont = true;
if ((userPickedTrackOnly || pickedSource!.enforceTrackOnly) && if ((userPickedTrackOnly || pickedSource!.enforceTrackOnly) &&
await showDialog( await showDialog(
@ -83,7 +81,6 @@ class _AddAppPageState extends State<AddAppPage> {
: tr('app') : tr('app')
]), ]),
items: const [], items: const [],
defaultValues: const [],
message: message:
'${pickedSource!.enforceTrackOnly ? tr('appsFromSourceAreTrackOnly') : tr('youPickedTrackOnly')}\n\n${tr('trackOnlyAppDescription')}', '${pickedSource!.enforceTrackOnly ? tr('appsFromSourceAreTrackOnly') : tr('youPickedTrackOnly')}\n\n${tr('trackOnlyAppDescription')}',
); );
@ -91,17 +88,32 @@ class _AddAppPageState extends State<AddAppPage> {
null) { null) {
cont = false; cont = false;
} }
if (userPickedNoVersionDetection &&
await showDialog(
context: context,
builder: (BuildContext ctx) {
return GeneratedFormModal(
title: tr('disableVersionDetection'),
items: const [],
message: tr('noVersionDetectionExplanation'),
);
}) ==
null) {
cont = false;
}
if (cont) { if (cont) {
HapticFeedback.selectionClick(); HapticFeedback.selectionClick();
var trackOnly = pickedSource!.enforceTrackOnly || userPickedTrackOnly; var trackOnly = pickedSource!.enforceTrackOnly || userPickedTrackOnly;
App app = await sourceProvider.getApp( App app = await sourceProvider.getApp(
pickedSource!, userInput, sourceSpecificAdditionalData, pickedSource!, userInput, additionalSettings,
trackOnly: trackOnly); trackOnlyOverride: trackOnly,
noVersionDetectionOverride: userPickedNoVersionDetection);
if (!trackOnly) { if (!trackOnly) {
await settingsProvider.getInstallPermission(); await settingsProvider.getInstallPermission();
} }
// Only download the APK here if you need to for the package ID // Only download the APK here if you need to for the package ID
if (sourceProvider.isTempId(app.id) && !app.trackOnly) { if (sourceProvider.isTempId(app.id) &&
app.additionalSettings['trackOnly'] != 'true') {
// ignore: use_build_context_synchronously // ignore: use_build_context_synchronously
var apkUrl = await appsProvider.confirmApkUrl(app, context); var apkUrl = await appsProvider.confirmApkUrl(app, context);
if (apkUrl == null) { if (apkUrl == null) {
@ -116,7 +128,7 @@ class _AddAppPageState extends State<AddAppPage> {
if (appsProvider.apps.containsKey(app.id)) { if (appsProvider.apps.containsKey(app.id)) {
throw ObtainiumError(tr('appAlreadyAdded')); throw ObtainiumError(tr('appAlreadyAdded'));
} }
if (app.trackOnly) { if (app.additionalSettings['trackOnly'] == 'true') {
app.installedVersion = app.latestVersion; app.installedVersion = app.latestVersion;
} }
await appsProvider.saveApps([app]); await appsProvider.saveApps([app]);
@ -156,34 +168,33 @@ class _AddAppPageState extends State<AddAppPage> {
Expanded( Expanded(
child: GeneratedForm( child: GeneratedForm(
items: [ items: [
[ [
GeneratedFormItem( GeneratedFormItem('appSourceURL',
label: tr('appSourceURL'), label: tr('appSourceURL'),
additionalValidators: [ additionalValidators: [
(value) { (value) {
try { try {
sourceProvider sourceProvider
.getSource(value ?? '') .getSource(value ?? '')
.standardizeURL( .standardizeURL(
preStandardizeUrl( preStandardizeUrl(
value ?? '')); value ?? ''));
} catch (e) { } catch (e) {
return e is String return e is String
? e ? e
: e is ObtainiumError : e is ObtainiumError
? e.toString() ? e.toString()
: tr('error'); : tr('error');
} }
return null; return null;
} }
]) ])
] ]
], ],
onValueChanges: (values, valid, isBuilding) { onValueChanges: (values, valid, isBuilding) {
changeUserInput( changeUserInput(values['appSourceURL']!,
values[0], valid, isBuilding); valid, isBuilding);
}, })),
defaultValues: const [])),
const SizedBox( const SizedBox(
width: 16, width: 16,
), ),
@ -193,13 +204,9 @@ class _AddAppPageState extends State<AddAppPage> {
onPressed: gettingAppInfo || onPressed: gettingAppInfo ||
pickedSource == null || pickedSource == null ||
(pickedSource! (pickedSource!
.additionalSourceAppSpecificFormItems .combinedAppSpecificSettingFormItems
.isNotEmpty && .isNotEmpty &&
!sourceSpecificDataIsValid) || !additionalSettingsValid)
(pickedSource!
.additionalAppSpecificSourceAgnosticDefaults
.isNotEmpty &&
!otherAdditionalDataIsValid)
? null ? null
: addApp, : addApp,
child: Text(tr('add'))) child: Text(tr('add')))
@ -224,7 +231,7 @@ class _AddAppPageState extends State<AddAppPage> {
child: GeneratedForm( child: GeneratedForm(
items: [ items: [
[ [
GeneratedFormItem( GeneratedFormItem('searchSomeSources',
label: tr('searchSomeSourcesLabel'), label: tr('searchSomeSourcesLabel'),
required: false), required: false),
] ]
@ -232,11 +239,11 @@ class _AddAppPageState extends State<AddAppPage> {
onValueChanges: (values, valid, isBuilding) { onValueChanges: (values, valid, isBuilding) {
if (values.isNotEmpty && valid) { if (values.isNotEmpty && valid) {
setState(() { setState(() {
searchQuery = values[0].trim(); searchQuery =
values['searchSomeSources']!.trim();
}); });
} }
}, }),
defaultValues: const ['']),
), ),
const SizedBox( const SizedBox(
width: 16, width: 16,
@ -292,15 +299,8 @@ class _AddAppPageState extends State<AddAppPage> {
], ],
), ),
if (pickedSource != null && if (pickedSource != null &&
(pickedSource!.additionalSourceAppSpecificDefaults (pickedSource!
.isNotEmpty || .combinedAppSpecificSettingFormItems.isNotEmpty))
pickedSource!
.additionalAppSpecificSourceAgnosticFormItems
.where((e) => pickedSource!.enforceTrackOnly
? e.key != 'trackOnlyFormItemKey'
: true)
.map((e) => [e])
.isNotEmpty))
Column( Column(
crossAxisAlignment: CrossAxisAlignment.stretch, crossAxisAlignment: CrossAxisAlignment.stretch,
children: [ children: [
@ -316,46 +316,17 @@ class _AddAppPageState extends State<AddAppPage> {
const SizedBox( const SizedBox(
height: 16, height: 16,
), ),
if (pickedSource!
.additionalSourceAppSpecificFormItems
.isNotEmpty)
GeneratedForm(
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( GeneratedForm(
items: pickedSource! items: pickedSource!
.additionalAppSpecificSourceAgnosticFormItems .combinedAppSpecificSettingFormItems,
.where((e) => pickedSource!.enforceTrackOnly
? e.key != 'trackOnlyFormItemKey'
: true)
.map((e) => [e])
.toList(),
onValueChanges: (values, valid, isBuilding) { onValueChanges: (values, valid, isBuilding) {
if (!isBuilding) { if (!isBuilding) {
setState(() { setState(() {
otherAdditionalData = values; additionalSettings = values;
otherAdditionalDataIsValid = valid; additionalSettingsValid = valid;
}); });
} }
}, }),
defaultValues: pickedSource!
.additionalAppSpecificSourceAgnosticDefaults),
], ],
) )
else else

View File

@ -40,16 +40,33 @@ class _AppPageState extends State<AppPage> {
prevApp = app; prevApp = app;
getUpdate(app.app.id); getUpdate(app.app.id);
} }
var trackOnly = app?.app.additionalSettings['trackOnly'] == 'true';
return Scaffold( return Scaffold(
appBar: settingsProvider.showAppWebpage ? AppBar() : null, appBar: settingsProvider.showAppWebpage ? AppBar() : null,
backgroundColor: Theme.of(context).colorScheme.surface, backgroundColor: Theme.of(context).colorScheme.surface,
body: RefreshIndicator( body: RefreshIndicator(
child: settingsProvider.showAppWebpage child: settingsProvider.showAppWebpage
? WebView( ? app != null
backgroundColor: Theme.of(context).colorScheme.background, ? WebViewWidget(
initialUrl: app?.app.url, controller: WebViewController()
javascriptMode: JavascriptMode.unrestricted, ..setJavaScriptMode(JavaScriptMode.unrestricted)
) ..setBackgroundColor(
Theme.of(context).colorScheme.background)
..setJavaScriptMode(JavaScriptMode.unrestricted)
..setNavigationDelegate(
NavigationDelegate(
onWebResourceError: (WebResourceError error) {
if (error.isForMainFrame == true) {
showError(
ObtainiumError(error.description,
unexpected: true),
context);
}
},
),
)
..loadRequest(Uri.parse(app.app.url)))
: Container()
: CustomScrollView( : CustomScrollView(
slivers: [ slivers: [
SliverFillRemaining( SliverFillRemaining(
@ -72,7 +89,9 @@ class _AppPageState extends State<AppPage> {
height: 25, height: 25,
), ),
Text( Text(
app?.installedInfo?.name ?? app?.app.name ?? 'App', app?.installedInfo?.name ??
app?.app.name ??
tr('app'),
textAlign: TextAlign.center, textAlign: TextAlign.center,
style: Theme.of(context).textTheme.displayLarge, style: Theme.of(context).textTheme.displayLarge,
), ),
@ -111,7 +130,7 @@ class _AppPageState extends State<AppPage> {
Text( Text(
'${tr('installedVersionX', args: [ '${tr('installedVersionX', args: [
app?.app.installedVersion ?? tr('none') app?.app.installedVersion ?? tr('none')
])}${app?.app.trackOnly == true ? ' ${tr('estimateInBrackets')}\n\n${tr('xIsTrackOnly', args: [ ])}${trackOnly ? ' ${tr('estimateInBrackets')}\n\n${tr('xIsTrackOnly', args: [
tr('app') tr('app')
])}' : ''}', ])}' : ''}',
textAlign: TextAlign.center, textAlign: TextAlign.center,
@ -151,7 +170,7 @@ class _AppPageState extends State<AppPage> {
mainAxisAlignment: MainAxisAlignment.spaceEvenly, mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [ children: [
if (app?.app.installedVersion != null && if (app?.app.installedVersion != null &&
app?.app.trackOnly == false && !trackOnly &&
app?.app.installedVersion != app?.app.latestVersion) app?.app.installedVersion != app?.app.latestVersion)
IconButton( IconButton(
onPressed: app?.downloadProgress != null onPressed: app?.downloadProgress != null
@ -199,30 +218,48 @@ class _AppPageState extends State<AppPage> {
); );
}); });
}, },
tooltip: 'Mark as Updated', tooltip: tr('markUpdated'),
icon: const Icon(Icons.done)), icon: const Icon(Icons.done)),
if (source != null && if (source != null &&
source.additionalSourceAppSpecificFormItems source
.isNotEmpty) .combinedAppSpecificSettingFormItems.isNotEmpty)
IconButton( IconButton(
onPressed: app?.downloadProgress != null onPressed: app?.downloadProgress != null
? null ? null
: () { : () {
showDialog<List<String>>( showDialog<Map<String, String>>(
context: context, context: context,
builder: (BuildContext ctx) { builder: (BuildContext ctx) {
var items = source
.combinedAppSpecificSettingFormItems
.map((row) {
row.map((e) {
if (app?.app.additionalSettings[
e.key] !=
null) {
e.defaultValue = app?.app
.additionalSettings[
e.key];
}
return e;
}).toList();
return row;
}).toList();
return GeneratedFormModal( return GeneratedFormModal(
title: 'Additional Options', title: tr('additionalOptions'),
items: source items: items);
.additionalSourceAppSpecificFormItems,
defaultValues: app != null
? app.app.additionalData
: source
.additionalSourceAppSpecificDefaults);
}).then((values) { }).then((values) {
if (app != null && values != null) { if (app != null && values != null) {
var changedApp = app.app; var changedApp = app.app;
changedApp.additionalData = values; changedApp.additionalSettings =
values;
if (source.enforceTrackOnly) {
changedApp.additionalSettings[
'trackOnly'] = 'true';
showError(
tr('appsFromSourceAreTrackOnly'),
context);
}
appsProvider.saveApps( appsProvider.saveApps(
[changedApp]).then((value) { [changedApp]).then((value) {
getUpdate(changedApp.id); getUpdate(changedApp.id);
@ -230,7 +267,7 @@ class _AppPageState extends State<AppPage> {
} }
}); });
}, },
tooltip: 'Additional Options', tooltip: tr('additionalOptions'),
icon: const Icon(Icons.settings)), icon: const Icon(Icons.settings)),
const SizedBox(width: 16.0), const SizedBox(width: 16.0),
Expanded( Expanded(
@ -242,7 +279,9 @@ class _AppPageState extends State<AppPage> {
? () { ? () {
HapticFeedback.heavyImpact(); HapticFeedback.heavyImpact();
() async { () async {
if (app?.app.trackOnly != true) { if (app?.app.additionalSettings[
'trackOnly'] !=
'true') {
await settingsProvider await settingsProvider
.getInstallPermission(); .getInstallPermission();
} }
@ -264,12 +303,12 @@ class _AppPageState extends State<AppPage> {
} }
: null, : null,
child: Text(app?.app.installedVersion == null child: Text(app?.app.installedVersion == null
? app?.app.trackOnly == false ? !trackOnly
? 'Install' ? tr('install')
: 'Mark Installed' : tr('markInstalled')
: app?.app.trackOnly == false : !trackOnly
? 'Update' ? tr('update')
: 'Mark Updated'))), : tr('markUpdated')))),
const SizedBox(width: 16.0), const SizedBox(width: 16.0),
ElevatedButton( ElevatedButton(
onPressed: app?.downloadProgress != null onPressed: app?.downloadProgress != null

View File

@ -139,14 +139,16 @@ class AppsPageState extends State<AppsPage> {
List<String> trackOnlyUpdateIdsAllOrSelected = []; List<String> trackOnlyUpdateIdsAllOrSelected = [];
existingUpdateIdsAllOrSelected = existingUpdateIdsAllOrSelected.where((id) { existingUpdateIdsAllOrSelected = existingUpdateIdsAllOrSelected.where((id) {
if (appsProvider.apps[id]!.app.trackOnly) { if (appsProvider.apps[id]!.app.additionalSettings['trackOnly'] ==
'true') {
trackOnlyUpdateIdsAllOrSelected.add(id); trackOnlyUpdateIdsAllOrSelected.add(id);
return false; return false;
} }
return true; return true;
}).toList(); }).toList();
newInstallIdsAllOrSelected = newInstallIdsAllOrSelected.where((id) { newInstallIdsAllOrSelected = newInstallIdsAllOrSelected.where((id) {
if (appsProvider.apps[id]!.app.trackOnly) { if (appsProvider.apps[id]!.app.additionalSettings['trackOnly'] ==
'true') {
trackOnlyUpdateIdsAllOrSelected.add(id); trackOnlyUpdateIdsAllOrSelected.add(id);
return false; return false;
} }
@ -271,7 +273,7 @@ class AppsPageState extends State<AppsPage> {
SizedBox( SizedBox(
width: 100, width: 100,
child: Text( child: Text(
'${sortedApps[index].app.installedVersion ?? tr('notInstalled')}${sortedApps[index].app.trackOnly == true ? ' ${tr('estimateInBrackets')}' : ''}', '${sortedApps[index].app.installedVersion ?? tr('notInstalled')}${sortedApps[index].app.additionalSettings['trackOnly'] == 'true' ? ' ${tr('estimateInBrackets')}' : ''}',
overflow: TextOverflow.fade, overflow: TextOverflow.fade,
textAlign: TextAlign.end, textAlign: TextAlign.end,
)), )),
@ -289,7 +291,7 @@ class AppsPageState extends State<AppsPage> {
child: appsProvider.areDownloadsRunning() child: appsProvider.areDownloadsRunning()
? Text(tr('pleaseWait')) ? Text(tr('pleaseWait'))
: Text( : Text(
'${tr('updateAvailable')}${sortedApps[index].app.trackOnly ? ' ${tr('estimateInBracketsShort')}' : ''}', '${tr('updateAvailable')}${sortedApps[index].app.additionalSettings['trackOnly'] == 'true' ? ' ${tr('estimateInBracketsShort')}' : ''}',
style: TextStyle( style: TextStyle(
fontStyle: FontStyle.italic, fontStyle: FontStyle.italic,
decoration: changesUrl == null decoration: changesUrl == null
@ -343,13 +345,12 @@ class AppsPageState extends State<AppsPage> {
: IconButton( : IconButton(
visualDensity: VisualDensity.compact, visualDensity: VisualDensity.compact,
onPressed: () { onPressed: () {
showDialog<List<String>?>( showDialog<Map<String, String>?>(
context: context, context: context,
builder: (BuildContext ctx) { builder: (BuildContext ctx) {
return GeneratedFormModal( return GeneratedFormModal(
title: tr('removeSelectedAppsQuestion'), title: tr('removeSelectedAppsQuestion'),
items: const [], items: const [],
defaultValues: const [],
initValid: true, initValid: true,
message: tr( message: tr(
'xWillBeRemovedButRemainInstalled', 'xWillBeRemovedButRemainInstalled',
@ -376,41 +377,42 @@ class AppsPageState extends State<AppsPage> {
? null ? null
: () { : () {
HapticFeedback.heavyImpact(); HapticFeedback.heavyImpact();
List<GeneratedFormItem> formInputs = []; List<GeneratedFormItem> formItems = [];
List<String> defaultValues = [];
if (existingUpdateIdsAllOrSelected.isNotEmpty) { if (existingUpdateIdsAllOrSelected.isNotEmpty) {
formInputs.add(GeneratedFormItem( formItems.add(GeneratedFormItem('updates',
label: tr('updateX', args: [ label: tr('updateX', args: [
plural('apps', plural('apps',
existingUpdateIdsAllOrSelected.length) existingUpdateIdsAllOrSelected.length)
]), ]),
type: FormItemType.bool, type: FormItemType.bool,
key: 'updates')); defaultValue: 'true'));
defaultValues.add('true');
} }
if (newInstallIdsAllOrSelected.isNotEmpty) { if (newInstallIdsAllOrSelected.isNotEmpty) {
formInputs.add(GeneratedFormItem( formItems.add(GeneratedFormItem('installs',
label: tr('installX', args: [ label: tr('installX', args: [
plural('apps', plural('apps',
newInstallIdsAllOrSelected.length) newInstallIdsAllOrSelected.length)
]), ]),
type: FormItemType.bool, type: FormItemType.bool,
key: 'installs')); defaultValue:
defaultValues existingUpdateIdsAllOrSelected.isNotEmpty
.add(defaultValues.isEmpty ? 'true' : ''); ? 'true'
: ''));
} }
if (trackOnlyUpdateIdsAllOrSelected.isNotEmpty) { if (trackOnlyUpdateIdsAllOrSelected.isNotEmpty) {
formInputs.add(GeneratedFormItem( formItems.add(GeneratedFormItem('trackonlies',
label: tr('markXTrackOnlyAsUpdated', args: [ label: tr('markXTrackOnlyAsUpdated', args: [
plural('apps', plural('apps',
trackOnlyUpdateIdsAllOrSelected.length) trackOnlyUpdateIdsAllOrSelected.length)
]), ]),
type: FormItemType.bool, type: FormItemType.bool,
key: 'trackonlies')); defaultValue: existingUpdateIdsAllOrSelected
defaultValues .isNotEmpty ||
.add(defaultValues.isEmpty ? 'true' : ''); newInstallIdsAllOrSelected.isNotEmpty
? 'true'
: ''));
} }
showDialog<List<String>?>( showDialog<Map<String, String>?>(
context: context, context: context,
builder: (BuildContext ctx) { builder: (BuildContext ctx) {
var totalApps = existingUpdateIdsAllOrSelected var totalApps = existingUpdateIdsAllOrSelected
@ -420,27 +422,21 @@ class AppsPageState extends State<AppsPage> {
return GeneratedFormModal( return GeneratedFormModal(
title: tr('changeX', title: tr('changeX',
args: [plural('apps', totalApps)]), args: [plural('apps', totalApps)]),
items: formInputs.map((e) => [e]).toList(), items: formItems.map((e) => [e]).toList(),
defaultValues: defaultValues,
initValid: true, initValid: true,
); );
}).then((values) { }).then((values) {
if (values != null) { if (values != null) {
if (values.isEmpty) { if (values.isEmpty) {
values = defaultValues; values = getDefaultValuesFromFormItems(
[formItems]);
} }
bool shouldInstallUpdates = bool shouldInstallUpdates =
findGeneratedFormValueByKey( values['updates'] == 'true';
formInputs, values, 'updates') ==
'true';
bool shouldInstallNew = bool shouldInstallNew =
findGeneratedFormValueByKey( values['installs'] == 'true';
formInputs, values, 'installs') ==
'true';
bool shouldMarkTrackOnlies = bool shouldMarkTrackOnlies =
findGeneratedFormValueByKey(formInputs, values['trackonlies'] == 'true';
values, 'trackonlies') ==
'true';
(() async { (() async {
if (shouldInstallNew || if (shouldInstallNew ||
shouldInstallUpdates) { shouldInstallUpdates) {
@ -613,7 +609,6 @@ class AppsPageState extends State<AppsPage> {
title: tr( title: tr(
'resetInstallStatusForSelectedAppsQuestion'), 'resetInstallStatusForSelectedAppsQuestion'),
items: const [], items: const [],
defaultValues: const [],
initValid: true, initValid: true,
message: tr( message: tr(
'installStatusOfXWillBeResetExplanation', 'installStatusOfXWillBeResetExplanation',
@ -683,36 +678,42 @@ class AppsPageState extends State<AppsPage> {
: FontWeight.bold), : FontWeight.bold),
), ),
onPressed: () { onPressed: () {
showDialog<List<String>?>( showDialog<Map<String, String>?>(
context: context, context: context,
builder: (BuildContext ctx) { builder: (BuildContext ctx) {
var vals = filter == null
? AppsFilter().toValuesMap()
: filter!.toValuesMap();
return GeneratedFormModal( return GeneratedFormModal(
title: tr('filterApps'), title: tr('filterApps'),
items: [ items: [
[ [
GeneratedFormItem( GeneratedFormItem('appName',
label: tr('appName'), required: false), label: tr('appName'),
GeneratedFormItem( required: false,
label: tr('author'), required: false) defaultValue: vals['appName']),
GeneratedFormItem('author',
label: tr('author'),
required: false,
defaultValue: vals['author'])
], ],
[ [
GeneratedFormItem( GeneratedFormItem('upToDateApps',
label: tr('upToDateApps'), label: tr('upToDateApps'),
type: FormItemType.bool) type: FormItemType.bool,
defaultValue: vals['upToDateApps'])
], ],
[ [
GeneratedFormItem( GeneratedFormItem('nonInstalledApps',
label: tr('nonInstalledApps'), label: tr('nonInstalledApps'),
type: FormItemType.bool) type: FormItemType.bool,
defaultValue: vals['nonInstalledApps'])
] ]
], ]);
defaultValues: filter == null
? AppsFilter().toValuesArray()
: filter!.toValuesArray());
}).then((values) { }).then((values) {
if (values != null) { if (values != null) {
setState(() { setState(() {
filter = AppsFilter.fromValuesArray(values); filter = AppsFilter.fromValuesMap(values);
if (AppsFilter().isIdenticalTo(filter!)) { if (AppsFilter().isIdenticalTo(filter!)) {
filter = null; filter = null;
} }
@ -740,20 +741,20 @@ class AppsFilter {
this.includeUptodate = true, this.includeUptodate = true,
this.includeNonInstalled = true}); this.includeNonInstalled = true});
List<String> toValuesArray() { Map<String, String> toValuesMap() {
return [ return {
nameFilter, 'appName': nameFilter,
authorFilter, 'author': authorFilter,
includeUptodate ? 'true' : '', 'upToDateApps': includeUptodate ? 'true' : '',
includeNonInstalled ? 'true' : '' 'nonInstalledApps': includeNonInstalled ? 'true' : ''
]; };
} }
AppsFilter.fromValuesArray(List<String> values) { AppsFilter.fromValuesMap(Map<String, String> values) {
nameFilter = values[0]; nameFilter = values['appName']!;
authorFilter = values[1]; authorFilter = values['author']!;
includeUptodate = values[2] == 'true'; includeUptodate = values['upToDateApps'] == 'true';
includeNonInstalled = values[3] == 'true'; includeNonInstalled = values['nonInstalledApps'] == 'true';
} }
bool isIdenticalTo(AppsFilter other) => bool isIdenticalTo(AppsFilter other) =>

View File

@ -145,7 +145,7 @@ class _ImportExportPageState extends State<ImportExportPage> {
title: tr('importFromURLList'), title: tr('importFromURLList'),
items: [ items: [
[ [
GeneratedFormItem( GeneratedFormItem('appURLList',
label: tr('appURLList'), label: tr('appURLList'),
max: 7, max: 7,
additionalValidators: [ additionalValidators: [
@ -172,7 +172,6 @@ class _ImportExportPageState extends State<ImportExportPage> {
]) ])
] ]
], ],
defaultValues: const [],
); );
}).then((values) { }).then((values) {
if (values != null) { if (values != null) {
@ -237,11 +236,11 @@ class _ImportExportPageState extends State<ImportExportPage> {
items: [ items: [
[ [
GeneratedFormItem( GeneratedFormItem(
'searchQuery',
label: tr( label: tr(
'searchQuery')) 'searchQuery'))
] ]
], ],
defaultValues: const [],
); );
}); });
if (values != null && if (values != null &&
@ -346,10 +345,10 @@ class _ImportExportPageState extends State<ImportExportPage> {
.requiredArgs .requiredArgs
.map( .map(
(e) => [ (e) => [
GeneratedFormItem(label: e) GeneratedFormItem(e,
label: e)
]) ])
.toList(), .toList(),
defaultValues: const [],
); );
}); });
if (values != null) { if (values != null) {

View File

@ -143,16 +143,11 @@ class _SettingsPageState extends State<SettingsPage> {
.toList(), .toList(),
onValueChanges: (values, valid, isBuilding) { onValueChanges: (values, valid, isBuilding) {
if (valid) { if (valid) {
for (var i = 0; i < values.length; i++) { values.forEach((key, value) {
settingsProvider.setSettingString( settingsProvider.setSettingString(key, value);
e.additionalSourceSpecificSettingFormItems[i].id, });
values[i]);
}
} }
}, });
defaultValues: e.additionalSourceSpecificSettingFormItems.map((e) {
return settingsProvider.getSettingString(e.id) ?? '';
}).toList());
} else { } else {
return Container(); return Container();
} }

View File

@ -313,7 +313,8 @@ class AppsProvider with ChangeNotifier {
throw ObtainiumError(tr('appNotFound')); throw ObtainiumError(tr('appNotFound'));
} }
String? apkUrl; String? apkUrl;
if (!apps[id]!.app.trackOnly) { var trackOnly = apps[id]!.app.additionalSettings['trackOnly'] == 'true';
if (!trackOnly) {
apkUrl = await confirmApkUrl(apps[id]!.app, context); apkUrl = await confirmApkUrl(apps[id]!.app, context);
} }
if (apkUrl != null) { if (apkUrl != null) {
@ -326,7 +327,7 @@ class AppsProvider with ChangeNotifier {
appsToInstall.add(id); appsToInstall.add(id);
} }
} }
if (apps[id]!.app.trackOnly) { if (trackOnly) {
trackOnlyAppsToUpdate.add(id); trackOnlyAppsToUpdate.add(id);
} }
} }
@ -451,9 +452,10 @@ class AppsProvider with ChangeNotifier {
// Don't save changes, just return the object if changes were made (else null) // Don't save changes, just return the object if changes were made (else null)
App? getCorrectedInstallStatusAppIfPossible(App app, AppInfo? installedInfo) { App? getCorrectedInstallStatusAppIfPossible(App app, AppInfo? installedInfo) {
var modded = false; var modded = false;
if (installedInfo == null && var trackOnly = app.additionalSettings['trackOnly'] == 'true';
app.installedVersion != null && var noVersionDetection =
!app.trackOnly) { app.additionalSettings['noVersionDetection'] == 'true';
if (installedInfo == null && app.installedVersion != null && !trackOnly) {
app.installedVersion = null; app.installedVersion = null;
modded = true; modded = true;
} else if (installedInfo?.versionName != null && } else if (installedInfo?.versionName != null &&
@ -461,7 +463,8 @@ class AppsProvider with ChangeNotifier {
app.installedVersion = installedInfo!.versionName; app.installedVersion = installedInfo!.versionName;
modded = true; modded = true;
} else if (installedInfo?.versionName != null && } else if (installedInfo?.versionName != null &&
installedInfo!.versionName != app.installedVersion) { installedInfo!.versionName != app.installedVersion &&
!noVersionDetection) {
String? correctedInstalledVersion = reconcileRealAndInternalVersions( String? correctedInstalledVersion = reconcileRealAndInternalVersions(
installedInfo.versionName!, app.installedVersion!); installedInfo.versionName!, app.installedVersion!);
if (correctedInstalledVersion != null) { if (correctedInstalledVersion != null) {
@ -470,7 +473,8 @@ class AppsProvider with ChangeNotifier {
} }
} }
if (app.installedVersion != null && if (app.installedVersion != null &&
app.installedVersion != app.latestVersion) { app.installedVersion != app.latestVersion &&
!noVersionDetection) {
app.installedVersion = reconcileRealAndInternalVersions( app.installedVersion = reconcileRealAndInternalVersions(
app.installedVersion!, app.latestVersion, app.installedVersion!, app.latestVersion,
matchMode: true) ?? matchMode: true) ??
@ -623,12 +627,8 @@ class AppsProvider with ChangeNotifier {
App newApp = await sourceProvider.getApp( App newApp = await sourceProvider.getApp(
sourceProvider.getSource(currentApp.url), sourceProvider.getSource(currentApp.url),
currentApp.url, currentApp.url,
currentApp.additionalData, currentApp.additionalSettings,
name: currentApp.name, currentApp: currentApp);
id: currentApp.id,
pinned: currentApp.pinned,
trackOnly: currentApp.trackOnly,
installedVersion: currentApp.installedVersion);
if (currentApp.preferredApkIndex < newApp.apkUrls.length) { if (currentApp.preferredApkIndex < newApp.apkUrls.length) {
newApp.preferredApkIndex = currentApp.preferredApkIndex; newApp.preferredApkIndex = currentApp.preferredApkIndex;
} }

View File

@ -80,11 +80,11 @@ class DownloadNotification extends ObtainiumNotification {
DownloadNotification(String appName, int progPercent) DownloadNotification(String appName, int progPercent)
: super( : super(
appName.hashCode, appName.hashCode,
'Downloading $appName', tr('downloadingX', args: [appName]),
'', '',
'APP_DOWNLOADING', 'APP_DOWNLOADING',
'Downloading App', tr('downloadingX', args: [tr('app')]),
'Notifies the user of the progress in downloading an App', tr('downloadNotifDescription'),
Importance.low, Importance.low,
onlyAlertOnce: true, onlyAlertOnce: true,
progPercent: progPercent); progPercent: progPercent);

View File

@ -44,10 +44,9 @@ class App {
late String latestVersion; late String latestVersion;
List<String> apkUrls = []; List<String> apkUrls = [];
late int preferredApkIndex; late int preferredApkIndex;
late List<String> additionalData; late Map<String, String> additionalSettings;
late DateTime? lastUpdateCheck; late DateTime? lastUpdateCheck;
bool pinned = false; bool pinned = false;
bool trackOnly = false;
App( App(
this.id, this.id,
this.url, this.url,
@ -57,39 +56,59 @@ class App {
this.latestVersion, this.latestVersion,
this.apkUrls, this.apkUrls,
this.preferredApkIndex, this.preferredApkIndex,
this.additionalData, this.additionalSettings,
this.lastUpdateCheck, this.lastUpdateCheck,
this.pinned, this.pinned);
this.trackOnly);
@override @override
String toString() { String toString() {
return 'ID: $id URL: $url INSTALLED: $installedVersion LATEST: $latestVersion APK: $apkUrls PREFERREDAPK: $preferredApkIndex ADDITIONALDATA: ${additionalData.toString()} LASTCHECK: ${lastUpdateCheck.toString()} PINNED $pinned'; return 'ID: $id URL: $url INSTALLED: $installedVersion LATEST: $latestVersion APK: $apkUrls PREFERREDAPK: $preferredApkIndex ADDITIONALSETTINGS: ${additionalSettings.toString()} LASTCHECK: ${lastUpdateCheck.toString()} PINNED $pinned';
} }
factory App.fromJson(Map<String, dynamic> json) => App( factory App.fromJson(Map<String, dynamic> json) {
json['id'] as String, var source = SourceProvider().getSource(json['url']);
json['url'] as String, var formItems = source.combinedAppSpecificSettingFormItems
json['author'] as String, .reduce((value, element) => [...value, ...element]);
json['name'] as String, Map<String, String> additionalSettings =
json['installedVersion'] == null getDefaultValuesFromFormItems([formItems]);
? null if (json['additionalSettings'] != null) {
: json['installedVersion'] as String, additionalSettings.addEntries(
json['latestVersion'] as String, Map<String, String>.from(jsonDecode(json['additionalSettings']))
json['apkUrls'] == null .entries);
? [] }
: List<String>.from(jsonDecode(json['apkUrls'])), // If needed, migrate old-style additionalData to new-style additionalSettings
json['preferredApkIndex'] == null ? 0 : json['preferredApkIndex'] as int, if (json['additionalData'] != null) {
json['additionalData'] == null List<String> temp = List<String>.from(jsonDecode(json['additionalData']));
? SourceProvider() temp.asMap().forEach((i, value) {
.getSource(json['url']) if (i < formItems.length) {
.additionalSourceAppSpecificDefaults additionalSettings[formItems[i].key] = value;
: List<String>.from(jsonDecode(json['additionalData'])), }
json['lastUpdateCheck'] == null });
? null additionalSettings['trackOnly'] = (json['trackOnly'] ?? false).toString();
: DateTime.fromMicrosecondsSinceEpoch(json['lastUpdateCheck']), additionalSettings['noVersionDetection'] =
json['pinned'] ?? false, (json['noVersionDetection'] ?? false).toString();
json['trackOnly'] ?? false); }
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() => { Map<String, dynamic> toJson() => {
'id': id, 'id': id,
@ -100,10 +119,9 @@ class App {
'latestVersion': latestVersion, 'latestVersion': latestVersion,
'apkUrls': jsonEncode(apkUrls), 'apkUrls': jsonEncode(apkUrls),
'preferredApkIndex': preferredApkIndex, 'preferredApkIndex': preferredApkIndex,
'additionalData': jsonEncode(additionalData), 'additionalSettings': jsonEncode(additionalSettings),
'lastUpdateCheck': lastUpdateCheck?.microsecondsSinceEpoch, 'lastUpdateCheck': lastUpdateCheck?.microsecondsSinceEpoch,
'pinned': pinned, 'pinned': pinned
'trackOnly': trackOnly
}; };
} }
@ -124,7 +142,7 @@ preStandardizeUrl(String url) {
return url; return url;
} }
const String noAPKFound = 'No APK found'; String noAPKFound = tr('noAPKFound');
List<String> getLinksFromParsedHTML( List<String> getLinksFromParsedHTML(
Document dom, RegExp hrefPattern, String prependToLinks) => Document dom, RegExp hrefPattern, String prependToLinks) =>
@ -137,6 +155,13 @@ List<String> getLinksFromParsedHTML(
.map((e) => '$prependToLinks${e.attributes['href']!}') .map((e) => '$prependToLinks${e.attributes['href']!}')
.toList(); .toList();
Map<String, String> getDefaultValuesFromFormItems(
List<List<GeneratedFormItem>> items) {
return Map.fromEntries(items
.map((row) => row.map((el) => MapEntry(el.key, el.defaultValue ?? '')))
.reduce((value, element) => [...value, ...element]));
}
class AppSource { class AppSource {
String? host; String? host;
late String name; late String name;
@ -151,23 +176,37 @@ class AppSource {
} }
Future<APKDetails> getLatestAPKDetails( Future<APKDetails> getLatestAPKDetails(
String standardUrl, List<String> additionalData, String standardUrl, Map<String, String> additionalSettings) {
{bool trackOnly = false}) {
throw NotImplementedError(); throw NotImplementedError();
} }
// Different Sources may need different kinds of additional data for Apps // Different Sources may need different kinds of additional data for Apps
List<List<GeneratedFormItem>> additionalSourceAppSpecificFormItems = []; List<List<GeneratedFormItem>> additionalSourceAppSpecificSettingFormItems =
List<String> additionalSourceAppSpecificDefaults = []; [];
// Some additional data may be needed for Apps regardless of Source // Some additional data may be needed for Apps regardless of Source
final List<GeneratedFormItem> additionalAppSpecificSourceAgnosticFormItems = [ final List<List<GeneratedFormItem>>
GeneratedFormItem( additionalAppSpecificSourceAgnosticSettingFormItems = [
[
GeneratedFormItem(
'trackOnly',
label: tr('trackOnly'), label: tr('trackOnly'),
type: FormItemType.bool, type: FormItemType.bool,
key: 'trackOnlyFormItemKey') )
],
[
GeneratedFormItem('noVersionDetection',
label: tr('noVersionDetection'), type: FormItemType.bool)
]
]; ];
final List<String> additionalAppSpecificSourceAgnosticDefaults = [''];
// Previous 2 variables combined into one at runtime for convenient usage
List<List<GeneratedFormItem>> get combinedAppSpecificSettingFormItems {
return [
...additionalSourceAppSpecificSettingFormItems,
...additionalAppSpecificSourceAgnosticSettingFormItems
];
}
// Some Sources may have additional settings at the Source level (not specific to Apps) - these use SettingsProvider // Some Sources may have additional settings at the Source level (not specific to Apps) - these use SettingsProvider
List<GeneratedFormItem> additionalSourceSpecificSettingFormItems = []; List<GeneratedFormItem> additionalSourceSpecificSettingFormItems = [];
@ -186,7 +225,7 @@ class AppSource {
} }
String? tryInferringAppId(String standardUrl, String? tryInferringAppId(String standardUrl,
{List<String> additionalData = const []}) { {Map<String, String> additionalSettings = const {}}) {
return null; return null;
} }
} }
@ -246,8 +285,8 @@ class SourceProvider {
return source; return source;
} }
bool ifSourceAppsRequireAdditionalData(AppSource source) { bool ifRequiredAppSpecificSettingsExist(AppSource source) {
for (var row in source.additionalSourceAppSpecificFormItems) { for (var row in source.combinedAppSpecificSettingFormItems) {
for (var element in row) { for (var element in row) {
if (element.required && element.opts == null) { if (element.required && element.opts == null) {
return true; return true;
@ -274,37 +313,44 @@ class SourceProvider {
return true; return true;
} }
Future<App> getApp(AppSource source, String url, List<String> additionalData, Future<App> getApp(
{String name = '', AppSource source, String url, Map<String, String> additionalSettings,
String? id, {App? currentApp,
bool pinned = false, bool trackOnlyOverride = false,
bool trackOnly = false, noVersionDetectionOverride = false}) async {
String? installedVersion}) async { if (trackOnlyOverride) {
additionalSettings['trackOnly'] = 'true';
}
if (noVersionDetectionOverride) {
additionalSettings['noVersionDetection'] = 'true';
}
var trackOnly = currentApp?.additionalSettings['trackOnly'] == 'true';
String standardUrl = source.standardizeURL(preStandardizeUrl(url)); String standardUrl = source.standardizeURL(preStandardizeUrl(url));
APKDetails apk = await source APKDetails apk =
.getLatestAPKDetails(standardUrl, additionalData, trackOnly: trackOnly); await source.getLatestAPKDetails(standardUrl, additionalSettings);
if (apk.apkUrls.isEmpty && !trackOnly) { if (apk.apkUrls.isEmpty && !trackOnly) {
throw NoAPKError(); throw NoAPKError();
} }
String apkVersion = apk.version.replaceAll('/', '-'); String apkVersion = apk.version.replaceAll('/', '-');
var name = currentApp?.name.trim() ??
apk.names.name[0].toUpperCase() + apk.names.name.substring(1);
return App( return App(
id ?? currentApp?.id ??
source.tryInferringAppId(standardUrl, source.tryInferringAppId(standardUrl,
additionalData: additionalData) ?? additionalSettings: additionalSettings) ??
generateTempID(apk.names, source), generateTempID(apk.names, source),
standardUrl, standardUrl,
apk.names.author[0].toUpperCase() + apk.names.author.substring(1), apk.names.author[0].toUpperCase() + apk.names.author.substring(1),
name.trim().isNotEmpty name.trim().isNotEmpty
? name ? name
: apk.names.name[0].toUpperCase() + apk.names.name.substring(1), : apk.names.name[0].toUpperCase() + apk.names.name.substring(1),
installedVersion, currentApp?.installedVersion,
apkVersion, apkVersion,
apk.apkUrls, apk.apkUrls,
apk.apkUrls.length - 1, apk.apkUrls.length - 1,
additionalData, additionalSettings,
DateTime.now(), DateTime.now(),
pinned, currentApp?.pinned ?? false);
trackOnly);
} }
// Returns errors in [results, errors] instead of throwing them // Returns errors in [results, errors] instead of throwing them
@ -316,7 +362,10 @@ class SourceProvider {
try { try {
var source = getSource(url); var source = getSource(url);
apps.add(await getApp( apps.add(await getApp(
source, url, source.additionalSourceAppSpecificDefaults)); source,
url,
getDefaultValuesFromFormItems(
source.combinedAppSpecificSettingFormItems)));
} catch (e) { } catch (e) {
errors.addAll(<String, dynamic>{url: e}); errors.addAll(<String, dynamic>{url: e});
} }

View File

@ -182,7 +182,7 @@ packages:
name: file_picker name: file_picker
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "5.2.3" version: "5.2.4"
flutter: flutter:
dependency: "direct main" dependency: "direct main"
description: flutter description: flutter
@ -215,14 +215,14 @@ packages:
name: flutter_local_notifications name: flutter_local_notifications
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "12.0.4" version: "13.0.0"
flutter_local_notifications_linux: flutter_local_notifications_linux:
dependency: transitive dependency: transitive
description: description:
name: flutter_local_notifications_linux name: flutter_local_notifications_linux
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.0.0" version: "3.0.0"
flutter_local_notifications_platform_interface: flutter_local_notifications_platform_interface:
dependency: transitive dependency: transitive
description: description:
@ -258,7 +258,7 @@ packages:
name: fluttertoast name: fluttertoast
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "8.1.1" version: "8.1.2"
html: html:
dependency: "direct main" dependency: "direct main"
description: description:
@ -510,7 +510,7 @@ packages:
name: provider name: provider
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "6.0.4" version: "6.0.5"
share_plus: share_plus:
dependency: "direct main" dependency: "direct main"
description: description:
@ -552,7 +552,7 @@ packages:
name: shared_preferences_linux name: shared_preferences_linux
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.1.1" version: "2.1.2"
shared_preferences_macos: shared_preferences_macos:
dependency: transitive dependency: transitive
description: description:
@ -580,7 +580,7 @@ packages:
name: shared_preferences_windows name: shared_preferences_windows
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.1.1" version: "2.1.2"
sky_engine: sky_engine:
dependency: transitive dependency: transitive
description: flutter description: flutter
@ -599,7 +599,7 @@ packages:
name: sqflite name: sqflite
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.2.1" version: "2.2.2"
sqflite_common: sqflite_common:
dependency: transitive dependency: transitive
description: description:
@ -739,35 +739,35 @@ packages:
name: webview_flutter name: webview_flutter
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "3.0.4" version: "4.0.0"
webview_flutter_android: webview_flutter_android:
dependency: transitive dependency: transitive
description: description:
name: webview_flutter_android name: webview_flutter_android
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.10.4" version: "3.0.0"
webview_flutter_platform_interface: webview_flutter_platform_interface:
dependency: transitive dependency: transitive
description: description:
name: webview_flutter_platform_interface name: webview_flutter_platform_interface
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.9.5" version: "2.0.0"
webview_flutter_wkwebview: webview_flutter_wkwebview:
dependency: transitive dependency: transitive
description: description:
name: webview_flutter_wkwebview name: webview_flutter_wkwebview
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.9.5" version: "3.0.0"
win32: win32:
dependency: transitive dependency: transitive
description: description:
name: win32 name: win32
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "3.1.2" version: "3.1.3"
xdg_directories: xdg_directories:
dependency: transitive dependency: transitive
description: description:

View File

@ -17,7 +17,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
# In Windows, build-name is used as the major, minor, and patch parts # In Windows, build-name is used as the major, minor, and patch parts
# of the product and file versions while build-number is used as the build suffix. # of the product and file versions while build-number is used as the build suffix.
version: 0.8.21+85 # When changing this, update the tag in main() accordingly version: 0.8.23+87 # When changing this, update the tag in main() accordingly
environment: environment:
sdk: '>=2.18.2 <3.0.0' sdk: '>=2.18.2 <3.0.0'
@ -38,10 +38,10 @@ dependencies:
cupertino_icons: ^1.0.5 cupertino_icons: ^1.0.5
path_provider: ^2.0.11 path_provider: ^2.0.11
flutter_fgbg: ^0.2.0 # Try removing reliance on this flutter_fgbg: ^0.2.0 # Try removing reliance on this
flutter_local_notifications: ^12.0.0 flutter_local_notifications: ^13.0.0
provider: ^6.0.3 provider: ^6.0.3
http: ^0.13.5 http: ^0.13.5
webview_flutter: ^3.0.4 webview_flutter: ^4.0.0
dynamic_color: ^1.5.4 dynamic_color: ^1.5.4
html: ^0.15.0 html: ^0.15.0
shared_preferences: ^2.0.15 shared_preferences: ^2.0.15