mirror of
				https://github.com/ImranR98/Obtainium.git
				synced 2025-10-29 20:43:28 +01:00 
			
		
		
		
	Compare commits
	
		
			21 Commits
		
	
	
		
			v0.9.4-bet
			...
			v0.9.9-bet
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|  | 6f27f64699 | ||
|  | 3341fecb68 | ||
|  | d3bce63ca4 | ||
|  | 8aa8b6b698 | ||
|  | 3d6c9bbf98 | ||
|  | 7af0a8628c | ||
|  | 4573ce6bcf | ||
|  | e29d38fa32 | ||
|  | dc82431235 | ||
|  | 424b0028bf | ||
|  | 46fba9e0a4 | ||
|  | b40be7569b | ||
|  | a173be11eb | ||
|  | 0c97b25d99 | ||
|  | f836fd20d8 | ||
|  | 2f6917592d | ||
|  | b864fef3ad | ||
|  | 8e487592b3 | ||
|  | e9a44746a5 | ||
|  | 9123737bf3 | ||
|  | 01fa9a2e96 | 
| @@ -13,10 +13,10 @@ | |||||||
|     "and": "és", |     "and": "és", | ||||||
|     "startedBgUpdateTask": "Háttérfrissítés ellenőrzési feladat elindítva", |     "startedBgUpdateTask": "Háttérfrissítés ellenőrzési feladat elindítva", | ||||||
|     "bgUpdateIgnoreAfterIs": "Háttérfrissítés ignoreAfter a következő: {}", |     "bgUpdateIgnoreAfterIs": "Háttérfrissítés ignoreAfter a következő: {}", | ||||||
|     "startedActualBGUpdateCheck": "Elkezdődött a tényleges BG frissítés ellenőrzése", |     "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", |     "bgUpdateTaskFinished": "A háttérfrissítés ellenőrzési feladat befejeződött", | ||||||
|     "firstRun": "Ez az Obtainium első futása", |     "firstRun": "Ez az Obtainium első futása", | ||||||
|     "settingUpdateCheckIntervalTo": "A frissítési intervallum beállítása a erre: {}", |     "settingUpdateCheckIntervalTo": "A frissítési intervallum beállítása erre: {}", | ||||||
|     "githubPATLabel": "GitHub személyes hozzáférési token (megnöveli a díjkorlátot)", |     "githubPATLabel": "GitHub személyes hozzáférési 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", | ||||||
| @@ -28,13 +28,13 @@ | |||||||
|     "noDescription": "Nincs leírás", |     "noDescription": "Nincs leírás", | ||||||
|     "cancel": "Mégse", |     "cancel": "Mégse", | ||||||
|     "continue": "Tovább", |     "continue": "Tovább", | ||||||
|     "requiredInBrackets": "(Kötlező)", |     "requiredInBrackets": "(Kötelező)", | ||||||
|     "dropdownNoOptsError": "HIBA: A LEDOBÁST LEGALÁBB EGY OPCIÓBAN KELL RENDELNI", |     "dropdownNoOptsError": "HIBA: A LEDOBÁST LEGALÁBB EGY OPCIÓHOZ KELL RENDELNI", | ||||||
|     "colour": "Szín", |     "colour": "Szín", | ||||||
|     "githubStarredRepos": "GitHub Csillagozott Repo-k", |     "githubStarredRepos": "GitHub Csillagos Repo-k", | ||||||
|     "uname": "Felh.név", |     "uname": "Felh.név", | ||||||
|     "wrongArgNum": "Rossz számú argumentumot adott meg", |     "wrongArgNum": "Rossz számú argumentumot adott meg", | ||||||
|     "xIsTrackOnly": "{} csak nyomon követhető", |     "xIsTrackOnly": "A(z) {} csak nyomkövethető", | ||||||
|     "source": "Forrás", |     "source": "Forrás", | ||||||
|     "app": "App", |     "app": "App", | ||||||
|     "appsFromSourceAreTrackOnly": "Az ebből a forrásból származó alkalmazások 'Csak nyomon követhetőek'.", |     "appsFromSourceAreTrackOnly": "Az ebből a forrásból származó alkalmazások 'Csak nyomon követhetőek'.", | ||||||
| @@ -56,25 +56,25 @@ | |||||||
|     "appsString": "Appok", |     "appsString": "Appok", | ||||||
|     "noApps": "Nincs App", |     "noApps": "Nincs App", | ||||||
|     "noAppsForFilter": "Nincsenek appok a szűrőhöz", |     "noAppsForFilter": "Nincsenek appok a szűrőhöz", | ||||||
|     "byX": "By {}", |     "byX": "{} által", | ||||||
|     "percentProgress": "Folyamat: {}%", |     "percentProgress": "Folyamat: {}%", | ||||||
|     "pleaseWait": "Kis türelmet", |     "pleaseWait": "Kis türelmet", | ||||||
|     "updateAvailable": "Frissítés elérhető", |     "updateAvailable": "Frissítés érhető el", | ||||||
|     "estimateInBracketsShort": "(Becsült)", |     "estimateInBracketsShort": "(Becsült)", | ||||||
|     "notInstalled": "Nem telepített", |     "notInstalled": "Nem telepített", | ||||||
|     "estimateInBrackets": "(Becslés)", |     "estimateInBrackets": "(Becslés)", | ||||||
|     "selectAll": "Mindet kiválaszt", |     "selectAll": "Mindet kiválaszt", | ||||||
|     "deselectN": "Törölje {} kijelölését", |     "deselectN": "Törölje {} kijelölését", | ||||||
|     "xWillBeRemovedButRemainInstalled": "{} el lesz távolítva az Obtainiumból, de továbbra is telepítve marad az eszközön.", |     "xWillBeRemovedButRemainInstalled": "A(z) {} el lesz távolítva az Obtainiumból, de továbbra is telepítve marad az eszközön.", | ||||||
|     "removeSelectedAppsQuestion": "Eltávolítja a kiválasztott appokat?", |     "removeSelectedAppsQuestion": "Eltávolítja a kiválasztott appokat?", | ||||||
|     "removeSelectedApps": "Távolítsa el a kiválasztott appokat", |     "removeSelectedApps": "Távolítsa el a kiválasztott appokat", | ||||||
|     "updateX": "Frissítés: {}", |     "updateX": "Frissítés: {}", | ||||||
|     "installX": "Telepítés {}", |     "installX": "Telepítés: {}", | ||||||
|     "markXTrackOnlyAsUpdated": "Jelölje meg: {}\n(Csak nyomon követhető)\nas Frissítve", |     "markXTrackOnlyAsUpdated": "Jelölje meg: {}\n(Csak nyomon követhető)\nmint Frissített", | ||||||
|     "changeX": "Változás {}", |     "changeX": "Változás {}", | ||||||
|     "installUpdateApps": "Appok telepítése/frissítése", |     "installUpdateApps": "Appok telepítése/frissítése", | ||||||
|     "installUpdateSelectedApps": "Telepítse/frissítse a kiválasztott appokat", |     "installUpdateSelectedApps": "Telepítse/frissítse a kiválasztott appokat", | ||||||
|     "onlyWorksWithNonEVDApps": "Csak azoknál az alkalmazásoknál működik, amelyek telepítési állapota nem észlelhető automatikusan (nem gyakori).", |     "onlyWorksWithNonEVDApps": "Csak azoknál az alkalmazásoknál működik, amelyek telepítési állapota nem észlelhető autom. (nem gyakori).", | ||||||
|     "markXSelectedAppsAsUpdated": "Megjelöl {} kiválasztott alkalmazást frissítettként?", |     "markXSelectedAppsAsUpdated": "Megjelöl {} kiválasztott alkalmazást frissítettként?", | ||||||
|     "no": "Nem", |     "no": "Nem", | ||||||
|     "yes": "Igen", |     "yes": "Igen", | ||||||
| @@ -86,8 +86,8 @@ | |||||||
|     "shareSelectedAppURLs": "Ossza meg a kiválasztott app URL címeit", |     "shareSelectedAppURLs": "Ossza meg a kiválasztott app URL címeit", | ||||||
|     "resetInstallStatus": "Telepítési állapot visszaállítása", |     "resetInstallStatus": "Telepítési állapot visszaállítása", | ||||||
|     "more": "További", |     "more": "További", | ||||||
|     "removeOutdatedFilter": "Távolítsa el az elavult alkalmazásszűrőt", |     "removeOutdatedFilter": "Távolítsa el az elavult app szűrőt", | ||||||
|     "showOutdatedOnly": "Csak az elavult alkalmazások megjelenítése", |     "showOutdatedOnly": "Csak az elavult appok megjelenítése", | ||||||
|     "filter": "Szűrő", |     "filter": "Szűrő", | ||||||
|     "filterActive": "Szűrő *", |     "filterActive": "Szűrő *", | ||||||
|     "filterApps": "Appok szűrése", |     "filterApps": "Appok szűrése", | ||||||
| @@ -118,7 +118,7 @@ | |||||||
|     "selectURLs": "Kiválasztott URL-ek", |     "selectURLs": "Kiválasztott URL-ek", | ||||||
|     "pick": "Válasszon", |     "pick": "Válasszon", | ||||||
|     "theme": "Téma", |     "theme": "Téma", | ||||||
|     "dark": "Söét", |     "dark": "Sötét", | ||||||
|     "light": "Világos", |     "light": "Világos", | ||||||
|     "followSystem": "Rendszer szerint", |     "followSystem": "Rendszer szerint", | ||||||
|     "obtainium": "Obtainium", |     "obtainium": "Obtainium", | ||||||
| @@ -126,11 +126,11 @@ | |||||||
|     "appSortBy": "App rendezés...", |     "appSortBy": "App rendezés...", | ||||||
|     "authorName": "Szerző/Név", |     "authorName": "Szerző/Név", | ||||||
|     "nameAuthor": "Név/Szerző", |     "nameAuthor": "Név/Szerző", | ||||||
|     "asAdded": "Mint hozzáadott", |     "asAdded": "Mint Hozzáadott", | ||||||
|     "appSortOrder": "Appok rendezése", |     "appSortOrder": "Appok rendezése", | ||||||
|     "ascending": "Emelkedő", |     "ascending": "Emelkedő", | ||||||
|     "descending": "Csökkenő", |     "descending": "Csökkenő", | ||||||
|     "bgUpdateCheckInterval": "Háttérfrissítés ellenőrzési időköz", |     "bgUpdateCheckInterval": "Háttérfrissítés ellenőrzés időköze", | ||||||
|     "neverManualOnly": "Soha – csak manuális", |     "neverManualOnly": "Soha – csak manuális", | ||||||
|     "appearance": "Megjelenés", |     "appearance": "Megjelenés", | ||||||
|     "showWebInAppView": "Forrás megjelenítése az Appok nézetben", |     "showWebInAppView": "Forrás megjelenítése az Appok nézetben", | ||||||
| @@ -145,7 +145,7 @@ | |||||||
|     "appNotFound": "App nem található", |     "appNotFound": "App nem található", | ||||||
|     "obtainiumExportHyphenatedLowercase": "obtainium-export", |     "obtainiumExportHyphenatedLowercase": "obtainium-export", | ||||||
|     "pickAnAPK": "Válasszon egy APK-t", |     "pickAnAPK": "Válasszon egy APK-t", | ||||||
|     "appHasMoreThanOnePackage": "{} egynél több csomaggal rendelkezik:", |     "appHasMoreThanOnePackage": "A(z) {} egynél több csomaggal rendelkezik:", | ||||||
|     "deviceSupportsXArch": "Eszköze támogatja a {} CPU architektúrát.", |     "deviceSupportsXArch": "Eszköze támogatja a {} CPU architektúrát.", | ||||||
|     "deviceSupportsFollowingArchs": "Az eszköze a következő CPU architektúrákat támogatja:", |     "deviceSupportsFollowingArchs": "Az eszköze a következő CPU architektúrákat támogatja:", | ||||||
|     "warning": "Figyelem", |     "warning": "Figyelem", | ||||||
| @@ -153,16 +153,16 @@ | |||||||
|     "updatesAvailable": "Frissítések érhetők el", |     "updatesAvailable": "Frissítések érhetők el", | ||||||
|     "updatesAvailableNotifDescription": "Értesíti a felhasználót, hogy frissítések állnak rendelkezésre egy vagy több, az Obtainium által nyomon követett alkalmazáshoz", |     "updatesAvailableNotifDescription": "Értesíti a felhasználót, hogy frissítések állnak rendelkezésre egy vagy több, az Obtainium által nyomon követett alkalmazáshoz", | ||||||
|     "noNewUpdates": "Nincsenek új frissítések.", |     "noNewUpdates": "Nincsenek új frissítések.", | ||||||
|     "xHasAnUpdate": "{} frissítést kapott.", |     "xHasAnUpdate": "A(z) {} frissítést kapott.", | ||||||
|     "appsUpdated": "Alkalmazások frissítve", |     "appsUpdated": "Alkalmazások frissítve", | ||||||
|     "appsUpdatedNotifDescription": "Értesíti a felhasználót, hogy egy vagy több app frissítése történt a háttérben", |     "appsUpdatedNotifDescription": "Értesíti a felhasználót, hogy egy/több app frissítése megtörtént a háttérben", | ||||||
|     "xWasUpdatedToY": "{} frissítve a következőre: {}.", |     "xWasUpdatedToY": "{} frissítve a következőre: {}.", | ||||||
|     "errorCheckingUpdates": "Hiba a frissítések keresésekor", |     "errorCheckingUpdates": "Hiba a frissítések keresésekor", | ||||||
|     "errorCheckingUpdatesNotifDescription": "Értesítés, amely akkor jelenik meg, ha a háttérbeli frissítések ellenőrzése sikertelen", |     "errorCheckingUpdatesNotifDescription": "Értesítés, amely akkor jelenik meg, ha a háttérbeli frissítések ellenőrzése sikertelen", | ||||||
|     "appsRemoved": "Alkalmazások eltávolítva", |     "appsRemoved": "Alkalmazások eltávolítva", | ||||||
|     "appsRemovedNotifDescription": "Értesíti a felhasználót egy vagy több alkalmazás eltávolításáról a betöltésük során fellépő hibák miatt", |     "appsRemovedNotifDescription": "Értesíti a felhasználót egy vagy több alkalmazás eltávolításáról a betöltésük során fellépő hibák miatt", | ||||||
|     "xWasRemovedDueToErrorY": "A(z) {} a következő hiba miatt lett eltávolítva: {}", |     "xWasRemovedDueToErrorY": "A(z) {} a következő hiba miatt lett eltávolítva: {}", | ||||||
|     "completeAppInstallation": "Teljes alkalmazástelepítés", |     "completeAppInstallation": "Teljes app telepítés", | ||||||
|     "obtainiumMustBeOpenToInstallApps": "Az Obtainiumnak megnyitva kell lennie az alkalmazások telepítéséhez", |     "obtainiumMustBeOpenToInstallApps": "Az Obtainiumnak megnyitva kell lennie az alkalmazások telepítéséhez", | ||||||
|     "completeAppInstallationNotifDescription": "Megkéri a felhasználót, hogy térjen vissza az Obtainiumhoz, hogy befejezze az alkalmazás telepítését", |     "completeAppInstallationNotifDescription": "Megkéri a felhasználót, hogy térjen vissza az Obtainiumhoz, hogy befejezze az alkalmazás telepítését", | ||||||
|     "checkingForUpdates": "Frissítések keresése", |     "checkingForUpdates": "Frissítések keresése", | ||||||
| @@ -184,31 +184,29 @@ | |||||||
|     "appIdOrName": "App ID vagy név", |     "appIdOrName": "App ID vagy név", | ||||||
|     "appWithIdOrNameNotFound": "Nem található app ezzel az azonosítóval vagy névvel", |     "appWithIdOrNameNotFound": "Nem található app ezzel az azonosítóval vagy névvel", | ||||||
|     "reposHaveMultipleApps": "A repók több alkalmazást is tartalmazhatnak", |     "reposHaveMultipleApps": "A repók több alkalmazást is tartalmazhatnak", | ||||||
|     "fdroidThirdPartyRepo": "F-Droid Harmadik fél Repo", |     "fdroidThirdPartyRepo": "F-Droid Harmadik-fél Repo", | ||||||
|     "steam": "Steam", |     "steam": "Steam", | ||||||
|     "steamMobile": "Steam Mobile", |     "steamMobile": "Steam Mobile", | ||||||
|     "steamChat": "Steam Chat", |     "steamChat": "Steam Chat", | ||||||
|     "install": "Install", |     "install": "Telepít", | ||||||
|     "markInstalled": "Mark Installed", |     "markInstalled": "Telepítettnek jelöl", | ||||||
|     "update": "Update", |     "update": "Frissít", | ||||||
|     "markUpdated": "Mark Updated", |     "markUpdated": "Frissítettnek jelöl", | ||||||
|     "additionalOptions": "Additional Options", |     "additionalOptions": "További lehetőségek", | ||||||
|     "disableVersionDetection": "Disable Version Detection", |     "disableVersionDetection": "Verzióérzékelés letiltása", | ||||||
|     "noVersionDetectionExplanation": "This option should only be used for Apps where version detection does not work correctly.", |     "noVersionDetectionExplanation": "Ezt a beállítást csak olyan alkalmazásoknál szabad használni, ahol a verzióérzékelés nem működik megfelelően.", | ||||||
|     "downloadingX": "Downloading {}", |     "downloadingX": "{} letöltés", | ||||||
|     "downloadNotifDescription": "Notifies the user of the progress in downloading an App", |     "downloadNotifDescription": "Értesíti a felhasználót az app letöltésének előrehaladásáról", | ||||||
|     "noAPKFound": "No APK found", |     "noAPKFound": "Nem található APK", | ||||||
|     "noVersionDetection": "No version detection", |     "noVersionDetection": "Nincs verzió érzékelés", | ||||||
|     "categorize": "Categorize", |     "categorize": "Kategorizálás", | ||||||
|     "categories": "Categories", |     "categories": "Kategóriák", | ||||||
|     "category": "Category", |     "category": "Kategória", | ||||||
|     "noCategory": "No Category", |     "noCategory": "Nincs kategória", | ||||||
|     "noCategories": "No Categories", |     "deleteCategoryQuestion": "Törli a kategóriát?", | ||||||
|     "deleteCategoriesQuestion": "Delete Categories?", |     "categoryDeleteWarning": "A(z) {} összes app kategorizálatlan állapotba kerül.", | ||||||
|     "categoryDeleteWarning": "All Apps in deleted categories will be set to uncategorized.", |     "addCategory": "Új kategória", | ||||||
|     "addCategory": "Add Category", |     "label": "Címke", | ||||||
|     "label": "Label", |  | ||||||
|     "language": "Language", |  | ||||||
|     "tooManyRequestsTryAgainInMinutes": { |     "tooManyRequestsTryAgainInMinutes": { | ||||||
|         "one": "Túl sok kérés (korlátozott arány) – próbálja újra {} perc múlva", |         "one": "Túl sok kérés (korlátozott arány) – próbálja újra {} perc múlva", | ||||||
|         "other": "Túl sok kérés (korlátozott arány) – próbálja újra {} perc múlva" |         "other": "Túl sok kérés (korlátozott arány) – próbálja újra {} perc múlva" | ||||||
|   | |||||||
| @@ -16,8 +16,8 @@ | |||||||
|     "startedActualBGUpdateCheck": "Avviato il controllo effettivo degli aggiornamenti in background", |     "startedActualBGUpdateCheck": "Avviato il controllo effettivo degli aggiornamenti in background", | ||||||
|     "bgUpdateTaskFinished": "Terminata l'attività di controllo degli aggiornamenti in background", |     "bgUpdateTaskFinished": "Terminata l'attività di controllo degli aggiornamenti in background", | ||||||
|     "firstRun": "Questo è il primo avvio di sempre di Obtainium", |     "firstRun": "Questo è il primo avvio di sempre di Obtainium", | ||||||
|     "settingUpdateCheckIntervalTo": "Imposta l'intervallo di aggiornamento a {}", |     "settingUpdateCheckIntervalTo": "Fissato intervallo di aggiornamento a {}", | ||||||
|     "githubPATLabel": "GitHub Personal Access Token (aumenta il limite di traffico)", |     "githubPATLabel": "GitHub Personal Access Token (diminuisce limite di traffico)", | ||||||
|     "githubPATHint": "PAT deve seguire questo formato: username:token", |     "githubPATHint": "PAT deve seguire questo formato: username:token", | ||||||
|     "githubPATFormat": "username:token", |     "githubPATFormat": "username:token", | ||||||
|     "githubPATLinkText": "Informazioni su GitHub PAT", |     "githubPATLinkText": "Informazioni su GitHub PAT", | ||||||
| @@ -31,18 +31,18 @@ | |||||||
|     "requiredInBrackets": "(richiesto)", |     "requiredInBrackets": "(richiesto)", | ||||||
|     "dropdownNoOptsError": "ERRORE: LA TENDINA DEVE AVERE ALMENO UN'OPZIONE", |     "dropdownNoOptsError": "ERRORE: LA TENDINA DEVE AVERE ALMENO UN'OPZIONE", | ||||||
|     "colour": "Colore", |     "colour": "Colore", | ||||||
|     "githubStarredRepos": "i repository stellati da GitHub", |     "githubStarredRepos": "repository stellati da GitHub", | ||||||
|     "uname": "Username", |     "uname": "Username", | ||||||
|     "wrongArgNum": "Numero di argomenti forniti errato", |     "wrongArgNum": "Numero di argomenti forniti errato", | ||||||
|     "xIsTrackOnly": "{} è Solo-Monitoraggio", |     "xIsTrackOnly": "{} è in modalità Solo-Monitoraggio", | ||||||
|     "source": "Fonte", |     "source": "Fonte", | ||||||
|     "app": "App", |     "app": "App", | ||||||
|     "appsFromSourceAreTrackOnly": "Le App da questa fonte sono in modalità 'Solo-Monitoraggio'.", |     "appsFromSourceAreTrackOnly": "Le App da questa fonte sono in modalità 'Solo-Monitoraggio'.", | ||||||
|     "youPickedTrackOnly": "Hai selezionato l'opzione 'Solo-Monitoraggio'.", |     "youPickedTrackOnly": "È stata selezionata l'opzione 'Solo-Monitoraggio'.", | ||||||
|     "trackOnlyAppDescription": "L'App sarà monitorata per gli aggiornamenti, ma Obtainium non sarà in grado di scaricarli o di installarli.", |     "trackOnlyAppDescription": "L'App sarà monitorata per gli aggiornamenti, ma Obtainium non sarà in grado di scaricarli o di installarli.", | ||||||
|     "cancelled": "Annullato", |     "cancelled": "Annullato", | ||||||
|     "appAlreadyAdded": "App già aggiunta", |     "appAlreadyAdded": "App già aggiunta", | ||||||
|     "alreadyUpToDateQuestion": "App già aggiornata?", |     "alreadyUpToDateQuestion": "L'App è già aggiornata?", | ||||||
|     "addApp": "Aggiungi App", |     "addApp": "Aggiungi App", | ||||||
|     "appSourceURL": "URL della fonte dell'App", |     "appSourceURL": "URL della fonte dell'App", | ||||||
|     "error": "Errore", |     "error": "Errore", | ||||||
| @@ -60,20 +60,20 @@ | |||||||
|     "percentProgress": "Progresso: {}%", |     "percentProgress": "Progresso: {}%", | ||||||
|     "pleaseWait": "Attendere prego", |     "pleaseWait": "Attendere prego", | ||||||
|     "updateAvailable": "Aggiornamento disponibile", |     "updateAvailable": "Aggiornamento disponibile", | ||||||
|     "estimateInBracketsShort": "(Prev.)", |     "estimateInBracketsShort": "(prev.)", | ||||||
|     "notInstalled": "Non installato", |     "notInstalled": "Non installato", | ||||||
|     "estimateInBrackets": "(Previsto)", |     "estimateInBrackets": "(previsto)", | ||||||
|     "selectAll": "Seleziona tutto", |     "selectAll": "Seleziona tutto", | ||||||
|     "deselectN": "Deseleziona {}", |     "deselectN": "Deseleziona {}", | ||||||
|     "xWillBeRemovedButRemainInstalled": "{} sarà rimosso da Obtainium ma resterà installato sul dispositivo.", |     "xWillBeRemovedButRemainInstalled": "Verà effettuata la rimozione di {}, ma non la disinstallazione.", | ||||||
|     "removeSelectedAppsQuestion": "Rimuovere le App selezionate?", |     "removeSelectedAppsQuestion": "Rimuovere le App selezionate?", | ||||||
|     "removeSelectedApps": "Rimuovi le App selezionate", |     "removeSelectedApps": "Rimuovi le App selezionate", | ||||||
|     "updateX": "Aggiorna {}", |     "updateX": "Aggiorna {}", | ||||||
|     "installX": "Installa {}", |     "installX": "Installa {}", | ||||||
|     "markXTrackOnlyAsUpdated": "Contrassegna {}\n(Solo-Monitoraggio)\ncome aggiornato", |     "markXTrackOnlyAsUpdated": "Contrassegna {}\n(Solo-Monitoraggio)\ncome aggiornato", | ||||||
|     "changeX": "modifica {}", |     "changeX": "Modifica {}", | ||||||
|     "installUpdateApps": "Installa/Aggiorna le App", |     "installUpdateApps": "Installa/Aggiorna App", | ||||||
|     "installUpdateSelectedApps": "Installa/Aggiornale le App selezionate", |     "installUpdateSelectedApps": "Installa/Aggiorna le App selezionate", | ||||||
|     "onlyWorksWithNonEVDApps": "Funziona solo per le App il cui stato d'installazione non può essere rilevato automaticamente (inconsueto).", |     "onlyWorksWithNonEVDApps": "Funziona solo per le App il cui stato d'installazione non può essere rilevato automaticamente (inconsueto).", | ||||||
|     "markXSelectedAppsAsUpdated": "Contrassegnare le {} App selezionate come aggiornate?", |     "markXSelectedAppsAsUpdated": "Contrassegnare le {} App selezionate come aggiornate?", | ||||||
|     "no": "No", |     "no": "No", | ||||||
| @@ -95,7 +95,7 @@ | |||||||
|     "author": "Autore", |     "author": "Autore", | ||||||
|     "upToDateApps": "App aggiornate", |     "upToDateApps": "App aggiornate", | ||||||
|     "nonInstalledApps": "App non installate", |     "nonInstalledApps": "App non installate", | ||||||
|     "importExport": "Importa/Esporta", |     "importExport": "Importa - Esporta", | ||||||
|     "settings": "Impostazioni", |     "settings": "Impostazioni", | ||||||
|     "exportedTo": "Esportato in {}", |     "exportedTo": "Esportato in {}", | ||||||
|     "obtainiumExport": "Esporta da Obtainium", |     "obtainiumExport": "Esporta da Obtainium", | ||||||
| @@ -134,7 +134,7 @@ | |||||||
|     "neverManualOnly": "Mai - Solo manuale", |     "neverManualOnly": "Mai - Solo manuale", | ||||||
|     "appearance": "Aspetto", |     "appearance": "Aspetto", | ||||||
|     "showWebInAppView": "Mostra pagina web dell'App se selezionata", |     "showWebInAppView": "Mostra pagina web dell'App se selezionata", | ||||||
|     "pinUpdates": "Fissa in alto gli aggiornamenti disponibili", |     "pinUpdates": "Fissa aggiornamenti disponibili in alto", | ||||||
|     "updates": "Aggiornamenti", |     "updates": "Aggiornamenti", | ||||||
|     "sourceSpecific": "Specifiche per la fonte", |     "sourceSpecific": "Specifiche per la fonte", | ||||||
|     "appSource": "Sorgente dell'App", |     "appSource": "Sorgente dell'App", | ||||||
| @@ -146,21 +146,21 @@ | |||||||
|     "obtainiumExportHyphenatedLowercase": "esportazione-obtainium", |     "obtainiumExportHyphenatedLowercase": "esportazione-obtainium", | ||||||
|     "pickAnAPK": "Seleziona un APK", |     "pickAnAPK": "Seleziona un APK", | ||||||
|     "appHasMoreThanOnePackage": "{} offre più di un pacchetto:", |     "appHasMoreThanOnePackage": "{} offre più di un pacchetto:", | ||||||
|     "deviceSupportsXArch": "Il tuo dispositivo supporta l'architettura {} della CPU.", |     "deviceSupportsXArch": "Il dispositivo in uso supporta l'architettura {} della CPU.", | ||||||
|     "deviceSupportsFollowingArchs": "Il tuo dispositivo supporta le seguenti architetture della CPU:", |     "deviceSupportsFollowingArchs": "Il dispositivo in uso supporta le seguenti architetture della CPU:", | ||||||
|     "warning": "Attenzione", |     "warning": "Attenzione", | ||||||
|     "sourceIsXButPackageFromYPrompt": "L'origine dell'App è '{}' ma il pacchetto della release proviene da '{}'. Continuare?", |     "sourceIsXButPackageFromYPrompt": "L'origine dell'App è '{}' ma il pacchetto della release proviene da '{}'. Continuare?", | ||||||
|     "updatesAvailable": "Aggiornamenti disponibili", |     "updatesAvailable": "Aggiornamenti disponibili", | ||||||
|     "updatesAvailableNotifDescription": "Avvisa l'utente che sono disponibili gli aggiornamenti di una o più App monitorate da Obtainium", |     "updatesAvailableNotifDescription": "Notifica all'utente che sono disponibili gli aggiornamenti di una o più App monitorate da Obtainium", | ||||||
|     "noNewUpdates": "Nessun nuovo aggiornamento.", |     "noNewUpdates": "Nessun nuovo aggiornamento.", | ||||||
|     "xHasAnUpdate": "{} è stato aggiornato.", |     "xHasAnUpdate": "Aggiornamento disponibile per {}", | ||||||
|     "appsUpdated": "App aggiornate", |     "appsUpdated": "App aggiornate", | ||||||
|     "appsUpdatedNotifDescription": "Avvisa l'utente che una o più App sono state aggiornate in background", |     "appsUpdatedNotifDescription": "Notifica all'utente che una o più App sono state aggiornate in background", | ||||||
|     "xWasUpdatedToY": "{} è stato aggiornato a {}.", |     "xWasUpdatedToY": "{} è stato aggiornato a {}.", | ||||||
|     "errorCheckingUpdates": "Controllo degli errori per gli aggiornamenti", |     "errorCheckingUpdates": "Controllo degli errori per gli aggiornamenti", | ||||||
|     "errorCheckingUpdatesNotifDescription": "Una notifica che mostra quando il controllo degli aggiornamenti in background fallisce", |     "errorCheckingUpdatesNotifDescription": "Una notifica che mostra quando il controllo degli aggiornamenti in background fallisce", | ||||||
|     "appsRemoved": "App rimosse", |     "appsRemoved": "App rimosse", | ||||||
|     "appsRemovedNotifDescription": "Avvisa l'utente che una o più App sono state rimosse a causa di errori durante il caricamento", |     "appsRemovedNotifDescription": "Notifica all'utente che una o più App sono state rimosse a causa di errori durante il caricamento", | ||||||
|     "xWasRemovedDueToErrorY": "{} è stata rimosso a causa di questo errore: {}", |     "xWasRemovedDueToErrorY": "{} è stata rimosso a causa di questo errore: {}", | ||||||
|     "completeAppInstallation": "Completa l'installazione dell'App", |     "completeAppInstallation": "Completa l'installazione dell'App", | ||||||
|     "obtainiumMustBeOpenToInstallApps": "Obtainium deve essere aperto per poter installare le App", |     "obtainiumMustBeOpenToInstallApps": "Obtainium deve essere aperto per poter installare le App", | ||||||
| @@ -178,7 +178,7 @@ | |||||||
|     "installedVersionX": "Versione installata: {}", |     "installedVersionX": "Versione installata: {}", | ||||||
|     "lastUpdateCheckX": "Ultimo controllo degli aggiornamenti: {}", |     "lastUpdateCheckX": "Ultimo controllo degli aggiornamenti: {}", | ||||||
|     "remove": "Rimuovi", |     "remove": "Rimuovi", | ||||||
|     "removeAppQuestion": "Rimuovere App?", |     "removeAppQuestion": "Rimuovere l'App?", | ||||||
|     "yesMarkUpdated": "Sì, contrassegna come aggiornato", |     "yesMarkUpdated": "Sì, contrassegna come aggiornato", | ||||||
|     "fdroid": "F-Droid", |     "fdroid": "F-Droid", | ||||||
|     "appIdOrName": "ID o nome dell'App", |     "appIdOrName": "ID o nome dell'App", | ||||||
| @@ -188,38 +188,38 @@ | |||||||
|     "steam": "Steam", |     "steam": "Steam", | ||||||
|     "steamMobile": "Steam Mobile", |     "steamMobile": "Steam Mobile", | ||||||
|     "steamChat": "Steam Chat", |     "steamChat": "Steam Chat", | ||||||
|     "install": "Install", |     "install": "Installa", | ||||||
|     "markInstalled": "Mark Installed", |     "markInstalled": "Contrassegna come installato", | ||||||
|     "update": "Update", |     "update": "Aggiorna", | ||||||
|     "markUpdated": "Mark Updated", |     "markUpdated": "Contrassegna come aggiornato", | ||||||
|     "additionalOptions": "Additional Options", |     "additionalOptions": "Opzioni aggiuntive", | ||||||
|     "disableVersionDetection": "Disable Version Detection", |     "disableVersionDetection": "Disattiva il rilevamento della versione", | ||||||
|     "noVersionDetectionExplanation": "This option should only be used for Apps where version detection does not work correctly.", |     "noVersionDetectionExplanation": "Questa opzione dovrebbe essere usata solo per le App la cui versione non viene rilevata correttamente.", | ||||||
|     "downloadingX": "Downloading {}", |     "downloadingX": "Scaricamento di {} in corso", | ||||||
|     "downloadNotifDescription": "Notifies the user of the progress in downloading an App", |     "downloadNotifDescription": "Notifica all'utente lo stato di avanzamento del download di un'App", | ||||||
|     "noAPKFound": "No APK found", |     "noAPKFound": "Nessun APK trovato", | ||||||
|     "noVersionDetection": "No version detection", |     "noVersionDetection": "Disattiva rilevamento di versione", | ||||||
|     "categorize": "Categorize", |     "categorize": "Aggiungi a categoria", | ||||||
|     "categories": "Categories", |     "categories": "Categorie", | ||||||
|     "category": "Category", |     "category": "Categoria", | ||||||
|     "noCategory": "No Category", |     "noCategory": "Nessuna categoria", | ||||||
|     "noCategories": "No Categories", |     "noCategories": "Nessuna categoria", | ||||||
|     "deleteCategoriesQuestion": "Delete Categories?", |     "deleteCategoriesQuestion": "Eliminare le categorie?", | ||||||
|     "categoryDeleteWarning": "All Apps in deleted categories will be set to uncategorized.", |     "categoryDeleteWarning": "Tutte le App nelle categorie eliminate saranno impostate come non categorizzate.", | ||||||
|     "addCategory": "Add Category", |     "addCategory": "Aggiungi categoria", | ||||||
|     "label": "Label", |     "label": "Etichetta", | ||||||
|     "language": "Language", |     "language": "Lingua", | ||||||
|     "tooManyRequestsTryAgainInMinutes": { |     "tooManyRequestsTryAgainInMinutes": { | ||||||
|         "one": "Troppe richieste (traffico limitato) - riprova tra {} minuto", |         "one": "Troppe richieste (traffico limitato) - riprova tra {} minuto", | ||||||
|         "other": "Troppe richieste (traffico limitato) - riprova tra {} minuti" |         "other": "Troppe richieste (traffico limitato) - riprova tra {} minuti" | ||||||
|     }, |     }, | ||||||
|     "bgUpdateGotErrorRetryInMinutes": { |     "bgUpdateGotErrorRetryInMinutes": { | ||||||
|         "one": "Il controllo degli aggiornamenti in background ha incontrato un {}, ricontrollo tra {} minuto", |         "one": "Il controllo degli aggiornamenti in background ha incontrato un {}, nuovo tentativo tra {} minuto", | ||||||
|         "other": "Il controllo degli aggiornamenti in background ha incontrato un {}, ricontrollo tra {} minuti" |         "other": "Il controllo degli aggiornamenti in background ha incontrato un {}, nuovo tentativo tra {} minuti" | ||||||
|     }, |     }, | ||||||
|     "bgCheckFoundUpdatesWillNotifyIfNeeded": { |     "bgCheckFoundUpdatesWillNotifyIfNeeded": { | ||||||
|         "one": "Il controllo degli aggiornamenti in background ha trovato {} aggiornamento - avviserà l'utento se necessario", |         "one": "Il controllo degli aggiornamenti in background ha trovato {} aggiornamento - notificherà l'utente se necessario", | ||||||
|         "other": "Il controllo degli aggiornamenti in background ha trovato {} aggiornamenti - avviserà l'utento se necessario" |         "other": "Il controllo degli aggiornamenti in background ha trovato {} aggiornamenti - notificherà l'utente se necessario" | ||||||
|     }, |     }, | ||||||
|     "apps": { |     "apps": { | ||||||
|         "one": "{} App", |         "one": "{} App", | ||||||
|   | |||||||
| @@ -46,7 +46,7 @@ class APKMirror extends AppSource { | |||||||
|       } |       } | ||||||
|       return APKDetails(version, [], getAppNames(standardUrl)); |       return APKDetails(version, [], getAppNames(standardUrl)); | ||||||
|     } else { |     } else { | ||||||
|       throw NoReleasesError(); |       throw getObtainiumHttpError(res); | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -54,7 +54,7 @@ class FDroid extends AppSource { | |||||||
|       return APKDetails(latestVersion, apkUrls, |       return APKDetails(latestVersion, apkUrls, | ||||||
|           AppNames(name, Uri.parse(standardUrl).pathSegments.last)); |           AppNames(name, Uri.parse(standardUrl).pathSegments.last)); | ||||||
|     } else { |     } else { | ||||||
|       throw NoReleasesError(); |       throw getObtainiumHttpError(res); | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -80,7 +80,7 @@ class FDroidRepo extends AppSource { | |||||||
|           .toList(); |           .toList(); | ||||||
|       return APKDetails(latestVersion, apkUrls, AppNames(authorName, appName)); |       return APKDetails(latestVersion, apkUrls, AppNames(authorName, appName)); | ||||||
|     } else { |     } else { | ||||||
|       throw NoReleasesError(); |       throw getObtainiumHttpError(res); | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -59,7 +59,7 @@ class GitLab extends AppSource { | |||||||
|       } |       } | ||||||
|       return APKDetails(version, apkUrls, GitHub().getAppNames(standardUrl)); |       return APKDetails(version, apkUrls, GitHub().getAppNames(standardUrl)); | ||||||
|     } else { |     } else { | ||||||
|       throw NoReleasesError(); |       throw getObtainiumHttpError(res); | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -43,7 +43,7 @@ class Mullvad extends AppSource { | |||||||
|           ['https://mullvad.net/download/app/apk/latest'], |           ['https://mullvad.net/download/app/apk/latest'], | ||||||
|           AppNames(name, 'Mullvad-VPN')); |           AppNames(name, 'Mullvad-VPN')); | ||||||
|     } else { |     } else { | ||||||
|       throw NoReleasesError(); |       throw getObtainiumHttpError(res); | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -33,7 +33,7 @@ class Signal extends AppSource { | |||||||
|       } |       } | ||||||
|       return APKDetails(version, apkUrls, AppNames(name, 'Signal')); |       return APKDetails(version, apkUrls, AppNames(name, 'Signal')); | ||||||
|     } else { |     } else { | ||||||
|       throw NoReleasesError(); |       throw getObtainiumHttpError(res); | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -57,7 +57,7 @@ class SourceForge extends AppSource { | |||||||
|           AppNames( |           AppNames( | ||||||
|               name, standardUrl.substring(standardUrl.lastIndexOf('/') + 1))); |               name, standardUrl.substring(standardUrl.lastIndexOf('/') + 1))); | ||||||
|     } else { |     } else { | ||||||
|       throw NoReleasesError(); |       throw getObtainiumHttpError(res); | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -54,7 +54,7 @@ class SteamMobile extends AppSource { | |||||||
|       var apkUrls = [links[0]]; |       var apkUrls = [links[0]]; | ||||||
|       return APKDetails(version, apkUrls, AppNames(name, apks[apkNamePrefix]!)); |       return APKDetails(version, apkUrls, AppNames(name, apks[apkNamePrefix]!)); | ||||||
|     } else { |     } else { | ||||||
|       throw NoReleasesError(); |       throw getObtainiumHttpError(res); | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -91,6 +91,7 @@ class GeneratedFormTagInput extends GeneratedFormItem { | |||||||
|   late bool singleSelect; |   late bool singleSelect; | ||||||
|   late WrapAlignment alignment; |   late WrapAlignment alignment; | ||||||
|   late String emptyMessage; |   late String emptyMessage; | ||||||
|  |   late bool showLabelWhenNotEmpty; | ||||||
|   GeneratedFormTagInput(String key, |   GeneratedFormTagInput(String key, | ||||||
|       {String label = 'Input', |       {String label = 'Input', | ||||||
|       List<Widget> belowWidgets = const [], |       List<Widget> belowWidgets = const [], | ||||||
| @@ -100,7 +101,8 @@ class GeneratedFormTagInput extends GeneratedFormItem { | |||||||
|       this.deleteConfirmationMessage, |       this.deleteConfirmationMessage, | ||||||
|       this.singleSelect = false, |       this.singleSelect = false, | ||||||
|       this.alignment = WrapAlignment.start, |       this.alignment = WrapAlignment.start, | ||||||
|       this.emptyMessage = 'Input'}) |       this.emptyMessage = 'Input', | ||||||
|  |       this.showLabelWhenNotEmpty = true}) | ||||||
|       : super(key, |       : super(key, | ||||||
|             label: label, |             label: label, | ||||||
|             belowWidgets: belowWidgets, |             belowWidgets: belowWidgets, | ||||||
| @@ -140,11 +142,11 @@ class _GeneratedFormState extends State<GeneratedForm> { | |||||||
|     for (int r = 0; r < widget.items.length; r++) { |     for (int r = 0; r < widget.items.length; r++) { | ||||||
|       for (int i = 0; i < widget.items[r].length; i++) { |       for (int i = 0; i < widget.items[r].length; i++) { | ||||||
|         if (formInputs[r][i] is TextFormField) { |         if (formInputs[r][i] is TextFormField) { | ||||||
|           valid = valid && |           var fieldState = | ||||||
|               ((formInputs[r][i].key as GlobalKey<FormFieldState>) |               (formInputs[r][i].key as GlobalKey<FormFieldState>).currentState; | ||||||
|                       .currentState |           if (fieldState != null) { | ||||||
|                       ?.isValid ?? |             valid = valid && fieldState.isValid; | ||||||
|                   false); |           } | ||||||
|         } |         } | ||||||
|       } |       } | ||||||
|     } |     } | ||||||
| @@ -152,7 +154,7 @@ class _GeneratedFormState extends State<GeneratedForm> { | |||||||
|   } |   } | ||||||
|  |  | ||||||
|   // Generates a random light color |   // Generates a random light color | ||||||
| // Courtesy of ChatGPT 😭 (with a bugfix 🥳) |   // Courtesy of ChatGPT 😭 (with a bugfix 🥳) | ||||||
|   Color generateRandomLightColor() { |   Color generateRandomLightColor() { | ||||||
|     // Create a random number generator |     // Create a random number generator | ||||||
|     final Random random = Random(); |     final Random random = Random(); | ||||||
| @@ -259,157 +261,185 @@ class _GeneratedFormState extends State<GeneratedForm> { | |||||||
|             ], |             ], | ||||||
|           ); |           ); | ||||||
|         } else if (widget.items[r][e] is GeneratedFormTagInput) { |         } else if (widget.items[r][e] is GeneratedFormTagInput) { | ||||||
|           formInputs[r][e] = Wrap( |           formInputs[r][e] = | ||||||
|             alignment: (widget.items[r][e] as GeneratedFormTagInput).alignment, |               Column(crossAxisAlignment: CrossAxisAlignment.stretch, children: [ | ||||||
|             crossAxisAlignment: WrapCrossAlignment.center, |             if ((values[widget.items[r][e].key] | ||||||
|             children: [ |                             as Map<String, MapEntry<int, bool>>?) | ||||||
|               (values[widget.items[r][e].key] |                         ?.isNotEmpty == | ||||||
|                               as Map<String, MapEntry<int, bool>>?) |                     true && | ||||||
|                           ?.isEmpty == |                 (widget.items[r][e] as GeneratedFormTagInput) | ||||||
|                       true |                     .showLabelWhenNotEmpty) | ||||||
|                   ? Text( |               Column( | ||||||
|                       (widget.items[r][e] as GeneratedFormTagInput) |                 crossAxisAlignment: | ||||||
|                           .emptyMessage, |                     (widget.items[r][e] as GeneratedFormTagInput).alignment == | ||||||
|                       style: const TextStyle(fontWeight: FontWeight.bold), |                             WrapAlignment.center | ||||||
|                     ) |                         ? CrossAxisAlignment.center | ||||||
|                   : const SizedBox.shrink(), |                         : CrossAxisAlignment.stretch, | ||||||
|               ...(values[widget.items[r][e].key] |                 children: [ | ||||||
|                           as Map<String, MapEntry<int, bool>>?) |                   Text(widget.items[r][e].label), | ||||||
|                       ?.entries |                   const SizedBox( | ||||||
|                       .map((e2) { |                     height: 8, | ||||||
|                     return Padding( |                   ), | ||||||
|                         padding: const EdgeInsets.symmetric(horizontal: 4), |                 ], | ||||||
|                         child: ChoiceChip( |               ), | ||||||
|                           label: Text(e2.key), |             Wrap( | ||||||
|                           backgroundColor: Color(e2.value.key).withAlpha(50), |               alignment: | ||||||
|                           selectedColor: Color(e2.value.key), |                   (widget.items[r][e] as GeneratedFormTagInput).alignment, | ||||||
|                           visualDensity: VisualDensity.compact, |               crossAxisAlignment: WrapCrossAlignment.center, | ||||||
|                           selected: e2.value.value, |               children: [ | ||||||
|                           onSelected: (value) { |                 (values[widget.items[r][e].key] | ||||||
|                             setState(() { |                                 as Map<String, MapEntry<int, bool>>?) | ||||||
|                               (values[widget.items[r][e].key] as Map<String, |                             ?.isEmpty == | ||||||
|                                       MapEntry<int, bool>>)[e2.key] = |                         true | ||||||
|                                   MapEntry( |                     ? Text( | ||||||
|                                       (values[widget.items[r][e].key] as Map< |                         (widget.items[r][e] as GeneratedFormTagInput) | ||||||
|                                               String, |                             .emptyMessage, | ||||||
|                                               MapEntry<int, bool>>)[e2.key]! |                       ) | ||||||
|                                           .key, |                     : const SizedBox.shrink(), | ||||||
|                                       value); |                 ...(values[widget.items[r][e].key] | ||||||
|                               if ((widget.items[r][e] as GeneratedFormTagInput) |                             as Map<String, MapEntry<int, bool>>?) | ||||||
|                                       .singleSelect && |                         ?.entries | ||||||
|                                   value == true) { |                         .map((e2) { | ||||||
|                                 for (var key in (values[widget.items[r][e].key] |                       return Padding( | ||||||
|                                         as Map<String, MapEntry<int, bool>>) |                           padding: const EdgeInsets.symmetric(horizontal: 4), | ||||||
|                                     .keys) { |                           child: ChoiceChip( | ||||||
|                                   if (key != e2.key) { |                             label: Text(e2.key), | ||||||
|                                     (values[widget.items[r][e].key] as Map< |                             backgroundColor: Color(e2.value.key).withAlpha(50), | ||||||
|                                         String, |                             selectedColor: Color(e2.value.key), | ||||||
|                                         MapEntry<int, |                             visualDensity: VisualDensity.compact, | ||||||
|                                             bool>>)[key] = MapEntry( |                             selected: e2.value.value, | ||||||
|  |                             onSelected: (value) { | ||||||
|  |                               setState(() { | ||||||
|  |                                 (values[widget.items[r][e].key] as Map<String, | ||||||
|  |                                         MapEntry<int, bool>>)[e2.key] = | ||||||
|  |                                     MapEntry( | ||||||
|                                         (values[widget.items[r][e].key] as Map< |                                         (values[widget.items[r][e].key] as Map< | ||||||
|                                                 String, |                                                 String, | ||||||
|                                                 MapEntry<int, bool>>)[key]! |                                                 MapEntry<int, bool>>)[e2.key]! | ||||||
|                                             .key, |                                             .key, | ||||||
|                                         false); |                                         value); | ||||||
|  |                                 if ((widget.items[r][e] | ||||||
|  |                                             as GeneratedFormTagInput) | ||||||
|  |                                         .singleSelect && | ||||||
|  |                                     value == true) { | ||||||
|  |                                   for (var key in (values[ | ||||||
|  |                                               widget.items[r][e].key] | ||||||
|  |                                           as Map<String, MapEntry<int, bool>>) | ||||||
|  |                                       .keys) { | ||||||
|  |                                     if (key != e2.key) { | ||||||
|  |                                       (values[widget.items[r][e].key] as Map< | ||||||
|  |                                               String, | ||||||
|  |                                               MapEntry<int, bool>>)[key] = | ||||||
|  |                                           MapEntry( | ||||||
|  |                                               (values[widget.items[r][e].key] | ||||||
|  |                                                       as Map< | ||||||
|  |                                                           String, | ||||||
|  |                                                           MapEntry<int, | ||||||
|  |                                                               bool>>)[key]! | ||||||
|  |                                                   .key, | ||||||
|  |                                               false); | ||||||
|  |                                     } | ||||||
|                                   } |                                   } | ||||||
|                                 } |                                 } | ||||||
|                               } |                                 someValueChanged(); | ||||||
|                               someValueChanged(); |                               }); | ||||||
|                             }); |                             }, | ||||||
|  |                           )); | ||||||
|  |                     }) ?? | ||||||
|  |                     [const SizedBox.shrink()], | ||||||
|  |                 (values[widget.items[r][e].key] | ||||||
|  |                                 as Map<String, MapEntry<int, bool>>?) | ||||||
|  |                             ?.values | ||||||
|  |                             .where((e) => e.value) | ||||||
|  |                             .isNotEmpty == | ||||||
|  |                         true | ||||||
|  |                     ? Padding( | ||||||
|  |                         padding: const EdgeInsets.symmetric(horizontal: 4), | ||||||
|  |                         child: IconButton( | ||||||
|  |                           onPressed: () { | ||||||
|  |                             fn() { | ||||||
|  |                               setState(() { | ||||||
|  |                                 var temp = values[widget.items[r][e].key] | ||||||
|  |                                     as Map<String, MapEntry<int, bool>>; | ||||||
|  |                                 temp.removeWhere((key, value) => value.value); | ||||||
|  |                                 values[widget.items[r][e].key] = temp; | ||||||
|  |                                 someValueChanged(); | ||||||
|  |                               }); | ||||||
|  |                             } | ||||||
|  |  | ||||||
|  |                             if ((widget.items[r][e] as GeneratedFormTagInput) | ||||||
|  |                                     .deleteConfirmationMessage != | ||||||
|  |                                 null) { | ||||||
|  |                               var message = | ||||||
|  |                                   (widget.items[r][e] as GeneratedFormTagInput) | ||||||
|  |                                       .deleteConfirmationMessage!; | ||||||
|  |                               showDialog<Map<String, dynamic>?>( | ||||||
|  |                                   context: context, | ||||||
|  |                                   builder: (BuildContext ctx) { | ||||||
|  |                                     return GeneratedFormModal( | ||||||
|  |                                         title: message.key, | ||||||
|  |                                         message: message.value, | ||||||
|  |                                         items: const []); | ||||||
|  |                                   }).then((value) { | ||||||
|  |                                 if (value != null) { | ||||||
|  |                                   fn(); | ||||||
|  |                                 } | ||||||
|  |                               }); | ||||||
|  |                             } else { | ||||||
|  |                               fn(); | ||||||
|  |                             } | ||||||
|                           }, |                           }, | ||||||
|                         )); |                           icon: const Icon(Icons.remove), | ||||||
|                   }) ?? |                           visualDensity: VisualDensity.compact, | ||||||
|                   [const SizedBox.shrink()], |                           tooltip: tr('remove'), | ||||||
|               (values[widget.items[r][e].key] |                         )) | ||||||
|                               as Map<String, MapEntry<int, bool>>?) |                     : const SizedBox.shrink(), | ||||||
|                           ?.values |                 Padding( | ||||||
|                           .where((e) => e.value) |                     padding: const EdgeInsets.symmetric(horizontal: 4), | ||||||
|                           .isNotEmpty == |                     child: IconButton( | ||||||
|                       true |                       onPressed: () { | ||||||
|                   ? Padding( |                         showDialog<Map<String, dynamic>?>( | ||||||
|                       padding: const EdgeInsets.symmetric(horizontal: 4), |                             context: context, | ||||||
|                       child: IconButton( |                             builder: (BuildContext ctx) { | ||||||
|                         onPressed: () { |                               return GeneratedFormModal( | ||||||
|                           fn() { |                                   title: widget.items[r][e].label, | ||||||
|  |                                   items: [ | ||||||
|  |                                     [ | ||||||
|  |                                       GeneratedFormTextField('label', | ||||||
|  |                                           label: tr('label')) | ||||||
|  |                                     ] | ||||||
|  |                                   ]); | ||||||
|  |                             }).then((value) { | ||||||
|  |                           String? label = value?['label']; | ||||||
|  |                           if (label != null) { | ||||||
|                             setState(() { |                             setState(() { | ||||||
|                               var temp = values[widget.items[r][e].key] |                               var temp = values[widget.items[r][e].key] | ||||||
|                                   as Map<String, MapEntry<int, bool>>; |                                   as Map<String, MapEntry<int, bool>>?; | ||||||
|                               temp.removeWhere((key, value) => value.value); |                               temp ??= {}; | ||||||
|                               values[widget.items[r][e].key] = temp; |                               if (temp[label] == null) { | ||||||
|                               someValueChanged(); |                                 var singleSelect = (widget.items[r][e] | ||||||
|                             }); |                                         as GeneratedFormTagInput) | ||||||
|                           } |                                     .singleSelect; | ||||||
|  |                                 var someSelected = temp.entries | ||||||
|                           if ((widget.items[r][e] as GeneratedFormTagInput) |                                     .where((element) => element.value.value) | ||||||
|                                   .deleteConfirmationMessage != |                                     .isNotEmpty; | ||||||
|                               null) { |                                 temp[label] = MapEntry( | ||||||
|                             var message = |                                     generateRandomLightColor().value, | ||||||
|                                 (widget.items[r][e] as GeneratedFormTagInput) |                                     !(someSelected && singleSelect)); | ||||||
|                                     .deleteConfirmationMessage!; |                                 values[widget.items[r][e].key] = temp; | ||||||
|                             showDialog<Map<String, dynamic>?>( |                                 someValueChanged(); | ||||||
|                                 context: context, |  | ||||||
|                                 builder: (BuildContext ctx) { |  | ||||||
|                                   return GeneratedFormModal( |  | ||||||
|                                       title: message.key, |  | ||||||
|                                       message: message.value, |  | ||||||
|                                       items: const []); |  | ||||||
|                                 }).then((value) { |  | ||||||
|                               if (value != null) { |  | ||||||
|                                 fn(); |  | ||||||
|                               } |                               } | ||||||
|                             }); |                             }); | ||||||
|                           } else { |  | ||||||
|                             fn(); |  | ||||||
|                           } |                           } | ||||||
|                         }, |                         }); | ||||||
|                         icon: const Icon(Icons.remove), |                       }, | ||||||
|                         visualDensity: VisualDensity.compact, |                       icon: const Icon(Icons.add), | ||||||
|                         tooltip: tr('remove'), |                       visualDensity: VisualDensity.compact, | ||||||
|                       )) |                       tooltip: tr('add'), | ||||||
|                   : const SizedBox.shrink(), |                     )), | ||||||
|               Padding( |               ], | ||||||
|                   padding: const EdgeInsets.symmetric(horizontal: 4), |             ) | ||||||
|                   child: IconButton( |           ]); | ||||||
|                     onPressed: () { |  | ||||||
|                       showDialog<Map<String, dynamic>?>( |  | ||||||
|                           context: context, |  | ||||||
|                           builder: (BuildContext ctx) { |  | ||||||
|                             return GeneratedFormModal( |  | ||||||
|                                 title: widget.items[r][e].label, |  | ||||||
|                                 items: [ |  | ||||||
|                                   [ |  | ||||||
|                                     GeneratedFormTextField('label', |  | ||||||
|                                         label: tr('label')) |  | ||||||
|                                   ] |  | ||||||
|                                 ]); |  | ||||||
|                           }).then((value) { |  | ||||||
|                         String? label = value?['label']; |  | ||||||
|                         if (label != null) { |  | ||||||
|                           setState(() { |  | ||||||
|                             var temp = values[widget.items[r][e].key] |  | ||||||
|                                 as Map<String, MapEntry<int, bool>>?; |  | ||||||
|                             temp ??= {}; |  | ||||||
|                             var singleSelect = |  | ||||||
|                                 (widget.items[r][e] as GeneratedFormTagInput) |  | ||||||
|                                     .singleSelect; |  | ||||||
|                             var someSelected = temp.entries |  | ||||||
|                                 .where((element) => element.value.value) |  | ||||||
|                                 .isNotEmpty; |  | ||||||
|                             temp[label] = MapEntry( |  | ||||||
|                                 generateRandomLightColor().value, |  | ||||||
|                                 !(someSelected && singleSelect)); |  | ||||||
|                             values[widget.items[r][e].key] = temp; |  | ||||||
|                             someValueChanged(); |  | ||||||
|                           }); |  | ||||||
|                         } |  | ||||||
|                       }); |  | ||||||
|                     }, |  | ||||||
|                     icon: const Icon(Icons.add), |  | ||||||
|                     visualDensity: VisualDensity.compact, |  | ||||||
|                     tooltip: tr('add'), |  | ||||||
|                   )), |  | ||||||
|             ], |  | ||||||
|           ); |  | ||||||
|         } |         } | ||||||
|       } |       } | ||||||
|     } |     } | ||||||
|   | |||||||
| @@ -9,12 +9,14 @@ class GeneratedFormModal extends StatefulWidget { | |||||||
|       required this.title, |       required this.title, | ||||||
|       required this.items, |       required this.items, | ||||||
|       this.initValid = false, |       this.initValid = false, | ||||||
|       this.message = ''}); |       this.message = '', | ||||||
|  |       this.additionalWidgets = const []}); | ||||||
|  |  | ||||||
|   final String title; |   final String title; | ||||||
|   final String message; |   final String message; | ||||||
|   final List<List<GeneratedFormItem>> items; |   final List<List<GeneratedFormItem>> items; | ||||||
|   final bool initValid; |   final bool initValid; | ||||||
|  |   final List<Widget> additionalWidgets; | ||||||
|  |  | ||||||
|   @override |   @override | ||||||
|   State<GeneratedFormModal> createState() => _GeneratedFormModalState(); |   State<GeneratedFormModal> createState() => _GeneratedFormModalState(); | ||||||
| @@ -54,7 +56,8 @@ class _GeneratedFormModalState extends State<GeneratedFormModal> { | |||||||
|                   this.valid = valid; |                   this.valid = valid; | ||||||
|                 }); |                 }); | ||||||
|               } |               } | ||||||
|             }) |             }), | ||||||
|  |         if (widget.additionalWidgets.isNotEmpty) ...widget.additionalWidgets | ||||||
|       ]), |       ]), | ||||||
|       actions: [ |       actions: [ | ||||||
|         TextButton( |         TextButton( | ||||||
|   | |||||||
| @@ -21,7 +21,7 @@ import 'package:easy_localization/src/easy_localization_controller.dart'; | |||||||
| // ignore: implementation_imports | // ignore: implementation_imports | ||||||
| import 'package:easy_localization/src/localization.dart'; | import 'package:easy_localization/src/localization.dart'; | ||||||
|  |  | ||||||
| const String currentVersion = '0.9.4'; | const String currentVersion = '0.9.9'; | ||||||
| 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 | ||||||
|  |  | ||||||
|   | |||||||
| @@ -8,6 +8,7 @@ import 'package:obtainium/custom_errors.dart'; | |||||||
| import 'package:obtainium/main.dart'; | import 'package:obtainium/main.dart'; | ||||||
| import 'package:obtainium/pages/app.dart'; | import 'package:obtainium/pages/app.dart'; | ||||||
| import 'package:obtainium/pages/import_export.dart'; | import 'package:obtainium/pages/import_export.dart'; | ||||||
|  | import 'package:obtainium/pages/settings.dart'; | ||||||
| import 'package:obtainium/providers/apps_provider.dart'; | import 'package:obtainium/providers/apps_provider.dart'; | ||||||
| import 'package:obtainium/providers/settings_provider.dart'; | import 'package:obtainium/providers/settings_provider.dart'; | ||||||
| import 'package:obtainium/providers/source_provider.dart'; | import 'package:obtainium/providers/source_provider.dart'; | ||||||
| @@ -29,6 +30,7 @@ class _AddAppPageState extends State<AddAppPage> { | |||||||
|   AppSource? pickedSource; |   AppSource? pickedSource; | ||||||
|   Map<String, dynamic> additionalSettings = {}; |   Map<String, dynamic> additionalSettings = {}; | ||||||
|   bool additionalSettingsValid = true; |   bool additionalSettingsValid = true; | ||||||
|  |   String? category; | ||||||
|  |  | ||||||
|   @override |   @override | ||||||
|   Widget build(BuildContext context) { |   Widget build(BuildContext context) { | ||||||
| @@ -37,25 +39,19 @@ class _AddAppPageState extends State<AddAppPage> { | |||||||
|  |  | ||||||
|     changeUserInput(String input, bool valid, bool isBuilding) { |     changeUserInput(String input, bool valid, bool isBuilding) { | ||||||
|       userInput = input; |       userInput = input; | ||||||
|       fn() { |       if (!isBuilding) { | ||||||
|         var source = valid ? sourceProvider.getSource(userInput) : null; |  | ||||||
|         if (pickedSource.runtimeType != source.runtimeType) { |  | ||||||
|           pickedSource = source; |  | ||||||
|           additionalSettings = source != null |  | ||||||
|               ? getDefaultValuesFromFormItems( |  | ||||||
|                   source.combinedAppSpecificSettingFormItems) |  | ||||||
|               : {}; |  | ||||||
|           additionalSettingsValid = source != null |  | ||||||
|               ? !sourceProvider.ifRequiredAppSpecificSettingsExist(source) |  | ||||||
|               : true; |  | ||||||
|         } |  | ||||||
|       } |  | ||||||
|  |  | ||||||
|       if (isBuilding) { |  | ||||||
|         fn(); |  | ||||||
|       } else { |  | ||||||
|         setState(() { |         setState(() { | ||||||
|           fn(); |           var source = valid ? sourceProvider.getSource(userInput) : null; | ||||||
|  |           if (pickedSource.runtimeType != source.runtimeType) { | ||||||
|  |             pickedSource = source; | ||||||
|  |             additionalSettings = source != null | ||||||
|  |                 ? getDefaultValuesFromFormItems( | ||||||
|  |                     source.combinedAppSpecificSettingFormItems) | ||||||
|  |                 : {}; | ||||||
|  |             additionalSettingsValid = source != null | ||||||
|  |                 ? !sourceProvider.ifRequiredAppSpecificSettingsExist(source) | ||||||
|  |                 : true; | ||||||
|  |           } | ||||||
|         }); |         }); | ||||||
|       } |       } | ||||||
|     } |     } | ||||||
| @@ -131,6 +127,9 @@ class _AddAppPageState extends State<AddAppPage> { | |||||||
|           if (app.additionalSettings['trackOnly'] == true) { |           if (app.additionalSettings['trackOnly'] == true) { | ||||||
|             app.installedVersion = app.latestVersion; |             app.installedVersion = app.latestVersion; | ||||||
|           } |           } | ||||||
|  |           if (category != null) { | ||||||
|  |             app.category = category; | ||||||
|  |           } | ||||||
|           await appsProvider.saveApps([app]); |           await appsProvider.saveApps([app]); | ||||||
|  |  | ||||||
|           return app; |           return app; | ||||||
| @@ -238,7 +237,9 @@ class _AddAppPageState extends State<AddAppPage> { | |||||||
|                                     ] |                                     ] | ||||||
|                                   ], |                                   ], | ||||||
|                                   onValueChanges: (values, valid, isBuilding) { |                                   onValueChanges: (values, valid, isBuilding) { | ||||||
|                                     if (values.isNotEmpty && valid) { |                                     if (values.isNotEmpty && | ||||||
|  |                                         valid && | ||||||
|  |                                         !isBuilding) { | ||||||
|                                       setState(() { |                                       setState(() { | ||||||
|                                         searchQuery = |                                         searchQuery = | ||||||
|                                             values['searchSomeSources']!.trim(); |                                             values['searchSomeSources']!.trim(); | ||||||
| @@ -299,9 +300,7 @@ class _AddAppPageState extends State<AddAppPage> { | |||||||
|                                 child: Text(tr('search'))) |                                 child: Text(tr('search'))) | ||||||
|                           ], |                           ], | ||||||
|                         ), |                         ), | ||||||
|                       if (pickedSource != null && |                       if (pickedSource != null) | ||||||
|                           (pickedSource! |  | ||||||
|                               .combinedAppSpecificSettingFormItems.isNotEmpty)) |  | ||||||
|                         Column( |                         Column( | ||||||
|                           crossAxisAlignment: CrossAxisAlignment.stretch, |                           crossAxisAlignment: CrossAxisAlignment.stretch, | ||||||
|                           children: [ |                           children: [ | ||||||
| @@ -328,6 +327,21 @@ class _AddAppPageState extends State<AddAppPage> { | |||||||
|                                     }); |                                     }); | ||||||
|                                   } |                                   } | ||||||
|                                 }), |                                 }), | ||||||
|  |                             Column( | ||||||
|  |                               children: [ | ||||||
|  |                                 const SizedBox( | ||||||
|  |                                   height: 16, | ||||||
|  |                                 ), | ||||||
|  |                                 CategoryEditorSelector( | ||||||
|  |                                     alignment: WrapAlignment.start, | ||||||
|  |                                     singleSelect: true, | ||||||
|  |                                     onSelected: (categories) { | ||||||
|  |                                       category = categories.isEmpty | ||||||
|  |                                           ? null | ||||||
|  |                                           : categories.first; | ||||||
|  |                                     }), | ||||||
|  |                               ], | ||||||
|  |                             ), | ||||||
|                           ], |                           ], | ||||||
|                         ) |                         ) | ||||||
|                       else |                       else | ||||||
|   | |||||||
| @@ -1,7 +1,6 @@ | |||||||
| 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: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/main.dart'; | ||||||
| @@ -35,7 +34,6 @@ class _AppPageState extends State<AppPage> { | |||||||
|       }); |       }); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     var categories = settingsProvider.categories; |  | ||||||
|     var sourceProvider = SourceProvider(); |     var sourceProvider = SourceProvider(); | ||||||
|     AppInMemory? app = appsProvider.apps[widget.appId]; |     AppInMemory? app = appsProvider.apps[widget.appId]; | ||||||
|     var source = app != null ? sourceProvider.getSource(app.app.url) : null; |     var source = app != null ? sourceProvider.getSource(app.app.url) : null; | ||||||
| @@ -72,11 +70,12 @@ class _AppPageState extends State<AppPage> { | |||||||
|                   : Container() |                   : Container() | ||||||
|               : CustomScrollView( |               : CustomScrollView( | ||||||
|                   slivers: [ |                   slivers: [ | ||||||
|                     SliverFillRemaining( |                     SliverToBoxAdapter( | ||||||
|                         child: Column( |                         child: Column( | ||||||
|                       mainAxisAlignment: MainAxisAlignment.center, |                       mainAxisAlignment: MainAxisAlignment.center, | ||||||
|                       crossAxisAlignment: CrossAxisAlignment.stretch, |                       crossAxisAlignment: CrossAxisAlignment.stretch, | ||||||
|                       children: [ |                       children: [ | ||||||
|  |                         const SizedBox(height: 150), | ||||||
|                         app?.installedInfo != null |                         app?.installedInfo != null | ||||||
|                             ? Row( |                             ? Row( | ||||||
|                                 mainAxisAlignment: MainAxisAlignment.center, |                                 mainAxisAlignment: MainAxisAlignment.center, | ||||||
| @@ -168,7 +167,8 @@ class _AppPageState extends State<AppPage> { | |||||||
|                                     : null; |                                     : null; | ||||||
|                                 appsProvider.saveApps([app.app]); |                                 appsProvider.saveApps([app.app]); | ||||||
|                               } |                               } | ||||||
|                             }) |                             }), | ||||||
|  |                         const SizedBox(height: 150) | ||||||
|                       ], |                       ], | ||||||
|                     )), |                     )), | ||||||
|                   ], |                   ], | ||||||
|   | |||||||
| @@ -7,6 +7,7 @@ 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/main.dart'; | ||||||
| import 'package:obtainium/pages/app.dart'; | import 'package:obtainium/pages/app.dart'; | ||||||
|  | import 'package:obtainium/pages/settings.dart'; | ||||||
| import 'package:obtainium/providers/apps_provider.dart'; | import 'package:obtainium/providers/apps_provider.dart'; | ||||||
| import 'package:obtainium/providers/settings_provider.dart'; | import 'package:obtainium/providers/settings_provider.dart'; | ||||||
| import 'package:obtainium/providers/source_provider.dart'; | import 'package:obtainium/providers/source_provider.dart'; | ||||||
| @@ -22,7 +23,8 @@ class AppsPage extends StatefulWidget { | |||||||
| } | } | ||||||
|  |  | ||||||
| class AppsPageState extends State<AppsPage> { | class AppsPageState extends State<AppsPage> { | ||||||
|   AppsFilter? filter; |   AppsFilter filter = AppsFilter(); | ||||||
|  |   final AppsFilter neutralFilter = AppsFilter(); | ||||||
|   var updatesOnlyFilter = |   var updatesOnlyFilter = | ||||||
|       AppsFilter(includeUptodate: false, includeNonInstalled: false); |       AppsFilter(includeUptodate: false, includeNonInstalled: false); | ||||||
|   Set<App> selectedApps = {}; |   Set<App> selectedApps = {}; | ||||||
| @@ -53,8 +55,7 @@ class AppsPageState extends State<AppsPage> { | |||||||
|     var appsProvider = context.watch<AppsProvider>(); |     var appsProvider = context.watch<AppsProvider>(); | ||||||
|     var settingsProvider = context.watch<SettingsProvider>(); |     var settingsProvider = context.watch<SettingsProvider>(); | ||||||
|     var sortedApps = appsProvider.apps.values.toList(); |     var sortedApps = appsProvider.apps.values.toList(); | ||||||
|     var currentFilterIsUpdatesOnly = |     var currentFilterIsUpdatesOnly = filter.isIdenticalTo(updatesOnlyFilter); | ||||||
|         filter?.isIdenticalTo(updatesOnlyFilter) ?? false; |  | ||||||
|  |  | ||||||
|     selectedApps = selectedApps |     selectedApps = selectedApps | ||||||
|         .where((element) => sortedApps.map((e) => e.app).contains(element)) |         .where((element) => sortedApps.map((e) => e.app).contains(element)) | ||||||
| @@ -70,45 +71,42 @@ class AppsPageState extends State<AppsPage> { | |||||||
|       }); |       }); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     if (filter != null) { |     sortedApps = sortedApps.where((app) { | ||||||
|       sortedApps = sortedApps.where((app) { |       if (app.app.installedVersion == app.app.latestVersion && | ||||||
|         if (app.app.installedVersion == app.app.latestVersion && |           !(filter.includeUptodate)) { | ||||||
|             !(filter!.includeUptodate)) { |         return false; | ||||||
|           return false; |       } | ||||||
|         } |       if (app.app.installedVersion == null && !(filter.includeNonInstalled)) { | ||||||
|         if (app.app.installedVersion == null && |         return false; | ||||||
|             !(filter!.includeNonInstalled)) { |       } | ||||||
|           return false; |       if (filter.nameFilter.isNotEmpty || filter.authorFilter.isNotEmpty) { | ||||||
|         } |         List<String> nameTokens = filter.nameFilter | ||||||
|         if (filter!.nameFilter.isNotEmpty || filter!.authorFilter.isNotEmpty) { |             .split(' ') | ||||||
|           List<String> nameTokens = filter!.nameFilter |             .where((element) => element.trim().isNotEmpty) | ||||||
|               .split(' ') |             .toList(); | ||||||
|               .where((element) => element.trim().isNotEmpty) |         List<String> authorTokens = filter.authorFilter | ||||||
|               .toList(); |             .split(' ') | ||||||
|           List<String> authorTokens = filter!.authorFilter |             .where((element) => element.trim().isNotEmpty) | ||||||
|               .split(' ') |             .toList(); | ||||||
|               .where((element) => element.trim().isNotEmpty) |  | ||||||
|               .toList(); |  | ||||||
|  |  | ||||||
|           for (var t in nameTokens) { |         for (var t in nameTokens) { | ||||||
|             var name = app.installedInfo?.name ?? app.app.name; |           var name = app.installedInfo?.name ?? app.app.name; | ||||||
|             if (!name.toLowerCase().contains(t.toLowerCase())) { |           if (!name.toLowerCase().contains(t.toLowerCase())) { | ||||||
|               return false; |             return false; | ||||||
|             } |  | ||||||
|           } |  | ||||||
|           for (var t in authorTokens) { |  | ||||||
|             if (!app.app.author.toLowerCase().contains(t.toLowerCase())) { |  | ||||||
|               return false; |  | ||||||
|             } |  | ||||||
|           } |           } | ||||||
|         } |         } | ||||||
|         if (filter!.categoryFilter.isNotEmpty && |         for (var t in authorTokens) { | ||||||
|             filter!.categoryFilter != app.app.category) { |           if (!app.app.author.toLowerCase().contains(t.toLowerCase())) { | ||||||
|           return false; |             return false; | ||||||
|  |           } | ||||||
|         } |         } | ||||||
|         return true; |       } | ||||||
|       }).toList(); |       if (filter.categoryFilter.isNotEmpty && | ||||||
|     } |           !filter.categoryFilter.contains(app.app.category)) { | ||||||
|  |         return false; | ||||||
|  |       } | ||||||
|  |       return true; | ||||||
|  |     }).toList(); | ||||||
|  |  | ||||||
|     sortedApps.sort((a, b) { |     sortedApps.sort((a, b) { | ||||||
|       var nameA = a.installedInfo?.name ?? a.app.name; |       var nameA = a.installedInfo?.name ?? a.app.name; | ||||||
| @@ -230,7 +228,7 @@ class AppsPageState extends State<AppsPage> { | |||||||
|                   decoration: BoxDecoration( |                   decoration: BoxDecoration( | ||||||
|                       border: Border.symmetric( |                       border: Border.symmetric( | ||||||
|                           vertical: BorderSide( |                           vertical: BorderSide( | ||||||
|                               width: 3, |                               width: 4, | ||||||
|                               color: Color(settingsProvider.categories[ |                               color: Color(settingsProvider.categories[ | ||||||
|                                       sortedApps[index].app.category] ?? |                                       sortedApps[index].app.category] ?? | ||||||
|                                   const Color.fromARGB(0, 0, 0, 0).value)))), |                                   const Color.fromARGB(0, 0, 0, 0).value)))), | ||||||
| @@ -339,21 +337,29 @@ class AppsPageState extends State<AppsPage> { | |||||||
|       persistentFooterButtons: [ |       persistentFooterButtons: [ | ||||||
|         Row( |         Row( | ||||||
|           children: [ |           children: [ | ||||||
|             IconButton( |             selectedApps.isEmpty | ||||||
|                 onPressed: () { |                 ? IconButton( | ||||||
|                   selectedApps.isEmpty |                     onPressed: () { | ||||||
|                       ? selectThese(sortedApps.map((e) => e.app).toList()) |                       selectThese(sortedApps.map((e) => e.app).toList()); | ||||||
|                       : clearSelected(); |                     }, | ||||||
|                 }, |                     icon: Icon( | ||||||
|                 icon: Icon( |                       Icons.select_all_outlined, | ||||||
|                   selectedApps.isEmpty |                       color: Theme.of(context).colorScheme.primary, | ||||||
|                       ? Icons.select_all_outlined |                     ), | ||||||
|                       : Icons.deselect_outlined, |                     tooltip: tr('selectAll')) | ||||||
|                   color: Theme.of(context).colorScheme.primary, |                 : TextButton.icon( | ||||||
|                 ), |                     onPressed: () { | ||||||
|                 tooltip: selectedApps.isEmpty |                       selectedApps.isEmpty | ||||||
|                     ? tr('selectAll') |                           ? selectThese(sortedApps.map((e) => e.app).toList()) | ||||||
|                     : tr('deselectN', args: [selectedApps.length.toString()])), |                           : clearSelected(); | ||||||
|  |                     }, | ||||||
|  |                     icon: Icon( | ||||||
|  |                       selectedApps.isEmpty | ||||||
|  |                           ? Icons.select_all_outlined | ||||||
|  |                           : Icons.deselect_outlined, | ||||||
|  |                       color: Theme.of(context).colorScheme.primary, | ||||||
|  |                     ), | ||||||
|  |                     label: Text(selectedApps.length.toString())), | ||||||
|             const VerticalDivider(), |             const VerticalDivider(), | ||||||
|             Expanded( |             Expanded( | ||||||
|                 child: Row( |                 child: Row( | ||||||
| @@ -663,7 +669,7 @@ class AppsPageState extends State<AppsPage> { | |||||||
|               onPressed: () { |               onPressed: () { | ||||||
|                 setState(() { |                 setState(() { | ||||||
|                   if (currentFilterIsUpdatesOnly) { |                   if (currentFilterIsUpdatesOnly) { | ||||||
|                     filter = null; |                     filter = AppsFilter(); | ||||||
|                   } else { |                   } else { | ||||||
|                     filter = updatesOnlyFilter; |                     filter = updatesOnlyFilter; | ||||||
|                   } |                   } | ||||||
| @@ -683,9 +689,11 @@ class AppsPageState extends State<AppsPage> { | |||||||
|                 ? const SizedBox() |                 ? const SizedBox() | ||||||
|                 : TextButton.icon( |                 : TextButton.icon( | ||||||
|                     label: Text( |                     label: Text( | ||||||
|                       filter == null ? tr('filter') : tr('filterActive'), |                       filter.isIdenticalTo(neutralFilter) | ||||||
|  |                           ? tr('filter') | ||||||
|  |                           : tr('filterActive'), | ||||||
|                       style: TextStyle( |                       style: TextStyle( | ||||||
|                           fontWeight: filter == null |                           fontWeight: filter.isIdenticalTo(neutralFilter) | ||||||
|                               ? FontWeight.normal |                               ? FontWeight.normal | ||||||
|                               : FontWeight.bold), |                               : FontWeight.bold), | ||||||
|                     ), |                     ), | ||||||
| @@ -693,44 +701,48 @@ class AppsPageState extends State<AppsPage> { | |||||||
|                       showDialog<Map<String, dynamic>?>( |                       showDialog<Map<String, dynamic>?>( | ||||||
|                           context: context, |                           context: context, | ||||||
|                           builder: (BuildContext ctx) { |                           builder: (BuildContext ctx) { | ||||||
|                             var vals = filter == null |                             var vals = filter.toFormValuesMap(); | ||||||
|                                 ? AppsFilter().toValuesMap() |  | ||||||
|                                 : filter!.toValuesMap(); |  | ||||||
|                             return GeneratedFormModal( |                             return GeneratedFormModal( | ||||||
|                                 title: tr('filterApps'), |                               initValid: true, | ||||||
|                                 items: [ |                               title: tr('filterApps'), | ||||||
|                                   [ |                               items: [ | ||||||
|                                     GeneratedFormTextField('appName', |                                 [ | ||||||
|                                         label: tr('appName'), |                                   GeneratedFormTextField('appName', | ||||||
|                                         required: false, |                                       label: tr('appName'), | ||||||
|                                         defaultValue: vals['appName']), |                                       required: false, | ||||||
|                                     GeneratedFormTextField('author', |                                       defaultValue: vals['appName']), | ||||||
|                                         label: tr('author'), |                                   GeneratedFormTextField('author', | ||||||
|                                         required: false, |                                       label: tr('author'), | ||||||
|                                         defaultValue: vals['author']) |                                       required: false, | ||||||
|                                   ], |                                       defaultValue: vals['author']) | ||||||
|                                   [ |                                 ], | ||||||
|                                     GeneratedFormSwitch('upToDateApps', |                                 [ | ||||||
|                                         label: tr('upToDateApps'), |                                   GeneratedFormSwitch('upToDateApps', | ||||||
|                                         defaultValue: vals['upToDateApps']) |                                       label: tr('upToDateApps'), | ||||||
|                                   ], |                                       defaultValue: vals['upToDateApps']) | ||||||
|                                   [ |                                 ], | ||||||
|                                     GeneratedFormSwitch('nonInstalledApps', |                                 [ | ||||||
|                                         label: tr('nonInstalledApps'), |                                   GeneratedFormSwitch('nonInstalledApps', | ||||||
|                                         defaultValue: vals['nonInstalledApps']) |                                       label: tr('nonInstalledApps'), | ||||||
|                                   ], |                                       defaultValue: vals['nonInstalledApps']) | ||||||
|                                   [ |                                 ] | ||||||
|                                     settingsProvider.getCategoryFormItem( |                               ], | ||||||
|                                         initCategory: vals['category'] ?? '') |                               additionalWidgets: [ | ||||||
|                                   ] |                                 const SizedBox( | ||||||
|                                 ]); |                                   height: 16, | ||||||
|  |                                 ), | ||||||
|  |                                 CategoryEditorSelector( | ||||||
|  |                                   preselected: filter.categoryFilter, | ||||||
|  |                                   onSelected: (categories) { | ||||||
|  |                                     filter.categoryFilter = categories.toSet(); | ||||||
|  |                                   }, | ||||||
|  |                                 ) | ||||||
|  |                               ], | ||||||
|  |                             ); | ||||||
|                           }).then((values) { |                           }).then((values) { | ||||||
|                         if (values != null) { |                         if (values != null) { | ||||||
|                           setState(() { |                           setState(() { | ||||||
|                             filter = AppsFilter.fromValuesMap(values); |                             filter.setFormValuesFromMap(values); | ||||||
|                             if (AppsFilter().isIdenticalTo(filter!)) { |  | ||||||
|                               filter = null; |  | ||||||
|                             } |  | ||||||
|                           }); |                           }); | ||||||
|                         } |                         } | ||||||
|                       }); |                       }); | ||||||
| @@ -748,31 +760,29 @@ class AppsFilter { | |||||||
|   late String authorFilter; |   late String authorFilter; | ||||||
|   late bool includeUptodate; |   late bool includeUptodate; | ||||||
|   late bool includeNonInstalled; |   late bool includeNonInstalled; | ||||||
|   late String categoryFilter; |   late Set<String> categoryFilter; | ||||||
|  |  | ||||||
|   AppsFilter( |   AppsFilter( | ||||||
|       {this.nameFilter = '', |       {this.nameFilter = '', | ||||||
|       this.authorFilter = '', |       this.authorFilter = '', | ||||||
|       this.includeUptodate = true, |       this.includeUptodate = true, | ||||||
|       this.includeNonInstalled = true, |       this.includeNonInstalled = true, | ||||||
|       this.categoryFilter = ''}); |       this.categoryFilter = const {}}); | ||||||
|  |  | ||||||
|   Map<String, dynamic> toValuesMap() { |   Map<String, dynamic> toFormValuesMap() { | ||||||
|     return { |     return { | ||||||
|       'appName': nameFilter, |       'appName': nameFilter, | ||||||
|       'author': authorFilter, |       'author': authorFilter, | ||||||
|       'upToDateApps': includeUptodate, |       'upToDateApps': includeUptodate, | ||||||
|       'nonInstalledApps': includeNonInstalled, |       'nonInstalledApps': includeNonInstalled | ||||||
|       'category': categoryFilter |  | ||||||
|     }; |     }; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   AppsFilter.fromValuesMap(Map<String, dynamic> values) { |   setFormValuesFromMap(Map<String, dynamic> values) { | ||||||
|     nameFilter = values['appName']!; |     nameFilter = values['appName']!; | ||||||
|     authorFilter = values['author']!; |     authorFilter = values['author']!; | ||||||
|     includeUptodate = values['upToDateApps']; |     includeUptodate = values['upToDateApps']; | ||||||
|     includeNonInstalled = values['nonInstalledApps']; |     includeNonInstalled = values['nonInstalledApps']; | ||||||
|     categoryFilter = values['category']!; |  | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   bool isIdenticalTo(AppsFilter other) => |   bool isIdenticalTo(AppsFilter other) => | ||||||
| @@ -780,5 +790,7 @@ class AppsFilter { | |||||||
|       nameFilter.trim() == other.nameFilter.trim() && |       nameFilter.trim() == other.nameFilter.trim() && | ||||||
|       includeUptodate == other.includeUptodate && |       includeUptodate == other.includeUptodate && | ||||||
|       includeNonInstalled == other.includeNonInstalled && |       includeNonInstalled == other.includeNonInstalled && | ||||||
|       categoryFilter.trim() == other.categoryFilter.trim(); |       categoryFilter.length == other.categoryFilter.length && | ||||||
|  |       categoryFilter.union(other.categoryFilter).length == | ||||||
|  |           categoryFilter.length; | ||||||
| } | } | ||||||
|   | |||||||
| @@ -338,7 +338,9 @@ class _ImportExportPageState extends State<ImportExportPage> { | |||||||
|                                             ? null |                                             ? null | ||||||
|                                             : () { |                                             : () { | ||||||
|                                                 () async { |                                                 () async { | ||||||
|                                                   var values = await showDialog( |                                                   var values = await showDialog< | ||||||
|  |                                                           Map<String, | ||||||
|  |                                                               dynamic>?>( | ||||||
|                                                       context: context, |                                                       context: context, | ||||||
|                                                       builder: |                                                       builder: | ||||||
|                                                           (BuildContext ctx) { |                                                           (BuildContext ctx) { | ||||||
| @@ -365,7 +367,10 @@ class _ImportExportPageState extends State<ImportExportPage> { | |||||||
|                                                     var urlsWithDescriptions = |                                                     var urlsWithDescriptions = | ||||||
|                                                         await source |                                                         await source | ||||||
|                                                             .getUrlsWithDescriptions( |                                                             .getUrlsWithDescriptions( | ||||||
|                                                                 values); |                                                                 values.values | ||||||
|  |                                                                     .map((e) => | ||||||
|  |                                                                         e.toString()) | ||||||
|  |                                                                     .toList()); | ||||||
|                                                     var selectedUrls = |                                                     var selectedUrls = | ||||||
|                                                         await showDialog< |                                                         await showDialog< | ||||||
|                                                                 List<String>?>( |                                                                 List<String>?>( | ||||||
|   | |||||||
| @@ -185,7 +185,7 @@ class _SettingsPageState extends State<SettingsPage> { | |||||||
|               return [e]; |               return [e]; | ||||||
|             }).toList(), |             }).toList(), | ||||||
|             onValueChanges: (values, valid, isBuilding) { |             onValueChanges: (values, valid, isBuilding) { | ||||||
|               if (valid) { |               if (valid && !isBuilding) { | ||||||
|                 values.forEach((key, value) { |                 values.forEach((key, value) { | ||||||
|                   settingsProvider.setSettingString(key, value); |                   settingsProvider.setSettingString(key, value); | ||||||
|                 }); |                 }); | ||||||
| @@ -286,7 +286,9 @@ class _SettingsPageState extends State<SettingsPage> { | |||||||
|                                   color: Theme.of(context).colorScheme.primary), |                                   color: Theme.of(context).colorScheme.primary), | ||||||
|                             ), |                             ), | ||||||
|                             height16, |                             height16, | ||||||
|                             const CategoryEditorSelector() |                             const CategoryEditorSelector( | ||||||
|  |                               showLabelWhenNotEmpty: false, | ||||||
|  |                             ) | ||||||
|                           ], |                           ], | ||||||
|                         ))), |                         ))), | ||||||
|           SliverToBoxAdapter( |           SliverToBoxAdapter( | ||||||
| @@ -407,12 +409,14 @@ class CategoryEditorSelector extends StatefulWidget { | |||||||
|   final bool singleSelect; |   final bool singleSelect; | ||||||
|   final Set<String> preselected; |   final Set<String> preselected; | ||||||
|   final WrapAlignment alignment; |   final WrapAlignment alignment; | ||||||
|  |   final bool showLabelWhenNotEmpty; | ||||||
|   const CategoryEditorSelector( |   const CategoryEditorSelector( | ||||||
|       {super.key, |       {super.key, | ||||||
|       this.onSelected, |       this.onSelected, | ||||||
|       this.singleSelect = false, |       this.singleSelect = false, | ||||||
|       this.preselected = const {}, |       this.preselected = const {}, | ||||||
|       this.alignment = WrapAlignment.start}); |       this.alignment = WrapAlignment.start, | ||||||
|  |       this.showLabelWhenNotEmpty = true}); | ||||||
|  |  | ||||||
|   @override |   @override | ||||||
|   State<CategoryEditorSelector> createState() => _CategoryEditorSelectorState(); |   State<CategoryEditorSelector> createState() => _CategoryEditorSelectorState(); | ||||||
| @@ -439,7 +443,8 @@ class _CategoryEditorSelectorState extends State<CategoryEditorSelector> { | |||||||
|                 deleteConfirmationMessage: MapEntry( |                 deleteConfirmationMessage: MapEntry( | ||||||
|                     tr('deleteCategoriesQuestion'), |                     tr('deleteCategoriesQuestion'), | ||||||
|                     tr('categoryDeleteWarning')), |                     tr('categoryDeleteWarning')), | ||||||
|                 singleSelect: widget.singleSelect) |                 singleSelect: widget.singleSelect, | ||||||
|  |                 showLabelWhenNotEmpty: widget.showLabelWhenNotEmpty) | ||||||
|           ] |           ] | ||||||
|         ], |         ], | ||||||
|         onValueChanges: ((values, valid, isBuilding) { |         onValueChanges: ((values, valid, isBuilding) { | ||||||
|   | |||||||
| @@ -331,13 +331,13 @@ class SourceProvider { | |||||||
|       {App? currentApp, |       {App? currentApp, | ||||||
|       bool trackOnlyOverride = false, |       bool trackOnlyOverride = false, | ||||||
|       noVersionDetectionOverride = false}) async { |       noVersionDetectionOverride = false}) async { | ||||||
|     if (trackOnlyOverride) { |     if (trackOnlyOverride || source.enforceTrackOnly) { | ||||||
|       additionalSettings['trackOnly'] = true; |       additionalSettings['trackOnly'] = true; | ||||||
|     } |     } | ||||||
|     if (noVersionDetectionOverride) { |     if (noVersionDetectionOverride) { | ||||||
|       additionalSettings['noVersionDetection'] = true; |       additionalSettings['noVersionDetection'] = true; | ||||||
|     } |     } | ||||||
|     var trackOnly = currentApp?.additionalSettings['trackOnly'] == true; |     var trackOnly = additionalSettings['trackOnly'] == true; | ||||||
|     String standardUrl = source.standardizeURL(preStandardizeUrl(url)); |     String standardUrl = source.standardizeURL(preStandardizeUrl(url)); | ||||||
|     APKDetails apk = |     APKDetails apk = | ||||||
|         await source.getLatestAPKDetails(standardUrl, additionalSettings); |         await source.getLatestAPKDetails(standardUrl, additionalSettings); | ||||||
|   | |||||||
| @@ -739,14 +739,14 @@ packages: | |||||||
|       name: webview_flutter |       name: webview_flutter | ||||||
|       url: "https://pub.dartlang.org" |       url: "https://pub.dartlang.org" | ||||||
|     source: hosted |     source: hosted | ||||||
|     version: "4.0.0" |     version: "4.0.1" | ||||||
|   webview_flutter_android: |   webview_flutter_android: | ||||||
|     dependency: transitive |     dependency: transitive | ||||||
|     description: |     description: | ||||||
|       name: webview_flutter_android |       name: webview_flutter_android | ||||||
|       url: "https://pub.dartlang.org" |       url: "https://pub.dartlang.org" | ||||||
|     source: hosted |     source: hosted | ||||||
|     version: "3.0.0" |     version: "3.1.1" | ||||||
|   webview_flutter_platform_interface: |   webview_flutter_platform_interface: | ||||||
|     dependency: transitive |     dependency: transitive | ||||||
|     description: |     description: | ||||||
| @@ -760,7 +760,7 @@ packages: | |||||||
|       name: webview_flutter_wkwebview |       name: webview_flutter_wkwebview | ||||||
|       url: "https://pub.dartlang.org" |       url: "https://pub.dartlang.org" | ||||||
|     source: hosted |     source: hosted | ||||||
|     version: "3.0.0" |     version: "3.0.1" | ||||||
|   win32: |   win32: | ||||||
|     dependency: transitive |     dependency: transitive | ||||||
|     description: |     description: | ||||||
|   | |||||||
| @@ -17,7 +17,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev | |||||||
| # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html | # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html | ||||||
| # In Windows, build-name is used as the major, minor, and patch parts | # In Windows, build-name is used as the major, minor, and patch parts | ||||||
| # of the product and file versions while build-number is used as the build suffix. | # of the product and file versions while build-number is used as the build suffix. | ||||||
| version: 0.9.4+92 # When changing this, update the tag in main() accordingly | version: 0.9.9+97 # 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' | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user