Compare commits

...

30 Commits

Author SHA1 Message Date
Imran Remtulla
5cb4bd998b Merge pull request #805 from ImranR98/dev
Update packages, increment version
2023-08-25 20:54:18 -04:00
Imran Remtulla
57f499c6a5 Update packages, increment version 2023-08-25 20:53:23 -04:00
Imran Remtulla
c2ae6e19f9 Merge pull request #804 from ImranR98/dev
'Verify latest tag' toggle (#798, #740)
2023-08-25 20:41:59 -04:00
Imran Remtulla
16104fde03 Merge branch 'main' into dev 2023-08-25 20:41:52 -04:00
Imran Remtulla
13e10692b1 'Verify latest tag' toggle (#798, #740) 2023-08-25 20:40:35 -04:00
Imran Remtulla
7fc93f23c0 Merge pull request #802 from Daviteusz/weblate-obtainium-translate
locale(pl): Update Polish translation
2023-08-25 17:01:09 -04:00
Daviteusz
d6ddf87365 locale(pl): Update Polish translations
Polish translation has been updated

Signed-off-by: Daviteusz <daviteusz0@gmail.com>
2023-08-25 22:49:54 +02:00
Imran Remtulla
05172744cd Merge pull request #800 from ImranR98/dev
Enable Background Updates (#25)
2023-08-25 10:54:38 -04:00
Imran Remtulla
2504ae24fc Fix bgcheck error reporting (per-app), move code around 2023-08-24 15:05:07 -04:00
Imran Remtulla
5e41d5762b BG task: notif, retry, log tweaks 2023-08-24 10:46:11 -04:00
Imran Remtulla
57d44c972f Update build script to create zips 2023-08-23 20:05:08 -04:00
Imran Remtulla
be0b57ac00 Added logs, BG install cooldown 2023-08-23 19:51:52 -04:00
Imran Remtulla
862bb2b276 chmod +x build.sh 2023-08-23 19:39:23 -04:00
Imran Remtulla
5eac851f80 Added convenience build script 2023-08-23 19:36:24 -04:00
Imran Remtulla
0deab8296f bug 2023-08-23 18:28:16 -04:00
Imran Remtulla
6785708661 BG update toggle has an effect 2023-08-22 19:57:42 -04:00
Imran Remtulla
5307fd0901 bug 2023-08-22 19:43:23 -04:00
Imran Remtulla
788c4c7917 Try less messy bg update method 2023-08-22 19:14:56 -04:00
Imran Remtulla
3f6d96289d Bugfix, logging 2023-08-22 18:13:28 -04:00
Imran Remtulla
5cfd80e510 Added debug menu with on-demand bg task 2023-08-22 17:36:13 -04:00
Imran Remtulla
9eb32ae55a Don't reschedule bg checks if app is restarted 2023-08-22 17:13:15 -04:00
Imran Remtulla
82e08150ab bugs 2023-08-22 16:28:22 -04:00
Imran Remtulla
e956ee9254 Trying a new recursive BG update task due2 mem limits 2023-08-22 12:51:55 -04:00
Imran Remtulla
bb4f34317b Bugfix + version increment + update packages 2023-08-21 23:55:54 -04:00
Imran Remtulla
d08ff3fbae Add BG update toggle 2023-08-21 20:32:41 -04:00
Imran Remtulla
f5479ec82f Max 5 attempts for bg check fail due to rate/net 2023-08-21 20:15:57 -04:00
Imran Remtulla
3eb25c4060 Switch to per-app BG update tasks 2023-08-21 20:10:30 -04:00
Imran Remtulla
03bb1ad9a6 bugfix 2023-08-21 09:48:35 -04:00
Imran Remtulla
b59d3e77f9 Enable experimental BG update support (not well tested) (#25) 2023-08-20 22:32:33 -04:00
Imran Remtulla
7c41692d5f Enable version correction in background (replace plugin) 2023-08-20 16:03:41 -04:00
24 changed files with 778 additions and 406 deletions

View File

@@ -71,4 +71,5 @@
<uses-permission <uses-permission
android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:name="android.permission.WRITE_EXTERNAL_STORAGE"
android:maxSdkVersion="29"/> android:maxSdkVersion="29"/>
<uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" />
</manifest> </manifest>

View File

@@ -1,4 +1,4 @@
{ {
"invalidURLForSource": "Nije važeći URL aplikacije {}", "invalidURLForSource": "Nije važeći URL aplikacije {}",
"noReleaseFound": "Nije moguće pronaći odgovarajuće izdanje", "noReleaseFound": "Nije moguće pronaći odgovarajuće izdanje",
"noVersionFound": "Nije moguće odrediti verziju izdanja", "noVersionFound": "Nije moguće odrediti verziju izdanja",
@@ -11,12 +11,6 @@
"unexpectedError": "Neočekivana greška", "unexpectedError": "Neočekivana greška",
"ok": "Dobro", "ok": "Dobro",
"and": "i", "and": "i",
"startedBgUpdateTask": "Započeo je pozadinski zadatak provjere ažuriranja",
"bgUpdateIgnoreAfterIs": "ignoreAfter pozadinskog zadataka je {}",
"startedActualBGUpdateCheck": "Započela je stvarna provjera ažuriranja",
"bgUpdateTaskFinished": "Završen zadatak provjere ažuriranja",
"firstRun": "Ovo je prvi put da pokrećete Obtainium",
"settingUpdateCheckIntervalTo": "Podešavanje intervala ažuriranja na {}",
"githubPATLabel": "GitHub token za lični pristup (eng. PAT, povećava ograničenje stope)", "githubPATLabel": "GitHub token za lični pristup (eng. PAT, povećava ograničenje stope)",
"githubPATHint": "PAT mora biti u ovom formatu: korisničko_ime:token", "githubPATHint": "PAT mora biti u ovom formatu: korisničko_ime:token",
"githubPATFormat": "korisničko_ime:token", "githubPATFormat": "korisničko_ime:token",
@@ -247,6 +241,12 @@
"sortByFileNamesNotLinks": "Sort by file names instead of full links", "sortByFileNamesNotLinks": "Sort by file names instead of full links",
"filterReleaseNotesByRegEx": "Filter Release Notes by Regular Expression", "filterReleaseNotesByRegEx": "Filter Release Notes by Regular Expression",
"customLinkFilterRegex": "Custom Link Filter by Regular Expression (Default '.apk$')", "customLinkFilterRegex": "Custom Link Filter by Regular Expression (Default '.apk$')",
"appsPossiblyUpdated": "App Updates Attempted",
"appsPossiblyUpdatedNotifDescription": "Notifies the user that updates to one or more Apps were potentially applied in the background",
"xWasPossiblyUpdatedToY": "{} may have been updated to {}.",
"backgroundUpdateReqsExplanation": "Background updates may not be possible for all apps.",
"backgroundUpdateLimitsExplanation": "The success of a background install can only be determined when Obtainium is opened.",
"verifyLatestTag": "Verify the 'latest' tag",
"removeAppQuestion": { "removeAppQuestion": {
"one": "Želite li ukloniti aplikaciju?", "one": "Želite li ukloniti aplikaciju?",
"other": "Želite li ukloniti aplikacije?" "other": "Želite li ukloniti aplikacije?"
@@ -294,5 +294,9 @@
"xAndNMoreUpdatesInstalled": { "xAndNMoreUpdatesInstalled": {
"one": "{} i još 1 aplikacija je ažurirana.", "one": "{} i još 1 aplikacija je ažurirana.",
"other": "{} i još {} aplikacija je ažurirano." "other": "{} i još {} aplikacija je ažurirano."
},
"xAndNMoreUpdatesPossiblyInstalled": {
"one": "{} and 1 more app may have been updated.",
"other": "{} and {} more apps may have been updated."
} }
} }

View File

@@ -11,12 +11,6 @@
"unexpectedError": "Unerwarteter Fehler", "unexpectedError": "Unerwarteter Fehler",
"ok": "Okay", "ok": "Okay",
"and": "und", "and": "und",
"startedBgUpdateTask": "Hintergrundaktualisierungsprüfung gestartet",
"bgUpdateIgnoreAfterIs": "Hintergrundaktualisierung 'ignoreAfter' ist {}",
"startedActualBGUpdateCheck": "Überprüfung der Hintergrundaktualisierung gestartet",
"bgUpdateTaskFinished": "Hintergrundaktualisierungsprüfung abgeschlossen",
"firstRun": "Dies ist der erste Start von Obtainium überhaupt",
"settingUpdateCheckIntervalTo": "Aktualisierungsintervall auf {} stellen",
"githubPATLabel": "GitHub Personal Access Token (Erhöht das Ratenlimit)", "githubPATLabel": "GitHub Personal Access Token (Erhöht das Ratenlimit)",
"githubPATHint": "PAT muss in diesem Format sein: Benutzername:Token", "githubPATHint": "PAT muss in diesem Format sein: Benutzername:Token",
"githubPATFormat": "Benutzername:Token", "githubPATFormat": "Benutzername:Token",
@@ -247,6 +241,12 @@
"sortByFileNamesNotLinks": "Sortiere nach Dateinamen, anstelle von ganzen Links", "sortByFileNamesNotLinks": "Sortiere nach Dateinamen, anstelle von ganzen Links",
"filterReleaseNotesByRegEx": "Versionshinweise nach regulärem Ausdruck filtern", "filterReleaseNotesByRegEx": "Versionshinweise nach regulärem Ausdruck filtern",
"customLinkFilterRegex": "Benutzerdefinierter Link Filter nach Regulärem Ausdruck (Standard '.apk$')", "customLinkFilterRegex": "Benutzerdefinierter Link Filter nach Regulärem Ausdruck (Standard '.apk$')",
"appsPossiblyUpdated": "App Updates Attempted",
"appsPossiblyUpdatedNotifDescription": "Notifies the user that updates to one or more Apps were potentially applied in the background",
"xWasPossiblyUpdatedToY": "{} may have been updated to {}.",
"backgroundUpdateReqsExplanation": "Background updates may not be possible for all apps.",
"backgroundUpdateLimitsExplanation": "The success of a background install can only be determined when Obtainium is opened.",
"verifyLatestTag": "Verify the 'latest' tag",
"removeAppQuestion": { "removeAppQuestion": {
"one": "App entfernen?", "one": "App entfernen?",
"other": "Apps entfernen?" "other": "Apps entfernen?"
@@ -294,5 +294,9 @@
"xAndNMoreUpdatesInstalled": { "xAndNMoreUpdatesInstalled": {
"one": "{} und 1 weitere Anwendung wurden aktualisiert.", "one": "{} und 1 weitere Anwendung wurden aktualisiert.",
"other": "{} und {} weitere Anwendungen wurden aktualisiert." "other": "{} und {} weitere Anwendungen wurden aktualisiert."
},
"xAndNMoreUpdatesPossiblyInstalled": {
"one": "{} and 1 more app may have been updated.",
"other": "{} and {} more apps may have been updated."
} }
} }

View File

@@ -11,12 +11,6 @@
"unexpectedError": "Unexpected Error", "unexpectedError": "Unexpected Error",
"ok": "Okay", "ok": "Okay",
"and": "and", "and": "and",
"startedBgUpdateTask": "Started BG update check task",
"bgUpdateIgnoreAfterIs": "Bg update ignoreAfter is {}",
"startedActualBGUpdateCheck": "Started actual BG update checking",
"bgUpdateTaskFinished": "Finished BG update check task",
"firstRun": "This is the first ever run of Obtainium",
"settingUpdateCheckIntervalTo": "Setting update interval to {}",
"githubPATLabel": "GitHub Personal Access Token (Increases Rate Limit)", "githubPATLabel": "GitHub Personal Access Token (Increases Rate Limit)",
"githubPATHint": "PAT must be in this format: username:token", "githubPATHint": "PAT must be in this format: username:token",
"githubPATFormat": "username:token", "githubPATFormat": "username:token",
@@ -247,6 +241,13 @@
"sortByFileNamesNotLinks": "Sort by file names instead of full links", "sortByFileNamesNotLinks": "Sort by file names instead of full links",
"filterReleaseNotesByRegEx": "Filter Release Notes by Regular Expression", "filterReleaseNotesByRegEx": "Filter Release Notes by Regular Expression",
"customLinkFilterRegex": "Custom Link Filter by Regular Expression (Default '.apk$')", "customLinkFilterRegex": "Custom Link Filter by Regular Expression (Default '.apk$')",
"appsPossiblyUpdated": "App Updates Attempted",
"appsPossiblyUpdatedNotifDescription": "Notifies the user that updates to one or more Apps were potentially applied in the background",
"xWasPossiblyUpdatedToY": "{} may have been updated to {}.",
"enableBackgroundUpdates": "Enable background updates",
"backgroundUpdateReqsExplanation": "Background updates may not be possible for all apps.",
"backgroundUpdateLimitsExplanation": "The success of a background install can only be determined when Obtainium is opened.",
"verifyLatestTag": "Verify the 'latest' tag",
"removeAppQuestion": { "removeAppQuestion": {
"one": "Remove App?", "one": "Remove App?",
"other": "Remove Apps?" "other": "Remove Apps?"
@@ -292,7 +293,11 @@
"other": "{} and {} more apps have updates." "other": "{} and {} more apps have updates."
}, },
"xAndNMoreUpdatesInstalled": { "xAndNMoreUpdatesInstalled": {
"one": "{} and 1 more app were updated.", "one": "{} and 1 more app was updated.",
"other": "{} and {} more apps were updated." "other": "{} and {} more apps were updated."
},
"xAndNMoreUpdatesPossiblyInstalled": {
"one": "{} and 1 more app may have been updated.",
"other": "{} and {} more apps may have been updated."
} }
} }

View File

@@ -11,12 +11,6 @@
"unexpectedError": "Error Inesperado", "unexpectedError": "Error Inesperado",
"ok": "Correcto", "ok": "Correcto",
"and": "y", "and": "y",
"startedBgUpdateTask": "Empezada la tarea de comprobación de actualizaciones en segundo plano",
"bgUpdateIgnoreAfterIs": "El parámetro ignoreAfter de la actualización en segundo plano es {}",
"startedActualBGUpdateCheck": "Ha comenzado la comprobación de actualizaciones en segundo plano",
"bgUpdateTaskFinished": "Ha finalizado la comprobación de actualizaciones en segundo plano",
"firstRun": "Esta es la primera ejecución de Obtainium",
"settingUpdateCheckIntervalTo": "Cambiando intervalo de actualización a {}",
"githubPATLabel": "Token de Acceso Personal de GitHub (Reduce tiempos de espera)", "githubPATLabel": "Token de Acceso Personal de GitHub (Reduce tiempos de espera)",
"githubPATHint": "El TAP debe tener este formato: nombre_de_usuario:token", "githubPATHint": "El TAP debe tener este formato: nombre_de_usuario:token",
"githubPATFormat": "nombre_de_usuario:token", "githubPATFormat": "nombre_de_usuario:token",
@@ -247,6 +241,12 @@
"sortByFileNamesNotLinks": "Sort by file names instead of full links", "sortByFileNamesNotLinks": "Sort by file names instead of full links",
"filterReleaseNotesByRegEx": "Filter Release Notes by Regular Expression", "filterReleaseNotesByRegEx": "Filter Release Notes by Regular Expression",
"customLinkFilterRegex": "Custom Link Filter by Regular Expression (Default '.apk$')", "customLinkFilterRegex": "Custom Link Filter by Regular Expression (Default '.apk$')",
"appsPossiblyUpdated": "App Updates Attempted",
"appsPossiblyUpdatedNotifDescription": "Notifies the user that updates to one or more Apps were potentially applied in the background",
"xWasPossiblyUpdatedToY": "{} may have been updated to {}.",
"backgroundUpdateReqsExplanation": "Background updates may not be possible for all apps.",
"backgroundUpdateLimitsExplanation": "The success of a background install can only be determined when Obtainium is opened.",
"verifyLatestTag": "Verify the 'latest' tag",
"removeAppQuestion": { "removeAppQuestion": {
"one": "¿Eliminar Aplicación?", "one": "¿Eliminar Aplicación?",
"other": "¿Eliminar Aplicaciones?" "other": "¿Eliminar Aplicaciones?"
@@ -294,5 +294,9 @@
"xAndNMoreUpdatesInstalled": { "xAndNMoreUpdatesInstalled": {
"one": "{} y 1 aplicación más han sido actualizadas.", "one": "{} y 1 aplicación más han sido actualizadas.",
"other": "{} y {} aplicaciones más han sido actualizadas." "other": "{} y {} aplicaciones más han sido actualizadas."
},
"xAndNMoreUpdatesPossiblyInstalled": {
"one": "{} and 1 more app may have been updated.",
"other": "{} and {} more apps may have been updated."
} }
} }

View File

@@ -11,12 +11,6 @@
"unexpectedError": "خطای غیرمنتظره", "unexpectedError": "خطای غیرمنتظره",
"ok": "باشه", "ok": "باشه",
"and": "و", "and": "و",
"startedBgUpdateTask": "شروع بررسی بروزرسانی BG",
"bgUpdateIgnoreAfterIs": "نادیده گرفتن بروزرسانی BG بعد از {} است",
"startedActualBGUpdateCheck": "بررسی به‌روزرسانی واقعی BG آغاز شد",
"bgUpdateTaskFinished": "کار بررسی به‌روزرسانی BG تمام شد",
"firstRun": "این اولین اجرای Obtainium است",
"settingUpdateCheckIntervalTo": "تنظیم فاصله به‌روزرسانی روی {}",
"githubPATLabel": "توکن دسترسی شخصی گیت هاب(محدودیت نرخ را افزایش میدهد)", "githubPATLabel": "توکن دسترسی شخصی گیت هاب(محدودیت نرخ را افزایش میدهد)",
"githubPATHint": "PAT باید در این قالب باشد: username:token", "githubPATHint": "PAT باید در این قالب باشد: username:token",
"githubPATFormat": "username:token", "githubPATFormat": "username:token",
@@ -247,6 +241,12 @@
"sortByFileNamesNotLinks": "مرتب سازی بر اساس نام فایل به جای پیوندهای کامل", "sortByFileNamesNotLinks": "مرتب سازی بر اساس نام فایل به جای پیوندهای کامل",
"filterReleaseNotesByRegEx": "یادداشت های انتشار را با بیان منظم فیلتر کنید", "filterReleaseNotesByRegEx": "یادداشت های انتشار را با بیان منظم فیلتر کنید",
"customLinkFilterRegex": "فیلتر پیوند سفارشی بر اساس عبارت منظم (پیش‌فرض '.apk$')", "customLinkFilterRegex": "فیلتر پیوند سفارشی بر اساس عبارت منظم (پیش‌فرض '.apk$')",
"appsPossiblyUpdated": "App Updates Attempted",
"appsPossiblyUpdatedNotifDescription": "Notifies the user that updates to one or more Apps were potentially applied in the background",
"xWasPossiblyUpdatedToY": "{} may have been updated to {}.",
"backgroundUpdateReqsExplanation": "Background updates may not be possible for all apps.",
"backgroundUpdateLimitsExplanation": "The success of a background install can only be determined when Obtainium is opened.",
"verifyLatestTag": "Verify the 'latest' tag",
"removeAppQuestion": { "removeAppQuestion": {
"one": "برنامه حذف شود؟", "one": "برنامه حذف شود؟",
"other": "برنامه ها حذف شوند؟" "other": "برنامه ها حذف شوند؟"
@@ -294,5 +294,9 @@
"xAndNMoreUpdatesInstalled": { "xAndNMoreUpdatesInstalled": {
"one": "{} و 1 برنامه دیگر به روز شدند.", "one": "{} و 1 برنامه دیگر به روز شدند.",
"other": "{} و {} برنامه دیگر به روز شدند." "other": "{} و {} برنامه دیگر به روز شدند."
},
"xAndNMoreUpdatesPossiblyInstalled": {
"one": "{} and 1 more app may have been updated.",
"other": "{} and {} more apps may have been updated."
} }
} }

View File

@@ -11,12 +11,6 @@
"unexpectedError": "Erreur inattendue", "unexpectedError": "Erreur inattendue",
"ok": "Okay", "ok": "Okay",
"and": "et", "and": "et",
"startedBgUpdateTask": "Démarrage de la tâche de vérification de mise à jour en arrière-plan",
"bgUpdateIgnoreAfterIs": "Mise à jour en arrière-plan est ignoré après {}",
"startedActualBGUpdateCheck": "Démarrage de la vérification de la mise à jour en arrière-plan",
"bgUpdateTaskFinished": "Tâche de vérification de la mise à jour en arrière-plan terminée",
"firstRun": "Il s'agit de la toute première exécution d'Obtainium",
"settingUpdateCheckIntervalTo": "Définition de l'intervalle de mise à jour sur {}",
"githubPATLabel": "Jeton d'Accès Personnel GitHub (Augmente la limite de débit)", "githubPATLabel": "Jeton d'Accès Personnel GitHub (Augmente la limite de débit)",
"githubPATHint": "Le JAP doit être dans ce format : username:token", "githubPATHint": "Le JAP doit être dans ce format : username:token",
"githubPATFormat": "username:token", "githubPATFormat": "username:token",
@@ -247,6 +241,12 @@
"sortByFileNamesNotLinks": "Sort by file names instead of full links", "sortByFileNamesNotLinks": "Sort by file names instead of full links",
"filterReleaseNotesByRegEx": "Filter Release Notes by Regular Expression", "filterReleaseNotesByRegEx": "Filter Release Notes by Regular Expression",
"customLinkFilterRegex": "Custom Link Filter by Regular Expression (Default '.apk$')", "customLinkFilterRegex": "Custom Link Filter by Regular Expression (Default '.apk$')",
"appsPossiblyUpdated": "App Updates Attempted",
"appsPossiblyUpdatedNotifDescription": "Notifies the user that updates to one or more Apps were potentially applied in the background",
"xWasPossiblyUpdatedToY": "{} may have been updated to {}.",
"backgroundUpdateReqsExplanation": "Background updates may not be possible for all apps.",
"backgroundUpdateLimitsExplanation": "The success of a background install can only be determined when Obtainium is opened.",
"verifyLatestTag": "Verify the 'latest' tag",
"removeAppQuestion": { "removeAppQuestion": {
"one": "Supprimer l'application ?", "one": "Supprimer l'application ?",
"other": "Supprimer les applications ?" "other": "Supprimer les applications ?"
@@ -294,5 +294,9 @@
"xAndNMoreUpdatesInstalled": { "xAndNMoreUpdatesInstalled": {
"one": "{} et 1 autre application ont été mises à jour.", "one": "{} et 1 autre application ont été mises à jour.",
"other": "{} et {} autres applications ont été mises à jour." "other": "{} et {} autres applications ont été mises à jour."
},
"xAndNMoreUpdatesPossiblyInstalled": {
"one": "{} and 1 more app may have been updated.",
"other": "{} and {} more apps may have been updated."
} }
} }

View File

@@ -11,12 +11,6 @@
"unexpectedError": "Váratlan hiba", "unexpectedError": "Váratlan hiba",
"ok": "Oké", "ok": "Oké",
"and": "és", "and": "és",
"startedBgUpdateTask": "Háttérfrissítés ellenőrzési feladat elindítva",
"bgUpdateIgnoreAfterIs": "Háttérfrissítés ignoreAfter a következő: {}",
"startedActualBGUpdateCheck": "Elkezdődött a tényleges háttérfrissítés ellenőrzése",
"bgUpdateTaskFinished": "A háttérfrissítés ellenőrzési feladat befejeződött",
"firstRun": "Ez az Obtainium első futása",
"settingUpdateCheckIntervalTo": "A frissítési intervallum beállítása erre: {}",
"githubPATLabel": "GitHub Personal Access Token (megnöveli a díjkorlátot)", "githubPATLabel": "GitHub Personal Access Token (megnöveli a díjkorlátot)",
"githubPATHint": "A PAT-nak a következő formátumban kell lennie: felhasználónév:token", "githubPATHint": "A PAT-nak a következő formátumban kell lennie: felhasználónév:token",
"githubPATFormat": "felhasználónév:token", "githubPATFormat": "felhasználónév:token",
@@ -246,6 +240,12 @@
"sortByFileNamesNotLinks": "Fájlnevek szerinti elrendezés teljes linkek helyett", "sortByFileNamesNotLinks": "Fájlnevek szerinti elrendezés teljes linkek helyett",
"filterReleaseNotesByRegEx": "Kiadási megjegyzések szűrése reguláris kifejezéssel", "filterReleaseNotesByRegEx": "Kiadási megjegyzések szűrése reguláris kifejezéssel",
"customLinkFilterRegex": "Custom Link Filter by Regular Expression (Default '.apk$')", "customLinkFilterRegex": "Custom Link Filter by Regular Expression (Default '.apk$')",
"appsPossiblyUpdated": "App Updates Attempted",
"appsPossiblyUpdatedNotifDescription": "Notifies the user that updates to one or more Apps were potentially applied in the background",
"xWasPossiblyUpdatedToY": "{} may have been updated to {}.",
"backgroundUpdateReqsExplanation": "Background updates may not be possible for all apps.",
"backgroundUpdateLimitsExplanation": "The success of a background install can only be determined when Obtainium is opened.",
"verifyLatestTag": "Verify the 'latest' tag",
"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?"
@@ -293,5 +293,9 @@
"xAndNMoreUpdatesInstalled": { "xAndNMoreUpdatesInstalled": {
"one": "A(z) {} és 1 további alkalmazás frissítve.", "one": "A(z) {} és 1 további alkalmazás frissítve.",
"other": "{} és {} további alkalmazás frissítve." "other": "{} és {} további alkalmazás frissítve."
},
"xAndNMoreUpdatesPossiblyInstalled": {
"one": "{} and 1 more app may have been updated.",
"other": "{} and {} more apps may have been updated."
} }
} }

View File

@@ -11,12 +11,6 @@
"unexpectedError": "Errore imprevisto", "unexpectedError": "Errore imprevisto",
"ok": "Va bene", "ok": "Va bene",
"and": "e", "and": "e",
"startedBgUpdateTask": "Avviata l'attività di controllo degli aggiornamenti in secondo piano",
"bgUpdateIgnoreAfterIs": "Il parametro di agg. in secondo piano 'ignoreAfter' è {}",
"startedActualBGUpdateCheck": "Avviato il controllo effettivo degli aggiornamenti in secondo piano",
"bgUpdateTaskFinished": "Terminata l'attività di controllo degli aggiornamenti in secondo piano",
"firstRun": "Questo è il primo avvio di sempre di Obtainium",
"settingUpdateCheckIntervalTo": "Fissato intervallo di aggiornamento a {}",
"githubPATLabel": "GitHub Personal Access Token (diminuisce limite di traffico)", "githubPATLabel": "GitHub Personal Access Token (diminuisce limite di traffico)",
"githubPATHint": "PAT deve seguire questo formato: nomeutente:token", "githubPATHint": "PAT deve seguire questo formato: nomeutente:token",
"githubPATFormat": "nomeutente:token", "githubPATFormat": "nomeutente:token",
@@ -247,6 +241,12 @@
"sortByFileNamesNotLinks": "Sort by file names instead of full links", "sortByFileNamesNotLinks": "Sort by file names instead of full links",
"filterReleaseNotesByRegEx": "Filter Release Notes by Regular Expression", "filterReleaseNotesByRegEx": "Filter Release Notes by Regular Expression",
"customLinkFilterRegex": "Custom Link Filter by Regular Expression (Default '.apk$')", "customLinkFilterRegex": "Custom Link Filter by Regular Expression (Default '.apk$')",
"appsPossiblyUpdated": "App Updates Attempted",
"appsPossiblyUpdatedNotifDescription": "Notifies the user that updates to one or more Apps were potentially applied in the background",
"xWasPossiblyUpdatedToY": "{} may have been updated to {}.",
"backgroundUpdateReqsExplanation": "Background updates may not be possible for all apps.",
"backgroundUpdateLimitsExplanation": "The success of a background install can only be determined when Obtainium is opened.",
"verifyLatestTag": "Verify the 'latest' tag",
"removeAppQuestion": { "removeAppQuestion": {
"one": "Rimuovere l'app?", "one": "Rimuovere l'app?",
"other": "Rimuovere le app?" "other": "Rimuovere le app?"
@@ -294,5 +294,9 @@
"xAndNMoreUpdatesInstalled": { "xAndNMoreUpdatesInstalled": {
"one": "{} e un'altra app sono state aggiornate.", "one": "{} e un'altra app sono state aggiornate.",
"other": "{} e altre {} app sono state aggiornate." "other": "{} e altre {} app sono state aggiornate."
},
"xAndNMoreUpdatesPossiblyInstalled": {
"one": "{} and 1 more app may have been updated.",
"other": "{} and {} more apps may have been updated."
} }
} }

View File

@@ -11,12 +11,6 @@
"unexpectedError": "予期せぬエラーが発生しました", "unexpectedError": "予期せぬエラーが発生しました",
"ok": "OK", "ok": "OK",
"and": "と", "and": "と",
"startedBgUpdateTask": "バックグラウンドのアップデート確認タスクを開始",
"bgUpdateIgnoreAfterIs": "Bg update ignoreAfter is {}",
"startedActualBGUpdateCheck": "実際のバックグラウンドのアップデート確認を開始",
"bgUpdateTaskFinished": "バックグラウンドのアップデート確認タスクを終了",
"firstRun": "これがObtainiumの最初の実行です",
"settingUpdateCheckIntervalTo": "確認間隔を{}に設定する",
"githubPATLabel": "GitHub パーソナルアクセストークン (レート制限の引き上げ)", "githubPATLabel": "GitHub パーソナルアクセストークン (レート制限の引き上げ)",
"githubPATHint": "PATは次の形式でなければなりません: ユーザー名:トークン", "githubPATHint": "PATは次の形式でなければなりません: ユーザー名:トークン",
"githubPATFormat": "ユーザー名:トークン", "githubPATFormat": "ユーザー名:トークン",
@@ -247,6 +241,12 @@
"sortByFileNamesNotLinks": "フルのリンクではなくファイル名でソートする", "sortByFileNamesNotLinks": "フルのリンクではなくファイル名でソートする",
"filterReleaseNotesByRegEx": "正規表現でリリースノートをフィルタリングする", "filterReleaseNotesByRegEx": "正規表現でリリースノートをフィルタリングする",
"customLinkFilterRegex": "正規表現によるカスタムリンクフィルター (デフォルト '.apk$')", "customLinkFilterRegex": "正規表現によるカスタムリンクフィルター (デフォルト '.apk$')",
"appsPossiblyUpdated": "App Updates Attempted",
"appsPossiblyUpdatedNotifDescription": "Notifies the user that updates to one or more Apps were potentially applied in the background",
"xWasPossiblyUpdatedToY": "{} may have been updated to {}.",
"backgroundUpdateReqsExplanation": "Background updates may not be possible for all apps.",
"backgroundUpdateLimitsExplanation": "The success of a background install can only be determined when Obtainium is opened.",
"verifyLatestTag": "Verify the 'latest' tag",
"removeAppQuestion": { "removeAppQuestion": {
"one": "アプリを削除しますか?", "one": "アプリを削除しますか?",
"other": "アプリを削除しますか?" "other": "アプリを削除しますか?"
@@ -294,5 +294,9 @@
"xAndNMoreUpdatesInstalled": { "xAndNMoreUpdatesInstalled": {
"one": "{} とさらに {} 個のアプリがアップデートされました", "one": "{} とさらに {} 個のアプリがアップデートされました",
"other": "{} とさらに {} 個のアプリがアップデートされました" "other": "{} とさらに {} 個のアプリがアップデートされました"
},
"xAndNMoreUpdatesPossiblyInstalled": {
"one": "{} and 1 more app may have been updated.",
"other": "{} and {} more apps may have been updated."
} }
} }

View File

@@ -19,12 +19,6 @@
"unexpectedError": "Nieoczekiwany błąd", "unexpectedError": "Nieoczekiwany błąd",
"ok": "Okej", "ok": "Okej",
"and": "i", "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ń)", "githubPATLabel": "Osobisty token dostępu GitHub (zwiększa limit zapytań)",
"githubPATHint": "Wymagany format: użytkownik:token", "githubPATHint": "Wymagany format: użytkownik:token",
"githubPATFormat": "użytkownik:token", "githubPATFormat": "użytkownik:token",
@@ -251,6 +245,12 @@
"sortByFileNamesNotLinks": "Sortuj wg nazw plików zamiast pełnych linków", "sortByFileNamesNotLinks": "Sortuj wg nazw plików zamiast pełnych linków",
"filterReleaseNotesByRegEx": "Filtruj informacje o wersji według wyrażenia regularnego", "filterReleaseNotesByRegEx": "Filtruj informacje o wersji według wyrażenia regularnego",
"customLinkFilterRegex": "Niestandardowy filtr linków wg. wyrażenia regularnego (domyślnie \".apk$\")", "customLinkFilterRegex": "Niestandardowy filtr linków wg. wyrażenia regularnego (domyślnie \".apk$\")",
"appsPossiblyUpdated": "Informuj o próbach aktualizacji",
"appsPossiblyUpdatedNotifDescription": "Powiadamiaj o potencjalnym zastosowaniu w tle aktualizacji jednej lub większej ilości aplikacji",
"xWasPossiblyUpdatedToY": "{} mógł zostać zaktualizowany do {}.",
"backgroundUpdateReqsExplanation": "Aktualizacje w tle mogą nie być możliwe dla wszystkich aplikacji.",
"backgroundUpdateLimitsExplanation": "Powodzenie instalacji w tle można określić dopiero po otwarciu Obtainium.",
"verifyLatestTag": "Verify the 'latest' tag",
"removeAppQuestion": { "removeAppQuestion": {
"one": "Usunąć aplikację?", "one": "Usunąć aplikację?",
"other": "Usunąć aplikacje?" "other": "Usunąć aplikacje?"
@@ -292,7 +292,12 @@
"other": "{} i {} aplik. otrzymało aktualizacje." "other": "{} i {} aplik. otrzymało aktualizacje."
}, },
"xAndNMoreUpdatesInstalled": { "xAndNMoreUpdatesInstalled": {
"one": "Zaktualizowano {} i jeszcze 1 aplikację.", "one": "{} i jeszcze 1 apka zostały zaktualizowane.",
"other": "Zaktualizowano {} i {} aplik." "other": "Zaktualizowano {} i {} aplik."
} },
"xAndNMoreUpdatesPossiblyInstalled": {
"one": "{} i jeszcze 1 apka mogły zostać zaktualizowane.",
"other": "{} i {} innych apek mogło zostać zaktualizowanych."
},
"enableBackgroundUpdates": "Włącz aktualizacje w tle"
} }

