Compare commits

...

54 Commits

Author SHA1 Message Date
0186c00d97 Merge pull request #603 from ImranR98/dev
Download file extension bugfix (#595)
2023-06-02 21:08:15 -04:00
9294540b5d Increment version, update modules 2023-06-02 21:07:28 -04:00
0b16c28224 Add Polish to menu 2023-06-02 21:03:45 -04:00
83028d405a Merge remote-tracking branch 'origin/main' into dev 2023-06-02 21:02:27 -04:00
c4262c3eaa Merge pull request #593 from Daviteusz/weblate-obtainium-translate
Add Polish language
2023-06-02 21:02:13 -04:00
f0e1831d30 APK extension bugfix (#595) 2023-06-02 20:48:32 -04:00
9efd0dd46e Translated using Weblate (Polish) 2023-06-02 23:02:45 +02:00
eb26c0be0b Translated using Weblate (Polish) 2023-05-28 11:59:04 +02:00
1ff1c6ca33 Added translation using Weblate (Polish) 2023-05-28 11:57:07 +02:00
6169915e63 Merge pull request #590 from ImranR98/dev
Infer GitHub App ID where possible (#588)
2023-05-27 21:02:20 -04:00
a0d466a074 Add toggle for App ID inferring where optional 2023-05-27 21:01:16 -04:00
6f9ef6d51e Merge remote-tracking branch 'origin/main' into dev 2023-05-27 20:38:39 -04:00
feb4c2eabc Increment version 2023-05-27 20:38:20 -04:00
c2cf39125d Merge pull request #589 from gidano/main
Update hu.json
2023-05-27 20:37:44 -04:00
833ece1ef5 Infer GitHub App ID where possible 2023-05-27 20:36:29 -04:00
fee23cadfa Update hu.json 2023-05-26 18:58:23 +02:00
4c6303f783 Merge pull request #586 from ImranR98/dev
Fix Android 7 Icon Bug (#475), Attempt Fix for Add App Error (#549), Directory Delete Bugfix, App Load Behaviour Changes (#579)
2023-05-22 15:25:20 -04:00
ce6e6c47db Increment version, update packages 2023-05-22 14:47:28 -04:00
2ccff15525 Fix Icon on Android 7 and lower (#475) 2023-05-22 13:45:09 -04:00
d24f2b4e6d Attempted fix for #549
APK stored in "ext storage" dir if cache dir not available
2023-05-22 13:02:18 -04:00
03fc6a530f App load optimizations, dir delete bugfix 2023-05-22 12:33:31 -04:00
4136734a60 Skip App loading on return to foreground 2023-05-22 12:10:58 -04:00
ca1371260c Merge pull request #568 from ImranR98/dev
App ID Filter (#564), Apps Page Bottom Buttons Menu UI Changes
2023-05-14 14:19:37 -04:00
03c2ce9a01 Changes to bottom buttons UI on Apps page 2023-05-14 14:18:31 -04:00
eda5fec37c Added App ID Filter 2023-05-14 13:57:01 -04:00
e21c6297ff Merge pull request #567 from ImranR98/dev
Add Tags-Only Support for GitHub (and Codeberg) Track-Only Apps (#566), Increase Size of Changelog Touch Target (#565), Make All Sources Accessible in Override Menu (#543), Other Bugfixes
2023-05-14 13:49:00 -04:00
c6297ea449 Increment version 2023-05-14 13:45:48 -04:00
e33cc00266 Make all sources override-eligible to account for subdomains 2023-05-14 13:42:09 -04:00
96c92c8df9 Add 'tags-only' support (for Track-Only) to GitHub (and Codeberg) 2023-05-14 13:25:09 -04:00
e256ada2dc Adjust Apps list trailing UI spacing and touch area 2023-05-14 12:53:40 -04:00
eb0be196da Fix 'Please Wait' message on Apps page 2023-05-14 12:40:26 -04:00
1606ad3442 Fix potential date parse error in SoureHut 2023-05-14 12:39:21 -04:00
d212f13345 Fixed code smells 2023-05-14 12:29:37 -04:00
f80c9ec33e Merge pull request #563 from ImranR98/dev
Flutter version - related change
2023-05-13 02:36:09 -04:00
7681e23de9 Flutter version - related change 2023-05-13 02:35:44 -04:00
22a60df40e Merge pull request #562 from ImranR98/dev
Fixed breaking bug for some sources (#561)
2023-05-13 02:32:23 -04:00
431a01f2a5 Fixed breaking bug for some sources (#561) 2023-05-13 02:32:06 -04:00
0cd4385de7 Merge pull request #544 from LilligantMatsuri/main
Update zh.json
2023-05-12 18:02:58 -04:00
0774b3ddc3 Merge pull request #558 from iDazai/patch-1
Update de.json
2023-05-12 18:02:52 -04:00
b60b1ed058 Merge pull request #560 from ImranR98/dev
XAPK Bugfixes #541, HTML User-Agent #545, Better APK Cleanup #551, Search UI Improvements #550
2023-05-12 18:02:21 -04:00
b196715d60 Search UI improvements 2023-05-12 18:00:21 -04:00
0673e90dff Better APK cleanup 2023-05-12 17:53:07 -04:00
59cfa242fb Update de.json
translate newly added English text
improved some German text
2023-05-10 18:20:40 +02:00
65ab72ba90 Increment version 2023-05-09 00:40:39 -04:00
408bca8951 XAPK bugfixes, HTML default User-Agent 2023-05-09 00:37:06 -04:00
480467492a Update zh.json
- Translate new strings
- Slight improvements

Signed-off-by: Matsuri <matsuri@vmoe.info>
2023-05-07 22:00:02 +08:00
219b04aedb Merge pull request #538 from bluefly000/japanese-translation
Update ja.json
2023-05-06 14:43:26 -04:00
a0709856ef Merge branch 'main' into japanese-translation 2023-05-06 14:43:14 -04:00
577642850f Merge pull request #542 from ImranR98/dev
Add (Incomplete) XAPK Support (#541), Auto-Check Updates on Start (#539), UI Tweaks (#540)
2023-05-06 14:42:46 -04:00
e1db024034 Increment version 2023-05-06 14:40:14 -04:00
cc268aeeda "Check updates on start" toggle 2023-05-06 14:25:17 -04:00
d5f7eced8b UI tweaks 2023-05-06 13:28:41 -04:00
cc3c4cc79f Add XAPK support (incomplete - OBB not copied) 2023-05-06 13:20:58 -04:00
89b61884f1 Update ja.json 2023-05-06 15:52:23 +09:00
43 changed files with 1104 additions and 436 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.6 KiB

View File

@ -26,6 +26,6 @@ subprojects {
project.evaluationDependsOn(':app') project.evaluationDependsOn(':app')
} }
task clean(type: Delete) { tasks.register("clean", Delete) {
delete rootProject.buildDir delete rootProject.buildDir
} }

BIN
assets/graphics/icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

View File

@ -121,12 +121,12 @@
"followSystem": "System folgen", "followSystem": "System folgen",
"obtainium": "Obtainium", "obtainium": "Obtainium",
"materialYou": "Material You", "materialYou": "Material You",
"useBlackTheme": "Use pure black dark theme", "useBlackTheme": "Verwende Pure Black Dark Theme",
"appSortBy": "App sortieren nach", "appSortBy": "App sortieren nach",
"authorName": "Autor/Name", "authorName": "Autor/Name",
"nameAuthor": "Name/Autor", "nameAuthor": "Name/Autor",
"asAdded": "Wie hinzugefügt", "asAdded": "Wie hinzugefügt",
"appSortOrder": "App Sortierung nach", "appSortOrder": "App sortieren nach",
"ascending": "Aufsteigend", "ascending": "Aufsteigend",
"descending": "Absteigend", "descending": "Absteigend",
"bgUpdateCheckInterval": "Prüfintervall für Hintergrundaktualisierung", "bgUpdateCheckInterval": "Prüfintervall für Hintergrundaktualisierung",
@ -180,6 +180,7 @@
"yesMarkUpdated": "Ja, als aktualisiert markieren", "yesMarkUpdated": "Ja, als aktualisiert markieren",
"fdroid": "F-Droid Official", "fdroid": "F-Droid Official",
"appIdOrName": "App ID oder Name", "appIdOrName": "App ID oder Name",
"appId": "App ID",
"appWithIdOrNameNotFound": "Es wurde keine App mit dieser ID oder diesem Namen gefunden", "appWithIdOrNameNotFound": "Es wurde keine App mit dieser ID oder diesem Namen gefunden",
"reposHaveMultipleApps": "Repos können mehrere Apps enthalten", "reposHaveMultipleApps": "Repos können mehrere Apps enthalten",
"fdroidThirdPartyRepo": "F-Droid Third-Party Repo", "fdroidThirdPartyRepo": "F-Droid Third-Party Repo",
@ -207,7 +208,7 @@
"addCategory": "Kategorie hinzufügen", "addCategory": "Kategorie hinzufügen",
"label": "Bezeichnung", "label": "Bezeichnung",
"language": "Sprache", "language": "Sprache",
"copiedToClipboard": "Copied to Clipboard", "copiedToClipboard": "In die Zwischenablage kopiert",
"storagePermissionDenied": "Speicherberechtigung verweigert", "storagePermissionDenied": "Speicherberechtigung verweigert",
"selectedCategorizeWarning": "Dadurch werden alle bestehenden Kategorieeinstellungen für die ausgewählten Apps ersetzt.", "selectedCategorizeWarning": "Dadurch werden alle bestehenden Kategorieeinstellungen für die ausgewählten Apps ersetzt.",
"filterAPKsByRegEx": "APKs nach regulärem Ausdruck filtern", "filterAPKsByRegEx": "APKs nach regulärem Ausdruck filtern",
@ -218,7 +219,7 @@
"releaseDateAsVersionExplanation": "Diese Option sollte nur für Apps verwendet werden, bei denen die Versionserkennung nicht korrekt funktioniert, aber ein Veröffentlichungsdatum verfügbar ist.", "releaseDateAsVersionExplanation": "Diese Option sollte nur für Apps verwendet werden, bei denen die Versionserkennung nicht korrekt funktioniert, aber ein Veröffentlichungsdatum verfügbar ist.",
"changes": "Änderungen", "changes": "Änderungen",
"releaseDate": "Veröffentlichungsdatum", "releaseDate": "Veröffentlichungsdatum",
"importFromURLsInFile": "Importieren von URLs aus Datei ( z.B. OPML)", "importFromURLsInFile": "Importieren von URLs aus Datei (z. B. OPML)",
"versionDetection": "Versionserkennung", "versionDetection": "Versionserkennung",
"standardVersionDetection": "Standardversionserkennung", "standardVersionDetection": "Standardversionserkennung",
"groupByCategory": "Nach Kategorie gruppieren", "groupByCategory": "Nach Kategorie gruppieren",
@ -227,10 +228,12 @@
"dontShowAgain": "Nicht noch einmal zeigen", "dontShowAgain": "Nicht noch einmal zeigen",
"dontShowTrackOnlyWarnings": "Warnung für 'Nur Nachverfolgen' nicht anzeigen", "dontShowTrackOnlyWarnings": "Warnung für 'Nur Nachverfolgen' nicht anzeigen",
"dontShowAPKOriginWarnings": "Warnung für APK-Herkunft nicht anzeigen", "dontShowAPKOriginWarnings": "Warnung für APK-Herkunft nicht anzeigen",
"moveNonInstalledAppsToBottom": "Move Non-Installed Apps to Bottom of Apps View", "moveNonInstalledAppsToBottom": "Nicht installierte Apps ans Ende der Apps Ansicht verschieben",
"gitlabPATLabel": "GitLab Personal Access Token (Enables Search)", "gitlabPATLabel": "GitLab Personal Access Token (Aktiviert Suche)",
"about": "About", "about": "Über",
"requiresCredentialsInSettings": "This needs additional credentials (in Settings)", "requiresCredentialsInSettings": "Benötigt zusätzliche Anmeldedaten (in den Einstellungen)",
"checkOnStart": "Überprüfe einmalig beim Start",
"tryInferAppIdFromCode": "Try inferring App ID from source code",
"removeAppQuestion": { "removeAppQuestion": {
"one": "App entfernen?", "one": "App entfernen?",
"other": "Apps entfernen?" "other": "Apps entfernen?"
@ -257,7 +260,7 @@
}, },
"minute": { "minute": {
"one": "{} Minute", "one": "{} Minute",
"other": "{} Minutes" "other": "{} Minuten"
}, },
"hour": { "hour": {
"one": "{} Stunde", "one": "{} Stunde",

View File

@ -121,7 +121,7 @@
"followSystem": "Follow System", "followSystem": "Follow System",
"obtainium": "Obtainium", "obtainium": "Obtainium",
"materialYou": "Material You", "materialYou": "Material You",
"useBlackTheme": "Use pure black dark theme", "useBlackTheme": "Use Pure Black Dark Theme",
"appSortBy": "App Sort By", "appSortBy": "App Sort By",
"authorName": "Author/Name", "authorName": "Author/Name",
"nameAuthor": "Name/Author", "nameAuthor": "Name/Author",
@ -180,6 +180,7 @@
"yesMarkUpdated": "Yes, Mark as Updated", "yesMarkUpdated": "Yes, Mark as Updated",
"fdroid": "F-Droid Official", "fdroid": "F-Droid Official",
"appIdOrName": "App ID or Name", "appIdOrName": "App ID or Name",
"appId": "App ID",
"appWithIdOrNameNotFound": "No App was found with that ID or Name", "appWithIdOrNameNotFound": "No App was found with that ID or Name",
"reposHaveMultipleApps": "Repos may contain multiple Apps", "reposHaveMultipleApps": "Repos may contain multiple Apps",
"fdroidThirdPartyRepo": "F-Droid Third-Party Repo", "fdroidThirdPartyRepo": "F-Droid Third-Party Repo",
@ -231,6 +232,8 @@
"gitlabPATLabel": "GitLab Personal Access Token (Enables Search)", "gitlabPATLabel": "GitLab Personal Access Token (Enables Search)",
"about": "About", "about": "About",
"requiresCredentialsInSettings": "This needs additional credentials (in Settings)", "requiresCredentialsInSettings": "This needs additional credentials (in Settings)",
"checkOnStart": "Check Once on Start",
"tryInferAppIdFromCode": "Try inferring App ID from source code",
"removeAppQuestion": { "removeAppQuestion": {
"one": "Remove App?", "one": "Remove App?",
"other": "Remove Apps?" "other": "Remove Apps?"

View File

@ -180,6 +180,7 @@
"yesMarkUpdated": "Sí, Marcar como Actualizada", "yesMarkUpdated": "Sí, Marcar como Actualizada",
"fdroid": "Repositorio oficial de F-Droid", "fdroid": "Repositorio oficial de F-Droid",
"appIdOrName": "ID o Nombre de la Aplicación", "appIdOrName": "ID o Nombre de la Aplicación",
"appId": "ID de la Aplicación",
"appWithIdOrNameNotFound": "No se han encontrado aplicaciones con esa ID o nombre", "appWithIdOrNameNotFound": "No se han encontrado aplicaciones con esa ID o nombre",
"reposHaveMultipleApps": "Los repositorios pueden contener varias aplicaciones", "reposHaveMultipleApps": "Los repositorios pueden contener varias aplicaciones",
"fdroidThirdPartyRepo": "Rpositorios de terceros de F-Droid", "fdroidThirdPartyRepo": "Rpositorios de terceros de F-Droid",
@ -231,6 +232,8 @@
"gitlabPATLabel": "GitLab Personal Access Token (Enables Search)", "gitlabPATLabel": "GitLab Personal Access Token (Enables Search)",
"about": "About", "about": "About",
"requiresCredentialsInSettings": "This needs additional credentials (in Settings)", "requiresCredentialsInSettings": "This needs additional credentials (in Settings)",
"checkOnStart": "Check Once on Start",
"tryInferAppIdFromCode": "Try inferring App ID from source code",
"removeAppQuestion": { "removeAppQuestion": {
"one": "¿Eliminar Aplicación?", "one": "¿Eliminar Aplicación?",
"other": "¿Eliminar Aplicaciones?" "other": "¿Eliminar Aplicaciones?"

View File

@ -180,6 +180,7 @@
"yesMarkUpdated": "بله، علامت گذاری به عنوان به روز شده", "yesMarkUpdated": "بله، علامت گذاری به عنوان به روز شده",
"fdroid": "F-Droid Official", "fdroid": "F-Droid Official",
"appIdOrName": "شناسه یا نام برنامه", "appIdOrName": "شناسه یا نام برنامه",
"appId": "App ID",
"appWithIdOrNameNotFound": "هیچ برنامه ای با آن شناسه یا نام یافت نشد", "appWithIdOrNameNotFound": "هیچ برنامه ای با آن شناسه یا نام یافت نشد",
"reposHaveMultipleApps": "مخازن ممکن است شامل چندین برنامه باشد", "reposHaveMultipleApps": "مخازن ممکن است شامل چندین برنامه باشد",
"fdroidThirdPartyRepo": "مخازن شخص ثالث F-Droid", "fdroidThirdPartyRepo": "مخازن شخص ثالث F-Droid",
@ -231,6 +232,8 @@
"gitlabPATLabel": "GitLab Personal Access Token (Enables Search)", "gitlabPATLabel": "GitLab Personal Access Token (Enables Search)",
"about": "About", "about": "About",
"requiresCredentialsInSettings": "This needs additional credentials (in Settings)", "requiresCredentialsInSettings": "This needs additional credentials (in Settings)",
"checkOnStart": "Check Once on Start",
"tryInferAppIdFromCode": "Try inferring App ID from source code",
"removeAppQuestion": { "removeAppQuestion": {
"one": "برنامه حذف شود؟", "one": "برنامه حذف شود؟",
"other": "برنامه ها حذف شوند؟" "other": "برنامه ها حذف شوند؟"

View File

@ -121,7 +121,7 @@
"followSystem": "Suivre le système", "followSystem": "Suivre le système",
"obtainium": "Obtainium", "obtainium": "Obtainium",
"materialYou": "Material You", "materialYou": "Material You",
"useBlackTheme": "Use pure black dark theme", "useBlackTheme": "Use Pure Black Dark Theme",
"appSortBy": "Applications triées par", "appSortBy": "Applications triées par",
"authorName": "Auteur/Nom", "authorName": "Auteur/Nom",
"nameAuthor": "Nom/Auteur", "nameAuthor": "Nom/Auteur",
@ -180,6 +180,7 @@
"yesMarkUpdated": "Oui, marquer comme mis à jour", "yesMarkUpdated": "Oui, marquer comme mis à jour",
"fdroid": "F-Droid Official", "fdroid": "F-Droid Official",
"appIdOrName": "ID ou nom de l'application", "appIdOrName": "ID ou nom de l'application",
"appId": "ID de l'application",
"appWithIdOrNameNotFound": "Aucune application n'a été trouvée avec cet identifiant ou ce nom", "appWithIdOrNameNotFound": "Aucune application n'a été trouvée avec cet identifiant ou ce nom",
"reposHaveMultipleApps": "Les dépôts peuvent contenir plusieurs applications", "reposHaveMultipleApps": "Les dépôts peuvent contenir plusieurs applications",
"fdroidThirdPartyRepo": "Dépôt tiers F-Droid", "fdroidThirdPartyRepo": "Dépôt tiers F-Droid",
@ -231,6 +232,8 @@
"gitlabPATLabel": "GitLab Personal Access Token (Enables Search)", "gitlabPATLabel": "GitLab Personal Access Token (Enables Search)",
"about": "About", "about": "About",
"requiresCredentialsInSettings": "This needs additional credentials (in Settings)", "requiresCredentialsInSettings": "This needs additional credentials (in Settings)",
"checkOnStart": "Check Once on Start",
"tryInferAppIdFromCode": "Try inferring App ID from source code",
"removeAppQuestion": { "removeAppQuestion": {
"one": "Supprimer l'application ?", "one": "Supprimer l'application ?",
"other": "Supprimer les applications ?" "other": "Supprimer les applications ?"

View File

@ -180,6 +180,7 @@
"yesMarkUpdated": "Igen, megjelölés frissítettként", "yesMarkUpdated": "Igen, megjelölés frissítettként",
"fdroid": "F-Droid Official", "fdroid": "F-Droid Official",
"appIdOrName": "App ID vagy név", "appIdOrName": "App ID vagy név",
"appId": "App ID",
"appWithIdOrNameNotFound": "Nem található app ezzel az azonosítóval vagy névvel", "appWithIdOrNameNotFound": "Nem található app ezzel az azonosítóval vagy névvel",
"reposHaveMultipleApps": "A repók több alkalmazást is tartalmazhatnak", "reposHaveMultipleApps": "A repók több alkalmazást is tartalmazhatnak",
"fdroidThirdPartyRepo": "F-Droid Harmadik-fél Repo", "fdroidThirdPartyRepo": "F-Droid Harmadik-fél Repo",
@ -226,10 +227,12 @@
"dontShowAgain": "Ne mutassa ezt újra", "dontShowAgain": "Ne mutassa ezt újra",
"dontShowTrackOnlyWarnings": "Ne jelenítsen meg 'Csak nyomon követés' figyelmeztetést", "dontShowTrackOnlyWarnings": "Ne jelenítsen meg 'Csak nyomon követés' figyelmeztetést",
"dontShowAPKOriginWarnings": "Ne jelenítsen meg az APK eredetére vonatkozó figyelmeztetéseket", "dontShowAPKOriginWarnings": "Ne jelenítsen meg az APK eredetére vonatkozó figyelmeztetéseket",
"moveNonInstalledAppsToBottom": "Move Non-Installed Apps to Bottom of Apps View", "moveNonInstalledAppsToBottom": "Helyezze át a nem telepített appokat az App nézet aljára",
"gitlabPATLabel": "GitLab Personal Access Token (Enables Search)", "gitlabPATLabel": "GitLab Personal Access Token (Engedélyezi a Keresést)",
"about": "About", "about": "Rólunk",
"requiresCredentialsInSettings": "This needs additional credentials (in Settings)", "requiresCredentialsInSettings": "Ehhez további hitelesítő adatokra van szükség (a Beállításokban)",
"checkOnStart": "Egyszer az indításkor",
"tryInferAppIdFromCode": "Try inferring App ID from source code",
"removeAppQuestion": { "removeAppQuestion": {
"one": "Eltávolítja az alkalmazást?", "one": "Eltávolítja az alkalmazást?",
"other": "Eltávolítja az alkalmazást?" "other": "Eltávolítja az alkalmazást?"

View File

@ -121,7 +121,7 @@
"followSystem": "Segui sistema", "followSystem": "Segui sistema",
"obtainium": "Obtainium", "obtainium": "Obtainium",
"materialYou": "Material You", "materialYou": "Material You",
"useBlackTheme": "Use pure black dark theme", "useBlackTheme": "Use Pure Black Dark Theme",
"appSortBy": "App ordinate per", "appSortBy": "App ordinate per",
"authorName": "Autore/Nome", "authorName": "Autore/Nome",
"nameAuthor": "Nome/Autore", "nameAuthor": "Nome/Autore",
@ -180,6 +180,7 @@
"yesMarkUpdated": "Sì, contrassegna come aggiornato", "yesMarkUpdated": "Sì, contrassegna come aggiornato",
"fdroid": "F-Droid Official", "fdroid": "F-Droid Official",
"appIdOrName": "ID o nome dell'App", "appIdOrName": "ID o nome dell'App",
"appId": "ID 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 F-Droid di terze parti", "fdroidThirdPartyRepo": "Repository F-Droid di terze parti",
@ -231,6 +232,8 @@
"gitlabPATLabel": "GitLab Personal Access Token (Enables Search)", "gitlabPATLabel": "GitLab Personal Access Token (Enables Search)",
"about": "About", "about": "About",
"requiresCredentialsInSettings": "This needs additional credentials (in Settings)", "requiresCredentialsInSettings": "This needs additional credentials (in Settings)",
"checkOnStart": "Check Once on Start",
"tryInferAppIdFromCode": "Try inferring App ID from source code",
"removeAppQuestion": { "removeAppQuestion": {
"one": "Rimuovere l'App?", "one": "Rimuovere l'App?",
"other": "Rimuovere le App?" "other": "Rimuovere le App?"

View File

@ -180,6 +180,7 @@
"yesMarkUpdated": "はい、アップデート済みとしてマークします", "yesMarkUpdated": "はい、アップデート済みとしてマークします",
"fdroid": "F-Droid Official", "fdroid": "F-Droid Official",
"appIdOrName": "アプリのIDまたは名前", "appIdOrName": "アプリのIDまたは名前",
"appId": "App ID",
"appWithIdOrNameNotFound": "そのIDや名前を持つアプリは見つかりませんでした", "appWithIdOrNameNotFound": "そのIDや名前を持つアプリは見つかりませんでした",
"reposHaveMultipleApps": "リポジトリには複数のアプリが含まれることがあります", "reposHaveMultipleApps": "リポジトリには複数のアプリが含まれることがあります",
"fdroidThirdPartyRepo": "F-Droid サードパーティリポジトリ", "fdroidThirdPartyRepo": "F-Droid サードパーティリポジトリ",
@ -227,10 +228,12 @@
"dontShowAgain": "二度と表示しない", "dontShowAgain": "二度と表示しない",
"dontShowTrackOnlyWarnings": "「追跡のみ」の警告を表示しない", "dontShowTrackOnlyWarnings": "「追跡のみ」の警告を表示しない",
"dontShowAPKOriginWarnings": "APK Originの警告を表示しない", "dontShowAPKOriginWarnings": "APK Originの警告を表示しない",
"moveNonInstalledAppsToBottom": "Move Non-Installed Apps to Bottom of Apps View", "moveNonInstalledAppsToBottom": "未インストールのアプリをアプリ一覧の下部に移動させる",
"gitlabPATLabel": "GitLab Personal Access Token (Enables Search)", "gitlabPATLabel": "GitLab パーソナルアクセストークン (検索を有効化する)",
"about": "About", "about": "概要",
"requiresCredentialsInSettings": "This needs additional credentials (in Settings)", "requiresCredentialsInSettings": "これには追加の認証が必要です (設定にて)",
"checkOnStart": "Check Once on Start",
"tryInferAppIdFromCode": "Try inferring App ID from source code",
"removeAppQuestion": { "removeAppQuestion": {
"one": "アプリを削除しますか?", "one": "アプリを削除しますか?",
"other": "アプリを削除しますか?" "other": "アプリを削除しますか?"

285
assets/translations/pl.json Normal file
View File

@ -0,0 +1,285 @@
{
"noDescription": "Brak opisu",
"no": "Nie",
"okay": "Okej",
"appId": "ID aplikacji",
"bgUpdateGotErrorRetryInMinutes": {
"one": "Sprawdzanie aktualizacji w tle napotkało {}, zaplanuje ponowne sprawdzenie za {} min.",
"other": "Sprawdzanie aktualizacji w tle napotkało {}, zaplanuje ponowne sprawdzenie za {} min."
},
"invalidURLForSource": "Nieprawidłowy adres URL aplikacji {}",
"noReleaseFound": "Nie można znaleźć odpowiedniego wydania",
"noVersionFound": "Nie można określić wersji wydania",
"urlMatchesNoSource": "Adres URL nie pasuje do znanego źródła",
"cantInstallOlderVersion": "Nie można zainstalować starszej wersji aplikacji",
"appIdMismatch": "Pobrany identyfikator pakietu nie pasuje do istniejącego identyfikatora aplikacji",
"functionNotImplemented": "Ta klasa nie zaimplementowała tej funkcji",
"placeholder": "Placeholder",
"someErrors": "Wystąpiły pewne błędy",
"unexpectedError": "Nieoczekiwany błąd",
"ok": "Okej",
"and": "i",
"startedBgUpdateTask": "Rozpoczęto zadanie sprawdzania aktualizacji w tle",
"bgUpdateIgnoreAfterIs": "Parametr ignoreAfter aktualizacji w tle to {}",
"startedActualBGUpdateCheck": "Rozpoczęto sprawdzanie aktualizacji w tle",
"bgUpdateTaskFinished": "Zakończono zadanie sprawdzania aktualizacji w tle",
"firstRun": "Jest to pierwsze uruchomienie Obtainium",
"settingUpdateCheckIntervalTo": "Ustawianie interwału aktualizacji na {}",
"githubPATLabel": "Osobisty Token Dostępu GitHub (zwiększa limit zapytań)",
"githubPATHint": "Wymagany format OTD: użytkownik:token",
"githubPATFormat": "użytkownik:token",
"includePrereleases": "Uwzględnij wersje wstępne",
"fallbackToOlderReleases": "Powracaj do starszych wersji",
"filterReleaseTitlesByRegEx": "Filtruj tytuły wydań wg. wyrażeń regularnych",
"invalidRegEx": "Nieprawidłowe wyrażenie regularne",
"cancel": "Anuluj",
"continue": "Kontynuuj",
"requiredInBrackets": "(Wymagane)",
"dropdownNoOptsError": "BŁĄD: LISTA ROZWIJANA MUSI MIEĆ CO NAJMNIEJ JEDNĄ OPCJĘ",
"colour": "Kolor",
"githubStarredRepos": "Repozytoria GitHub oznaczone gwiazdką",
"uname": "Nazwa użytkownika",
"wrongArgNum": "Nieprawidłowa liczba podanych argumentów",
"xIsTrackOnly": "{} jest tylko obserwowana",
"source": "Źródło",
"app": "Aplikacja",
"appsFromSourceAreTrackOnly": "Aplikacje z tego źródła są „Obserwowane”.",
"youPickedTrackOnly": "Wybrano opcję „Tylko obserwuj”.",
"trackOnlyAppDescription": "Aplikacja będzie obserwowana pod kątem aktualizacji, ale Obtainium nie będzie w stanie jej pobrać ani zainstalować.",
"cancelled": "Anulowano",
"appAlreadyAdded": "Aplikacja już została dodana",
"alreadyUpToDateQuestion": "Aplikacja jest już aktualna?",
"addApp": "Dodaj apkę",
"appSourceURL": "Adres URL źródła aplikacji",
"error": "Błąd",
"add": "Dodaj",
"searchSomeSourcesLabel": "Szukaj (tylko niektóre źródła)",
"search": "Szukaj",
"additionalOptsFor": "Dodatkowe opcje dla {}",
"supportedSourcesBelow": "Obsługiwane źródła:",
"trackOnlyInBrackets": "(tylko obserwowane)",
"searchableInBrackets": "(Wyszukiwalne)",
"appsString": "Aplikacje",
"noApps": "Brak aplikacji",
"noAppsForFilter": "Brak aplikacji dla filtra",
"byX": "Autorstwa {}",
"percentProgress": "Postęp: {}%",
"pleaseWait": "Proszę czekać",
"updateAvailable": "Dostępna aktualizacja",
"estimateInBracketsShort": "(Szac.)",
"notInstalled": "Nie zainstalowano",
"estimateInBrackets": "(Szacunkowo)",
"selectAll": "Zaznacz wszystkie",
"deselectN": "Odznacz {}",
"xWillBeRemovedButRemainInstalled": "{} zostanie usunięty z Obtainium, ale pozostanie zainstalowany na urządzeniu.",
"removeSelectedAppsQuestion": "Usunąć wybrane aplikacje?",
"removeSelectedApps": "Usuń wybrane aplikacje",
"updateX": "Zaktualizuj {}",
"installX": "Zainstaluj {}",
"markXTrackOnlyAsUpdated": "Oznacz {}\n(Tylko obserwowana)\njako zaktualizowaną",
"changeX": "Zmień {}",
"installUpdateApps": "Instaluj/aktualizuj aplikacje",
"installUpdateSelectedApps": "Zainstaluj/zaktualizuj wybrane aplikacje",
"markXSelectedAppsAsUpdated": "Oznaczyć {} wybranych aplikacji jako zaktualizowane?",
"yes": "Tak",
"markSelectedAppsUpdated": "Oznacz wybrane aplikacje jako zaktualizowane",
"pinToTop": "Przypnij",
"unpinFromTop": "Odepnij",
"resetInstallStatusForSelectedAppsQuestion": "Zresetować status instalacji dla wybranych aplikacji?",
"installStatusOfXWillBeResetExplanation": "Stan instalacji wybranych aplikacji zostanie zresetowany.\n\nMoże być to pomocne, gdy wersja aplikacji wyświetlana w Obtainium jest nieprawidłowa z powodu nieudanych aktualizacji lub innych problemów.",
"shareSelectedAppURLs": "Udostępnij wybrane adresy URL aplikacji",
"resetInstallStatus": "Zresetuj stan instalacji",
"more": "Więcej",
"removeOutdatedFilter": "Usuń filtr nieaktualnych aplikacji",
"showOutdatedOnly": "Pokaż tylko nieaktualne aplikacje",
"filter": "FIltr",
"filterActive": "Filtruj *",
"filterApps": "Filtruj aplikacje",
"appName": "Nazwa aplikacji",
"author": "Autor",
"upToDateApps": "Aktualne aplikacje",
"nonInstalledApps": "Niezainstalowane aplikacje",
"importExport": "Import/Eksport",
"settings": "Ustawienia",
"exportedTo": "Wyeksportowano do {}",
"obtainiumExport": "Eksportuj Obtainium",
"invalidInput": "Nieprawidłowe wprowadzenie",
"importedX": "Zaimportowano {}",
"obtainiumImport": "Import Obtainium",
"importFromURLList": "Importuj z listy adresów URL",
"searchQuery": "Wyszukiwane zapytanie",
"appURLList": "Lista adresów URL aplikacji",
"line": "Linia",
"searchX": "Przeszukaj {}",
"noResults": "Nie znaleziono wyników",
"importX": "Importuj {}",
"importedAppsIdDisclaimer": "Zaimportowane aplikacje mogą być wyświetlane jako „Niezainstalowane”.\nAby to naprawić, zainstaluj je ponownie za pomocą Obtainium.\nNie powinno to mieć wpływu na dane aplikacji.\n\nDotyczy tylko adresów URL i metod importu innych aplikacji.",
"importErrors": "Błędy importowania",
"importedXOfYApps": "Zaimportowano {} z {} aplikacji.",
"followingURLsHadErrors": "Następujące adresy URL zawierały błędy:",
"selectURL": "Wybierz adres URL",
"selectURLs": "Wybierz adresy URL",
"pick": "Wybierz",
"theme": "Motyw",
"dark": "Ciemny",
"light": "Jasny",
"followSystem": "Zgodny z systemem",
"obtainium": "Obtainium",
"materialYou": "Material You",
"useBlackTheme": "Użyj czarnego motywu",
"appSortBy": "Sortuj aplikacje według",
"authorName": "Autor/Nazwa",
"nameAuthor": "Nazwa/Autor",
"asAdded": "Dodania",
"appSortOrder": "Kolejność sortowania aplikacji",
"ascending": "Rosnąco",
"descending": "Malejąco",
"bgUpdateCheckInterval": "Częstotliwość sprawdzania aktualizacji w tle",
"neverManualOnly": "Nigdy - tylko ręcznie",
"appearance": "Wygląd",
"showWebInAppView": "Pokaż stronę źródłową w widoku aplikacji",
"pinUpdates": "Przypnij aktualizacje na górze widoku aplikacji",
"updates": "Aktualizacje",
"sourceSpecific": "Zależnie od źródła",
"appSource": "Źródło aplikacji",
"noLogs": "Brak logów",
"appLogs": "Logi aplikacji",
"close": "Zamknij",
"share": "Udostępnij",
"appNotFound": "Nie znaleziono aplikacji",
"obtainiumExportHyphenatedLowercase": "obtainium-eksport",
"pickAnAPK": "Wybierz plik APK",
"appHasMoreThanOnePackage": "{} ma więcej niż jeden pakiet:",
"deviceSupportsXArch": "Urządzenie obsługuje architekturę procesora {}.",
"deviceSupportsFollowingArchs": "Urządzenie obsługuje następujące architektury procesora:",
"warning": "Uwaga",
"sourceIsXButPackageFromYPrompt": "Źródłem aplikacji jest '{}', ale pakiet wydania pochodzi z '{}'. Kontynuować?",
"updatesAvailable": "Dostępne aktualizacje",
"updatesAvailableNotifDescription": "Powiadamia użytkownika o dostępności aktualizacji dla jednej lub więcej aplikacji obserwowanych przez Obtainium",
"noNewUpdates": "Brak nowych aktualizacji.",
"xHasAnUpdate": "{} ma aktualizację.",
"appsUpdated": "Zaktualizowane aplikacje",
"appsUpdatedNotifDescription": "Powiadamia użytkownika, gdy jedna lub więcej aplikacji zostało zaktualizowanych w tle",
"xWasUpdatedToY": "{} zaktualizowano do {}.",
"errorCheckingUpdates": "Sprawdzanie błędów aktualizacji",
"errorCheckingUpdatesNotifDescription": "Powiadomienie wyświetlane, gdy sprawdzanie aktualizacji w tle nie powiedzie się",
"appsRemoved": "Usunięte aplikacje",
"appsRemovedNotifDescription": "Powiadamia użytkownika, gdy jedna lub więcej aplikacji zostało usuniętych z powodu błędów wczytywania",
"xWasRemovedDueToErrorY": "Usunięto {} z powodu błędu: {}",
"completeAppInstallation": "Ukończenie instalacji aplikacji",
"obtainiumMustBeOpenToInstallApps": "Aby zainstalować aplikacje, Obtainium musi być otwarte",
"completeAppInstallationNotifDescription": "Prosi użytkownika o powrót do Obtainium w celu dokończenia instalacji aplikacji",
"checkingForUpdates": "Sprawdzanie aktualizacji",
"checkingForUpdatesNotifDescription": "Tymczasowe powiadomienie pojawiające się podczas sprawdzania aktualizacji",
"pleaseAllowInstallPerm": "Pozwól Obtainium instalować aplikacje",
"trackOnly": "Tylko obserwuj",
"errorWithHttpStatusCode": "Błąd {}",
"versionCorrectionDisabled": "Korekta wersji wyłączona (wtyczka wydaje się nie działać)",
"unknown": "Nieznane",
"none": "Brak",
"never": "Nigdy",
"latestVersionX": "Najnowsza wersja: {}",
"installedVersionX": "Zainstalowana wersja: {}",
"lastUpdateCheckX": "Ostatnio sprawdzono: {}",
"remove": "Usuń",
"yesMarkUpdated": "Tak, oznacz jako zaktualizowane",
"fdroid": "Oficjalny F-Droid",
"appIdOrName": "ID aplikacji lub nazwa",
"appWithIdOrNameNotFound": "Nie znaleziono aplikacji o tym identyfikatorze lub nazwie",
"reposHaveMultipleApps": "Repozytoria mogą zawierać wiele aplikacji",
"fdroidThirdPartyRepo": "Zewnętrzne repo F-Droid",
"steam": "Steam",
"steamMobile": "Mobilny Steam",
"steamChat": "Steam Chat",
"install": "Instaluj",
"markInstalled": "Oznacz jako zainstalowane",
"update": "Zaktualizuj",
"markUpdated": "Oznacz jako zaktualizowane",
"additionalOptions": "Dodatkowe opcje",
"disableVersionDetection": "Wyłącz wykrywanie wersji",
"noVersionDetectionExplanation": "Opcja ta powinna być używana tylko w przypadku aplikacji, w których wykrywanie wersji nie działa poprawnie.",
"downloadingX": "Pobieranie {}",
"downloadNotifDescription": "Powiadamia użytkownika o postępach w pobieraniu aplikacji",
"noAPKFound": "Nie znaleziono pakietu APK",
"noVersionDetection": "Bez wykrywania wersji",
"categorize": "Kategoryzuj",
"categories": "Kategorie",
"category": "Kategoria",
"noCategory": "Bez kategorii",
"noCategories": "Brak kategorii",
"deleteCategoriesQuestion": "Usunąć kategorie?",
"categoryDeleteWarning": "Wszystkie aplikacje w usuniętych kategoriach zostaną ustawione jako nieskategoryzowane.",
"addCategory": "Dodaj kategorię",
"label": "Etykieta",
"language": "Język",
"copiedToClipboard": "Skopiowano do schowka",
"storagePermissionDenied": "Odmówiono zezwolenia dostępu do pamięci",
"selectedCategorizeWarning": "Spowoduje to zastąpienie wszystkich istniejących ustawień kategorii dla wybranych aplikacji.",
"filterAPKsByRegEx": "Filtruj pliki APK według wyrażeń regularnych",
"removeFromObtainium": "Usuń z Obtainium",
"uninstallFromDevice": "Odinstaluj z urządzenia",
"onlyWorksWithNonVersionDetectApps": "Działa tylko w przypadku aplikacji z wyłączonym wykrywaniem wersji.",
"releaseDateAsVersion": "Użyj daty wydania jako wersji",
"releaseDateAsVersionExplanation": "Opcja ta powinna być używana tylko w przypadku aplikacji, w których wykrywanie wersji nie działa poprawnie, ale dostępna jest data wydania.",
"changes": "Zmiany",
"releaseDate": "Data wydania",
"importFromURLsInFile": "Importuj z adresów URL w pliku (typu OPML)",
"versionDetection": "Wykrywanie wersji",
"standardVersionDetection": "Standardowe wykrywanie wersji",
"groupByCategory": "Grupuj według kategorii",
"autoApkFilterByArch": "Spróbuj filtrować pliki APK według architektury procesora, jeśli to możliwe",
"overrideSource": "Nadpisz źródło",
"dontShowAgain": "Nie pokazuj tego ponownie",
"dontShowTrackOnlyWarnings": "Nie wyświetlaj ostrzeżeń „Tylko obserwowana”",
"dontShowAPKOriginWarnings": "Nie pokazuj ostrzeżeń o pochodzeniu APK",
"moveNonInstalledAppsToBottom": "Przenieś niezainstalowane aplikacje na dół widoku aplikacji",
"gitlabPATLabel": "Osobisty Token Dostępu GitLab (umożliwia wyszukiwanie)",
"about": "Więcej informacji",
"requiresCredentialsInSettings": "Wymaga to dodatkowych poświadczeń (w Ustawieniach)",
"checkOnStart": "Sprawdź raz przy starcie",
"tryInferAppIdFromCode": "Spróbuj wywnioskować identyfikator aplikacji z kodu źródłowego",
"removeAppQuestion": {
"one": "Usunąć aplikację?",
"other": "Usunąć aplikacje?"
},
"tooManyRequestsTryAgainInMinutes": {
"one": "Zbyt wiele żądań (ograniczona częstotliwość) - spróbuj ponownie za {} min.",
"other": "Zbyt wiele żądań (ograniczona częstotliwość) - spróbuj ponownie za {} min."
},
"bgCheckFoundUpdatesWillNotifyIfNeeded": {
"one": "Podczas sprawdzania aktualizacji w tle znaleziono {} aktualizację - w razie potrzeby użytkownik zostanie o tym powiadomiony",
"other": "Podczas sprawdzania aktualizacji w tle znaleziono {} akt. - w razie potrzeby użytkownik zostanie o tym powiadomiony"
},
"apps": {
"one": "{} aplik.",
"other": "{} aplik."
},
"url": {
"one": "{} adres URL",
"other": "{} adr. URL"
},
"minute": {
"one": "{} min.",
"other": "{} min."
},
"hour": {
"one": "{} godz.",
"other": "{} godz."
},
"day": {
"one": "{} dzień",
"other": "{} dni"
},
"clearedNLogsBeforeXAfterY": {
"one": "Wyczyszczono {n} log (przed = {before}, po = {after})",
"other": "Wyczyszczono logi: {n} (przed = {before}, po = {after})"
},
"xAndNMoreUpdatesAvailable": {
"one": "{} i jeszcze 1 aplikacja mają aktualizacje.",
"other": "{} i {} aplik. otrzymało aktualizacje."
},
"xAndNMoreUpdatesInstalled": {
"one": "Zaktualizowano {} i jeszcze 1 aplikację.",
"other": "Zaktualizowano {} i {} aplik."
}
}

View File

@ -33,7 +33,7 @@
"githubStarredRepos": "GitHub 已星标仓库", "githubStarredRepos": "GitHub 已星标仓库",
"uname": "用户名", "uname": "用户名",
"wrongArgNum": "参数数量错误", "wrongArgNum": "参数数量错误",
"xIsTrackOnly": "{} 为“仅追踪”模式", "xIsTrackOnly": "{}为“仅追踪”模式",
"source": "源代码", "source": "源代码",
"app": "应用", "app": "应用",
"appsFromSourceAreTrackOnly": "此来源的应用为“仅追踪”模式。", "appsFromSourceAreTrackOnly": "此来源的应用为“仅追踪”模式。",
@ -50,8 +50,8 @@
"search": "搜索", "search": "搜索",
"additionalOptsFor": "{} 的更多选项", "additionalOptsFor": "{} 的更多选项",
"supportedSourcesBelow": "支持的来源:", "supportedSourcesBelow": "支持的来源:",
"trackOnlyInBrackets": "仅追踪", "trackOnlyInBrackets": "(仅追踪)",
"searchableInBrackets": "可搜索", "searchableInBrackets": "(可搜索)",
"appsString": "应用列表", "appsString": "应用列表",
"noApps": "无应用", "noApps": "无应用",
"noAppsForFilter": "没有符合条件的应用", "noAppsForFilter": "没有符合条件的应用",
@ -59,9 +59,9 @@
"percentProgress": "进度:{}%", "percentProgress": "进度:{}%",
"pleaseWait": "请稍候", "pleaseWait": "请稍候",
"updateAvailable": "更新可用", "updateAvailable": "更新可用",
"estimateInBracketsShort": "(预计)", "estimateInBracketsShort": "(推测)",
"notInstalled": "未安装", "notInstalled": "未安装",
"estimateInBrackets": "(预计)", "estimateInBrackets": "(推测)",
"selectAll": "全选", "selectAll": "全选",
"deselectN": "取消选择 {}", "deselectN": "取消选择 {}",
"xWillBeRemovedButRemainInstalled": "{} 将从 Obtainium 中删除,但仍安装在您的设备中。", "xWillBeRemovedButRemainInstalled": "{} 将从 Obtainium 中删除,但仍安装在您的设备中。",
@ -74,8 +74,8 @@
"installUpdateApps": "安装/更新应用", "installUpdateApps": "安装/更新应用",
"installUpdateSelectedApps": "安装/更新选中的应用", "installUpdateSelectedApps": "安装/更新选中的应用",
"markXSelectedAppsAsUpdated": "是否将选中的 {} 个应用标记为已更新?", "markXSelectedAppsAsUpdated": "是否将选中的 {} 个应用标记为已更新?",
"no": "不要", "no": "",
"yes": "好的", "yes": "",
"markSelectedAppsUpdated": "将选中的应用标记为已更新", "markSelectedAppsUpdated": "将选中的应用标记为已更新",
"pinToTop": "置顶", "pinToTop": "置顶",
"unpinFromTop": "取消置顶", "unpinFromTop": "取消置顶",
@ -142,7 +142,7 @@
"close": "关闭", "close": "关闭",
"share": "分享", "share": "分享",
"appNotFound": "未找到应用", "appNotFound": "未找到应用",
"obtainiumExportHyphenatedLowercase": "obtainium-导出", "obtainiumExportHyphenatedLowercase": "obtainium-export",
"pickAnAPK": "选择一个 APK 文件", "pickAnAPK": "选择一个 APK 文件",
"appHasMoreThanOnePackage": "{} 有多个架构可用:", "appHasMoreThanOnePackage": "{} 有多个架构可用:",
"deviceSupportsXArch": "您的设备支持 {} 架构。", "deviceSupportsXArch": "您的设备支持 {} 架构。",
@ -172,14 +172,15 @@
"versionCorrectionDisabled": "禁用版本号更正(插件似乎未起作用)", "versionCorrectionDisabled": "禁用版本号更正(插件似乎未起作用)",
"unknown": "未知", "unknown": "未知",
"none": "无", "none": "无",
"never": "从", "never": "从",
"latestVersionX": "最新版本:{}", "latestVersionX": "最新版本:{}",
"installedVersionX": "当前版本:{}", "installedVersionX": "当前版本:{}",
"lastUpdateCheckX": "上次更新检查:{}", "lastUpdateCheckX": "上次更新检查:{}",
"remove": "删除", "remove": "删除",
"yesMarkUpdated": "是,标记为已更新", "yesMarkUpdated": "是,标记为已更新",
"fdroid": "F-Droid Official", "fdroid": "F-Droid 官方存储库",
"appIdOrName": "应用 ID 或名称", "appIdOrName": "应用 ID 或名称",
"appId": "App ID",
"appWithIdOrNameNotFound": "未找到符合此 ID 或名称的应用", "appWithIdOrNameNotFound": "未找到符合此 ID 或名称的应用",
"reposHaveMultipleApps": "存储库中可能包含多个应用", "reposHaveMultipleApps": "存储库中可能包含多个应用",
"fdroidThirdPartyRepo": "F-Droid 第三方存储库", "fdroidThirdPartyRepo": "F-Droid 第三方存储库",
@ -193,7 +194,7 @@
"additionalOptions": "附加选项", "additionalOptions": "附加选项",
"disableVersionDetection": "禁用版本检测", "disableVersionDetection": "禁用版本检测",
"noVersionDetectionExplanation": "此选项应该仅用于无法进行版本检测的应用。", "noVersionDetectionExplanation": "此选项应该仅用于无法进行版本检测的应用。",
"downloadingX": "正在下载 {}", "downloadingX": "正在下载{}",
"downloadNotifDescription": "提示应用的下载进度", "downloadNotifDescription": "提示应用的下载进度",
"noAPKFound": "未找到 APK 文件", "noAPKFound": "未找到 APK 文件",
"noVersionDetection": "禁用版本检测", "noVersionDetection": "禁用版本检测",
@ -222,15 +223,17 @@
"versionDetection": "版本检测", "versionDetection": "版本检测",
"standardVersionDetection": "常规版本检测", "standardVersionDetection": "常规版本检测",
"groupByCategory": "按类别分组显示", "groupByCategory": "按类别分组显示",
"autoApkFilterByArch": "如果可能,尝试按 CPU 架构筛选 APK 文件", "autoApkFilterByArch": "如果可能,尝试按设备支持的 CPU 架构筛选 APK 文件",
"overrideSource": "Override Source", "overrideSource": "覆盖来源",
"dontShowAgain": "Don't show this again", "dontShowAgain": "不再显示",
"dontShowTrackOnlyWarnings": "Don't Show the 'Track-Only' Warning", "dontShowTrackOnlyWarnings": "不显示“仅追踪”模式警告",
"dontShowAPKOriginWarnings": "Don't Show APK Origin Warnings", "dontShowAPKOriginWarnings": "不显示 APK 文件来源警告",
"moveNonInstalledAppsToBottom": "Move Non-Installed Apps to Bottom of Apps View", "moveNonInstalledAppsToBottom": "将未安装应用置底",
"gitlabPATLabel": "GitLab Personal Access Token (Enables Search)", "gitlabPATLabel": "GitLab 个人访问令牌(用于搜索)",
"about": "About", "about": "相关文档",
"requiresCredentialsInSettings": "This needs additional credentials (in Settings)", "requiresCredentialsInSettings": "此功能需要额外的凭据(在“设置”中添加)",
"checkOnStart": "启动时进行一次检查",
"tryInferAppIdFromCode": "Try inferring App ID from source code",
"removeAppQuestion": { "removeAppQuestion": {
"one": "是否删除应用?", "one": "是否删除应用?",
"other": "是否删除应用?" "other": "是否删除应用?"

View File

@ -1,6 +1,5 @@
import 'package:easy_localization/easy_localization.dart'; import 'package:easy_localization/easy_localization.dart';
import 'package:html/parser.dart'; import 'package:html/parser.dart';
import 'package:http/http.dart';
import 'package:obtainium/custom_errors.dart'; import 'package:obtainium/custom_errors.dart';
import 'package:obtainium/providers/source_provider.dart'; import 'package:obtainium/providers/source_provider.dart';
@ -20,8 +19,8 @@ class APKCombo extends AppSource {
} }
@override @override
String? tryInferringAppId(String standardUrl, Future<String?> tryInferringAppId(String standardUrl,
{Map<String, dynamic> additionalSettings = const {}}) { {Map<String, dynamic> additionalSettings = const {}}) async {
return Uri.parse(standardUrl).pathSegments.last; return Uri.parse(standardUrl).pathSegments.last;
} }
@ -84,8 +83,7 @@ class APKCombo extends AppSource {
String standardUrl, String standardUrl,
Map<String, dynamic> additionalSettings, Map<String, dynamic> additionalSettings,
) async { ) async {
String appId = tryInferringAppId(standardUrl)!; String appId = (await tryInferringAppId(standardUrl))!;
String host = Uri.parse(standardUrl).host;
var preres = await sourceRequest(standardUrl); var preres = await sourceRequest(standardUrl);
if (preres.statusCode != 200) { if (preres.statusCode != 200) {
throw getObtainiumHttpError(preres); throw getObtainiumHttpError(preres);

View File

@ -1,6 +1,5 @@
import 'package:easy_localization/easy_localization.dart'; import 'package:easy_localization/easy_localization.dart';
import 'package:html/parser.dart'; import 'package:html/parser.dart';
import 'package:http/http.dart';
import 'package:obtainium/custom_errors.dart'; import 'package:obtainium/custom_errors.dart';
import 'package:obtainium/providers/source_provider.dart'; import 'package:obtainium/providers/source_provider.dart';
@ -25,8 +24,8 @@ class APKPure extends AppSource {
} }
@override @override
String? tryInferringAppId(String standardUrl, Future<String?> tryInferringAppId(String standardUrl,
{Map<String, dynamic> additionalSettings = const {}}) { {Map<String, dynamic> additionalSettings = const {}}) async {
return Uri.parse(standardUrl).pathSegments.last; return Uri.parse(standardUrl).pathSegments.last;
} }
@ -35,7 +34,7 @@ class APKPure extends AppSource {
String standardUrl, String standardUrl,
Map<String, dynamic> additionalSettings, Map<String, dynamic> additionalSettings,
) async { ) async {
String appId = tryInferringAppId(standardUrl)!; String appId = (await tryInferringAppId(standardUrl))!;
String host = Uri.parse(standardUrl).host; String host = Uri.parse(standardUrl).host;
var res = await sourceRequest('$standardUrl/download'); var res = await sourceRequest('$standardUrl/download');
if (res.statusCode == 200) { if (res.statusCode == 200) {
@ -57,9 +56,9 @@ class APKPure extends AppSource {
} catch (err) { } catch (err) {
// ignore // ignore
} }
String type = html.querySelector('a.info-tag')?.text.trim() ?? 'APK';
List<MapEntry<String, String>> apkUrls = [ List<MapEntry<String, String>> apkUrls = [
MapEntry('$appId.apk', 'https://d.$host/b/APK/$appId?version=latest') MapEntry('$appId.apk', 'https://d.$host/b/$type/$appId?version=latest')
]; ];
String author = html String author = html
.querySelector('span.info-sdk') .querySelector('span.info-sdk')

View File

@ -1,6 +1,4 @@
import 'dart:convert';
import 'package:easy_localization/easy_localization.dart'; import 'package:easy_localization/easy_localization.dart';
import 'package:http/http.dart';
import 'package:obtainium/app_sources/github.dart'; import 'package:obtainium/app_sources/github.dart';
import 'package:obtainium/components/generated_form.dart'; import 'package:obtainium/components/generated_form.dart';
import 'package:obtainium/custom_errors.dart'; import 'package:obtainium/custom_errors.dart';
@ -9,7 +7,6 @@ import 'package:obtainium/providers/source_provider.dart';
class Codeberg extends AppSource { class Codeberg extends AppSource {
Codeberg() { Codeberg() {
host = 'codeberg.org'; host = 'codeberg.org';
overrideEligible = true;
additionalSourceSpecificSettingFormItems = []; additionalSourceSpecificSettingFormItems = [];
@ -58,10 +55,10 @@ class Codeberg extends AppSource {
String standardUrl, String standardUrl,
Map<String, dynamic> additionalSettings, Map<String, dynamic> additionalSettings,
) async { ) async {
return gh.getLatestAPKDetailsCommon( return await gh.getLatestAPKDetailsCommon2(standardUrl, additionalSettings,
'https://$host/api/v1/repos${standardUrl.substring('https://$host'.length)}/releases?per_page=100', (bool useTagUrl) async {
standardUrl, return 'https://$host/api/v1/repos${standardUrl.substring('https://$host'.length)}/${useTagUrl ? 'tags' : 'releases'}?per_page=100';
additionalSettings); }, null);
} }
AppNames getAppNames(String standardUrl) { AppNames getAppNames(String standardUrl) {

View File

@ -31,8 +31,8 @@ class FDroid extends AppSource {
} }
@override @override
String? tryInferringAppId(String standardUrl, Future<String?> tryInferringAppId(String standardUrl,
{Map<String, dynamic> additionalSettings = const {}}) { {Map<String, dynamic> additionalSettings = const {}}) async {
return Uri.parse(standardUrl).pathSegments.last; return Uri.parse(standardUrl).pathSegments.last;
} }
@ -63,7 +63,7 @@ class FDroid extends AppSource {
String standardUrl, String standardUrl,
Map<String, dynamic> additionalSettings, Map<String, dynamic> additionalSettings,
) async { ) async {
String? appId = tryInferringAppId(standardUrl); String? appId = await tryInferringAppId(standardUrl);
String host = Uri.parse(standardUrl).host; String host = Uri.parse(standardUrl).host;
return getAPKUrlsFromFDroidPackagesAPIResponse( return getAPKUrlsFromFDroidPackagesAPIResponse(
await sourceRequest('https://$host/api/v1/packages/$appId'), await sourceRequest('https://$host/api/v1/packages/$appId'),

View File

@ -1,6 +1,5 @@
import 'package:easy_localization/easy_localization.dart'; import 'package:easy_localization/easy_localization.dart';
import 'package:html/parser.dart'; import 'package:html/parser.dart';
import 'package:http/http.dart';
import 'package:obtainium/components/generated_form.dart'; import 'package:obtainium/components/generated_form.dart';
import 'package:obtainium/custom_errors.dart'; import 'package:obtainium/custom_errors.dart';
import 'package:obtainium/providers/source_provider.dart'; import 'package:obtainium/providers/source_provider.dart';
@ -8,7 +7,6 @@ import 'package:obtainium/providers/source_provider.dart';
class FDroidRepo extends AppSource { class FDroidRepo extends AppSource {
FDroidRepo() { FDroidRepo() {
name = tr('fdroidThirdPartyRepo'); name = tr('fdroidThirdPartyRepo');
overrideEligible = true;
additionalSourceAppSpecificSettingFormItems = [ additionalSourceAppSpecificSettingFormItems = [
[ [

View File

@ -6,6 +6,7 @@ import 'package:obtainium/app_sources/html.dart';
import 'package:obtainium/components/generated_form.dart'; import 'package:obtainium/components/generated_form.dart';
import 'package:obtainium/custom_errors.dart'; import 'package:obtainium/custom_errors.dart';
import 'package:obtainium/providers/apps_provider.dart'; import 'package:obtainium/providers/apps_provider.dart';
import 'package:obtainium/providers/logs_provider.dart';
import 'package:obtainium/providers/settings_provider.dart'; import 'package:obtainium/providers/settings_provider.dart';
import 'package:obtainium/providers/source_provider.dart'; import 'package:obtainium/providers/source_provider.dart';
import 'package:url_launcher/url_launcher_string.dart'; import 'package:url_launcher/url_launcher_string.dart';
@ -13,7 +14,7 @@ import 'package:url_launcher/url_launcher_string.dart';
class GitHub extends AppSource { class GitHub extends AppSource {
GitHub() { GitHub() {
host = 'github.com'; host = 'github.com';
overrideEligible = true; appIdInferIsOptional = true;
additionalSourceSpecificSettingFormItems = [ additionalSourceSpecificSettingFormItems = [
GeneratedFormTextField('github-creds', GeneratedFormTextField('github-creds',
@ -80,6 +81,44 @@ class GitHub extends AppSource {
canSearch = true; canSearch = true;
} }
@override
Future<String?> tryInferringAppId(String standardUrl,
{Map<String, dynamic> additionalSettings = const {}}) async {
const possibleBuildGradleLocations = [
'/app/build.gradle',
'android/app/build.gradle',
'src/app/build.gradle'
];
for (var path in possibleBuildGradleLocations) {
try {
var res = await sourceRequest(
'${await convertStandardUrlToAPIUrl(standardUrl)}/contents/$path');
if (res.statusCode == 200) {
try {
var body = jsonDecode(res.body);
var appId = utf8
.decode(base64
.decode(body['content'].toString().split('\n').join('')))
.split('\n')
.map((e) => e.trim())
.where((l) => l.startsWith('applicationId "'))
.first
.split('"')[1];
if (appId.isNotEmpty) {
return appId;
}
} catch (err) {
LogsProvider().add(
'Error parsing build.gradle from ${res.request!.url.toString()}: ${err.toString()}');
}
}
} catch (err) {
// Ignore - ID will be extracted from the APK
}
}
return null;
}
@override @override
String sourceSpecificStandardizeURL(String url) { String sourceSpecificStandardizeURL(String url) {
RegExp standardUrlRegEx = RegExp('^https?://$host/[^/]+/[^/]+'); RegExp standardUrlRegEx = RegExp('^https?://$host/[^/]+/[^/]+');
@ -98,6 +137,12 @@ class GitHub extends AppSource {
return creds != null && creds.isNotEmpty ? '$creds@' : ''; return creds != null && creds.isNotEmpty ? '$creds@' : '';
} }
Future<String> getAPIHost() async =>
'https://${await getCredentialPrefixIfAny()}api.$host';
Future<String> convertStandardUrlToAPIUrl(String standardUrl) async =>
'${await getAPIHost()}/repos${standardUrl.substring('https://$host'.length)}';
@override @override
String? changeLogPageFromStandardUrl(String standardUrl) => String? changeLogPageFromStandardUrl(String standardUrl) =>
'$standardUrl/releases'; '$standardUrl/releases';
@ -143,15 +188,17 @@ class GitHub extends AppSource {
} else if (b == null) { } else if (b == null) {
return 1; return 1;
} else { } else {
var stdFormats = findStandardFormatsForVersion(a['tag_name'], true) var nameA = a['tag_name'] ?? a['name'];
.intersection(findStandardFormatsForVersion(b['tag_name'], true)); var nameB = b['tag_name'] ?? b['name'];
var stdFormats = findStandardFormatsForVersion(nameA, true)
.intersection(findStandardFormatsForVersion(nameB, true));
if (stdFormats.isNotEmpty) { if (stdFormats.isNotEmpty) {
var reg = RegExp(stdFormats.first); var reg = RegExp(stdFormats.first);
var matchA = reg.firstMatch(a['tag_name']); var matchA = reg.firstMatch(nameA);
var matchB = reg.firstMatch(b['tag_name']); var matchB = reg.firstMatch(nameB);
return compareAlphaNumeric( return compareAlphaNumeric(
(a['tag_name'] as String).substring(matchA!.start, matchA.end), (nameA as String).substring(matchA!.start, matchA.end),
(b['tag_name'] as String).substring(matchB!.start, matchB.end)); (nameB as String).substring(matchB!.start, matchB.end));
} else { } else {
return getReleaseDateFromRelease(a)! return getReleaseDateFromRelease(a)!
.compareTo(getReleaseDateFromRelease(b)!); .compareTo(getReleaseDateFromRelease(b)!);
@ -191,7 +238,7 @@ class GitHub extends AppSource {
if (targetRelease == null) { if (targetRelease == null) {
throw NoReleasesError(); throw NoReleasesError();
} }
String? version = targetRelease['tag_name']; String? version = targetRelease['tag_name'] ?? targetRelease['name'];
DateTime? releaseDate = getReleaseDateFromRelease(targetRelease); DateTime? releaseDate = getReleaseDateFromRelease(targetRelease);
if (version == null) { if (version == null) {
throw NoVersionError(); throw NoVersionError();
@ -211,15 +258,35 @@ class GitHub extends AppSource {
} }
} }
getLatestAPKDetailsCommon2(
String standardUrl,
Map<String, dynamic> additionalSettings,
Future<String> Function(bool) reqUrlGenerator,
dynamic Function(Response)? onHttpErrorCode) async {
try {
return await getLatestAPKDetailsCommon(
await reqUrlGenerator(false), standardUrl, additionalSettings,
onHttpErrorCode: onHttpErrorCode);
} catch (err) {
if (err is NoReleasesError && additionalSettings['trackOnly'] == true) {
return await getLatestAPKDetailsCommon(
await reqUrlGenerator(true), standardUrl, additionalSettings,
onHttpErrorCode: onHttpErrorCode);
} else {
rethrow;
}
}
}
@override @override
Future<APKDetails> getLatestAPKDetails( Future<APKDetails> getLatestAPKDetails(
String standardUrl, String standardUrl,
Map<String, dynamic> additionalSettings, Map<String, dynamic> additionalSettings,
) async { ) async {
return getLatestAPKDetailsCommon( return await getLatestAPKDetailsCommon2(standardUrl, additionalSettings,
'https://${await getCredentialPrefixIfAny()}api.$host/repos${standardUrl.substring('https://$host'.length)}/releases?per_page=100', (bool useTagUrl) async {
standardUrl, return '${await convertStandardUrlToAPIUrl(standardUrl)}/${useTagUrl ? 'tags' : 'releases'}?per_page=100';
additionalSettings, onHttpErrorCode: (Response res) { }, (Response res) {
rateLimitErrorCheck(res); rateLimitErrorCheck(res);
}); });
} }
@ -260,7 +327,7 @@ class GitHub extends AppSource {
Future<Map<String, List<String>>> search(String query) async { Future<Map<String, List<String>>> search(String query) async {
return searchCommon( return searchCommon(
query, query,
'https://${await getCredentialPrefixIfAny()}api.$host/search/repositories?q=${Uri.encodeQueryComponent(query)}&per_page=100', '${await getAPIHost()}/search/repositories?q=${Uri.encodeQueryComponent(query)}&per_page=100',
'items', onHttpErrorCode: (Response res) { 'items', onHttpErrorCode: (Response res) {
rateLimitErrorCheck(res); rateLimitErrorCheck(res);
}); });

View File

@ -14,7 +14,6 @@ import 'package:url_launcher/url_launcher_string.dart';
class GitLab extends AppSource { class GitLab extends AppSource {
GitLab() { GitLab() {
host = 'gitlab.com'; host = 'gitlab.com';
overrideEligible = true;
canSearch = true; canSearch = true;
additionalSourceSpecificSettingFormItems = [ additionalSourceSpecificSettingFormItems = [
@ -83,12 +82,12 @@ class GitLab extends AppSource {
} }
var json = jsonDecode(res.body) as List<dynamic>; var json = jsonDecode(res.body) as List<dynamic>;
Map<String, List<String>> results = {}; Map<String, List<String>> results = {};
json.forEach((element) { for (var element in json) {
results['https://$host/${element['path_with_namespace']}'] = [ results['https://$host/${element['path_with_namespace']}'] = [
element['name_with_namespace'], element['name_with_namespace'],
element['description'] ?? tr('noDescription') element['description'] ?? tr('noDescription')
]; ];
}); }
return results; return results;
} }

View File

@ -85,9 +85,12 @@ bool _isNumeric(String s) {
} }
class HTML extends AppSource { class HTML extends AppSource {
HTML() { @override
overrideEligible = true; // TODO: implement requestHeaders choice, hardcoded for now
} Map<String, String>? get requestHeaders => {
"User-Agent":
"Mozilla/5.0 (X11; Linux x86_64; rv:60.0) Gecko/20100101 Firefox/81.0"
};
@override @override
String sourceSpecificStandardizeURL(String url) { String sourceSpecificStandardizeURL(String url) {

View File

@ -1,4 +1,3 @@
import 'package:http/http.dart';
import 'package:obtainium/app_sources/fdroid.dart'; import 'package:obtainium/app_sources/fdroid.dart';
import 'package:obtainium/custom_errors.dart'; import 'package:obtainium/custom_errors.dart';
import 'package:obtainium/providers/source_provider.dart'; import 'package:obtainium/providers/source_provider.dart';
@ -19,8 +18,8 @@ class IzzyOnDroid extends AppSource {
} }
@override @override
String? tryInferringAppId(String standardUrl, Future<String?> tryInferringAppId(String standardUrl,
{Map<String, dynamic> additionalSettings = const {}}) { {Map<String, dynamic> additionalSettings = const {}}) async {
return FDroid().tryInferringAppId(standardUrl); return FDroid().tryInferringAppId(standardUrl);
} }
@ -29,7 +28,7 @@ class IzzyOnDroid extends AppSource {
String standardUrl, String standardUrl,
Map<String, dynamic> additionalSettings, Map<String, dynamic> additionalSettings,
) async { ) async {
String? appId = tryInferringAppId(standardUrl); String? appId = await tryInferringAppId(standardUrl);
return FDroid().getAPKUrlsFromFDroidPackagesAPIResponse( return FDroid().getAPKUrlsFromFDroidPackagesAPIResponse(
await sourceRequest( await sourceRequest(
'https://apt.izzysoft.de/fdroid/api/v1/packages/$appId'), 'https://apt.izzysoft.de/fdroid/api/v1/packages/$appId'),

View File

@ -6,11 +6,9 @@ import 'package:obtainium/providers/source_provider.dart';
class Jenkins extends AppSource { class Jenkins extends AppSource {
Jenkins() { Jenkins() {
overrideEligible = true;
overrideVersionDetectionFormDefault('releaseDateAsVersion', true); overrideVersionDetectionFormDefault('releaseDateAsVersion', true);
} }
@override
String trimJobUrl(String url) { String trimJobUrl(String url) {
RegExp standardUrlRegEx = RegExp('.*/job/[^/]+'); RegExp standardUrlRegEx = RegExp('.*/job/[^/]+');
RegExpMatch? match = standardUrlRegEx.firstMatch(url); RegExpMatch? match = standardUrlRegEx.firstMatch(url);

View File

@ -6,7 +6,6 @@ import 'package:obtainium/providers/source_provider.dart';
class SourceForge extends AppSource { class SourceForge extends AppSource {
SourceForge() { SourceForge() {
host = 'sourceforge.net'; host = 'sourceforge.net';
overrideEligible = true;
} }
@override @override

View File

@ -1,6 +1,5 @@
import 'package:html/parser.dart'; import 'package:html/parser.dart';
import 'package:http/http.dart'; import 'package:http/http.dart';
import 'package:obtainium/app_sources/github.dart';
import 'package:obtainium/app_sources/html.dart'; import 'package:obtainium/app_sources/html.dart';
import 'package:obtainium/custom_errors.dart'; import 'package:obtainium/custom_errors.dart';
import 'package:obtainium/providers/source_provider.dart'; import 'package:obtainium/providers/source_provider.dart';
@ -10,7 +9,6 @@ import 'package:easy_localization/easy_localization.dart';
class SourceHut extends AppSource { class SourceHut extends AppSource {
SourceHut() { SourceHut() {
host = 'git.sr.ht'; host = 'git.sr.ht';
overrideEligible = true;
additionalSourceAppSpecificSettingFormItems = [ additionalSourceAppSpecificSettingFormItems = [
[ [
@ -58,11 +56,19 @@ class SourceHut extends AppSource {
throw NoVersionError(); throw NoVersionError();
} }
String? releaseDateString = entry.querySelector('pubDate')?.innerHtml; String? releaseDateString = entry.querySelector('pubDate')?.innerHtml;
var link = entry.querySelector('link');
String releasePage = '$standardUrl/refs/$version'; String releasePage = '$standardUrl/refs/$version';
DateTime? releaseDate = releaseDateString != null DateTime? releaseDate;
? DateFormat('EEE, dd MMM yyyy HH:mm:ss Z').parse(releaseDateString) try {
releaseDate = releaseDateString != null
? DateFormat('E, dd MMM yyyy HH:mm:ss Z').parse(releaseDateString)
: null; : null;
releaseDate = releaseDateString != null
? DateFormat('EEE, dd MMM yyyy HH:mm:ss Z')
.parse(releaseDateString)
: null;
} catch (e) {
// ignore
}
var res2 = await sourceRequest(releasePage); var res2 = await sourceRequest(releasePage);
List<MapEntry<String, String>> apkUrls = []; List<MapEntry<String, String>> apkUrls = [];
if (res2.statusCode == 200) { if (res2.statusCode == 200) {

View File

@ -1,6 +1,5 @@
import 'package:html/parser.dart'; import 'package:html/parser.dart';
import 'package:http/http.dart'; import 'package:http/http.dart';
import 'package:obtainium/app_sources/html.dart';
import 'package:obtainium/custom_errors.dart'; import 'package:obtainium/custom_errors.dart';
import 'package:obtainium/providers/source_provider.dart'; import 'package:obtainium/providers/source_provider.dart';

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.13.0'; const String currentVersion = '0.13.7';
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
@ -37,6 +37,7 @@ List<MapEntry<Locale, String>> supportedLocales = const [
MapEntry(Locale('fa'), 'فارسی'), MapEntry(Locale('fa'), 'فارسی'),
MapEntry(Locale('fr'), 'Français'), MapEntry(Locale('fr'), 'Français'),
MapEntry(Locale('es'), 'Español'), MapEntry(Locale('es'), 'Español'),
MapEntry(Locale('pl'), 'Polski'),
]; ];
const fallbackLocale = Locale('en'); const fallbackLocale = Locale('en');
const localeDir = 'assets/translations'; const localeDir = 'assets/translations';

View File

@ -33,6 +33,7 @@ class _AddAppPageState extends State<AddAppPage> {
AppSource? pickedSource; AppSource? pickedSource;
Map<String, dynamic> additionalSettings = {}; Map<String, dynamic> additionalSettings = {};
bool additionalSettingsValid = true; bool additionalSettingsValid = true;
bool inferAppIdIfOptional = true;
List<String> pickedCategories = []; List<String> pickedCategories = [];
int searchnum = 0; int searchnum = 0;
SourceProvider sourceProvider = SourceProvider(); SourceProvider sourceProvider = SourceProvider();
@ -78,6 +79,7 @@ class _AddAppPageState extends State<AddAppPage> {
additionalSettingsValid = source != null additionalSettingsValid = source != null
? !sourceProvider.ifRequiredAppSpecificSettingsExist(source) ? !sourceProvider.ifRequiredAppSpecificSettingsExist(source)
: true; : true;
inferAppIdIfOptional = true;
} }
}); });
} }
@ -147,7 +149,8 @@ class _AddAppPageState extends State<AddAppPage> {
app = await sourceProvider.getApp( app = await sourceProvider.getApp(
pickedSource!, userInput, additionalSettings, pickedSource!, userInput, additionalSettings,
trackOnlyOverride: trackOnly, trackOnlyOverride: trackOnly,
overrideSource: pickedSourceOverride); overrideSource: pickedSourceOverride,
inferAppIdIfOptional: inferAppIdIfOptional);
// 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) && if (sourceProvider.isTempId(app) &&
app.additionalSettings['trackOnly'] != true) { app.additionalSettings['trackOnly'] != true) {
@ -159,9 +162,16 @@ class _AddAppPageState extends State<AddAppPage> {
app.preferredApkIndex = app.preferredApkIndex =
app.apkUrls.map((e) => e.value).toList().indexOf(apkUrl.value); app.apkUrls.map((e) => e.value).toList().indexOf(apkUrl.value);
// ignore: use_build_context_synchronously // ignore: use_build_context_synchronously
var downloadedApk = await appsProvider.downloadApp( var downloadedArtifact = await appsProvider.downloadApp(
app, globalNavigatorKey.currentContext); app, globalNavigatorKey.currentContext);
app.id = downloadedApk.appId; DownloadedApk? downloadedFile;
DownloadedXApkDir? downloadedDir;
if (downloadedArtifact is DownloadedApk) {
downloadedFile = downloadedArtifact;
} else {
downloadedDir = downloadedArtifact as DownloadedXApkDir;
}
app.id = downloadedFile?.appId ?? downloadedDir!.appId;
} }
if (appsProvider.apps.containsKey(app.id)) { if (appsProvider.apps.containsKey(app.id)) {
throw ObtainiumError(tr('appAlreadyAdded')); throw ObtainiumError(tr('appAlreadyAdded'));
@ -276,6 +286,9 @@ class _AddAppPageState extends State<AddAppPage> {
} }
si++; si++;
} }
if (res.isEmpty) {
throw ObtainiumError(tr('noResults'));
}
List<String>? selectedUrls = res.isEmpty List<String>? selectedUrls = res.isEmpty
? [] ? []
// ignore: use_build_context_synchronously // ignore: use_build_context_synchronously
@ -311,10 +324,8 @@ class _AddAppPageState extends State<AddAppPage> {
'overrideSource', 'overrideSource',
defaultValue: HTML().runtimeType.toString(), defaultValue: HTML().runtimeType.toString(),
[ [
...sourceProvider.sources ...sourceProvider.sources.map(
.where((s) => s.overrideEligible) (s) => MapEntry(s.runtimeType.toString(), s.name))
.map((s) =>
MapEntry(s.runtimeType.toString(), s.name))
], ],
label: tr('overrideSource')) label: tr('overrideSource'))
] ]
@ -370,7 +381,9 @@ class _AddAppPageState extends State<AddAppPage> {
const SizedBox( const SizedBox(
width: 16, width: 16,
), ),
ElevatedButton( searching
? const CircularProgressIndicator()
: ElevatedButton(
onPressed: searchQuery.isEmpty || doingSomething onPressed: searchQuery.isEmpty || doingSomething
? null ? null
: () { : () {
@ -418,6 +431,23 @@ class _AddAppPageState extends State<AddAppPage> {
}), }),
], ],
), ),
if (pickedSource != null && pickedSource!.appIdInferIsOptional)
GeneratedForm(
key: const Key('inferAppIdIfOptional'),
items: [
[
GeneratedFormSwitch('inferAppIdIfOptional',
label: tr('tryInferAppIdFromCode'),
defaultValue: inferAppIdIfOptional)
]
],
onValueChanges: (values, valid, isBuilding) {
if (!isBuilding) {
setState(() {
inferAppIdIfOptional = values['inferAppIdIfOptional'];
});
}
}),
], ],
); );

View File

@ -32,6 +32,7 @@ class _AppPageState extends State<AppPage> {
getUpdate(String id) { getUpdate(String id) {
appsProvider.checkUpdate(id).catchError((e) { appsProvider.checkUpdate(id).catchError((e) {
showError(e, context); showError(e, context);
return null;
}); });
} }
@ -444,7 +445,9 @@ class _AppPageState extends State<AppPage> {
Padding( Padding(
padding: const EdgeInsets.fromLTRB(0, 8, 0, 0), padding: const EdgeInsets.fromLTRB(0, 8, 0, 0),
child: LinearProgressIndicator( child: LinearProgressIndicator(
value: app!.downloadProgress! / 100)) value: app!.downloadProgress! >= 0
? app.downloadProgress! / 100
: null))
], ],
)); ));

View File

@ -52,14 +52,37 @@ class AppsPageState extends State<AppsPage> {
} }
} }
final GlobalKey<RefreshIndicatorState> _refreshIndicatorKey =
GlobalKey<RefreshIndicatorState>();
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
var appsProvider = context.watch<AppsProvider>(); var appsProvider = context.watch<AppsProvider>();
var settingsProvider = context.watch<SettingsProvider>(); var settingsProvider = context.watch<SettingsProvider>();
var sourceProvider = SourceProvider(); var sourceProvider = SourceProvider();
var listedApps = appsProvider.getAppValues().toList(); var listedApps = appsProvider.getAppValues().toList();
var currentFilterIsUpdatesOnly =
filter.isIdenticalTo(updatesOnlyFilter, settingsProvider); refresh() {
HapticFeedback.lightImpact();
setState(() {
refreshingSince = DateTime.now();
});
return appsProvider.checkUpdates().catchError((e) {
showError(e, context);
return <App>[];
}).whenComplete(() {
setState(() {
refreshingSince = null;
});
});
}
if (!appsProvider.loadingApps &&
appsProvider.apps.isNotEmpty &&
settingsProvider.checkJustStarted() &&
settingsProvider.checkOnStart) {
_refreshIndicatorKey.currentState?.show();
}
selectedAppIds = selectedAppIds selectedAppIds = selectedAppIds
.where((element) => listedApps.map((e) => e.app.id).contains(element)) .where((element) => listedApps.map((e) => e.app.id).contains(element))
@ -104,6 +127,11 @@ class AppsPageState extends State<AppsPage> {
} }
} }
} }
if (filter.idFilter.isNotEmpty) {
if (!app.app.id.contains(filter.idFilter)) {
return false;
}
}
if (filter.categoryFilter.isNotEmpty && if (filter.categoryFilter.isNotEmpty &&
filter.categoryFilter filter.categoryFilter
.intersection(app.app.categories.toSet()) .intersection(app.app.categories.toSet())
@ -294,28 +322,28 @@ class AppsPageState extends State<AppsPage> {
getLoadingWidgets() { getLoadingWidgets() {
return [ return [
if (appsProvider.loadingApps || listedApps.isEmpty) if (listedApps.isEmpty)
SliverFillRemaining( SliverFillRemaining(
child: Center( child: Center(
child: appsProvider.loadingApps child: Text(
? const CircularProgressIndicator() appsProvider.apps.isEmpty ? tr('noApps') : tr('noAppsForFilter'),
: Text(
appsProvider.apps.isEmpty
? tr('noApps')
: tr('noAppsForFilter'),
style: Theme.of(context).textTheme.headlineMedium, style: Theme.of(context).textTheme.headlineMedium,
textAlign: TextAlign.center, textAlign: TextAlign.center,
))), ))),
if (refreshingSince != null) if (refreshingSince != null || appsProvider.loadingApps)
SliverToBoxAdapter( SliverToBoxAdapter(
child: LinearProgressIndicator( child: LinearProgressIndicator(
value: appsProvider value: appsProvider.loadingApps
? null
: appsProvider
.getAppValues() .getAppValues()
.where((element) => !(element.app.lastUpdateCheck .where((element) => !(element.app.lastUpdateCheck
?.isBefore(refreshingSince!) ?? ?.isBefore(refreshingSince!) ??
true)) true))
.length / .length /
appsProvider.apps.length, (appsProvider.apps.isNotEmpty
? appsProvider.apps.length
: 1),
), ),
) )
]; ];
@ -355,6 +383,7 @@ class AppsPageState extends State<AppsPage> {
[listedApps[appIndex].app.id], [listedApps[appIndex].app.id],
globalNavigatorKey.currentContext).catchError((e) { globalNavigatorKey.currentContext).catchError((e) {
showError(e, context); showError(e, context);
return <String>[];
}); });
}, },
icon: Icon( icon: Icon(
@ -417,7 +446,9 @@ class AppsPageState extends State<AppsPage> {
width: 10, width: 10,
) )
: const SizedBox.shrink(), : const SizedBox.shrink(),
Column( GestureDetector(
onTap: showChangesFn,
child: Column(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.end, crossAxisAlignment: CrossAxisAlignment.end,
children: [ children: [
@ -425,29 +456,25 @@ class AppsPageState extends State<AppsPage> {
Container( Container(
constraints: BoxConstraints( constraints: BoxConstraints(
maxWidth: MediaQuery.of(context).size.width / 4), maxWidth: MediaQuery.of(context).size.width / 4),
child: Text( child: Text(getVersionText(index),
getVersionText(index),
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
textAlign: TextAlign.end, textAlign: TextAlign.end)),
)),
]), ]),
Row( Row(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
GestureDetector( Text(
onTap: showChangesFn,
child: Text(
getChangesButtonString(index, showChangesFn != null), getChangesButtonString(index, showChangesFn != null),
style: TextStyle( style: TextStyle(
fontStyle: FontStyle.italic, fontStyle: FontStyle.italic,
decoration: showChangesFn != null decoration: showChangesFn != null
? TextDecoration.underline ? TextDecoration.underline
: TextDecoration.none), : TextDecoration.none),
)) )
], ],
), ),
], ],
) ))
], ],
); );
@ -515,10 +542,21 @@ class AppsPageState extends State<AppsPage> {
? FontWeight.bold ? FontWeight.bold
: FontWeight.normal)), : FontWeight.normal)),
trailing: listedApps[index].downloadProgress != null trailing: listedApps[index].downloadProgress != null
? Text(tr('percentProgress', args: [ ? SizedBox(
listedApps[index].downloadProgress?.toInt().toString() ?? width: 90,
'100' child: Text(
])) listedApps[index].downloadProgress! >= 0
? tr('percentProgress', args: [
listedApps[index]
.downloadProgress!
.toInt()
.toString()
])
: tr('pleaseWait'),
textAlign: (listedApps[index].downloadProgress! >= 0)
? TextAlign.start
: TextAlign.end,
))
: trailingRow, : trailingRow,
onTap: () { onTap: () {
if (selectedAppIds.isNotEmpty) { if (selectedAppIds.isNotEmpty) {
@ -651,6 +689,7 @@ class AppsPageState extends State<AppsPage> {
settingsProvider: settingsProvider) settingsProvider: settingsProvider)
.catchError((e) { .catchError((e) {
showError(e, context); showError(e, context);
return <String>[];
}); });
} }
}); });
@ -846,10 +885,17 @@ class AppsPageState extends State<AppsPage> {
}); });
} }
getMainBottomButtonsRow() { getMainBottomButtons() {
return Row( return [
mainAxisAlignment: MainAxisAlignment.spaceEvenly, IconButton(
children: [ visualDensity: VisualDensity.compact,
onPressed: getMassObtainFunction(),
tooltip: selectedAppIds.isEmpty
? tr('installUpdateApps')
: tr('installUpdateSelectedApps'),
icon: const Icon(
Icons.file_download_outlined,
)),
IconButton( IconButton(
visualDensity: VisualDensity.compact, visualDensity: VisualDensity.compact,
onPressed: selectedAppIds.isEmpty onPressed: selectedAppIds.isEmpty
@ -861,15 +907,6 @@ class AppsPageState extends State<AppsPage> {
tooltip: tr('removeSelectedApps'), tooltip: tr('removeSelectedApps'),
icon: const Icon(Icons.delete_outline_outlined), icon: const Icon(Icons.delete_outline_outlined),
), ),
IconButton(
visualDensity: VisualDensity.compact,
onPressed: getMassObtainFunction(),
tooltip: selectedAppIds.isEmpty
? tr('installUpdateApps')
: tr('installUpdateSelectedApps'),
icon: const Icon(
Icons.file_download_outlined,
)),
IconButton( IconButton(
visualDensity: VisualDensity.compact, visualDensity: VisualDensity.compact,
onPressed: selectedAppIds.isEmpty ? null : launchCategorizeDialog(), onPressed: selectedAppIds.isEmpty ? null : launchCategorizeDialog(),
@ -882,8 +919,7 @@ class AppsPageState extends State<AppsPage> {
tooltip: tr('more'), tooltip: tr('more'),
icon: const Icon(Icons.more_horiz), icon: const Icon(Icons.more_horiz),
), ),
], ];
);
} }
showFilterDialog() async { showFilterDialog() async {
@ -905,6 +941,12 @@ class AppsPageState extends State<AppsPage> {
required: false, required: false,
defaultValue: vals['author']) defaultValue: vals['author'])
], ],
[
GeneratedFormTextField('appId',
label: tr('appId'),
required: false,
defaultValue: vals['appId'])
],
[ [
GeneratedFormSwitch('upToDateApps', GeneratedFormSwitch('upToDateApps',
label: tr('upToDateApps'), label: tr('upToDateApps'),
@ -950,50 +992,33 @@ class AppsPageState extends State<AppsPage> {
} }
getFilterButtonsRow() { getFilterButtonsRow() {
var isFilterOff = filter.isIdenticalTo(neutralFilter, settingsProvider);
return Row( return Row(
children: [ children: [
getSelectAllButton(), getSelectAllButton(),
const VerticalDivider(),
Expanded(
child: SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: getMainBottomButtonsRow())),
const VerticalDivider(),
IconButton( IconButton(
visualDensity: VisualDensity.compact, color: Theme.of(context).colorScheme.primary,
onPressed: () { style: const ButtonStyle(visualDensity: VisualDensity.compact),
tooltip: isFilterOff ? tr('filter') : tr('filterActive'),
onPressed: isFilterOff
? showFilterDialog
: () {
setState(() { setState(() {
if (currentFilterIsUpdatesOnly) {
filter = AppsFilter(); filter = AppsFilter();
} else {
filter = updatesOnlyFilter;
}
}); });
}, },
tooltip: currentFilterIsUpdatesOnly icon: Icon(isFilterOff
? tr('removeOutdatedFilter') ? Icons.filter_list_rounded
: tr('showOutdatedOnly'), : Icons.filter_list_off_rounded)),
icon: Icon( const SizedBox(
currentFilterIsUpdatesOnly width: 10,
? Icons.update_disabled_rounded
: Icons.update_rounded,
color: Theme.of(context).colorScheme.primary,
), ),
), const VerticalDivider(),
TextButton.icon( Expanded(
style: const ButtonStyle(visualDensity: VisualDensity.compact), child: Row(
label: Text( mainAxisAlignment: MainAxisAlignment.spaceEvenly,
filter.isIdenticalTo(neutralFilter, settingsProvider) children: getMainBottomButtons(),
? tr('filter') )),
: tr('filterActive'),
style: TextStyle(
fontWeight:
filter.isIdenticalTo(neutralFilter, settingsProvider)
? FontWeight.normal
: FontWeight.bold),
),
onPressed: showFilterDialog,
icon: const Icon(Icons.filter_list_rounded))
], ],
); );
} }
@ -1017,19 +1042,8 @@ class AppsPageState extends State<AppsPage> {
return Scaffold( return Scaffold(
backgroundColor: Theme.of(context).colorScheme.surface, backgroundColor: Theme.of(context).colorScheme.surface,
body: RefreshIndicator( body: RefreshIndicator(
onRefresh: () { key: _refreshIndicatorKey,
HapticFeedback.lightImpact(); onRefresh: refresh,
setState(() {
refreshingSince = DateTime.now();
});
return appsProvider.checkUpdates().catchError((e) {
showError(e, context);
}).whenComplete(() {
setState(() {
refreshingSince = null;
});
});
},
child: CustomScrollView(slivers: <Widget>[ child: CustomScrollView(slivers: <Widget>[
CustomAppBar(title: tr('appsString')), CustomAppBar(title: tr('appsString')),
...getLoadingWidgets(), ...getLoadingWidgets(),
@ -1047,6 +1061,7 @@ class AppsPageState extends State<AppsPage> {
class AppsFilter { class AppsFilter {
late String nameFilter; late String nameFilter;
late String authorFilter; late String authorFilter;
late String idFilter;
late bool includeUptodate; late bool includeUptodate;
late bool includeNonInstalled; late bool includeNonInstalled;
late Set<String> categoryFilter; late Set<String> categoryFilter;
@ -1055,6 +1070,7 @@ class AppsFilter {
AppsFilter( AppsFilter(
{this.nameFilter = '', {this.nameFilter = '',
this.authorFilter = '', this.authorFilter = '',
this.idFilter = '',
this.includeUptodate = true, this.includeUptodate = true,
this.includeNonInstalled = true, this.includeNonInstalled = true,
this.categoryFilter = const {}, this.categoryFilter = const {},
@ -1064,6 +1080,7 @@ class AppsFilter {
return { return {
'appName': nameFilter, 'appName': nameFilter,
'author': authorFilter, 'author': authorFilter,
'appId': idFilter,
'upToDateApps': includeUptodate, 'upToDateApps': includeUptodate,
'nonInstalledApps': includeNonInstalled, 'nonInstalledApps': includeNonInstalled,
'sourceFilter': sourceFilter 'sourceFilter': sourceFilter
@ -1073,6 +1090,7 @@ class AppsFilter {
setFormValuesFromMap(Map<String, dynamic> values) { setFormValuesFromMap(Map<String, dynamic> values) {
nameFilter = values['appName']!; nameFilter = values['appName']!;
authorFilter = values['author']!; authorFilter = values['author']!;
idFilter = values['appId']!;
includeUptodate = values['upToDateApps']; includeUptodate = values['upToDateApps'];
includeNonInstalled = values['nonInstalledApps']; includeNonInstalled = values['nonInstalledApps'];
sourceFilter = values['sourceFilter']; sourceFilter = values['sourceFilter'];
@ -1081,6 +1099,7 @@ class AppsFilter {
bool isIdenticalTo(AppsFilter other, SettingsProvider settingsProvider) => bool isIdenticalTo(AppsFilter other, SettingsProvider settingsProvider) =>
authorFilter.trim() == other.authorFilter.trim() && authorFilter.trim() == other.authorFilter.trim() &&
nameFilter.trim() == other.nameFilter.trim() && nameFilter.trim() == other.nameFilter.trim() &&
idFilter.trim() == other.idFilter.trim() &&
includeUptodate == other.includeUptodate && includeUptodate == other.includeUptodate &&
includeNonInstalled == other.includeNonInstalled && includeNonInstalled == other.includeNonInstalled &&
settingsProvider.setEqual(categoryFilter, other.categoryFilter) && settingsProvider.setEqual(categoryFilter, other.categoryFilter) &&

View File

@ -27,6 +27,7 @@ class NavigationPageItem {
class _HomePageState extends State<HomePage> { class _HomePageState extends State<HomePage> {
List<int> selectedIndexHistory = []; List<int> selectedIndexHistory = [];
int prevAppCount = -1; int prevAppCount = -1;
bool prevIsLoading = true;
List<NavigationPageItem> pages = [ List<NavigationPageItem> pages = [
NavigationPageItem(tr('appsString'), Icons.apps, NavigationPageItem(tr('appsString'), Icons.apps,
@ -64,13 +65,15 @@ class _HomePageState extends State<HomePage> {
} }
} }
if (prevAppCount >= 0 && if (!prevIsLoading &&
prevAppCount >= 0 &&
appsProvider.apps.length > prevAppCount && appsProvider.apps.length > prevAppCount &&
selectedIndexHistory.isNotEmpty && selectedIndexHistory.isNotEmpty &&
selectedIndexHistory.last == 1) { selectedIndexHistory.last == 1) {
switchToPage(0); switchToPage(0);
} }
prevAppCount = appsProvider.apps.length; prevAppCount = appsProvider.apps.length;
prevIsLoading = appsProvider.loadingApps;
return WillPopScope( return WillPopScope(
child: Scaffold( child: Scaffold(

View File

@ -323,8 +323,8 @@ class _ImportExportPageState extends State<ImportExportPage> {
], ],
), ),
if (importInProgress) if (importInProgress)
Column( const Column(
children: const [ children: [
SizedBox( SizedBox(
height: 14, height: 14,
), ),

View File

@ -228,6 +228,18 @@ class _SettingsPageState extends State<SettingsPage> {
color: Theme.of(context).colorScheme.primary), color: Theme.of(context).colorScheme.primary),
), ),
intervalDropdown, intervalDropdown,
height16,
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Flexible(child: Text(tr('checkOnStart'))),
Switch(
value: settingsProvider.checkOnStart,
onChanged: (value) {
settingsProvider.checkOnStart = value;
})
],
),
height32, height32,
Text( Text(
tr('sourceSpecific'), tr('sourceSpecific'),

View File

@ -27,6 +27,7 @@ import 'package:flutter_fgbg/flutter_fgbg.dart';
import 'package:obtainium/providers/source_provider.dart'; import 'package:obtainium/providers/source_provider.dart';
import 'package:http/http.dart'; import 'package:http/http.dart';
import 'package:android_intent_plus/android_intent.dart'; import 'package:android_intent_plus/android_intent.dart';
import 'package:archive/archive.dart';
class AppInMemory { class AppInMemory {
late App app; late App app;
@ -46,6 +47,13 @@ class DownloadedApk {
DownloadedApk(this.appId, this.file); DownloadedApk(this.appId, this.file);
} }
class DownloadedXApkDir {
String appId;
File file;
Directory extracted;
DownloadedXApkDir(this.appId, this.file, this.extracted);
}
List<String> generateStandardVersionRegExStrings() { List<String> generateStandardVersionRegExStrings() {
// TODO: Look into RegEx for non-Latin characters / non-Arabic numerals // TODO: Look into RegEx for non-Latin characters / non-Arabic numerals
var basics = [ var basics = [
@ -100,6 +108,7 @@ class AppsProvider with ChangeNotifier {
bool isForeground = true; bool isForeground = true;
late Stream<FGBGType>? foregroundStream; late Stream<FGBGType>? foregroundStream;
late StreamSubscription<FGBGType>? foregroundSubscription; late StreamSubscription<FGBGType>? foregroundSubscription;
late Directory APKDir;
Iterable<AppInMemory> getAppValues() => apps.values.map((a) => a.deepCopy()); Iterable<AppInMemory> getAppValues() => apps.values.map((a) => a.deepCopy());
@ -108,35 +117,56 @@ class AppsProvider with ChangeNotifier {
foregroundStream = FGBGEvents.stream.asBroadcastStream(); foregroundStream = FGBGEvents.stream.asBroadcastStream();
foregroundSubscription = foregroundStream?.listen((event) async { foregroundSubscription = foregroundStream?.listen((event) async {
isForeground = event == FGBGType.foreground; isForeground = event == FGBGType.foreground;
if (isForeground) await loadApps(); if (isForeground) await refreshInstallStatuses();
}); });
() async { () async {
var cacheDirs = await getExternalCacheDirectories();
if (cacheDirs?.isNotEmpty ?? false) {
APKDir = cacheDirs!.first;
} else {
APKDir =
Directory('${(await getExternalStorageDirectory())!.path}/apks');
if (!APKDir.existsSync()) {
APKDir.createSync();
}
}
// Load Apps into memory (in background, this is done later instead of in the constructor) // Load Apps into memory (in background, this is done later instead of in the constructor)
await loadApps(); await loadApps();
// Delete any partial APKs // Delete any partial APKs
(await getExternalCacheDirectories()) var cutoff = DateTime.now().subtract(const Duration(days: 7));
?.first APKDir.listSync()
.listSync() .where((element) =>
.where((element) => element.path.endsWith('.apk.part')) element.path.endsWith('.part') ||
element.statSync().modified.isBefore(cutoff))
.forEach((partialApk) { .forEach((partialApk) {
partialApk.delete(); partialApk.delete(recursive: true);
}); });
}(); }();
} }
downloadFile(String url, String fileName, Function? onProgress, Future<File> downloadFile(
String url, String fileNameNoExt, Function? onProgress,
{bool useExisting = true, Map<String, String>? headers}) async { {bool useExisting = true, Map<String, String>? headers}) async {
var destDir = (await getExternalCacheDirectories())!.first.path; var destDir = APKDir.path;
var req = Request('GET', Uri.parse(url)); var req = Request('GET', Uri.parse(url));
if (headers != null) { if (headers != null) {
req.headers.addAll(headers); req.headers.addAll(headers);
} }
StreamedResponse response = await Client().send(req); var client = Client();
File downloadedFile = File('$destDir/$fileName'); StreamedResponse response = await client.send(req);
String ext =
response.headers['content-disposition']?.split('.').last ?? 'apk';
if (ext.endsWith('"') || ext.endsWith("other")) {
ext = ext.substring(0, ext.length - 1);
}
if (url.toLowerCase().endsWith('.apk') && ext != 'apk') {
ext = 'apk';
}
File downloadedFile = File('$destDir/$fileNameNoExt.$ext');
if (!(downloadedFile.existsSync() && useExisting)) { if (!(downloadedFile.existsSync() && useExisting)) {
File tempDownloadedFile = File('${downloadedFile.path}.part'); File tempDownloadedFile = File('${downloadedFile.path}.part');
if (tempDownloadedFile.existsSync()) { if (tempDownloadedFile.existsSync()) {
tempDownloadedFile.deleteSync(); tempDownloadedFile.deleteSync(recursive: true);
} }
var length = response.contentLength; var length = response.contentLength;
var received = 0; var received = 0;
@ -156,15 +186,38 @@ class AppsProvider with ChangeNotifier {
onProgress(progress); onProgress(progress);
} }
if (response.statusCode != 200) { if (response.statusCode != 200) {
tempDownloadedFile.deleteSync(); tempDownloadedFile.deleteSync(recursive: true);
throw response.reasonPhrase ?? tr('unexpectedError'); throw response.reasonPhrase ?? tr('unexpectedError');
} }
tempDownloadedFile.renameSync(downloadedFile.path); tempDownloadedFile.renameSync(downloadedFile.path);
} else {
client.close();
} }
return downloadedFile; return downloadedFile;
} }
Future<DownloadedApk> downloadApp(App app, BuildContext? context) async { Future<File> handleAPKIDChange(App app, PackageArchiveInfo newInfo,
File downloadedFile, String downloadUrl) async {
// If the APK package ID is different from the App ID, it is either new (using a placeholder ID) or the ID has changed
// The former case should be handled (give the App its real ID), the latter is a security issue
if (app.id != newInfo.packageName) {
var isTempId = SourceProvider().isTempId(app);
if (apps[app.id] != null && !isTempId) {
throw IDChangedError();
}
var originalAppId = app.id;
app.id = newInfo.packageName;
downloadedFile = downloadedFile.renameSync(
'${downloadedFile.parent.path}/${app.id}-${downloadUrl.hashCode}.${downloadedFile.path.split('.').last}');
if (apps[originalAppId] != null) {
await removeApps([originalAppId]);
await saveApps([app], onlyIfExists: !isTempId);
}
}
return downloadedFile;
}
Future<Object> downloadApp(App app, BuildContext? context) async {
NotificationsProvider? notificationsProvider = NotificationsProvider? notificationsProvider =
context?.read<NotificationsProvider>(); context?.read<NotificationsProvider>();
var notifId = DownloadNotification(app.finalName, 0).id; var notifId = DownloadNotification(app.finalName, 0).id;
@ -177,11 +230,11 @@ class AppsProvider with ChangeNotifier {
.getSource(app.url, overrideSource: app.overrideSource); .getSource(app.url, overrideSource: app.overrideSource);
String downloadUrl = await source.apkUrlPrefetchModifier( String downloadUrl = await source.apkUrlPrefetchModifier(
app.apkUrls[app.preferredApkIndex].value, app.url); app.apkUrls[app.preferredApkIndex].value, app.url);
var fileName = '${app.id}-${downloadUrl.hashCode}.apk';
var notif = DownloadNotification(app.finalName, 100); var notif = DownloadNotification(app.finalName, 100);
notificationsProvider?.cancel(notif.id); notificationsProvider?.cancel(notif.id);
int? prevProg; int? prevProg;
File downloadedFile = await downloadFile(downloadUrl, fileName, var fileNameNoExt = '${app.id}-${downloadUrl.hashCode}';
var downloadedFile = await downloadFile(downloadUrl, fileNameNoExt,
headers: source.requestHeaders, (double? progress) { headers: source.requestHeaders, (double? progress) {
int? prog = progress?.ceil(); int? prog = progress?.ceil();
if (apps[app.id] != null) { if (apps[app.id] != null) {
@ -194,33 +247,45 @@ class AppsProvider with ChangeNotifier {
} }
prevProg = prog; prevProg = prog;
}); });
// If the APK package ID is different from the App ID, it is either new (using a placeholder ID) or the ID has changed // Set to 90 for remaining steps, will make null in 'finally'
// The former case should be handled (give the App its real ID), the latter is a security issue if (apps[app.id] != null) {
var newInfo = await PackageArchiveInfo.fromPath(downloadedFile.path); apps[app.id]!.downloadProgress = -1;
if (app.id != newInfo.packageName) { notifyListeners();
var isTempId = SourceProvider().isTempId(app); notif = DownloadNotification(app.finalName, -1);
if (apps[app.id] != null && !isTempId) { notificationsProvider?.notify(notif);
throw IDChangedError();
} }
var originalAppId = app.id; PackageArchiveInfo? newInfo;
app.id = newInfo.packageName; var isAPK = downloadedFile.path.toLowerCase().endsWith('.apk');
downloadedFile = downloadedFile.renameSync( Directory? xapkDir;
'${downloadedFile.parent.path}/${app.id}-${downloadUrl.hashCode}.apk'); if (isAPK) {
if (apps[originalAppId] != null) { newInfo = await PackageArchiveInfo.fromPath(downloadedFile.path);
await removeApps([originalAppId]); } else {
await saveApps([app], onlyIfExists: !isTempId); // Assume XAPK
String xapkDirPath = '${downloadedFile.path}-dir';
unzipFile(downloadedFile.path, '${downloadedFile.path}-dir');
xapkDir = Directory(xapkDirPath);
var apks = xapkDir
.listSync()
.where((e) => e.path.toLowerCase().endsWith('.apk'))
.toList();
newInfo = await PackageArchiveInfo.fromPath(apks.first.path);
} }
} downloadedFile =
// Delete older versions of the APK if any await handleAPKIDChange(app, newInfo, downloadedFile, downloadUrl);
// Delete older versions of the file if any
for (var file in downloadedFile.parent.listSync()) { for (var file in downloadedFile.parent.listSync()) {
var fn = file.path.split('/').last; var fn = file.path.split('/').last;
if (fn.startsWith('${app.id}-') && if (fn.startsWith('${app.id}-') &&
fn.endsWith('.apk') && FileSystemEntity.isFileSync(file.path) &&
fn != downloadedFile.path.split('/').last) { file.path != downloadedFile.path) {
file.delete(); file.delete(recursive: true);
} }
} }
if (isAPK) {
return DownloadedApk(app.id, downloadedFile); return DownloadedApk(app.id, downloadedFile);
} else {
return DownloadedXApkDir(app.id, downloadedFile, xapkDir!);
}
} finally { } finally {
notificationsProvider?.cancel(notifId); notificationsProvider?.cancel(notifId);
if (apps[app.id] != null) { if (apps[app.id] != null) {
@ -267,11 +332,43 @@ class AppsProvider with ChangeNotifier {
} }
} }
// Unfortunately this 'await' does not actually wait for the APK to finish installing void unzipFile(String filePath, String destinationPath) {
// So we only know that the install prompt was shown, but the user could still cancel w/o us knowing final bytes = File(filePath).readAsBytesSync();
// If appropriate criteria are met, the update (never a fresh install) happens silently in the background final archive = ZipDecoder().decodeBytes(bytes);
// But even then, we don't know if it actually succeeded
Future<void> installApk(DownloadedApk file, {bool silent = false}) async { for (final file in archive) {
final filename = '$destinationPath/${file.name}';
if (file.isFile) {
final data = file.content as List<int>;
File(filename)
..createSync(recursive: true)
..writeAsBytesSync(data);
} else {
Directory(filename).create(recursive: true);
}
}
}
Future<void> installXApkDir(DownloadedXApkDir dir,
{bool silent = false}) async {
try {
var somethingInstalled = false;
for (var apk in dir.extracted
.listSync()
.where((f) => f is File && f.path.toLowerCase().endsWith('.apk'))) {
somethingInstalled = somethingInstalled ||
await installApk(DownloadedApk(dir.appId, apk as File),
silent: silent);
}
if (somethingInstalled) {
dir.file.delete(recursive: true);
}
} finally {
dir.extracted.delete(recursive: true);
}
}
Future<bool> installApk(DownloadedApk file, {bool silent = false}) async {
// TODO: Use 'silent' when/if ever possible // TODO: Use 'silent' when/if ever possible
var newInfo = await PackageArchiveInfo.fromPath(file.file.path); var newInfo = await PackageArchiveInfo.fromPath(file.file.path);
AppInfo? appInfo; AppInfo? appInfo;
@ -287,14 +384,17 @@ class AppsProvider with ChangeNotifier {
} }
int? code = int? code =
await AndroidPackageInstaller.installApk(apkFilePath: file.file.path); await AndroidPackageInstaller.installApk(apkFilePath: file.file.path);
bool installed = false;
if (code != null && code != 0 && code != 3) { if (code != null && code != 0 && code != 3) {
throw InstallError(code); throw InstallError(code);
} else if (code == 0) { } else if (code == 0) {
installed = true;
apps[file.appId]!.app.installedVersion = apps[file.appId]!.app.installedVersion =
apps[file.appId]!.app.latestVersion; apps[file.appId]!.app.latestVersion;
file.file.delete(); file.file.delete(recursive: true);
} }
await saveApps([apps[file.appId]!.app]); await saveApps([apps[file.appId]!.app]);
return installed;
} }
void uninstallApp(String appId) async { void uninstallApp(String appId) async {
@ -420,9 +520,16 @@ class AppsProvider with ChangeNotifier {
for (var id in appsToInstall) { for (var id in appsToInstall) {
try { try {
// ignore: use_build_context_synchronously // ignore: use_build_context_synchronously
var downloadedFile = await downloadApp(apps[id]!.app, context); var downloadedArtifact = await downloadApp(apps[id]!.app, context);
bool willBeSilent = DownloadedApk? downloadedFile;
await canInstallSilently(apps[downloadedFile.appId]!.app); DownloadedXApkDir? downloadedDir;
if (downloadedArtifact is DownloadedApk) {
downloadedFile = downloadedArtifact;
} else {
downloadedDir = downloadedArtifact as DownloadedXApkDir;
}
bool willBeSilent = await canInstallSilently(
apps[downloadedFile?.appId ?? downloadedDir!.appId]!.app);
willBeSilent = false; // TODO: Remove this when silent updates work willBeSilent = false; // TODO: Remove this when silent updates work
if (!(await settingsProvider?.getInstallPermission(enforce: false) ?? if (!(await settingsProvider?.getInstallPermission(enforce: false) ??
true)) { true)) {
@ -432,7 +539,18 @@ class AppsProvider with ChangeNotifier {
// ignore: use_build_context_synchronously // ignore: use_build_context_synchronously
await waitForUserToReturnToForeground(context); await waitForUserToReturnToForeground(context);
} }
apps[id]?.downloadProgress = -1;
notifyListeners();
try {
if (downloadedFile != null) {
await installApk(downloadedFile, silent: willBeSilent); await installApk(downloadedFile, silent: willBeSilent);
} else {
await installXApkDir(downloadedDir!, silent: willBeSilent);
}
} finally {
apps[id]?.downloadProgress = null;
notifyListeners();
}
installedIds.add(id); installedIds.add(id);
} catch (e) { } catch (e) {
errors.add(id, e.toString()); errors.add(id, e.toString());
@ -597,42 +715,31 @@ class AppsProvider with ChangeNotifier {
} }
loadingApps = true; loadingApps = true;
notifyListeners(); notifyListeners();
List<App> newApps = (await getAppsDir()) var sp = SourceProvider();
List<List<String>> errors = [];
List<FileSystemEntity> newApps = (await getAppsDir())
.listSync() .listSync()
.where((item) => item.path.toLowerCase().endsWith('.json')) .where((item) => item.path.toLowerCase().endsWith('.json'))
.map((e) { .toList();
for (var e in newApps) {
try { try {
return App.fromJson(jsonDecode(File(e.path).readAsStringSync())); var app = App.fromJson(jsonDecode(File(e.path).readAsStringSync()));
try {
var info = await getInstalledInfo(app.id);
sp.getSource(app.url, overrideSource: app.overrideSource);
apps[app.id] = AppInMemory(app, null, info);
notifyListeners();
} catch (e) {
errors.add([app.id, app.finalName, e.toString()]);
}
} catch (err) { } catch (err) {
if (err is FormatException) { if (err is FormatException) {
logs.add('Corrupt JSON when loading App (will be ignored): $e'); logs.add('Corrupt JSON when loading App (will be ignored): $e');
e.renameSync('${e.path}.corrupt'); e.renameSync('${e.path}.corrupt');
return App(
'', '', '', '', '', '', [], 0, {}, DateTime.now(), false);
} else { } else {
rethrow; rethrow;
} }
} }
})
.where((element) => element.id.isNotEmpty)
.toList();
var idsToDelete = apps.values
.map((e) => e.app.id)
.toSet()
.difference(newApps.map((e) => e.id).toSet());
for (var id in idsToDelete) {
apps.remove(id);
}
var sp = SourceProvider();
List<List<String>> errors = [];
for (int i = 0; i < newApps.length; i++) {
var info = await getInstalledInfo(newApps[i].id);
try {
sp.getSource(newApps[i].url, overrideSource: newApps[i].overrideSource);
apps[newApps[i].id] = AppInMemory(newApps[i], null, info);
} catch (e) {
errors.add([newApps[i].id, newApps[i].finalName, e.toString()]);
}
} }
if (errors.isNotEmpty) { if (errors.isNotEmpty) {
removeApps(errors.map((e) => e[0]).toList()); removeApps(errors.map((e) => e[0]).toList());
@ -641,6 +748,10 @@ class AppsProvider with ChangeNotifier {
} }
loadingApps = false; loadingApps = false;
notifyListeners(); notifyListeners();
refreshInstallStatuses();
}
Future<void> refreshInstallStatuses() async {
if (await doesInstalledAppsPluginWork()) { if (await doesInstalledAppsPluginWork()) {
List<App> modifiedApps = []; List<App> modifiedApps = [];
for (var app in apps.values) { for (var app in apps.values) {
@ -684,11 +795,18 @@ class AppsProvider with ChangeNotifier {
} }
Future<void> removeApps(List<String> appIds) async { Future<void> removeApps(List<String> appIds) async {
var apkFiles = APKDir.listSync();
for (var appId in appIds) { for (var appId in appIds) {
File file = File('${(await getAppsDir()).path}/$appId.json'); File file = File('${(await getAppsDir()).path}/$appId.json');
if (file.existsSync()) { if (file.existsSync()) {
file.deleteSync(); file.deleteSync(recursive: true);
} }
apkFiles
.where(
(element) => element.path.split('/').last.startsWith('$appId-'))
.forEach((element) {
element.delete(recursive: true);
});
if (apps.containsKey(appId)) { if (apps.containsKey(appId)) {
apps.remove(appId); apps.remove(appId);
} }
@ -734,7 +852,7 @@ class AppsProvider with ChangeNotifier {
apps[i].installedVersion = null; apps[i].installedVersion = null;
} }
} }
await saveApps(apps, attemptToCorrectInstallStatus: !remove); await saveApps(apps, attemptToCorrectInstallStatus: false);
} }
if (remove) { if (remove) {
await removeApps(apps.map((e) => e.id).toList()); await removeApps(apps.map((e) => e.id).toList());

View File

@ -167,7 +167,8 @@ class NotificationsProvider {
progress: progPercent ?? 0, progress: progPercent ?? 0,
maxProgress: 100, maxProgress: 100,
showProgress: progPercent != null, showProgress: progPercent != null,
onlyAlertOnce: onlyAlertOnce))); onlyAlertOnce: onlyAlertOnce,
indeterminate: progPercent != null && progPercent < 0)));
} }
Future<void> notify(ObtainiumNotification notif, Future<void> notify(ObtainiumNotification notif,

View File

@ -35,6 +35,7 @@ List<int> updateIntervals = [15, 30, 60, 120, 180, 360, 720, 1440, 4320, 0]
class SettingsProvider with ChangeNotifier { class SettingsProvider with ChangeNotifier {
SharedPreferences? prefs; SharedPreferences? prefs;
bool justStarted = true;
String sourceUrl = 'https://github.com/ImranR98/Obtainium'; String sourceUrl = 'https://github.com/ImranR98/Obtainium';
@ -92,6 +93,15 @@ class SettingsProvider with ChangeNotifier {
notifyListeners(); notifyListeners();
} }
bool get checkOnStart {
return prefs?.getBool('checkOnStart') ?? false;
}
set checkOnStart(bool checkOnStart) {
prefs?.setBool('checkOnStart', checkOnStart);
notifyListeners();
}
SortColumnSettings get sortColumn { SortColumnSettings get sortColumn {
return SortColumnSettings.values[ return SortColumnSettings.values[
prefs?.getInt('sortColumn') ?? SortColumnSettings.nameAuthor.index]; prefs?.getInt('sortColumn') ?? SortColumnSettings.nameAuthor.index];
@ -120,6 +130,14 @@ class SettingsProvider with ChangeNotifier {
return result; return result;
} }
bool checkJustStarted() {
if (justStarted) {
justStarted = false;
return true;
}
return false;
}
Future<bool> getInstallPermission({bool enforce = false}) async { Future<bool> getInstallPermission({bool enforce = false}) async {
while (!(await Permission.requestInstallPackages.isGranted)) { while (!(await Permission.requestInstallPackages.isGranted)) {
// Explicit request as InstallPlugin request sometimes bugged // Explicit request as InstallPlugin request sometimes bugged

View File

@ -7,7 +7,6 @@ import 'package:device_info_plus/device_info_plus.dart';
import 'package:easy_localization/easy_localization.dart'; import 'package:easy_localization/easy_localization.dart';
import 'package:html/dom.dart'; import 'package:html/dom.dart';
import 'package:http/http.dart'; import 'package:http/http.dart';
import 'package:obtainium/app_sources/apkcombo.dart';
import 'package:obtainium/app_sources/apkmirror.dart'; import 'package:obtainium/app_sources/apkmirror.dart';
import 'package:obtainium/app_sources/apkpure.dart'; import 'package:obtainium/app_sources/apkpure.dart';
import 'package:obtainium/app_sources/codeberg.dart'; import 'package:obtainium/app_sources/codeberg.dart';
@ -318,7 +317,7 @@ abstract class AppSource {
late String name; late String name;
bool enforceTrackOnly = false; bool enforceTrackOnly = false;
bool changeLogIfAnyIsMarkDown = true; bool changeLogIfAnyIsMarkDown = true;
bool overrideEligible = false; bool appIdInferIsOptional = false;
AppSource() { AppSource() {
name = runtimeType.toString(); name = runtimeType.toString();
@ -436,8 +435,8 @@ abstract class AppSource {
throw NotImplementedError(); throw NotImplementedError();
} }
String? tryInferringAppId(String standardUrl, Future<String?> tryInferringAppId(String standardUrl,
{Map<String, dynamic> additionalSettings = const {}}) { {Map<String, dynamic> additionalSettings = const {}}) async {
return null; return null;
} }
} }
@ -554,7 +553,8 @@ class SourceProvider {
AppSource source, String url, Map<String, dynamic> additionalSettings, AppSource source, String url, Map<String, dynamic> additionalSettings,
{App? currentApp, {App? currentApp,
bool trackOnlyOverride = false, bool trackOnlyOverride = false,
String? overrideSource}) async { String? overrideSource,
bool inferAppIdIfOptional = false}) async {
if (trackOnlyOverride || source.enforceTrackOnly) { if (trackOnlyOverride || source.enforceTrackOnly) {
additionalSettings['trackOnly'] = true; additionalSettings['trackOnly'] = true;
} }
@ -594,8 +594,11 @@ class SourceProvider {
: apk.names.name[0].toUpperCase() + apk.names.name.substring(1); : apk.names.name[0].toUpperCase() + apk.names.name.substring(1);
return App( return App(
currentApp?.id ?? currentApp?.id ??
source.tryInferringAppId(standardUrl, ((!source.appIdInferIsOptional ||
additionalSettings: additionalSettings) ?? (source.appIdInferIsOptional && inferAppIdIfOptional))
? await source.tryInferringAppId(standardUrl,
additionalSettings: additionalSettings)
: null) ??
generateTempID(standardUrl, additionalSettings), generateTempID(standardUrl, additionalSettings),
standardUrl, standardUrl,
apk.names.author[0].toUpperCase() + apk.names.author.substring(1), apk.names.author[0].toUpperCase() + apk.names.author.substring(1),

View File

@ -5,18 +5,18 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: android_alarm_manager_plus name: android_alarm_manager_plus
sha256: "88a8001851fdc9bd54fa4e30d0277bb900a50f3d86ff244da7f027400bf23ac0" sha256: "80f963d47cb7ab0818144c7b0668aea4c038f9cb8626626e89a4ea77375defb7"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.1.4" version: "3.0.1"
android_intent_plus: android_intent_plus:
dependency: "direct main" dependency: "direct main"
description: description:
name: android_intent_plus name: android_intent_plus
sha256: "04cbc7c332a6f0bba88fed354de78813e9d24049c1800aaf10f449c7adc22603" sha256: "2c87d8330ba5deef5fe20e77f4d178190b3b24531dce08368030ab4be40a9d4e"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.1.9" version: "4.0.1"
android_package_installer: android_package_installer:
dependency: "direct main" dependency: "direct main"
description: description:
@ -34,6 +34,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.0.7" version: "2.0.7"
archive:
dependency: "direct main"
description:
name: archive
sha256: "0c8368c9b3f0abbc193b9d6133649a614204b528982bebc7026372d61677ce3a"
url: "https://pub.dev"
source: hosted
version: "3.3.7"
args: args:
dependency: transitive dependency: transitive
description: description:
@ -46,10 +54,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: async name: async
sha256: bfe67ef28df125b7dddcea62755991f807aa39a2492a23e1550161692950bbe0 sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.10.0" version: "2.11.0"
boolean_selector: boolean_selector:
dependency: transitive dependency: transitive
description: description:
@ -62,10 +70,26 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: characters name: characters
sha256: e6a326c8af69605aec75ed6c187d06b349707a27fbff8222ca9cc2cff167975c sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.2.1" version: "1.3.0"
checked_yaml:
dependency: transitive
description:
name: checked_yaml
sha256: feb6bed21949061731a7a75fc5d2aa727cf160b91af9a3e464c5e3a32e28b5ff
url: "https://pub.dev"
source: hosted
version: "2.0.3"
cli_util:
dependency: transitive
description:
name: cli_util
sha256: b8db3080e59b2503ca9e7922c3df2072cf13992354d5e944074ffa836fba43b7
url: "https://pub.dev"
source: hosted
version: "0.4.0"
clock: clock:
dependency: transitive dependency: transitive
description: description:
@ -78,10 +102,18 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: collection name: collection
sha256: cfc915e6923fe5ce6e153b0723c753045de46de1b4d63771530504004a45fae0 sha256: "4a07be6cb69c84d677a6c3096fcf960cc3285a8330b4603e0d463d15d9bd934c"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.17.0" version: "1.17.1"
convert:
dependency: transitive
description:
name: convert
sha256: "0f08b14755d163f6e2134cb58222dd25ea2a2ee8a195e53983d57c075324d592"
url: "https://pub.dev"
source: hosted
version: "3.1.1"
cross_file: cross_file:
dependency: transitive dependency: transitive
description: description:
@ -102,10 +134,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: csslib name: csslib
sha256: b36c7f7e24c0bdf1bf9a3da461c837d1de64b9f8beb190c9011d8c72a3dfd745 sha256: "831883fb353c8bdc1d71979e5b342c7d88acfbc643113c14ae51e2442ea0f20f"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.17.2" version: "0.17.3"
cupertino_icons: cupertino_icons:
dependency: "direct main" dependency: "direct main"
description: description:
@ -126,10 +158,10 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: device_info_plus name: device_info_plus
sha256: f52ab3b76b36ede4d135aab80194df8925b553686f0fa12226b4e2d658e45903 sha256: "2c35b6d1682b028e42d07b3aee4b98fa62996c10bc12cb651ec856a80d6a761b"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "8.2.2" version: "9.0.2"
device_info_plus_platform_interface: device_info_plus_platform_interface:
dependency: transitive dependency: transitive
description: description:
@ -142,18 +174,18 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: dynamic_color name: dynamic_color
sha256: bbebb1b7ebed819e0ec83d4abdc2a8482d934f6a85289ffc1c6acf7589fa2aad sha256: "74dff1435a695887ca64899b8990004f8d1232b0e84bfc4faa1fdda7c6f57cc1"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.6.3" version: "1.6.5"
easy_localization: easy_localization:
dependency: "direct main" dependency: "direct main"
description: description:
name: easy_localization name: easy_localization
sha256: "6a2e99fa0bfe5765bf4c6ca9b137d5de2c75593007178c5e4cd2ae985f870080" sha256: "30ebf25448ffe169e0bd9bc4b5da94faa8398967a2ad2ca09f438be8b6953645"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.0.1" version: "3.0.2"
easy_logger: easy_logger:
dependency: transitive dependency: transitive
description: description:
@ -174,10 +206,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: ffi name: ffi
sha256: a38574032c5f1dd06c4aee541789906c12ccaab8ba01446e800d9c5b79c4a978 sha256: ed5337a5660c506388a9f012be0288fb38b49020ce2b45fe1f8b8323fe429f99
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.0.1" version: "2.0.2"
file: file:
dependency: transitive dependency: transitive
description: description:
@ -190,10 +222,10 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: file_picker name: file_picker
sha256: b85eb92b175767fdaa0c543bf3b0d1f610fe966412ea72845fe5ba7801e763ff sha256: "9d6e95ec73abbd31ec54d0e0df8a961017e165aba1395e462e5b31ea0c165daf"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "5.2.10" version: "5.3.1"
flutter: flutter:
dependency: "direct main" dependency: "direct main"
description: flutter description: flutter
@ -207,6 +239,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.2.2" version: "0.2.2"
flutter_launcher_icons:
dependency: "direct dev"
description:
name: flutter_launcher_icons
sha256: "526faf84284b86a4cb36d20a5e45147747b7563d921373d4ee0559c54fcdbcea"
url: "https://pub.dev"
source: hosted
version: "0.13.1"
flutter_lints: flutter_lints:
dependency: "direct dev" dependency: "direct dev"
description: description:
@ -219,26 +259,26 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: flutter_local_notifications name: flutter_local_notifications
sha256: "2876372952b65ca7f684e698eba22bda1cf581fa071dd30ba2f01900f507d0d1" sha256: "812791d43ccfc1b443a0d39fa02a206fc228c597e28ff9337e09e3ca8d370391"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "14.0.0+1" version: "14.1.1"
flutter_local_notifications_linux: flutter_local_notifications_linux:
dependency: transitive dependency: transitive
description: description:
name: flutter_local_notifications_linux name: flutter_local_notifications_linux
sha256: "909bb95de05a2e793503a2437146285a2f600cd0b3f826e26b870a334d8586d7" sha256: "33f741ef47b5f63cc7f78fe75eeeac7e19f171ff3c3df054d84c1e38bedb6a03"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "4.0.0" version: "4.0.0+1"
flutter_local_notifications_platform_interface: flutter_local_notifications_platform_interface:
dependency: transitive dependency: transitive
description: description:
name: flutter_local_notifications_platform_interface name: flutter_local_notifications_platform_interface
sha256: "63235c42de5b6c99846969a27ad0209c401e6b77b0498939813725b5791c107c" sha256: "7cf643d6d5022f3baed0be777b0662cce5919c0a7b86e700299f22dc4ae660ef"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "7.0.0" version: "7.0.0+1"
flutter_localizations: flutter_localizations:
dependency: transitive dependency: transitive
description: flutter description: flutter
@ -248,18 +288,18 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: flutter_markdown name: flutter_markdown
sha256: "7b25c10de1fea883f3c4f9b8389506b54053cd00807beab69fd65c8653a2711f" sha256: dc6d5258653f6857135b32896ccda7f7af0c54dcec832495ad6835154c6c77c0
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.6.14" version: "0.6.15"
flutter_plugin_android_lifecycle: flutter_plugin_android_lifecycle:
dependency: transitive dependency: transitive
description: description:
name: flutter_plugin_android_lifecycle name: flutter_plugin_android_lifecycle
sha256: "96af49aa6b57c10a312106ad6f71deed5a754029c24789bbf620ba784f0bd0b0" sha256: "950e77c2bbe1692bc0874fc7fb491b96a4dc340457f4ea1641443d0a6c1ea360"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.0.14" version: "2.0.15"
flutter_test: flutter_test:
dependency: "direct dev" dependency: "direct dev"
description: flutter description: flutter
@ -274,10 +314,10 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: fluttertoast name: fluttertoast
sha256: "2f9c4d3f4836421f7067a28f8939814597b27614e021da9d63e5d3fb6e212d25" sha256: "474f7d506230897a3cd28c965ec21c5328ae5605fc9c400cd330e9e9d6ac175c"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "8.2.1" version: "8.2.2"
html: html:
dependency: "direct main" dependency: "direct main"
description: description:
@ -290,10 +330,10 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: http name: http
sha256: "5895291c13fa8a3bd82e76d5627f69e0d85ca6a30dcac95c4ea19a5d555879c2" sha256: "4c3f04bfb64d3efd508d06b41b825542f08122d30bda4933fb95c069d22a4fa3"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.13.6" version: "1.0.0"
http_parser: http_parser:
dependency: transitive dependency: transitive
description: description:
@ -302,6 +342,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "4.0.2" version: "4.0.2"
image:
dependency: transitive
description:
name: image
sha256: a72242c9a0ffb65d03de1b7113bc4e189686fc07c7147b8b41811d0dd0e0d9bf
url: "https://pub.dev"
source: hosted
version: "4.0.17"
installed_apps: installed_apps:
dependency: "direct main" dependency: "direct main"
description: description:
@ -314,26 +362,34 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: intl name: intl
sha256: "910f85bce16fb5c6f614e117efa303e85a1731bb0081edf3604a2ae6e9a3cc91" sha256: a3715e3bc90294e971cb7dc063fbf3cd9ee0ebf8604ffeafabd9e6f16abbdbe6
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.17.0" version: "0.18.0"
js: js:
dependency: transitive dependency: transitive
description: description:
name: js name: js
sha256: "5528c2f391ededb7775ec1daa69e65a2d61276f7552de2b5f7b8d34ee9fd4ab7" sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.6.5" version: "0.6.7"
json_annotation:
dependency: transitive
description:
name: json_annotation
sha256: b10a7b2ff83d83c777edba3c6a0f97045ddadd56c944e1a23a3fdf43a1bf4467
url: "https://pub.dev"
source: hosted
version: "4.8.1"
lints: lints:
dependency: transitive dependency: transitive
description: description:
name: lints name: lints
sha256: "5e4a9cd06d447758280a8ac2405101e0e2094d2a1dbdd3756aec3fe7775ba593" sha256: "0a217c6c989d21039f1498c3ed9f3ed71b354e69873f13a8dfc3c9fe76f1b452"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.0.1" version: "2.1.1"
markdown: markdown:
dependency: transitive dependency: transitive
description: description:
@ -346,10 +402,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: matcher name: matcher
sha256: "16db949ceee371e9b99d22f88fa3a73c4e59fd0afed0bd25fc336eb76c198b72" sha256: "6501fbd55da300384b768785b83e5ce66991266cec21af89ab9ae7f5ce1c4cbb"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.12.13" version: "0.12.15"
material_color_utilities: material_color_utilities:
dependency: transitive dependency: transitive
description: description:
@ -362,10 +418,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: meta name: meta
sha256: "6c268b42ed578a53088d834796959e4a1814b5e9e164f147f580a386e5decf42" sha256: "3c74dbf8763d36539f114c799d8a2d87343b5067e9d796ca22b5eb8437090ee3"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.8.0" version: "1.9.1"
mime: mime:
dependency: transitive dependency: transitive
description: description:
@ -402,18 +458,18 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: path name: path
sha256: db9d4f58c908a4ba5953fcee2ae317c94889433e5024c27ce74a37f94267945b sha256: "8829d8a55c13fc0e37127c29fedf290c102f4e40ae94ada574091fe0ff96c917"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.8.2" version: "1.8.3"
path_provider: path_provider:
dependency: "direct main" dependency: "direct main"
description: description:
name: path_provider name: path_provider
sha256: c7edf82217d4b2952b2129a61d3ad60f1075b9299e629e149a8d2e39c2e6aad4 sha256: "3087813781ab814e4157b172f1a11c46be20179fcc9bea043e0fba36bc0acaa2"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.0.14" version: "2.0.15"
path_provider_android: path_provider_android:
dependency: transitive dependency: transitive
description: description:
@ -426,18 +482,18 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: path_provider_foundation name: path_provider_foundation
sha256: ad4c4d011830462633f03eb34445a45345673dfd4faf1ab0b4735fbd93b19183 sha256: "1995d88ec2948dac43edf8fe58eb434d35d22a2940ecee1a9fefcd62beee6eb3"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.2.2" version: "2.2.3"
path_provider_linux: path_provider_linux:
dependency: transitive dependency: transitive
description: description:
name: path_provider_linux name: path_provider_linux
sha256: "2ae08f2216225427e64ad224a24354221c2c7907e448e6e0e8b57b1eb9f10ad1" sha256: ffbb8cc9ed2c9ec0e4b7a541e56fd79b138e8f47d2fb86815f15358a349b3b57
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.1.10" version: "2.1.11"
path_provider_platform_interface: path_provider_platform_interface:
dependency: transitive dependency: transitive
description: description:
@ -466,10 +522,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: permission_handler_android name: permission_handler_android
sha256: "8028362b40c4a45298f1cbfccd227c8dd6caf0e27088a69f2ba2ab15464159e2" sha256: d8cc6a62ded6d0f49c6eac337e080b066ee3bce4d405bd9439a61e1f1927bfe8
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "10.2.0" version: "10.2.1"
permission_handler_apple: permission_handler_apple:
dependency: transitive dependency: transitive
description: description:
@ -498,10 +554,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: petitparser name: petitparser
sha256: "49392a45ced973e8d94a85fdb21293fbb40ba805fc49f2965101ae748a3683b4" sha256: cb3798bef7fc021ac45b308f4b51208a152792445cce0448c9a4ba5879dd8750
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "5.1.0" version: "5.4.0"
platform: platform:
dependency: transitive dependency: transitive
description: description:
@ -518,6 +574,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.1.4" version: "2.1.4"
pointycastle:
dependency: transitive
description:
name: pointycastle
sha256: "7c1e5f0d23c9016c5bbd8b1473d0d3fb3fc851b876046039509e18e0c7485f2c"
url: "https://pub.dev"
source: hosted
version: "3.7.3"
process: process:
dependency: transitive dependency: transitive
description: description:
@ -538,10 +602,10 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: share_plus name: share_plus
sha256: b1f15232d41e9701ab2f04181f21610c36c83a12ae426b79b4bd011c567934b1 sha256: ed3fcea4f789ed95913328e629c0c53e69e80e08b6c24542f1b3576046c614e8
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "6.3.4" version: "7.0.2"
share_plus_platform_interface: share_plus_platform_interface:
dependency: transitive dependency: transitive
description: description:
@ -554,10 +618,10 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: shared_preferences name: shared_preferences
sha256: "858aaa72d8f61637d64e776aca82e1c67e6d9ee07979123c5d17115031c1b13b" sha256: "16d3fb6b3692ad244a695c0183fca18cf81fd4b821664394a781de42386bf022"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.1.0" version: "2.1.1"
shared_preferences_android: shared_preferences_android:
dependency: transitive dependency: transitive
description: description:
@ -570,10 +634,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: shared_preferences_foundation name: shared_preferences_foundation
sha256: "0c1c16c56c9708aa9c361541a6f0e5cc6fc12a3232d866a687a7b7db30032b07" sha256: e014107bb79d6d3297196f4f2d0db54b5d1f85b8ea8ff63b8e8b391a02700feb
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.2.1" version: "2.2.2"
shared_preferences_linux: shared_preferences_linux:
dependency: transitive dependency: transitive
description: description:
@ -623,10 +687,10 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: sqflite name: sqflite
sha256: acf091c6e55c50d00b30b8532b2dd23e393cf775861665ebd0f15cdd6ebfb079 sha256: b4d6710e1200e96845747e37338ea8a819a12b51689a3bcf31eff0003b37a0b9
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.2.8+1" version: "2.2.8+4"
sqflite_common: sqflite_common:
dependency: transitive dependency: transitive
description: description:
@ -679,10 +743,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: test_api name: test_api
sha256: ad540f65f92caa91bf21dfc8ffb8c589d6e4dc0c2267818b4cc2792857706206 sha256: eb6ac1540b26de412b3403a163d919ba86f6a973fe6cc50ae3541b80092fdcfb
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.4.16" version: "0.5.1"
timezone: timezone:
dependency: transitive dependency: transitive
description: description:
@ -695,26 +759,26 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: typed_data name: typed_data
sha256: "26f87ade979c47a150c9eaab93ccd2bebe70a27dc0b4b29517f2904f04eb11a5" sha256: facc8d6582f16042dd49f2463ff1bd6e2c9ef9f3d5da3d9b087e244a7b564b3c
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.3.1" version: "1.3.2"
url_launcher: url_launcher:
dependency: "direct main" dependency: "direct main"
description: description:
name: url_launcher name: url_launcher
sha256: "75f2846facd11168d007529d6cd8fcb2b750186bea046af9711f10b907e1587e" sha256: eb1e00ab44303d50dd487aab67ebc575456c146c6af44422f9c13889984c00f3
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "6.1.10" version: "6.1.11"
url_launcher_android: url_launcher_android:
dependency: transitive dependency: transitive
description: description:
name: url_launcher_android name: url_launcher_android
sha256: "22f8db4a72be26e9e3a4aa3f194b1f7afbc76d20ec141f84be1d787db2155cbd" sha256: eed4e6a1164aa9794409325c3b707ff424d4d1c2a785e7db67f8bbda00e36e51
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "6.0.31" version: "6.0.35"
url_launcher_ios: url_launcher_ios:
dependency: transitive dependency: transitive
description: description:
@ -751,10 +815,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: url_launcher_web name: url_launcher_web
sha256: "81fe91b6c4f84f222d186a9d23c73157dc4c8e1c71489c4d08be1ad3b228f1aa" sha256: "6bb1e5d7fe53daf02a8fee85352432a40b1f868a81880e99ec7440113d5cfcab"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.0.16" version: "2.0.17"
url_launcher_windows: url_launcher_windows:
dependency: transitive dependency: transitive
description: description:
@ -783,42 +847,50 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: webview_flutter name: webview_flutter
sha256: "1a37bdbaaf5fbe09ad8579ab09ecfd473284ce482f900b5aea27cf834386a567" sha256: "5604dac1178680a34fbe4a08c7b69ec42cca6601dc300009ec9ff69bef284cc2"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "4.2.0" version: "4.2.1"
webview_flutter_android: webview_flutter_android:
dependency: transitive dependency: transitive
description: description:
name: webview_flutter_android name: webview_flutter_android
sha256: "1acea8def62592123e2fbbca164ed8681a98a890bdcbb88f916d5b4a22687759" sha256: "57a22c86065375c1598b57224f92d6008141be0c877c64100de8bfb6f71083d8"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.7.0" version: "3.7.1"
webview_flutter_platform_interface: webview_flutter_platform_interface:
dependency: transitive dependency: transitive
description: description:
name: webview_flutter_platform_interface name: webview_flutter_platform_interface
sha256: "78715dc442b7849dbde74e92bb67de1cecf5addf95531c5fb474e72f5fe9a507" sha256: "656e2aeaef318900fffd21468b6ddc7958c7092a642f0e7220bac328b70d4a81"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.3.0" version: "2.3.1"
webview_flutter_wkwebview: webview_flutter_wkwebview:
dependency: transitive dependency: transitive
description: description:
name: webview_flutter_wkwebview name: webview_flutter_wkwebview
sha256: "61f33512810bf1ee9ac89761a4b02663ff64e8227b7dc80654642acd660fd49d" sha256: "6bbc6ade302b842999b27cbaa7171241c273deea8a9c73f92ceb3d811c767de2"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.4.2" version: "3.4.4"
win32: win32:
dependency: transitive dependency: transitive
description: description:
name: win32 name: win32
sha256: a6f0236dbda0f63aa9a25ad1ff9a9d8a4eaaa5012da0dc59d21afdb1dc361ca4 sha256: "5a751eddf9db89b3e5f9d50c20ab8612296e4e8db69009788d6c8b060a84191c"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.1.4" version: "4.1.4"
win32_registry:
dependency: transitive
description:
name: win32_registry
sha256: "1c52f994bdccb77103a6231ad4ea331a244dbcef5d1f37d8462f713143b0bfae"
url: "https://pub.dev"
source: hosted
version: "1.1.0"
xdg_directories: xdg_directories:
dependency: transitive dependency: transitive
description: description:
@ -831,10 +903,18 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: xml name: xml
sha256: "979ee37d622dec6365e2efa4d906c37470995871fe9ae080d967e192d88286b5" sha256: "5bc72e1e45e941d825fd7468b9b4cc3b9327942649aeb6fc5cdbf135f0a86e84"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "6.2.2" version: "6.3.0"
yaml:
dependency: transitive
description:
name: yaml
sha256: "75769501ea3489fca56601ff33454fe45507ea3bfb014161abc3b43ae25989d5"
url: "https://pub.dev"
source: hosted
version: "3.1.2"
sdks: sdks:
dart: ">=2.19.0 <3.0.0" dart: ">=3.0.0 <4.0.0"
flutter: ">=3.4.0-17.0.pre" flutter: ">=3.4.0-17.0.pre"

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.13.0+164 # When changing this, update the tag in main() accordingly version: 0.13.7+171 # 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'
@ -40,7 +40,7 @@ dependencies:
flutter_fgbg: ^0.2.0 # Try removing reliance on this flutter_fgbg: ^0.2.0 # Try removing reliance on this
flutter_local_notifications: ^14.0.0+1 flutter_local_notifications: ^14.0.0+1
provider: ^6.0.3 provider: ^6.0.3
http: ^0.13.5 http: ^1.0.0
webview_flutter: ^4.0.0 webview_flutter: ^4.0.0
dynamic_color: ^1.5.4 dynamic_color: ^1.5.4
html: ^0.15.0 html: ^0.15.0
@ -48,26 +48,28 @@ dependencies:
url_launcher: ^6.1.5 url_launcher: ^6.1.5
permission_handler: ^10.0.0 permission_handler: ^10.0.0
fluttertoast: ^8.0.9 fluttertoast: ^8.0.9
device_info_plus: ^8.0.0 device_info_plus: ^9.0.0
file_picker: ^5.2.10 file_picker: ^5.2.10
animations: ^2.0.4 animations: ^2.0.4
android_package_installer: android_package_installer:
git: git:
url: https://github.com/ImranR98/android_package_installer url: https://github.com/ImranR98/android_package_installer
ref: main ref: main
share_plus: ^6.0.1 share_plus: ^7.0.0
installed_apps: ^1.3.1 installed_apps: ^1.3.1
package_archive_info: ^0.1.0 package_archive_info: ^0.1.0
android_alarm_manager_plus: ^2.1.0 android_alarm_manager_plus: ^3.0.0
sqflite: ^2.2.0+3 sqflite: ^2.2.0+3
easy_localization: ^3.0.1 easy_localization: ^3.0.1
android_intent_plus: ^3.1.5 android_intent_plus: ^4.0.0
flutter_markdown: ^0.6.14 flutter_markdown: ^0.6.14
archive: ^3.3.7
dev_dependencies: dev_dependencies:
flutter_test: flutter_test:
sdk: flutter sdk: flutter
flutter_launcher_icons: ^0.13.1
# The "flutter_lints" package below contains a set of recommended lints to # The "flutter_lints" package below contains a set of recommended lints to
# encourage good coding practices. The lint set provided by the package is # encourage good coding practices. The lint set provided by the package is
@ -76,6 +78,10 @@ dev_dependencies:
# rules and activating additional ones. # rules and activating additional ones.
flutter_lints: ^2.0.1 flutter_lints: ^2.0.1
flutter_launcher_icons:
android: "ic_launcher"
image_path: "assets/graphics/icon.png"
# For information on the generic Dart part of this file, see the # For information on the generic Dart part of this file, see the
# following page: https://dart.dev/tools/pub/pubspec # following page: https://dart.dev/tools/pub/pubspec