View File

@@ -11,12 +11,6 @@
"unexpectedError": "Неожиданная ошибка", "unexpectedError": "Неожиданная ошибка",
"ok": "Окей", "ok": "Окей",
"and": "и", "and": "и",
"startedBgUpdateTask": "Запущена задача фоновой проверки обновлений",
"bgUpdateIgnoreAfterIs": "Параметр игнорирования фоновых обновлений: {}",
"startedActualBGUpdateCheck": "Запущена фактическая проверка фоновых обновлений",
"bgUpdateTaskFinished": "Завершена задача фоновой проверки обновлений",
"firstRun": "Это первый запуск Obtainium",
"settingUpdateCheckIntervalTo": "Установка интервала проверки обновлений: {}",
"githubPATLabel": "Персональный токен доступа GitHub (увеличивает лимит запросов)", "githubPATLabel": "Персональный токен доступа GitHub (увеличивает лимит запросов)",
"githubPATHint": "Токен доступа должен быть в формате: имя_пользователя:токен", "githubPATHint": "Токен доступа должен быть в формате: имя_пользователя:токен",
"githubPATFormat": "имя_пользователя:токен", "githubPATFormat": "имя_пользователя:токен",
@@ -247,6 +241,12 @@
"sortByFileNamesNotLinks": "Sort by file names instead of full links", "sortByFileNamesNotLinks": "Sort by file names instead of full links",
"filterReleaseNotesByRegEx": "Filter Release Notes by Regular Expression", "filterReleaseNotesByRegEx": "Filter Release Notes by Regular Expression",
"customLinkFilterRegex": "Custom Link Filter by Regular Expression (Default '.apk$')", "customLinkFilterRegex": "Custom Link Filter by Regular Expression (Default '.apk$')",
"appsPossiblyUpdated": "App Updates Attempted",
"appsPossiblyUpdatedNotifDescription": "Notifies the user that updates to one or more Apps were potentially applied in the background",
"xWasPossiblyUpdatedToY": "{} may have been updated to {}.",
"backgroundUpdateReqsExplanation": "Background updates may not be possible for all apps.",
"backgroundUpdateLimitsExplanation": "The success of a background install can only be determined when Obtainium is opened.",
"verifyLatestTag": "Verify the 'latest' tag",
"removeAppQuestion": { "removeAppQuestion": {
"one": "Удалить приложение?", "one": "Удалить приложение?",
"other": "Удалить приложения?" "other": "Удалить приложения?"
@@ -294,5 +294,9 @@
"xAndNMoreUpdatesInstalled": { "xAndNMoreUpdatesInstalled": {
"one": "{} и еще 1 приложение были обновлены.", "one": "{} и еще 1 приложение были обновлены.",
"other": "{} и еще {} приложений были обновлены." "other": "{} и еще {} приложений были обновлены."
},
"xAndNMoreUpdatesPossiblyInstalled": {
"one": "{} and 1 more app may have been updated.",
"other": "{} and {} more apps may have been updated."
} }
} }

View File

@@ -11,12 +11,6 @@
"unexpectedError": "意外错误", "unexpectedError": "意外错误",
"ok": "好的", "ok": "好的",
"and": "和", "and": "和",
"startedBgUpdateTask": "后台更新检查任务已启动",
"bgUpdateIgnoreAfterIs": "后台更新检查间隔为 {}",
"startedActualBGUpdateCheck": "开始后台更新检查",
"bgUpdateTaskFinished": "后台更新检查任务已完成",
"firstRun": "这是 Obtainium 首次启动",
"settingUpdateCheckIntervalTo": "更新检查间隔设置为 {}",
"githubPATLabel": "GitHub 个人访问令牌(提升 API 请求限额)", "githubPATLabel": "GitHub 个人访问令牌(提升 API 请求限额)",
"githubPATHint": "个人访问令牌必须为“username:token”的格式", "githubPATHint": "个人访问令牌必须为“username:token”的格式",
"githubPATFormat": "username:token", "githubPATFormat": "username:token",
@@ -247,6 +241,12 @@
"sortByFileNamesNotLinks": "根据文件名而不是完整链接来排序", "sortByFileNamesNotLinks": "根据文件名而不是完整链接来排序",
"filterReleaseNotesByRegEx": "用正则表达式筛选发布说明", "filterReleaseNotesByRegEx": "用正则表达式筛选发布说明",
"customLinkFilterRegex": "用正则表达式自定义链接筛选(默认 '.apk$'", "customLinkFilterRegex": "用正则表达式自定义链接筛选(默认 '.apk$'",
"appsPossiblyUpdated": "App Updates Attempted",
"appsPossiblyUpdatedNotifDescription": "Notifies the user that updates to one or more Apps were potentially applied in the background",
"xWasPossiblyUpdatedToY": "{} may have been updated to {}.",
"backgroundUpdateReqsExplanation": "Background updates may not be possible for all apps.",
"backgroundUpdateLimitsExplanation": "The success of a background install can only be determined when Obtainium is opened.",
"verifyLatestTag": "Verify the 'latest' tag",
"removeAppQuestion": { "removeAppQuestion": {
"one": "是否删除应用?", "one": "是否删除应用?",
"other": "是否删除应用?" "other": "是否删除应用?"
@@ -294,5 +294,9 @@
"xAndNMoreUpdatesInstalled": { "xAndNMoreUpdatesInstalled": {
"one": "{} 和另外 1 个应用已更新。", "one": "{} 和另外 1 个应用已更新。",
"other": "{} 和另外 {} 个应用已更新。" "other": "{} 和另外 {} 个应用已更新。"
},
"xAndNMoreUpdatesPossiblyInstalled": {
"one": "{} and 1 more app may have been updated.",
"other": "{} and {} more apps may have been updated."
} }
} }

18
build.sh Executable file
View File

@@ -0,0 +1,18 @@
#!/bin/bash
# Convenience script
CURR_DIR="$(pwd)"
trap "cd "$CURR_DIR"" EXIT
git fetch && git merge origin/main && git push # Typically run after a PR to main, so bring dev up to date
rm ./build/app/outputs/flutter-apk/* 2>/dev/null # Get rid of older builds if any
flutter build apk && flutter build apk --split-per-abi # Build (both split and combined APKs)
for file in ./build/app/outputs/flutter-apk/*.sha1; do gpg --sign --detach-sig "$file"; done # Generate PGP signatures
rsync -r ./build/app/outputs/flutter-apk/ ~/Downloads/Obtainium-build/ # Dropoff in Downloads to allow for drag-drop into Flatpak Firefox
cd ~/Downloads/Obtainium-build/ # Make zips just in case (for in-comment uploads)
for apk in *.apk; do
PREFIX="$(echo "$apk" | head -c -5)"
zip "$PREFIX" "$PREFIX"*
done
mkdir -p zips
mv *.zip zips/

View File

@@ -85,6 +85,10 @@ class GitHub extends AppSource {
return regExValidator(value); return regExValidator(value);
} }
]) ])
],
[
GeneratedFormSwitch('verifyLatestTag',
label: tr('verifyLatestTag'), defaultValue: false)
] ]
]; ];
@@ -212,6 +216,21 @@ class GitHub extends AppSource {
true true
? additionalSettings['filterReleaseNotesByRegEx'] ? additionalSettings['filterReleaseNotesByRegEx']
: null; : null;
bool verifyLatestTag = additionalSettings['verifyLatestTag'] == true;
String? latestTag;
if (verifyLatestTag) {
var temp = requestUrl.split('?');
Response res = await sourceRequest(
'${temp[0]}/latest${temp.length > 1 ? '?${temp.sublist(1).join('?')}' : ''}');
if (res.statusCode != 200) {
if (onHttpErrorCode != null) {
onHttpErrorCode(res);
}
throw getObtainiumHttpError(res);
}
var jsres = jsonDecode(res.body);
latestTag = jsres['tag_name'] ?? jsres['name'];
}
Response res = await sourceRequest(requestUrl); Response res = await sourceRequest(requestUrl);
if (res.statusCode == 200) { if (res.statusCode == 200) {
var releases = jsonDecode(res.body) as List<dynamic>; var releases = jsonDecode(res.body) as List<dynamic>;
@@ -258,6 +277,17 @@ class GitHub extends AppSource {
} }
} }
}); });
if (latestTag != null &&
releases.isNotEmpty &&
latestTag !=
(releases[releases.length - 1]['tag_name'] ??
releases[0]['name'])) {
var ind = releases.indexWhere(
(element) => latestTag == (element['tag_name'] ?? element['name']));
if (ind >= 0) {
releases.add(releases.removeAt(ind));
}
}
releases = releases.reversed.toList(); releases = releases.reversed.toList();
dynamic targetRelease; dynamic targetRelease;
var prerrelsSkipped = 0; var prerrelsSkipped = 0;

View File

@@ -1,10 +1,7 @@
import 'dart:io'; import 'dart:io';
import 'dart:math';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:http/http.dart';
import 'package:obtainium/custom_errors.dart';
import 'package:obtainium/pages/home.dart'; import 'package:obtainium/pages/home.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/logs_provider.dart';
@@ -22,7 +19,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.26'; const String currentVersion = '0.14.0';
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
@@ -72,89 +69,6 @@ Future<void> loadTranslations() async {
fallbackTranslations: controller.fallbackTranslations); fallbackTranslations: controller.fallbackTranslations);
} }
@pragma('vm:entry-point')
Future<void> bgUpdateCheck(int taskId, Map<String, dynamic>? params) async {
WidgetsFlutterBinding.ensureInitialized();
await EasyLocalization.ensureInitialized();
await loadTranslations();
LogsProvider logs = LogsProvider();
logs.add(tr('startedBgUpdateTask'));
int? ignoreAfterMicroseconds = params?['ignoreAfterMicroseconds'];
await AndroidAlarmManager.initialize();
DateTime? ignoreAfter = ignoreAfterMicroseconds != null
? DateTime.fromMicrosecondsSinceEpoch(ignoreAfterMicroseconds)
: null;
logs.add(tr('bgUpdateIgnoreAfterIs', args: [ignoreAfter.toString()]));
var notificationsProvider = NotificationsProvider();
await notificationsProvider.notify(checkingUpdatesNotification);
try {
var appsProvider = AppsProvider();
await notificationsProvider.cancel(ErrorCheckingUpdatesNotification('').id);
await appsProvider.loadApps();
List<String> existingUpdateIds =
appsProvider.findExistingUpdates(installedOnly: true);
DateTime nextIgnoreAfter = DateTime.now();
String? err;
try {
logs.add(tr('startedActualBGUpdateCheck'));
await appsProvider.checkUpdates(
ignoreAppsCheckedAfter: ignoreAfter, throwErrorsForRetry: true);
} catch (e) {
if (e is RateLimitError || e is ClientException) {
var remainingMinutes = e is RateLimitError ? e.remainingMinutes : 15;
logs.add(
plural('bgUpdateGotErrorRetryInMinutes', remainingMinutes, args: [
e is ClientException
? '${(e).message}, ${e.uri?.path}'
: e.toString(),
remainingMinutes.toString()
]));
AndroidAlarmManager.oneShot(Duration(minutes: remainingMinutes),
Random().nextInt(pow(2, 31) as int), bgUpdateCheck, params: {
'ignoreAfterMicroseconds': nextIgnoreAfter.microsecondsSinceEpoch
});
} else {
err = e.toString();
}
}
List<App> newUpdates = appsProvider
.findExistingUpdates(installedOnly: true)
.where((id) => !existingUpdateIds.contains(id))
.map((e) => appsProvider.apps[e]!.app)
.toList();
// TODO: This silent update code doesn't work yet
// List<String> silentlyUpdated = await appsProvider
// .downloadAndInstallLatestApp(
// [...newUpdates.map((e) => e.id), ...existingUpdateIds], null);
// if (silentlyUpdated.isNotEmpty) {
// newUpdates = newUpdates
// .where((element) => !silentlyUpdated.contains(element.id))
// .toList();
// notificationsProvider.notify(
// SilentUpdateNotification(
// silentlyUpdated.map((e) => appsProvider.apps[e]!.app).toList()),
// cancelExisting: true);
// }
logs.add(
plural('bgCheckFoundUpdatesWillNotifyIfNeeded', newUpdates.length));
if (newUpdates.isNotEmpty) {
notificationsProvider.notify(UpdateNotification(newUpdates));
}
if (err != null) {
throw err;
}
} catch (e) {
notificationsProvider
.notify(ErrorCheckingUpdatesNotification(e.toString()));
} finally {
logs.add(tr('bgUpdateTaskFinished'));
await notificationsProvider.cancel(checkingUpdatesNotification.id);
}
}
void main() async { void main() async {
WidgetsFlutterBinding.ensureInitialized(); WidgetsFlutterBinding.ensureInitialized();
try { try {
@@ -212,7 +126,7 @@ class _ObtainiumState extends State<Obtainium> {
} else { } else {
bool isFirstRun = settingsProvider.checkAndFlipFirstRun(); bool isFirstRun = settingsProvider.checkAndFlipFirstRun();
if (isFirstRun) { if (isFirstRun) {
logs.add(tr('firstRun')); logs.add('This is the first ever run of Obtainium.');
// If this is the first run, ask for notification permissions and add Obtainium to the Apps list // If this is the first run, ask for notification permissions and add Obtainium to the Apps list
Permission.notification.request(); Permission.notification.request();
appsProvider.saveApps([ appsProvider.saveApps([
@@ -239,22 +153,28 @@ class _ObtainiumState extends State<Obtainium> {
settingsProvider.resetLocaleSafe(context); settingsProvider.resetLocaleSafe(context);
} }
// Register the background update task according to the user's setting // Register the background update task according to the user's setting
if (existingUpdateInterval != settingsProvider.updateInterval) { var actualUpdateInterval = settingsProvider.updateInterval;
if (existingUpdateInterval != -1) { if (existingUpdateInterval != actualUpdateInterval) {
logs.add(tr('settingUpdateCheckIntervalTo', if (actualUpdateInterval == 0) {
args: [settingsProvider.updateInterval.toString()]));
}
existingUpdateInterval = settingsProvider.updateInterval;
if (existingUpdateInterval == 0) {
AndroidAlarmManager.cancel(bgUpdateCheckAlarmId); AndroidAlarmManager.cancel(bgUpdateCheckAlarmId);
} else { } else {
AndroidAlarmManager.periodic( var settingChanged = existingUpdateInterval != -1;
Duration(minutes: existingUpdateInterval), var lastCheckWasTooLongAgo = actualUpdateInterval != 0 &&
bgUpdateCheckAlarmId, settingsProvider.lastBGCheckTime
bgUpdateCheck, .add(Duration(minutes: actualUpdateInterval + 60))
rescheduleOnReboot: true, .isBefore(DateTime.now());
wakeup: true); if (settingChanged || lastCheckWasTooLongAgo) {
logs.add(
'Update interval was set to ${actualUpdateInterval.toString()} (reason: ${settingChanged ? 'setting changed' : 'last check was ${settingsProvider.lastBGCheckTime.toLocal().toString()}'}).');
AndroidAlarmManager.periodic(
Duration(minutes: actualUpdateInterval),
bgUpdateCheckAlarmId,
bgUpdateCheck,
rescheduleOnReboot: true,
wakeup: true);
}
} }
existingUpdateInterval = actualUpdateInterval;
} }
} }

View File

@@ -153,10 +153,10 @@ class _AppPageState extends State<AppPage> {
crossAxisAlignment: CrossAxisAlignment.stretch, crossAxisAlignment: CrossAxisAlignment.stretch,
children: [ children: [
const SizedBox(height: 125), const SizedBox(height: 125),
app?.installedInfo != null app?.icon != null
? Row(mainAxisAlignment: MainAxisAlignment.center, children: [ ? Row(mainAxisAlignment: MainAxisAlignment.center, children: [
Image.memory( Image.memory(
app!.installedInfo!.icon!, app!.icon!,
height: 150, height: 150,
gaplessPlayback: true, gaplessPlayback: true,
) )
@@ -339,7 +339,8 @@ class _AppPageState extends State<AppPage> {
HapticFeedback.heavyImpact(); HapticFeedback.heavyImpact();
var res = await appsProvider.downloadAndInstallLatestApps( var res = await appsProvider.downloadAndInstallLatestApps(
app?.app.id != null ? [app!.app.id] : [], app?.app.id != null ? [app!.app.id] : [],
globalNavigatorKey.currentContext); globalNavigatorKey.currentContext,
settingsProvider);
if (res.isNotEmpty && mounted) { if (res.isNotEmpty && mounted) {
Navigator.of(context).pop(); Navigator.of(context).pop();
} }

View File

@@ -381,7 +381,8 @@ class AppsPageState extends State<AppsPage> {
: () { : () {
appsProvider.downloadAndInstallLatestApps( appsProvider.downloadAndInstallLatestApps(
[listedApps[appIndex].app.id], [listedApps[appIndex].app.id],
globalNavigatorKey.currentContext).catchError((e) { globalNavigatorKey.currentContext,
settingsProvider).catchError((e) {
showError(e, context); showError(e, context);
return <String>[]; return <String>[];
}); });
@@ -393,9 +394,9 @@ class AppsPageState extends State<AppsPage> {
} }
getAppIcon(int appIndex) { getAppIcon(int appIndex) {
return listedApps[appIndex].installedInfo != null return listedApps[appIndex].icon != null
? Image.memory( ? Image.memory(
listedApps[appIndex].installedInfo!.icon!, listedApps[appIndex].icon!,
gaplessPlayback: true, gaplessPlayback: true,
) )
: Row( : Row(
@@ -683,9 +684,8 @@ class AppsPageState extends State<AppsPage> {
toInstall.addAll(trackOnlyUpdateIdsAllOrSelected); toInstall.addAll(trackOnlyUpdateIdsAllOrSelected);
} }
appsProvider appsProvider
.downloadAndInstallLatestApps( .downloadAndInstallLatestApps(toInstall,
toInstall, globalNavigatorKey.currentContext, globalNavigatorKey.currentContext, settingsProvider)
settingsProvider: settingsProvider)
.catchError((e) { .catchError((e) {
showError(e, context); showError(e, context);
return <String>[]; return <String>[];

View File

@@ -1,3 +1,5 @@
import 'package:android_alarm_manager_plus/android_alarm_manager_plus.dart';
import 'package:device_info_plus/device_info_plus.dart';
import 'package:easy_localization/easy_localization.dart'; import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:obtainium/components/custom_app_bar.dart'; import 'package:obtainium/components/custom_app_bar.dart';
@@ -184,6 +186,10 @@ class _SettingsPageState extends State<SettingsPage> {
} }
}); });
const height8 = SizedBox(
height: 8,
);
const height16 = SizedBox( const height16 = SizedBox(
height: 16, height: 16,
); );
@@ -211,6 +217,47 @@ class _SettingsPageState extends State<SettingsPage> {
color: Theme.of(context).colorScheme.primary), color: Theme.of(context).colorScheme.primary),
), ),
intervalDropdown, intervalDropdown,
FutureBuilder(
builder: (ctx, val) {
return (val.data?.version.sdkInt ?? 0) >= 30
? Column(
crossAxisAlignment:
CrossAxisAlignment.start,
children: [
height16,
Row(
mainAxisAlignment:
MainAxisAlignment
.spaceBetween,
children: [
Flexible(
child: Text(tr(
'enableBackgroundUpdates'))),
Switch(
value: settingsProvider
.enableBackgroundUpdates,
onChanged: (value) {
settingsProvider
.enableBackgroundUpdates =
value;
})
],
),
height8,
Text(tr('backgroundUpdateReqsExplanation'),
style: Theme.of(context)
.textTheme
.labelSmall),
Text(tr('backgroundUpdateLimitsExplanation'),
style: Theme.of(context)
.textTheme
.labelSmall),
height8
],
)
: const SizedBox.shrink();
},
future: DeviceInfoPlugin().androidInfo),
height16, height16,
Row( Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
@@ -462,7 +509,44 @@ class _SettingsPageState extends State<SettingsPage> {
label: Text(tr('appLogs'))), label: Text(tr('appLogs'))),
], ],
), ),
height16, const Divider(
height: 32,
),
Padding(
padding: const EdgeInsets.fromLTRB(16, 0, 16, 16),
child: Column(children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
const Flexible(child: Text('Debug Menu')),
Switch(
value: settingsProvider.showDebugOpts,
onChanged: (value) {
settingsProvider.showDebugOpts = value;
})
],
),
if (settingsProvider.showDebugOpts)
Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
height16,
TextButton(
onPressed: () {
AndroidAlarmManager.oneShot(
const Duration(seconds: 0),
bgUpdateCheckAlarmId + 200,
bgUpdateCheck);
showError(
'Background task started - check logs.',
context);
},
child:
const Text('Run Background Update Check Now'))
],
),
]),
),
], ],
), ),
) )

View File

@@ -5,6 +5,7 @@ import 'dart:async';
import 'dart:convert'; import 'dart:convert';
import 'dart:io'; import 'dart:io';
import 'package:android_alarm_manager_plus/android_alarm_manager_plus.dart';
import 'package:android_intent_plus/flag.dart'; import 'package:android_intent_plus/flag.dart';
import 'package:android_package_installer/android_package_installer.dart'; import 'package:android_package_installer/android_package_installer.dart';
import 'package:android_package_manager/android_package_manager.dart'; import 'package:android_package_manager/android_package_manager.dart';
@@ -12,15 +13,13 @@ 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:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:installed_apps/app_info.dart';
import 'package:installed_apps/installed_apps.dart';
import 'package:obtainium/components/generated_form.dart'; import 'package:obtainium/components/generated_form.dart';
import 'package:obtainium/components/generated_form_modal.dart'; import 'package:obtainium/components/generated_form_modal.dart';
import 'package:obtainium/custom_errors.dart'; import 'package:obtainium/custom_errors.dart';
import 'package:obtainium/main.dart';
import 'package:obtainium/providers/logs_provider.dart'; import 'package:obtainium/providers/logs_provider.dart';
import 'package:obtainium/providers/notifications_provider.dart'; import 'package:obtainium/providers/notifications_provider.dart';
import 'package:obtainium/providers/settings_provider.dart'; import 'package:obtainium/providers/settings_provider.dart';
import 'package:package_archive_info/package_archive_info.dart';
import 'package:permission_handler/permission_handler.dart'; import 'package:permission_handler/permission_handler.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'package:path_provider/path_provider.dart'; import 'package:path_provider/path_provider.dart';
@@ -35,13 +34,14 @@ final pm = AndroidPackageManager();
class AppInMemory { class AppInMemory {
late App app; late App app;
double? downloadProgress; double? downloadProgress;
AppInfo? installedInfo; PackageInfo? installedInfo;
Uint8List? icon;
AppInMemory(this.app, this.downloadProgress, this.installedInfo); AppInMemory(this.app, this.downloadProgress, this.installedInfo, this.icon);
AppInMemory deepCopy() => AppInMemory deepCopy() =>
AppInMemory(app.deepCopy(), downloadProgress, installedInfo); AppInMemory(app.deepCopy(), downloadProgress, installedInfo, icon);
String get name => app.overrideName ?? installedInfo?.name ?? app.finalName; String get name => app.overrideName ?? app.finalName;
} }
class DownloadedApk { class DownloadedApk {
@@ -100,6 +100,38 @@ Set<String> findStandardFormatsForVersion(String version, bool strict) {
return results; return results;
} }
moveStrToEnd(List<String> arr, String str, {String? strB}) {
String? temp;
arr.removeWhere((element) {
bool res = element == str || element == strB;
if (res) {
temp = element;
}
return res;
});
if (temp != null) {
arr = [...arr, temp!];
}
return arr;
}
moveStrToEndMapEntryWithCount(
List<MapEntry<String, int>> arr, MapEntry<String, int> str,
{MapEntry<String, int>? strB}) {
MapEntry<String, int>? temp;
arr.removeWhere((element) {
bool res = element.key == str.key || element.key == strB?.key;
if (res) {
temp = element;
}
return res;
});
if (temp != null) {
arr = [...arr, temp!];
}
return arr;
}
class AppsProvider with ChangeNotifier { class AppsProvider with ChangeNotifier {
// In memory App state (should always be kept in sync with local storage versions) // In memory App state (should always be kept in sync with local storage versions)
Map<String, AppInMemory> apps = {}; Map<String, AppInMemory> apps = {};
@@ -115,7 +147,7 @@ class AppsProvider with ChangeNotifier {
Iterable<AppInMemory> getAppValues() => apps.values.map((a) => a.deepCopy()); Iterable<AppInMemory> getAppValues() => apps.values.map((a) => a.deepCopy());
AppsProvider() { AppsProvider({isBg = false}) {
// Subscribe to changes in the app foreground status // Subscribe to changes in the app foreground status
foregroundStream = FGBGEvents.stream.asBroadcastStream(); foregroundStream = FGBGEvents.stream.asBroadcastStream();
foregroundSubscription = foregroundStream?.listen((event) async { foregroundSubscription = foregroundStream?.listen((event) async {
@@ -133,17 +165,21 @@ class AppsProvider with ChangeNotifier {
APKDir.createSync(); APKDir.createSync();
} }
} }
// Load Apps into memory (in background, this is done later instead of in the constructor) if (!isBg) {
await loadApps(); // Load Apps into memory (in background processes, this is done later instead of in the constructor)
// Delete any partial APKs await loadApps();
var cutoff = DateTime.now().subtract(const Duration(days: 7)); // Delete any partial APKs (if safe to do so)
APKDir.listSync() var cutoff = DateTime.now().subtract(const Duration(days: 7));
.where((element) => APKDir.listSync()
element.path.endsWith('.part') || .where((element) =>
element.statSync().modified.isBefore(cutoff)) element.path.endsWith('.part') ||
.forEach((partialApk) { element.statSync().modified.isBefore(cutoff))
partialApk.delete(recursive: true); .forEach((partialApk) {
}); if (!areDownloadsRunning()) {
partialApk.delete(recursive: true);
}
});
}
}(); }();
} }
@@ -218,19 +254,19 @@ class AppsProvider with ChangeNotifier {
return downloadedFile; return downloadedFile;
} }
Future<File> handleAPKIDChange(App app, PackageArchiveInfo newInfo, Future<File> handleAPKIDChange(App app, PackageInfo newInfo,
File downloadedFile, String downloadUrl) async { 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 // 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 // The former case should be handled (give the App its real ID), the latter is a security issue
if (app.id != newInfo.packageName) { if (app.id != newInfo.packageName) {
var isTempId = SourceProvider().isTempId(app); var isTempId = SourceProvider().isTempId(app);
if (apps[app.id] != null && !isTempId && !app.allowIdChange) { if (apps[app.id] != null && !isTempId && !app.allowIdChange) {
throw IDChangedError(newInfo.packageName); throw IDChangedError(newInfo.packageName!);
} }
var idChangeWasAllowed = app.allowIdChange; var idChangeWasAllowed = app.allowIdChange;
app.allowIdChange = false; app.allowIdChange = false;
var originalAppId = app.id; var originalAppId = app.id;
app.id = newInfo.packageName; app.id = newInfo.packageName!;
downloadedFile = downloadedFile.renameSync( downloadedFile = downloadedFile.renameSync(
'${downloadedFile.parent.path}/${app.id}-${downloadUrl.hashCode}.${downloadedFile.path.split('.').last}'); '${downloadedFile.parent.path}/${app.id}-${downloadUrl.hashCode}.${downloadedFile.path.split('.').last}');
if (apps[originalAppId] != null) { if (apps[originalAppId] != null) {
@@ -241,9 +277,8 @@ class AppsProvider with ChangeNotifier {
return downloadedFile; return downloadedFile;
} }
Future<Object> downloadApp(App app, BuildContext? context) async { Future<Object> downloadApp(App app, BuildContext? context,
NotificationsProvider? notificationsProvider = {NotificationsProvider? notificationsProvider}) async {
context?.read<NotificationsProvider>();
var notifId = DownloadNotification(app.finalName, 0).id; var notifId = DownloadNotification(app.finalName, 0).id;
if (apps[app.id] != null) { if (apps[app.id] != null) {
apps[app.id]!.downloadProgress = 0; apps[app.id]!.downloadProgress = 0;
@@ -279,11 +314,12 @@ class AppsProvider with ChangeNotifier {
notif = DownloadNotification(app.finalName, -1); notif = DownloadNotification(app.finalName, -1);
notificationsProvider?.notify(notif); notificationsProvider?.notify(notif);
} }
PackageArchiveInfo? newInfo; PackageInfo? newInfo;
var isAPK = downloadedFile.path.toLowerCase().endsWith('.apk'); var isAPK = downloadedFile.path.toLowerCase().endsWith('.apk');
Directory? xapkDir; Directory? xapkDir;
if (isAPK) { if (isAPK) {
newInfo = await PackageArchiveInfo.fromPath(downloadedFile.path); newInfo = await pm.getPackageArchiveInfo(
archiveFilePath: downloadedFile.path);
} else { } else {
// Assume XAPK // Assume XAPK
String xapkDirPath = '${downloadedFile.path}-dir'; String xapkDirPath = '${downloadedFile.path}-dir';
@@ -293,10 +329,11 @@ class AppsProvider with ChangeNotifier {
.listSync() .listSync()
.where((e) => e.path.toLowerCase().endsWith('.apk')) .where((e) => e.path.toLowerCase().endsWith('.apk'))
.toList(); .toList();
newInfo = await PackageArchiveInfo.fromPath(apks.first.path); newInfo =
await pm.getPackageArchiveInfo(archiveFilePath: apks.first.path);
} }
downloadedFile = downloadedFile =
await handleAPKIDChange(app, newInfo, downloadedFile, downloadUrl); await handleAPKIDChange(app, newInfo!, downloadedFile, downloadUrl);
// Delete older versions of the file if any // 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;
@@ -324,7 +361,11 @@ class AppsProvider with ChangeNotifier {
.where((element) => element.downloadProgress != null) .where((element) => element.downloadProgress != null)
.isNotEmpty; .isNotEmpty;
Future<bool> canInstallSilently(App app) async { Future<bool> canInstallSilently(
App app, SettingsProvider settingsProvider) async {
if (!settingsProvider.enableBackgroundUpdates) {
return false;
}
if (app.apkUrls.length > 1) { if (app.apkUrls.length > 1) {
// Manual API selection means silent install is not possible // Manual API selection means silent install is not possible
return false; return false;
@@ -344,14 +385,8 @@ class AppsProvider with ChangeNotifier {
// If we did not install the app (or it isn't installed), silent install is not possible // If we did not install the app (or it isn't installed), silent install is not possible
return false; return false;
} }
int? targetSDK; int? targetSDK =
try { (await getInstalledInfo(app.id))?.applicationInfo?.targetSdkVersion;
targetSDK = (await pm.getPackageInfo(packageName: app.id))
?.applicationInfo
?.targetSdkVersion;
} catch (e) {
// Weird if you get here - ignore
}
// The OS must also be new enough and the APK should target a new enough API // The OS must also be new enough and the APK should target a new enough API
return osInfo.version.sdkInt >= 30 && return osInfo.version.sdkInt >= 30 &&
@@ -371,14 +406,8 @@ class AppsProvider with ChangeNotifier {
} }
} }
Future<bool> canDowngradeApps() async { Future<bool> canDowngradeApps() async =>
try { (await getInstalledInfo('com.berdik.letmedowngrade')) != null;
await InstalledApps.getAppInfo('com.berdik.letmedowngrade');
return true;
} catch (e) {
return false;
}
}
Future<void> unzipFile(String filePath, String destinationPath) async { Future<void> unzipFile(String filePath, String destinationPath) async {
await ZipFile.extractToDirectory( await ZipFile.extractToDirectory(
@@ -419,15 +448,11 @@ class AppsProvider with ChangeNotifier {
} }
Future<bool> installApk(DownloadedApk file) async { Future<bool> installApk(DownloadedApk file) async {
var newInfo = await PackageArchiveInfo.fromPath(file.file.path); var newInfo =
AppInfo? appInfo; await pm.getPackageArchiveInfo(archiveFilePath: file.file.path);
try { PackageInfo? appInfo = await getInstalledInfo(apps[file.appId]!.app.id);
appInfo = await InstalledApps.getAppInfo(apps[file.appId]!.app.id);
} catch (e) {
// OK
}
if (appInfo != null && if (appInfo != null &&
int.parse(newInfo.buildNumber) < appInfo.versionCode! && newInfo!.versionCode! < appInfo.versionCode! &&
!(await canDowngradeApps())) { !(await canDowngradeApps())) {
throw DowngradeError(); throw DowngradeError();
} }
@@ -521,9 +546,11 @@ class AppsProvider with ChangeNotifier {
// If no BuildContext is provided, apps that require user interaction are ignored // If no BuildContext is provided, apps that require user interaction are ignored
// If user input is needed and the App is in the background, a notification is sent to get the user's attention // If user input is needed and the App is in the background, a notification is sent to get the user's attention
// Returns an array of Ids for Apps that were successfully downloaded, regardless of installation result // Returns an array of Ids for Apps that were successfully downloaded, regardless of installation result
Future<List<String>> downloadAndInstallLatestApps( Future<List<String>> downloadAndInstallLatestApps(List<String> appIds,
List<String> appIds, BuildContext? context, BuildContext? context, SettingsProvider settingsProvider,
{SettingsProvider? settingsProvider}) async { {NotificationsProvider? notificationsProvider}) async {
notificationsProvider =
notificationsProvider ?? context?.read<NotificationsProvider>();
List<String> appsToInstall = []; List<String> appsToInstall = [];
List<String> trackOnlyAppsToUpdate = []; List<String> trackOnlyAppsToUpdate = [];
// For all specified Apps, filter out those for which: // For all specified Apps, filter out those for which:
@@ -549,7 +576,8 @@ class AppsProvider with ChangeNotifier {
apps[id]!.app.preferredApkIndex = urlInd; apps[id]!.app.preferredApkIndex = urlInd;
await saveApps([apps[id]!.app]); await saveApps([apps[id]!.app]);
} }
if (context != null || await canInstallSilently(apps[id]!.app)) { if (context != null ||
await canInstallSilently(apps[id]!.app, settingsProvider)) {
appsToInstall.add(id); appsToInstall.add(id);
} }
} }
@@ -569,22 +597,15 @@ class AppsProvider with ChangeNotifier {
List<String> installedIds = []; List<String> installedIds = [];
// Move Obtainium to the end of the line (let all other apps update first) // Move Obtainium to the end of the line (let all other apps update first)
String? temp; appsToInstall =
appsToInstall.removeWhere((element) { moveStrToEnd(appsToInstall, obtainiumId, strB: obtainiumTempId);
bool res = element == obtainiumId || element == obtainiumTempId;
if (res) {
temp = element;
}
return res;
});
if (temp != null) {
appsToInstall = [...appsToInstall, temp!];
}
for (var id in appsToInstall) { for (var id in appsToInstall) {
try { try {
// ignore: use_build_context_synchronously var downloadedArtifact =
var downloadedArtifact = await downloadApp(apps[id]!.app, context); // ignore: use_build_context_synchronously
await downloadApp(apps[id]!.app, context,
notificationsProvider: notificationsProvider);
DownloadedApk? downloadedFile; DownloadedApk? downloadedFile;
DownloadedXApkDir? downloadedDir; DownloadedXApkDir? downloadedDir;
if (downloadedArtifact is DownloadedApk) { if (downloadedArtifact is DownloadedApk) {
@@ -592,10 +613,10 @@ class AppsProvider with ChangeNotifier {
} else { } else {
downloadedDir = downloadedArtifact as DownloadedXApkDir; downloadedDir = downloadedArtifact as DownloadedXApkDir;
} }
bool willBeSilent = await canInstallSilently( var appId = downloadedFile?.appId ?? downloadedDir!.appId;
apps[downloadedFile?.appId ?? downloadedDir!.appId]!.app); bool willBeSilent =
if (!(await settingsProvider?.getInstallPermission(enforce: false) ?? await canInstallSilently(apps[appId]!.app, settingsProvider);
true)) { if (!(await settingsProvider.getInstallPermission(enforce: false))) {
throw ObtainiumError(tr('cancelled')); throw ObtainiumError(tr('cancelled'));
} }
if (!willBeSilent && context != null) { if (!willBeSilent && context != null) {
@@ -606,9 +627,24 @@ class AppsProvider with ChangeNotifier {
notifyListeners(); notifyListeners();
try { try {
if (downloadedFile != null) { if (downloadedFile != null) {
await installApk(downloadedFile); if (willBeSilent && context == null) {
// Would await forever - workaround - TODO
installApk(downloadedFile);
} else {
await installApk(downloadedFile);
}
} else { } else {
await installXApkDir(downloadedDir!); if (willBeSilent && context == null) {
// Would await forever - workaround - TODO
installXApkDir(downloadedDir!);
} else {
await installXApkDir(downloadedDir!);
}
}
if (willBeSilent && context == null) {
notificationsProvider?.notify(SilentUpdateAttemptNotification(
[apps[appId]!.app],
id: appId.hashCode));
} }
} finally { } finally {
apps[id]?.downloadProgress = null; apps[id]?.downloadProgress = null;
@@ -624,8 +660,6 @@ class AppsProvider with ChangeNotifier {
throw errors; throw errors;
} }
NotificationsProvider().cancel(UpdateNotification([]).id);
return installedIds; return installedIds;
} }
@@ -638,27 +672,17 @@ class AppsProvider with ChangeNotifier {
return appsDir; return appsDir;
} }
Future<AppInfo?> getInstalledInfo(String? packageName) async { Future<PackageInfo?> getInstalledInfo(String? packageName) async {
if (packageName != null) { if (packageName != null) {
try { try {
return await InstalledApps.getAppInfo(packageName); return await pm.getPackageInfo(packageName: packageName);
} catch (e) { } catch (e) {
// OK print(e); // OK
} }
} }
return null; return null;
} }
Future<bool> doesInstalledAppsPluginWork() async {
bool res = false;
try {
res = (await InstalledApps.getAppInfo(obtainiumId)).versionName != null;
} catch (e) {
//
}
return res;
}
bool isVersionDetectionPossible(AppInMemory? app) { bool isVersionDetectionPossible(AppInMemory? app) {
return app?.app.additionalSettings['trackOnly'] != true && return app?.app.additionalSettings['trackOnly'] != true &&
app?.app.additionalSettings['versionDetection'] != app?.app.additionalSettings['versionDetection'] !=
@@ -672,7 +696,8 @@ class AppsProvider with ChangeNotifier {
// Given an App and it's on-device info... // Given an App and it's on-device info...
// Reconcile unexpected differences between its reported installed version, real installed version, and reported latest version // Reconcile unexpected differences between its reported installed version, real installed version, and reported latest version
App? getCorrectedInstallStatusAppIfPossible(App app, AppInfo? installedInfo) { App? getCorrectedInstallStatusAppIfPossible(
App app, PackageInfo? installedInfo) {
var modded = false; var modded = false;
var trackOnly = app.additionalSettings['trackOnly'] == true; var trackOnly = app.additionalSettings['trackOnly'] == true;
var noVersionDetection = app.additionalSettings['versionDetection'] != var noVersionDetection = app.additionalSettings['versionDetection'] !=
@@ -718,22 +743,12 @@ class AppsProvider with ChangeNotifier {
if (installedInfo != null && if (installedInfo != null &&
app.additionalSettings['versionDetection'] == app.additionalSettings['versionDetection'] ==
'standardVersionDetection' && 'standardVersionDetection' &&
!isVersionDetectionPossible(AppInMemory(app, null, installedInfo))) { !isVersionDetectionPossible(
AppInMemory(app, null, installedInfo, null))) {
app.additionalSettings['versionDetection'] = 'noVersionDetection'; app.additionalSettings['versionDetection'] = 'noVersionDetection';
logs.add('Could not reconcile version formats for: ${app.id}'); logs.add('Could not reconcile version formats for: ${app.id}');
modded = true; modded = true;
} }
// if (app.installedVersion != null &&
// app.additionalSettings['versionDetection'] ==
// 'standardVersionDetection') {
// var correctedInstalledVersion =
// reconcileVersionDifferences(app.installedVersion!, app.latestVersion);
// if (correctedInstalledVersion == null) {
// app.additionalSettings['versionDetection'] = 'noVersionDetection';
// logs.add('Could not reconcile version formats for: ${app.id}');
// modded = true;
// }
// }
return modded ? app : null; return modded ? app : null;
} }
@@ -772,7 +787,7 @@ class AppsProvider with ChangeNotifier {
: false; : false;
} }
Future<void> loadApps() async { Future<void> loadApps({String? singleId}) async {
while (loadingApps) { while (loadingApps) {
await Future.delayed(const Duration(microseconds: 1)); await Future.delayed(const Duration(microseconds: 1));
} }
@@ -783,6 +798,10 @@ class AppsProvider with ChangeNotifier {
List<App?> newApps = (await getAppsDir()) // Parse Apps from JSON List<App?> newApps = (await getAppsDir()) // Parse Apps from JSON
.listSync() .listSync()
.where((item) => item.path.toLowerCase().endsWith('.json')) .where((item) => item.path.toLowerCase().endsWith('.json'))
.where((item) =>
singleId == null ||
item.path.split('/').last.toLowerCase() ==
'${singleId.toLowerCase()}.json')
.map((e) { .map((e) {
try { try {
return App.fromJson(jsonDecode(File(e.path).readAsStringSync())); return App.fromJson(jsonDecode(File(e.path).readAsStringSync()));
@@ -802,9 +821,9 @@ class AppsProvider with ChangeNotifier {
sp.getSource(app.url, overrideSource: app.overrideSource); sp.getSource(app.url, overrideSource: app.overrideSource);
apps.update( apps.update(
app.id, app.id,
(value) => (value) => AppInMemory(
AppInMemory(app, value.downloadProgress, value.installedInfo), app, value.downloadProgress, value.installedInfo, value.icon),
ifAbsent: () => AppInMemory(app, null, null)); ifAbsent: () => AppInMemory(app, null, null, null));
} catch (e) { } catch (e) {
errors.add([app.id, app.finalName, e.toString()]); errors.add([app.id, app.finalName, e.toString()]);
} }
@@ -817,34 +836,39 @@ class AppsProvider with ChangeNotifier {
AppsRemovedNotification(errors.map((e) => [e[1], e[2]]).toList())); AppsRemovedNotification(errors.map((e) => [e[1], e[2]]).toList()));
} }
if (await doesInstalledAppsPluginWork()) { for (var app in apps.values) {
for (var app in apps.values) { // Get install status and other OS info for each App (slow)
// Check install status for each App (slow) apps[app.app.id]?.installedInfo = await getInstalledInfo(app.app.id);
apps[app.app.id]?.installedInfo = await getInstalledInfo(app.app.id); apps[app.app.id]?.icon =
notifyListeners(); await apps[app.app.id]?.installedInfo?.applicationInfo?.getAppIcon();
apps[app.app.id]?.app.name = await (apps[app.app.id]
?.installedInfo
?.applicationInfo
?.getAppLabel()) ??
app.name;
notifyListeners();
}
// Reconcile version differences
List<App> modifiedApps = [];
for (var app in apps.values) {
var moddedApp =
getCorrectedInstallStatusAppIfPossible(app.app, app.installedInfo);
if (moddedApp != null) {
modifiedApps.add(moddedApp);
} }
// Reconcile version differences }
List<App> modifiedApps = []; if (modifiedApps.isNotEmpty) {
for (var app in apps.values) { await saveApps(modifiedApps, attemptToCorrectInstallStatus: false);
var moddedApp = var removedAppIds = modifiedApps
getCorrectedInstallStatusAppIfPossible(app.app, app.installedInfo); .where((a) => a.installedVersion == null)
if (moddedApp != null) { .map((e) => e.id)
modifiedApps.add(moddedApp); .toList();
} // After reconciliation, delete externally uninstalled Apps if needed
} if (removedAppIds.isNotEmpty) {
if (modifiedApps.isNotEmpty) { var settingsProvider = SettingsProvider();
await saveApps(modifiedApps, attemptToCorrectInstallStatus: false); await settingsProvider.initializeSettings();
var removedAppIds = modifiedApps if (settingsProvider.removeOnExternalUninstall) {
.where((a) => a.installedVersion == null) await removeApps(removedAppIds);
.map((e) => e.id)
.toList();
// After reconciliation, delete externally uninstalled Apps if needed
if (removedAppIds.isNotEmpty) {
var settingsProvider = SettingsProvider();
await settingsProvider.initializeSettings();
if (settingsProvider.removeOnExternalUninstall) {
await removeApps(removedAppIds);
}
} }
} }
} }
@@ -856,12 +880,12 @@ class AppsProvider with ChangeNotifier {
Future<void> saveApps(List<App> apps, Future<void> saveApps(List<App> apps,
{bool attemptToCorrectInstallStatus = true, {bool attemptToCorrectInstallStatus = true,
bool onlyIfExists = true}) async { bool onlyIfExists = true}) async {
attemptToCorrectInstallStatus = attemptToCorrectInstallStatus = attemptToCorrectInstallStatus;
attemptToCorrectInstallStatus && (await doesInstalledAppsPluginWork());
for (var a in apps) { for (var a in apps) {
var app = a.deepCopy(); var app = a.deepCopy();
AppInfo? info = await getInstalledInfo(app.id); PackageInfo? info = await getInstalledInfo(app.id);
app.name = info?.name ?? app.name; var icon = await info?.applicationInfo?.getAppIcon();
app.name = await (info?.applicationInfo?.getAppLabel()) ?? app.name;
if (attemptToCorrectInstallStatus) { if (attemptToCorrectInstallStatus) {
app = getCorrectedInstallStatusAppIfPossible(app, info) ?? app; app = getCorrectedInstallStatusAppIfPossible(app, info) ?? app;
} }
@@ -870,9 +894,10 @@ class AppsProvider with ChangeNotifier {
.writeAsStringSync(jsonEncode(app.toJson())); .writeAsStringSync(jsonEncode(app.toJson()));
} }
try { try {
this.apps.update( this.apps.update(app.id,
app.id, (value) => AppInMemory(app, value.downloadProgress, info), (value) => AppInMemory(app, value.downloadProgress, info, icon),
ifAbsent: onlyIfExists ? null : () => AppInMemory(app, null, info)); ifAbsent:
onlyIfExists ? null : () => AppInMemory(app, null, info, icon));
} catch (e) { } catch (e) {
if (e is! ArgumentError || e.name != 'key') { if (e is! ArgumentError || e.name != 'key') {
rethrow; rethrow;
@@ -987,6 +1012,22 @@ class AppsProvider with ChangeNotifier {
return newApp.latestVersion != currentApp.latestVersion ? newApp : null; return newApp.latestVersion != currentApp.latestVersion ? newApp : null;
} }
List<String> getAppsSortedByUpdateCheckTime(
{DateTime? ignoreAppsCheckedAfter}) {
List<String> appIds = apps.values
.where((app) =>
app.app.lastUpdateCheck == null ||
ignoreAppsCheckedAfter == null ||
app.app.lastUpdateCheck!.isBefore(ignoreAppsCheckedAfter))
.map((e) => e.app.id)
.toList();
appIds.sort((a, b) =>
(apps[a]!.app.lastUpdateCheck ?? DateTime.fromMicrosecondsSinceEpoch(0))
.compareTo(apps[b]!.app.lastUpdateCheck ??
DateTime.fromMicrosecondsSinceEpoch(0)));
return appIds;
}
Future<List<App>> checkUpdates( Future<List<App>> checkUpdates(
{DateTime? ignoreAppsCheckedAfter, {DateTime? ignoreAppsCheckedAfter,
bool throwErrorsForRetry = false}) async { bool throwErrorsForRetry = false}) async {
@@ -995,17 +1036,8 @@ class AppsProvider with ChangeNotifier {
if (!gettingUpdates) { if (!gettingUpdates) {
gettingUpdates = true; gettingUpdates = true;
try { try {
List<String> appIds = apps.values List<String> appIds = getAppsSortedByUpdateCheckTime(
.where((app) => ignoreAppsCheckedAfter: ignoreAppsCheckedAfter);
app.app.lastUpdateCheck == null ||
ignoreAppsCheckedAfter == null ||
app.app.lastUpdateCheck!.isBefore(ignoreAppsCheckedAfter))
.map((e) => e.app.id)
.toList();
appIds.sort((a, b) => (apps[a]!.app.lastUpdateCheck ??
DateTime.fromMicrosecondsSinceEpoch(0))
.compareTo(apps[b]!.app.lastUpdateCheck ??
DateTime.fromMicrosecondsSinceEpoch(0)));
for (int i = 0; i < appIds.length; i++) { for (int i = 0; i < appIds.length; i++) {
App? newApp; App? newApp;
try { try {
@@ -1222,3 +1254,206 @@ class _APKOriginWarningDialogState extends State<APKOriginWarningDialog> {
); );
} }
} }
/// Background updater function
///
/// @param List<String>? toCheck: The appIds to check for updates (default to all apps sorted by last update check time)
///
/// @param List<String>? toInstall: The appIds to attempt to update (defaults to an empty array)
///
/// @param int? attemptCount: The number of times the function has failed up to this point (defaults to 0)
///
/// When toCheck is empty, the function is in "install mode" (else it is in "update mode").
/// In update mode, all apps in toCheck are checked for updates.
/// If an update is available, the appId is either added to toInstall (if a background update is possible) or the user is notified.
/// If there is an error, the function tries to continue after a few minutes (duration depends on the error), up to a maximum of 5 tries.
///
/// Once all update checks are complete, the function is called again in install mode.
/// In this mode, all apps in toInstall are downloaded and installed in the background (install result is unknown).
/// If there is an error, the function tries to continue after a few minutes (duration depends on the error), up to a maximum of 5 tries.
///
/// In either mode, if the function fails after the maximum number of tries, the user is notified.
@pragma('vm:entry-point')
Future<void> bgUpdateCheck(int taskId, Map<String, dynamic>? params) async {
WidgetsFlutterBinding.ensureInitialized();
await EasyLocalization.ensureInitialized();
await AndroidAlarmManager.initialize();
await loadTranslations();
LogsProvider logs = LogsProvider();
NotificationsProvider notificationsProvider = NotificationsProvider();
AppsProvider appsProvider = AppsProvider(isBg: true);
await appsProvider.loadApps();
var settingsProvider = SettingsProvider();
await settingsProvider.initializeSettings();
int maxAttempts = 4;
params ??= {};
if (params['toCheck'] == null) {
settingsProvider.lastBGCheckTime = DateTime.now();
}
List<MapEntry<String, int>> toCheck = <MapEntry<String, int>>[
...(params['toCheck']
?.map((entry) => MapEntry<String, int>(
entry['key'] as String, entry['value'] as int))
.toList() ??
appsProvider
.getAppsSortedByUpdateCheckTime()
.map((e) => MapEntry(e, 0)))
];
List<MapEntry<String, int>> toInstall = <MapEntry<String, int>>[
...(params['toInstall']
?.map((entry) => MapEntry<String, int>(
entry['key'] as String, entry['value'] as int))
.toList() ??
(<List<MapEntry<String, int>>>[]))
];
bool installMode = toCheck.isEmpty &&
toInstall.isNotEmpty; // Task is either in update mode or install mode
logs.add(
'BG ${installMode ? 'install' : 'update'} task $taskId: Started (${installMode ? toInstall.length : toCheck.length}).');
if (!installMode) {
// If in update mode...
var didCompleteChecking = false;
CheckingUpdatesNotification? notif;
// Loop through all updates and check each
for (int i = 0; i < toCheck.length; i++) {
var appId = toCheck[i].key;
var retryCount = toCheck[i].value;
AppInMemory? app = appsProvider.apps[appId];
if (app?.app.installedVersion != null) {
try {
notificationsProvider.notify(
notif = CheckingUpdatesNotification(app?.name ?? appId),
cancelExisting: true);
App? newApp = await appsProvider.checkUpdate(appId);
if (newApp != null) {
if (!(await appsProvider.canInstallSilently(
app!.app, settingsProvider))) {
notificationsProvider.notify(
UpdateNotification([newApp], id: newApp.id.hashCode - 1));
} else {
toInstall.add(MapEntry(appId, 0));
}
}
if (i == (toCheck.length - 1)) {
didCompleteChecking = true;
}
} catch (e) {
// If you got an error, move the offender to the back of the line (increment their fail count) and schedule another task to continue checking shortly
logs.add(
'BG update task $taskId: Got error on checking for $appId \'${e.toString()}\'.');
if (retryCount < maxAttempts) {
var remainingSeconds = e is RateLimitError
? (i == 0 ? (e.remainingMinutes * 60) : (5 * 60))
: e is ClientException
? (15 * 60)
: (retryCount ^ 2);
logs.add(
'BG update task $taskId: Will continue in $remainingSeconds seconds (with $appId moved to the end of the line).');
var remainingToCheck = moveStrToEndMapEntryWithCount(
toCheck.sublist(i), MapEntry(appId, retryCount + 1));
AndroidAlarmManager.oneShot(
Duration(seconds: remainingSeconds), taskId + 1, bgUpdateCheck,
params: {
'toCheck': remainingToCheck
.map((entry) => {'key': entry.key, 'value': entry.value})
.toList(),
'toInstall': toInstall
.map((entry) => {'key': entry.key, 'value': entry.value})
.toList(),
});
break;
} else {
// If the offender has reached its fail limit, notify the user and remove it from the list (task can continue)
toCheck.removeAt(i);
i--;
notificationsProvider
.notify(ErrorCheckingUpdatesNotification(e.toString()));
}
} finally {
if (notif != null) {
notificationsProvider.cancel(notif.id);
}
}
}
}
// If you're done checking and found some silently installable updates, schedule another task which will run in install mode
if (didCompleteChecking && toInstall.isNotEmpty) {
logs.add(
'BG update task $taskId: Done. Scheduling install task to run immediately.');
AndroidAlarmManager.oneShot(
const Duration(minutes: 0), taskId + 1, bgUpdateCheck,
params: {
'toCheck': [],
'toInstall': toInstall
.map((entry) => {'key': entry.key, 'value': entry.value})
.toList()
});
} else if (didCompleteChecking) {
logs.add('BG install task $taskId: Done.');
}
} else {
// If in install mode...
var didCompleteInstalling = false;
var tempObtArr = toInstall.where((element) => element.key == obtainiumId);
if (tempObtArr.isNotEmpty) {
// Move obtainium to the end of the list as it must always install last
var obt = tempObtArr.first;
toInstall = moveStrToEndMapEntryWithCount(toInstall, obt);
}
// Loop through all updates and install each
for (var i = 0; i < toInstall.length; i++) {
var appId = toInstall[i].key;
var retryCount = toInstall[i].value;
try {
logs.add(
'BG install task $taskId: Attempting to update $appId in the background.');
await appsProvider.downloadAndInstallLatestApps(
[appId], null, settingsProvider,
notificationsProvider: notificationsProvider);
await Future.delayed(const Duration(
seconds:
5)); // Just in case task ending causes install fail (not clear)
if (i == (toCheck.length - 1)) {
didCompleteInstalling = true;
}
} catch (e) {
// If you got an error, move the offender to the back of the line (increment their fail count) and schedule another task to continue installing shortly
logs.add(
'BG install task $taskId: Got error on updating $appId \'${e.toString()}\'.');
if (retryCount < maxAttempts) {
var remainingSeconds = retryCount;
logs.add(
'BG install task $taskId: Will continue in $remainingSeconds seconds (with $appId moved to the end of the line).');
var remainingToInstall = moveStrToEndMapEntryWithCount(
toInstall.sublist(i), MapEntry(appId, retryCount + 1));
AndroidAlarmManager.oneShot(
Duration(seconds: remainingSeconds), taskId + 1, bgUpdateCheck,
params: {
'toCheck': toCheck
.map((entry) => {'key': entry.key, 'value': entry.value})
.toList(),
'toInstall': remainingToInstall
.map((entry) => {'key': entry.key, 'value': entry.value})
.toList(),
});
break;
} else {
// If the offender has reached its fail limit, notify the user and remove it from the list (task can continue)
toInstall.removeAt(i);
i--;
notificationsProvider
.notify(ErrorCheckingUpdatesNotification(e.toString()));
}
}
if (didCompleteInstalling) {
logs.add('BG install task $taskId: Done.');
}
}
}
}

View File

@@ -22,9 +22,9 @@ class ObtainiumNotification {
} }
class UpdateNotification extends ObtainiumNotification { class UpdateNotification extends ObtainiumNotification {
UpdateNotification(List<App> updates) UpdateNotification(List<App> updates, {int? id})
: super( : super(
2, id ?? 2,
tr('updatesAvailable'), tr('updatesAvailable'),
'', '',
'UPDATES_AVAILABLE', 'UPDATES_AVAILABLE',
@@ -41,8 +41,8 @@ class UpdateNotification extends ObtainiumNotification {
} }
class SilentUpdateNotification extends ObtainiumNotification { class SilentUpdateNotification extends ObtainiumNotification {
SilentUpdateNotification(List<App> updates) SilentUpdateNotification(List<App> updates, {int? id})
: super(3, tr('appsUpdated'), '', 'APPS_UPDATED', tr('appsUpdated'), : super(id ?? 3, tr('appsUpdated'), '', 'APPS_UPDATED', tr('appsUpdated'),
tr('appsUpdatedNotifDescription'), Importance.defaultImportance) { tr('appsUpdatedNotifDescription'), Importance.defaultImportance) {
message = updates.length == 1 message = updates.length == 1
? tr('xWasUpdatedToY', ? tr('xWasUpdatedToY',
@@ -52,10 +52,28 @@ class SilentUpdateNotification extends ObtainiumNotification {
} }
} }
class ErrorCheckingUpdatesNotification extends ObtainiumNotification { class SilentUpdateAttemptNotification extends ObtainiumNotification {
ErrorCheckingUpdatesNotification(String error) SilentUpdateAttemptNotification(List<App> updates, {int? id})
: super( : super(
5, id ?? 3,
tr('appsPossiblyUpdated'),
'',
'APPS_POSSIBLY_UPDATED',
tr('appsPossiblyUpdated'),
tr('appsPossiblyUpdatedNotifDescription'),
Importance.defaultImportance) {
message = updates.length == 1
? tr('xWasPossiblyUpdatedToY',
args: [updates[0].finalName, updates[0].latestVersion])
: plural('xAndNMoreUpdatesPossiblyInstalled', updates.length - 1,
args: [updates[0].finalName, (updates.length - 1).toString()]);
}
}
class ErrorCheckingUpdatesNotification extends ObtainiumNotification {
ErrorCheckingUpdatesNotification(String error, {int? id})
: super(
id ?? 5,
tr('errorCheckingUpdates'), tr('errorCheckingUpdates'),
error, error,
'BG_UPDATE_CHECK_ERROR', 'BG_UPDATE_CHECK_ERROR',
@@ -99,14 +117,17 @@ final completeInstallationNotification = ObtainiumNotification(
tr('completeAppInstallationNotifDescription'), tr('completeAppInstallationNotifDescription'),
Importance.max); Importance.max);
final checkingUpdatesNotification = ObtainiumNotification( class CheckingUpdatesNotification extends ObtainiumNotification {
4, CheckingUpdatesNotification(String appName)
tr('checkingForUpdates'), : super(
'', 4,
'BG_UPDATE_CHECK', tr('checkingForUpdates'),
tr('checkingForUpdates'), appName,
tr('checkingForUpdatesNotifDescription'), 'BG_UPDATE_CHECK',
Importance.min); tr('checkingForUpdates'),
tr('checkingForUpdatesNotifDescription'),
Importance.min);
}
class NotificationsProvider { class NotificationsProvider {
FlutterLocalNotificationsPlugin notifications = FlutterLocalNotificationsPlugin notifications =

View File

@@ -309,4 +309,34 @@ class SettingsProvider with ChangeNotifier {
prefs?.setBool('reversePageTransitions', show); prefs?.setBool('reversePageTransitions', show);
notifyListeners(); notifyListeners();
} }
bool get enableBackgroundUpdates {
return prefs?.getBool('enableBackgroundUpdates') ?? true;
}
set enableBackgroundUpdates(bool val) {
prefs?.setBool('enableBackgroundUpdates', val);
notifyListeners();
}
DateTime get lastBGCheckTime {
int? temp = prefs?.getInt('lastBGCheckTime');
return temp != null
? DateTime.fromMillisecondsSinceEpoch(temp)
: DateTime.fromMillisecondsSinceEpoch(0);
}
set lastBGCheckTime(DateTime val) {
prefs?.setInt('lastBGCheckTime', val.millisecondsSinceEpoch);
notifyListeners();
}
bool get showDebugOpts {
return prefs?.getBool('showDebugOpts') ?? false;
}
set showDebugOpts(bool val) {
prefs?.setBool('showDebugOpts', val);
notifyListeners();
}
} }

View File

@@ -31,7 +31,7 @@ packages:
description: description:
path: "." path: "."
ref: master ref: master
resolved-ref: "6e68991ef9c6232695abce2eef345d3cca2f52ac" resolved-ref: c7c2f992a9dc452393c94d96cdf2b1f5a5ce7c80
url: "https://github.com/ImranR98/android_package_manager" url: "https://github.com/ImranR98/android_package_manager"
source: git source: git
version: "0.5.4" version: "0.5.4"
@@ -111,10 +111,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: collection name: collection
sha256: "4a07be6cb69c84d677a6c3096fcf960cc3285a8330b4603e0d463d15d9bd934c" sha256: f092b211a4319e98e5ff58223576de6c2803db36221657b46c82574721240687
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.17.1" version: "1.17.2"
convert: convert:
dependency: transitive dependency: transitive
description: description:
@@ -231,10 +231,10 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: file_picker name: file_picker
sha256: "21145c9c268d54b1f771d8380c195d2d6f655e0567dc1ca2f9c134c02c819e0a" sha256: bdfa035a974a0c080576c4c8ed01cdf9d1b406a04c7daa05443ef0383a97bedc
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "5.3.3" version: "5.3.4"
flutter: flutter:
dependency: "direct main" dependency: "direct main"
description: flutter description: flutter
@@ -276,10 +276,10 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: flutter_local_notifications name: flutter_local_notifications
sha256: "3cc40fe8c50ab8383f3e053a499f00f975636622ecdc8e20a77418ece3b1e975" sha256: "3002092e5b8ce2f86c3361422e52e6db6776c23ee21e0b2f71b892bf4259ef04"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "15.1.0+1" version: "15.1.1"
flutter_local_notifications_linux: flutter_local_notifications_linux:
dependency: transitive dependency: transitive
description: description:
@@ -375,22 +375,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "4.0.17" version: "4.0.17"
installed_apps:
dependency: "direct main"
description:
name: installed_apps
sha256: "145af8eb6e4e7c830e9888d6de0573ae5c139e8e0742a3e67316e1db21ab6fe0"
url: "https://pub.dev"
source: hosted
version: "1.3.1"
intl: intl:
dependency: transitive dependency: transitive
description: description:
name: intl name: intl
sha256: a3715e3bc90294e971cb7dc063fbf3cd9ee0ebf8604ffeafabd9e6f16abbdbe6 sha256: "3bc132a9dbce73a7e4a21a17d06e1878839ffbf975568bc875c60537824b0c4d"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.18.0" version: "0.18.1"
js: js:
dependency: transitive dependency: transitive
description: description:
@@ -427,18 +419,18 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: matcher name: matcher
sha256: "6501fbd55da300384b768785b83e5ce66991266cec21af89ab9ae7f5ce1c4cbb" sha256: "1803e76e6653768d64ed8ff2e1e67bea3ad4b923eb5c56a295c3e634bad5960e"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.12.15" version: "0.12.16"
material_color_utilities: material_color_utilities:
dependency: transitive dependency: transitive
description: description:
name: material_color_utilities name: material_color_utilities
sha256: d92141dc6fe1dad30722f9aa826c7fbc896d021d792f80678280601aff8cf724 sha256: "9528f2f296073ff54cb9fee677df673ace1218163c3bc7628093e7eed5203d41"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.2.0" version: "0.5.0"
meta: meta:
dependency: transitive dependency: transitive
description: description:
@@ -463,22 +455,6 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.0.0" version: "1.0.0"
package_archive_info:
dependency: "direct main"
description:
name: package_archive_info
sha256: "8f671a29b79d15f192e5e2b0dab9d0bad66b9ee93fb58d4e0afdb62f91a259be"
url: "https://pub.dev"
source: hosted
version: "0.1.0"
package_info:
dependency: transitive
description:
name: package_info
sha256: "6c07d9d82c69e16afeeeeb6866fe43985a20b3b50df243091bfc4a4ad2b03b75"
url: "https://pub.dev"
source: hosted
version: "2.0.2"
path: path:
dependency: transitive dependency: transitive
description: description:
@@ -696,10 +672,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: source_span name: source_span
sha256: dd904f795d4b4f3b870833847c461801f6750a9fa8e61ea5ac53f9422b31f250 sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.9.1" version: "1.10.0"
sqflite: sqflite:
dependency: "direct main" dependency: "direct main"
description: description:
@@ -760,10 +736,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: test_api name: test_api
sha256: eb6ac1540b26de412b3403a163d919ba86f6a973fe6cc50ae3541b80092fdcfb sha256: "75760ffd7786fffdfb9597c35c5b27eaeec82be8edfb6d71d32651128ed7aab8"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.5.1" version: "0.6.0"
timezone: timezone:
dependency: transitive dependency: transitive
description: description:
@@ -860,14 +836,22 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.1.4" version: "2.1.4"
web:
dependency: transitive
description:
name: web
sha256: dc8ccd225a2005c1be616fe02951e2e342092edf968cf0844220383757ef8f10
url: "https://pub.dev"
source: hosted
version: "0.1.4-beta"
webview_flutter: webview_flutter:
dependency: "direct main" dependency: "direct main"
description: description:
name: webview_flutter name: webview_flutter
sha256: "789d52bd789373cc1e100fb634af2127e86c99cf9abde09499743270c5de8d00" sha256: "04a0782fb058b7c71f2048935583488f4d32e9147ca403abc4e58f1de9964629"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "4.2.2" version: "4.2.3"
webview_flutter_android: webview_flutter_android:
dependency: transitive dependency: transitive
description: description:
@@ -896,10 +880,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: win32 name: win32
sha256: f2add6fa510d3ae152903412227bda57d0d5a8da61d2c39c1fb022c9429a41c0 sha256: "9e82a402b7f3d518fb9c02d0e9ae45952df31b9bf34d77baf19da2de03fc2aaa"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "5.0.6" version: "5.0.7"
win32_registry: win32_registry:
dependency: transitive dependency: transitive
description: description:
@@ -933,5 +917,5 @@ packages:
source: hosted source: hosted
version: "3.1.2" version: "3.1.2"
sdks: sdks:
dart: ">=3.0.0 <4.0.0" dart: ">=3.1.0-185.0.dev <4.0.0"
flutter: ">=3.10.0" flutter: ">=3.10.0"

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.26+190 # When changing this, update the tag in main() accordingly version: 0.14.0+192 # 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'
@@ -60,8 +60,6 @@ dependencies:
url: https://github.com/ImranR98/android_package_manager url: https://github.com/ImranR98/android_package_manager
ref: master ref: master
share_plus: ^7.0.0 share_plus: ^7.0.0
installed_apps: ^1.3.1 # TODO: Remove when android_package_manager supports getting icon as UInt8List and versionCode
package_archive_info: ^0.1.0 # TODO: Remove when android_package_manager supports getting versionCode
android_alarm_manager_plus: ^3.0.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