mirror of
				https://github.com/ImranR98/Obtainium.git
				synced 2025-11-04 07:13:28 +01:00 
			
		
		
		
	Compare commits
	
		
			37 Commits
		
	
	
		
			v0.13.18-b
			...
			v0.13.21-b
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					d9225fd639 | ||
| 
						 | 
					995d44551c | ||
| 
						 | 
					68c0224b98 | ||
| 
						 | 
					7767468d5d | ||
| 
						 | 
					f9f83d8243 | ||
| 
						 | 
					cc4cec829f | ||
| 
						 | 
					f665bf1eb2 | ||
| 
						 | 
					bccd52054e | ||
| 
						 | 
					3a4c782aab | ||
| 
						 | 
					6e047e96fa | ||
| 
						 | 
					42f8753166 | ||
| 
						 | 
					b6a64129b3 | ||
| 
						 | 
					bb5bd23263 | ||
| 
						 | 
					58361a0493 | ||
| 
						 | 
					7671ab95f9 | ||
| 
						 | 
					41f102c0ce | ||
| 
						 | 
					5cee527d6f | ||
| 
						 | 
					c2eb13d5e5 | ||
| 
						 | 
					ed89e20826 | ||
| 
						 | 
					e50c7554e3 | ||
| 
						 | 
					00354f10a3 | ||
| 
						 | 
					031d0b7444 | ||
| 
						 | 
					2ced06367b | ||
| 
						 | 
					9ef3279bae | ||
| 
						 | 
					b055486d74 | ||
| 
						 | 
					c70f2c481e | ||
| 
						 | 
					e4b26be01f | ||
| 
						 | 
					b27bdc63c1 | ||
| 
						 | 
					543e7d8cdc | ||
| 
						 | 
					852decbe7d | ||
| 
						 | 
					d2150320fa | ||
| 
						 | 
					9c723c7522 | ||
| 
						 | 
					ac5660de88 | ||
| 
						 | 
					e6467452a6 | ||
| 
						 | 
					04b49c2e61 | ||
| 
						 | 
					324773ba58 | ||
| 
						 | 
					d885ca5db7 | 
							
								
								
									
										291
									
								
								assets/translations/bs.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										291
									
								
								assets/translations/bs.json
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,291 @@
 | 
			
		||||
 {
 | 
			
		||||
   "invalidURLForSource": "Nije važeći URL aplikacije {}",
 | 
			
		||||
   "noReleaseFound": "Nije moguće pronaći odgovarajuće izdanje",
 | 
			
		||||
   "noVersionFound": "Nije moguće odrediti verziju izdanja",
 | 
			
		||||
   "urlMatchesNoSource": "URL se ne podudara s poznatim izvorom",
 | 
			
		||||
   "cantInstallOlderVersion": "Nije moguće instalirati stariju verziju aplikacije",
 | 
			
		||||
   "appIdMismatch": "ID preuzetog paketa se ne podudara s postojećim ID-om aplikacije",
 | 
			
		||||
   "functionNotImplemented": "Ova klasa nije implementirala ovu funkciju",
 | 
			
		||||
   "placeholder": "Rezervirano mjesto",
 | 
			
		||||
   "someErrors": "Došlo je do nekih grešaka",
 | 
			
		||||
   "unexpectedError": "Neočekivana greška",
 | 
			
		||||
   "ok": "Dobro",
 | 
			
		||||
   "and": "i",
 | 
			
		||||
   "startedBgUpdateTask": "Započeo je pozadinski zadatak provjere ažuriranja",
 | 
			
		||||
   "bgUpdateIgnoreAfterIs": "ignoreAfter pozadinskog zadataka je  {}",
 | 
			
		||||
   "startedActualBGUpdateCheck": "Započela je stvarna provjera ažuriranja",
 | 
			
		||||
   "bgUpdateTaskFinished": "Završen zadatak provjere ažuriranja",
 | 
			
		||||
   "firstRun": "Ovo je prvi put da pokrećete Obtainium",
 | 
			
		||||
   "settingUpdateCheckIntervalTo": "Podešavanje intervala ažuriranja na {}",
 | 
			
		||||
   "githubPATLabel": "GitHub token za lični pristup (eng. PAT, povećava ograničenje stope)",
 | 
			
		||||
   "githubPATHint": "PAT mora biti u ovom formatu: korisničko_ime:token",
 | 
			
		||||
   "githubPATFormat": "korisničko_ime:token",
 | 
			
		||||
   "includePrereleases": "Uključi preliminarna izdanja",
 | 
			
		||||
   "fallbackToOlderReleases": "Povratak na starija izdanja",
 | 
			
		||||
   "filterReleaseTitlesByRegEx": "Filtrirajte naslove izdanja prema regularnom izrazu",
 | 
			
		||||
   "invalidRegEx": "Nevažeći regularni izraz",
 | 
			
		||||
   "noDescription": "Bez opisa",
 | 
			
		||||
   "cancel": "Otkaži",
 | 
			
		||||
   "continue": "Nastavite",
 | 
			
		||||
   "requiredInBrackets": "(obavezno)",
 | 
			
		||||
   "dropdownNoOptsError": "GREŠKA: PADAJUĆI MENI MORA IMATI NAJMANJE JEDNU OPCIJU",
 | 
			
		||||
   "colour": "Boja",
 | 
			
		||||
   "githubStarredRepos": "GitHub repo-i sa zvjezdicom",
 | 
			
		||||
   "uname": "Korisničko ime",
 | 
			
		||||
   "wrongArgNum": "Naveden je pogrešan broj argumenata",
 | 
			
		||||
   "xIsTrackOnly": "{} je samo za praćenje",
 | 
			
		||||
   "source": "Izvor",
 | 
			
		||||
   "app": "Aplikacija. ",
 | 
			
		||||
   "appsFromSourceAreTrackOnly": "Aplikacije iz ovog izvora su 'Samo za praćenje'.",
 | 
			
		||||
   "youPickedTrackOnly": "Odabrali ste opciju „Samo za praćenje”.",
 | 
			
		||||
   "trackOnlyAppDescription": "Aplikacija će se pratiti radi ažuriranja, ali Obtainium neće moći da je preuzme ili instalira.",
 | 
			
		||||
   "cancelled": "Otkazano",
 | 
			
		||||
   "appAlreadyAdded": "Aplikacija je već dodana",
 | 
			
		||||
   "alreadyUpToDateQuestion": "Aplikacija je već ažurirana?",
 | 
			
		||||
   "addApp": "Dodaj aplikaciju",
 | 
			
		||||
   "appSourceURL": "Izvorni URL aplikacije",
 | 
			
		||||
   "error": "Greška",
 | 
			
		||||
   "add": "Dodaj",
 | 
			
		||||
   "searchSomeSourcesLabel": "Pretraživanje (samo neki izvori)",
 | 
			
		||||
   "search": "Pretraživanje",
 | 
			
		||||
   "additionalOptsFor": "Dodatne opcije za {}",
 | 
			
		||||
   "supportedSourcesBelow": "Podržani izvori:",
 | 
			
		||||
   "trackOnlyInBrackets": "(Samo za praćenje)",
 | 
			
		||||
   "searchableInBrackets": "(Može se pretraživati)",
 | 
			
		||||
   "appsString": "Aplikacije",
 | 
			
		||||
   "noApps": "Nema aplikacija",
 | 
			
		||||
   "noAppsForFilter": "Nema aplikacija za filter",
 | 
			
		||||
   "byX": "Autor {}",
 | 
			
		||||
   "percentProgress": "Napredak: {}%",
 | 
			
		||||
   "pleaseWait": "Molimo sačekajte",
 | 
			
		||||
   "updateAvailable": "Ažuriranje dostupno",
 | 
			
		||||
   "estimateInBracketsShort": "(Procjena)",
 | 
			
		||||
   "notInstalled": "Nije instalirano",
 | 
			
		||||
   "estimateInBrackets": "(Procjena)",
 | 
			
		||||
   "selectAll": "Označi sve",
 | 
			
		||||
   "deselectN": "Poništi odabir {}",
 | 
			
		||||
   "xWillBeRemovedButRemainInstalled": "{} će biti uklonjen iz Obtainiuma, ali će ostati instaliran na uređaju.",
 | 
			
		||||
   "removeSelectedAppsQuestion": "Želite li ukloniti odabrane aplikacije?",
 | 
			
		||||
   "removeSelectedApps": "Ukloni odabrane aplikacije",
 | 
			
		||||
   "updateX": "Nadogradi {}",
 | 
			
		||||
   "installX": "Instaliraj {}",
 | 
			
		||||
   "markXTrackOnlyAsUpdated": "Označi {}\n(samo za praćenje)\nkao ažurirano",
 | 
			
		||||
   "changeX": "Promjena {}",
 | 
			
		||||
   "installUpdateApps": "Instalirajte/ažurirajte aplikacije",
 | 
			
		||||
   "installUpdateSelectedApps": "Instalirajte/ažurirajte odabrane aplikacije",
 | 
			
		||||
   "markXSelectedAppsAsUpdated": "Označite {} odabrane aplikacije kao ažurirane?",
 | 
			
		||||
   "no": "Ne",
 | 
			
		||||
   "yes": "Da",
 | 
			
		||||
   "markSelectedAppsUpdated": "Označi odabrane aplikacije kao ažurirane",
 | 
			
		||||
   "pinToTop": "Prikvači na vrh",
 | 
			
		||||
   "unpinFromTop": "Otkvači sa vrha",
 | 
			
		||||
   "resetInstallStatusForSelectedAppsQuestion": "Resetujte status instalacije za odabrane aplikacije?",
 | 
			
		||||
   "installStatusOfXWillBeResetExplanation": "Status instalacije bilo koje odabrane aplikacije će se resetovati.\n\nTo može pomoći kada je verzija aplikacije prikazana u Obtainiumu netačna zbog neuspjelih ažuriranja ili drugih problema.",
 | 
			
		||||
   "shareSelectedAppURLs": "Podijeli odabrane URL-ove aplikacija",
 | 
			
		||||
   "resetInstallStatus": "Resetujte status instalacije",
 | 
			
		||||
   "more": "Više",
 | 
			
		||||
   "removeOutdatedFilter": "Uklonite zastarjeli filter aplikacija",
 | 
			
		||||
   "showOutdatedOnly": "Prikaži samo zastarjele aplikacije",
 | 
			
		||||
   "filter": "Filtriranje",
 | 
			
		||||
   "filterActive": "Filtriranje",
 | 
			
		||||
   "filterApps": "Filtriraj aplikacije",
 | 
			
		||||
   "appName": "Naziv aplikacije",
 | 
			
		||||
   "author": "Autor",
 | 
			
		||||
   "upToDateApps": "Ažurirane aplikacije",
 | 
			
		||||
   "nonInstalledApps": "Neinstalirane aplikacije",
 | 
			
		||||
   "importExport": "Uvoz/izvoz",
 | 
			
		||||
   "settings": "Postavke",
 | 
			
		||||
   "exportedTo": "Izvezeno u {}",
 | 
			
		||||
   "obtainiumExport": "Obtainium Export",
 | 
			
		||||
   "invalidInput": "Neispravan unos.",
 | 
			
		||||
   "importedX": "Uvezeno {}",
 | 
			
		||||
   "obtainiumImport": "Obtainium uvoz",
 | 
			
		||||
   "importFromURLList": "Uvoz iz URL liste",
 | 
			
		||||
   "searchQuery": "Pretraga za: ",
 | 
			
		||||
   "appURLList": "Lista URL adresa aplikacija",
 | 
			
		||||
   "line": "Linija",
 | 
			
		||||
   "searchX": "Pretraživanje {}",
 | 
			
		||||
   "noResults": "Nema rezultata",
 | 
			
		||||
   "importX": "Uvoz {}",
 | 
			
		||||
   "importedAppsIdDisclaimer": "Uvezene aplikacije mogu se pogrešno prikazati kao „Nije instalirano”.\nDa biste to riješili, ponovo ih instalirajte putem aplikacije Obtainium.\nTo ne bi trebalo uticati na podatke aplikacije.\n\nUtječe samo na URL i metode uvoza treće strane.",
 | 
			
		||||
   "importErrors": "Uvezi greške",
 | 
			
		||||
   "importedXOfYApps": "{} od {} aplikacija uvezeno.",
 | 
			
		||||
   "followingURLsHadErrors": "Sljedeći URL-ovi su imali greške:",
 | 
			
		||||
   "okay": "Dobro",
 | 
			
		||||
   "selectURL": "Odaberite URL",
 | 
			
		||||
   "selectURLs": "Odaberite URL-ove",
 | 
			
		||||
   "pick": "Odaberi",
 | 
			
		||||
   "theme": "Tema",
 | 
			
		||||
   "dark": "Tamna",
 | 
			
		||||
   "light": "Svijetla",
 | 
			
		||||
   "followSystem": "Pratite sistem",
 | 
			
		||||
   "obtainium": "Obtainium",
 | 
			
		||||
   "materialYou": "Material You",
 | 
			
		||||
   "useBlackTheme": "Koristite čisto crnu tamnu temu",
 | 
			
		||||
   "appSortBy": "Aplikacije sortirane po",
 | 
			
		||||
   "authorName": "Autor/Ime",
 | 
			
		||||
   "nameAuthor": "Ime/Autor",
 | 
			
		||||
   "asAdded": "Kao što je dodano",
 | 
			
		||||
   "appSortOrder": "Redoslijed sortiranja aplikacija",
 | 
			
		||||
   "ascending": "Uzlazno",
 | 
			
		||||
   "descending": "Silazno",
 | 
			
		||||
   "bgUpdateCheckInterval": "Interval provjere ažuriranja u pozadini",
 | 
			
		||||
   "neverManualOnly": "Nikada - samo ručno",
 | 
			
		||||
   "appearance": "Izgled",
 | 
			
		||||
   "showWebInAppView": "Prikaži izvornu web stranicu u prikazu aplikacije",
 | 
			
		||||
   "pinUpdates": "Prikvačite ažuriranja na vrh prikaza aplikacija",
 | 
			
		||||
   "updates": "Nadogradnje",
 | 
			
		||||
   "sourceSpecific": "Specifično za izvor",
 | 
			
		||||
   "appSource": "Izvor aplikacije",
 | 
			
		||||
   "noLogs": "Nema evidencije",
 | 
			
		||||
   "appLogs": "Evidencije aplikacija",
 | 
			
		||||
   "close": "Zatvori",
 | 
			
		||||
   "share": "Podijeli",
 | 
			
		||||
   "appNotFound": "Aplikacija nije pronađena",
 | 
			
		||||
   "obtainiumExportHyphenatedLowercase": "obtainium-export",
 | 
			
		||||
   "pickAnAPK": "Odaberite APK",
 | 
			
		||||
   "appHasMoreThanOnePackage": "{} ima više od jednog paketa:",
 | 
			
		||||
   "deviceSupportsXArch": "Vaš uređaj podržava {} arhitekturu procesora.",
 | 
			
		||||
   "deviceSupportsFollowingArchs": "Vaš uređaj podržava sljedeće arhitekture procesora:",
 | 
			
		||||
   "warning": "Upozorenje",
 | 
			
		||||
   "sourceIsXButPackageFromYPrompt": "Izvor aplikacije je '{}', ali paket za izdavanje dolazi iz '{}'. Želite li nastaviti?",
 | 
			
		||||
   "updatesAvailable": "Dostupna ažuriranja",
 | 
			
		||||
   "updatesAvailableNotifDescription": "Obavještava korisnika da su ažuriranja dostupna za jednu ili više aplikacija koje prati Obtainium",
 | 
			
		||||
   "noNewUpdates": "Nema novih ažuriranja.",
 | 
			
		||||
   "xHasAnUpdate": "{} ima ažuriranje.",
 | 
			
		||||
   "appsUpdated": "Aplikacije su ažurirane",
 | 
			
		||||
   "appsUpdatedNotifDescription": "Obavještava korisnika da su u pozadini primijenjena ažuriranja na jednu ili više aplikacija",
 | 
			
		||||
   "xWasUpdatedToY": "{} je ažuriran na {}.",
 | 
			
		||||
   "errorCheckingUpdates": "Greška pri provjeri ažuriranja",
 | 
			
		||||
   "errorCheckingUpdatesNotifDescription": "Obavijest koja se prikazuje kada provjera sigurnosnog ažuriranja ne uspije",
 | 
			
		||||
   "appsRemoved": "Aplikacije su uklonjene",
 | 
			
		||||
   "appsRemovedNotifDescription": "Obavještava korisnika da je jedna ili više aplikacija uklonjeno zbog grešaka prilikom učitavanja",
 | 
			
		||||
   "xWasRemovedDueToErrorY": "{} je uklonjen zbog ove greške: {}",
 | 
			
		||||
   "completeAppInstallation": "Dovršite instalaciju aplikacije",
 | 
			
		||||
   "obtainiumMustBeOpenToInstallApps": "Obtainium mora biti otvoren za instalaciju aplikacija",
 | 
			
		||||
   "completeAppInstallationNotifDescription": "Traži od korisnika da se vrati u Obtainium kako bi dovršio instalaciju aplikacije",
 | 
			
		||||
   "checkingForUpdates": "Tražim moguće nadogradnje",
 | 
			
		||||
   "checkingForUpdatesNotifDescription": "Privremeno obavještenje koje se pojavljuje prilikom provjere ažuriranja",
 | 
			
		||||
   "pleaseAllowInstallPerm": "Dozvolite Obtainiumu da instalira aplikacije",
 | 
			
		||||
   "trackOnly": "Samo za praćenje",
 | 
			
		||||
   "errorWithHttpStatusCode": "Greška {}",
 | 
			
		||||
   "versionCorrectionDisabled": "Ispravka verzije je onemogućena (izgleda da plugin ne radi)",
 | 
			
		||||
   "unknown": "Nepoznato",
 | 
			
		||||
   "none": "Ništa",
 | 
			
		||||
   "never": "Nikad",
 | 
			
		||||
   "latestVersionX": "Najnovija verzija: {}",
 | 
			
		||||
   "installedVersionX": "Instalirana verzija: {}",
 | 
			
		||||
   "lastUpdateCheckX": "Posljednja provjera ažuriranja: {}",
 | 
			
		||||
   "remove": "Izbriši",
 | 
			
		||||
   "yesMarkUpdated": "Da, označi kao ažurirano",
 | 
			
		||||
   "fdroid": "F-Droid Official",
 | 
			
		||||
   "appIdOrName": "ID ili ime aplikacije",
 | 
			
		||||
   "appId": "Apl ID",
 | 
			
		||||
   "appWithIdOrNameNotFound": "Nije pronađena aplikacija s tim ID-om ili imenom",
 | 
			
		||||
   "reposHaveMultipleApps": "Repo-i mogu sadržavati više aplikacija",
 | 
			
		||||
   "fdroidThirdPartyRepo": "F-Droid Repo treće strane",
 | 
			
		||||
   "steam": "Steam",
 | 
			
		||||
   "steamMobile": "Steam Mobile",
 | 
			
		||||
   "steamChat": "Razgovor na Steamu (chat)",
 | 
			
		||||
   "install": "Instaliraj",
 | 
			
		||||
   "markInstalled": "Označi kao instalirano",
 | 
			
		||||
   "update": "Nadogradi",
 | 
			
		||||
   "markUpdated": "Označi kao ažurirano",
 | 
			
		||||
   "additionalOptions": "Dodatne opcije",
 | 
			
		||||
   "disableVersionDetection": "Onemogući detekciju verzije",
 | 
			
		||||
   "noVersionDetectionExplanation": "Ova opcija bi se trebala koristiti samo za aplikacije gdje detekcija verzije ne radi ispravno.",
 | 
			
		||||
   "downloadingX": "Preuzimanje {}",
 | 
			
		||||
   "downloadNotifDescription": "Obavještava korisnika o napretku u preuzimanju aplikacije",
 | 
			
		||||
   "noAPKFound": "APK nije pronađen",
 | 
			
		||||
   "noVersionDetection": "Nema detekcije verzije",
 | 
			
		||||
   "categorize": "Kategoriziraj",
 | 
			
		||||
   "categories": "Kategorije",
 | 
			
		||||
   "category": "Kategorija",
 | 
			
		||||
   "noCategory": "Nema kategorije",
 | 
			
		||||
   "noCategories": "Nema kategorija",
 | 
			
		||||
   "deleteCategoriesQuestion": "Želite li izbrisati kategorije?",
 | 
			
		||||
   "categoryDeleteWarning": "Sve aplikacije u izbrisanim kategorijama će biti postavljene kao nekategorisane.",
 | 
			
		||||
   "addCategory": "Dodaj kategoriju",
 | 
			
		||||
   "label": "Oznaka",
 | 
			
		||||
   "language": "Jezik",
 | 
			
		||||
   "copiedToClipboard": "Podaci kopirani u međuspremnik",
 | 
			
		||||
   "storagePermissionDenied": "Dozvola za pohranu je odbijena",
 | 
			
		||||
   "selectedCategorizeWarning": "Ovo će zamijeniti sve postojeće postavke kategorije za odabrane aplikacije.",
 | 
			
		||||
   "filterAPKsByRegEx": "Filtrirajte APK-ove prema regularnom izrazu",
 | 
			
		||||
   "removeFromObtainium": "Ukloni iz Obtainiuma",
 | 
			
		||||
   "uninstallFromDevice": "Deinstaliraj s uređaja",
 | 
			
		||||
   "onlyWorksWithNonVersionDetectApps": "Radi samo za aplikacije s onemogućenom detekcijom verzije.",
 | 
			
		||||
   "releaseDateAsVersion": "Koristi datum izdanja kao verziju",
 | 
			
		||||
   "releaseDateAsVersionExplanation": "Ova opcija bi se trebala koristiti samo za aplikacije gdje detekcija verzije ne radi ispravno, ali je datum izdavanja dostupan.",
 | 
			
		||||
   "changes": "Promjene",
 | 
			
		||||
   "releaseDate": "Datum izdavanja",
 | 
			
		||||
   "importFromURLsInFile": "Uvoz iz URL-ova u datoteci (kao što je OPML)",
 | 
			
		||||
   "versionDetection": "Otkrivanje verzije",
 | 
			
		||||
   "standardVersionDetection": "Detekcija standardne verzije",
 | 
			
		||||
   "groupByCategory": "Grupiši po kategoriji",
 | 
			
		||||
   "autoApkFilterByArch": "Pokušajte filtrirati APK-ove po arhitekturi procesora ako je moguće",
 | 
			
		||||
   "overrideSource": "Premosti izvor",
 | 
			
		||||
   "dontShowAgain": "Ne prikazuj ovo ponovo",
 | 
			
		||||
   "dontShowTrackOnlyWarnings": "Ne prikazuj upozorenja „Samo za  praćenje”",
 | 
			
		||||
   "dontShowAPKOriginWarnings": "Ne prikazuj upozorenja o porijeklu APK-a",
 | 
			
		||||
   "moveNonInstalledAppsToBottom": "Premjesti neinstalirane aplikacije na dno prikaza aplikacija",
 | 
			
		||||
   "gitlabPATLabel": "GitLab token za lični pristup\n(Omogućava pretraživanje i bolje otkrivanje APK-a)",
 | 
			
		||||
   "about": "O nama",
 | 
			
		||||
   "requiresCredentialsInSettings": "Za ovo su potrebni dodatni akreditivi (u Postavkama)",
 | 
			
		||||
   "checkOnStart": "Provjerite ima li novosti pri pokretanju",
 | 
			
		||||
   "tryInferAppIdFromCode": "Pokušati otkriti ID aplikacije iz izvornog koda",
 | 
			
		||||
   "removeOnExternalUninstall": "Automatski ukloni eksterno deinstalirane aplikacije",
 | 
			
		||||
   "pickHighestVersionCode": "Automatski odaberite najviši kôd verzije APK-a",
 | 
			
		||||
   "checkUpdateOnDetailPage": "Provjerite ima li novosti pri otvaranju stranice s detaljima aplikacije",
 | 
			
		||||
   "disablePageTransitions": "Ugasite animaciju prijelaza stranice",
 | 
			
		||||
   "reversePageTransitions": "Reverzne animacije prijelaza stranice",
 | 
			
		||||
   "minStarCount": "Minimum Star Count",
 | 
			
		||||
   "removeAppQuestion": {
 | 
			
		||||
      "one": "Želite li ukloniti aplikaciju?",
 | 
			
		||||
      "other": "Želite li ukloniti aplikacije?"
 | 
			
		||||
   },
 | 
			
		||||
   "tooManyRequestsTryAgainInMinutes": {
 | 
			
		||||
      "one": "Previše zahtjeva (ograničena broj zahteva) - pokušajte ponovo za {} minutu",
 | 
			
		||||
      "other": "Previše zahtjeva (ograničena cijena) - pokušajte ponovo za {} min."
 | 
			
		||||
   },
 | 
			
		||||
   "bgUpdateGotErrorRetryInMinutes": {
 | 
			
		||||
      "one": "Provjera ažuriranja u pozadini naišla je na {}, zakazuje se ponovni pokušaj za {} minutu",
 | 
			
		||||
      "other": "Provjera ažuriranja u pozadini naišla je na {}, zakazuje se ponovni pokušaj za {} min."
 | 
			
		||||
   },
 | 
			
		||||
   "bgCheckFoundUpdatesWillNotifyIfNeeded": {
 | 
			
		||||
      "one": "Provjera ažuriranja u pozadini je pronašla {} ažuriranje - korisnik će biti obavješten ako je to potrebno",
 | 
			
		||||
      "other": "Provjera ažuriranja u pozadini je pronašla {} ažuriranja - korisnik će biti obavješten ako je to potrebno"
 | 
			
		||||
   },
 | 
			
		||||
   "apps": {
 | 
			
		||||
      "one": "{} aplikacija",
 | 
			
		||||
      "other": "{} aplikacije"
 | 
			
		||||
   },
 | 
			
		||||
   "url": {
 | 
			
		||||
      "one": "{} URL",
 | 
			
		||||
      "other": "{} URL-ovi"
 | 
			
		||||
   },
 | 
			
		||||
   "minute": {
 | 
			
		||||
      "one": "{} minuta",
 | 
			
		||||
      "other": "min."
 | 
			
		||||
   },
 | 
			
		||||
   "hour": {
 | 
			
		||||
      "one": "{} sat",
 | 
			
		||||
      "other": "{} sat/i"
 | 
			
		||||
   },
 | 
			
		||||
   "day": {
 | 
			
		||||
      "one": "{} dan",
 | 
			
		||||
      "other": "{} dana"
 | 
			
		||||
   },
 | 
			
		||||
   "clearedNLogsBeforeXAfterY": {
 | 
			
		||||
      "one": "Izbrisan {n} log (prije = {before}, nakon = {after})",
 | 
			
		||||
      "other": "Izbrisano {n} log-ova (prije = {before}, nakon = {after})"
 | 
			
		||||
   },
 | 
			
		||||
   "xAndNMoreUpdatesAvailable": {
 | 
			
		||||
      "one": "{} i još 1 aplikacija ima ažuriranja.",
 | 
			
		||||
      "other": "{} i još {} aplikacija imaju ažuriranja."
 | 
			
		||||
   },
 | 
			
		||||
   "xAndNMoreUpdatesInstalled": {
 | 
			
		||||
      "one": "{} i još 1 aplikacija je ažurirana.",
 | 
			
		||||
      "other": "{} i još {} aplikacija je ažurirano."
 | 
			
		||||
   }
 | 
			
		||||
}
 | 
			
		||||
@@ -239,6 +239,7 @@
 | 
			
		||||
    "checkUpdateOnDetailPage": "Check for updates on opening an App detail page",
 | 
			
		||||
    "disablePageTransitions": "Disable page transition animations",
 | 
			
		||||
    "reversePageTransitions": "Reverse page transition animations",
 | 
			
		||||
    "minStarCount": "Minimum Star Count",
 | 
			
		||||
    "removeAppQuestion": {
 | 
			
		||||
        "one": "App entfernen?",
 | 
			
		||||
        "other": "Apps entfernen?"
 | 
			
		||||
 
 | 
			
		||||
@@ -239,6 +239,7 @@
 | 
			
		||||
    "checkUpdateOnDetailPage": "Check for updates on opening an App detail page",
 | 
			
		||||
    "disablePageTransitions": "Disable page transition animations",
 | 
			
		||||
    "reversePageTransitions": "Reverse page transition animations",
 | 
			
		||||
    "minStarCount": "Minimum Star Count",
 | 
			
		||||
    "removeAppQuestion": {
 | 
			
		||||
        "one": "Remove App?",
 | 
			
		||||
        "other": "Remove Apps?"
 | 
			
		||||
 
 | 
			
		||||
@@ -239,6 +239,7 @@
 | 
			
		||||
    "checkUpdateOnDetailPage": "Check for updates on opening an App detail page",
 | 
			
		||||
    "disablePageTransitions": "Disable page transition animations",
 | 
			
		||||
    "reversePageTransitions": "Reverse page transition animations",
 | 
			
		||||
    "minStarCount": "Minimum Star Count",
 | 
			
		||||
    "removeAppQuestion": {
 | 
			
		||||
        "one": "¿Eliminar Aplicación?",
 | 
			
		||||
        "other": "¿Eliminar Aplicaciones?"
 | 
			
		||||
 
 | 
			
		||||
@@ -239,6 +239,7 @@
 | 
			
		||||
    "checkUpdateOnDetailPage": "Check for updates on opening an App detail page",
 | 
			
		||||
    "disablePageTransitions": "Disable page transition animations",
 | 
			
		||||
    "reversePageTransitions": "Reverse page transition animations",
 | 
			
		||||
    "minStarCount": "Minimum Star Count",
 | 
			
		||||
    "removeAppQuestion": {
 | 
			
		||||
        "one": "برنامه حذف شود؟",
 | 
			
		||||
        "other": "برنامه ها حذف شوند؟"
 | 
			
		||||
 
 | 
			
		||||
@@ -239,6 +239,7 @@
 | 
			
		||||
    "checkUpdateOnDetailPage": "Check for updates on opening an App detail page",
 | 
			
		||||
    "disablePageTransitions": "Disable page transition animations",
 | 
			
		||||
    "reversePageTransitions": "Reverse page transition animations",
 | 
			
		||||
    "minStarCount": "Minimum Star Count",
 | 
			
		||||
    "removeAppQuestion": {
 | 
			
		||||
        "one": "Supprimer l'application ?",
 | 
			
		||||
        "other": "Supprimer les applications ?"
 | 
			
		||||
 
 | 
			
		||||
@@ -236,8 +236,9 @@
 | 
			
		||||
    "removeOnExternalUninstall": "A külsőleg eltávolított appok auto. eltávolítása",
 | 
			
		||||
    "pickHighestVersionCode": "A legmagasabb verziószámú APK auto. kiválasztása",
 | 
			
		||||
    "checkUpdateOnDetailPage": "Frissítések keresése az app részleteit tartalmazó oldal megnyitásakor",
 | 
			
		||||
    "disablePageTransitions": "Disable page transition animations",
 | 
			
		||||
    "reversePageTransitions": "Reverse page transition animations",
 | 
			
		||||
    "disablePageTransitions": "Lap áttűnési animációk tiltása",
 | 
			
		||||
    "reversePageTransitions": "Fordított lap áttűnési animációk",
 | 
			
		||||
    "minStarCount": "Minimális csillag szám",
 | 
			
		||||
    "removeAppQuestion": {
 | 
			
		||||
        "one": "Eltávolítja az alkalmazást?",
 | 
			
		||||
        "other": "Eltávolítja az alkalmazást?"
 | 
			
		||||
 
 | 
			
		||||
@@ -239,6 +239,7 @@
 | 
			
		||||
    "checkUpdateOnDetailPage": "Check for updates on opening an App detail page",
 | 
			
		||||
    "disablePageTransitions": "Disable page transition animations",
 | 
			
		||||
    "reversePageTransitions": "Reverse page transition animations",
 | 
			
		||||
    "minStarCount": "Minimum Star Count",
 | 
			
		||||
    "removeAppQuestion": {
 | 
			
		||||
        "one": "Rimuovere l'app?",
 | 
			
		||||
        "other": "Rimuovere le app?"
 | 
			
		||||
 
 | 
			
		||||
@@ -237,8 +237,9 @@
 | 
			
		||||
    "removeOnExternalUninstall": "外部でアンインストールされたアプリを自動的に削除する",
 | 
			
		||||
    "pickHighestVersionCode": "最も高いバージョンコードのAPKを自動的に選択する",
 | 
			
		||||
    "checkUpdateOnDetailPage": "アプリの詳細ページを開く際にアップデートを確認する",
 | 
			
		||||
    "disablePageTransitions": "Disable page transition animations",
 | 
			
		||||
    "reversePageTransitions": "Reverse page transition animations",
 | 
			
		||||
    "disablePageTransitions": "ページ遷移アニメーションを無効化する",
 | 
			
		||||
    "reversePageTransitions": "ページ遷移アニメーションを反転する",
 | 
			
		||||
    "minStarCount": "Minimum Star Count",
 | 
			
		||||
    "removeAppQuestion": {
 | 
			
		||||
        "one": "アプリを削除しますか?",
 | 
			
		||||
        "other": "アプリを削除しますか?"
 | 
			
		||||
 
 | 
			
		||||
@@ -12,7 +12,7 @@
 | 
			
		||||
    "noVersionFound": "Nie można określić wersji wydania",
 | 
			
		||||
    "urlMatchesNoSource": "Adres URL nie pasuje do znanego źródła",
 | 
			
		||||
    "cantInstallOlderVersion": "Nie można zainstalować starszej wersji aplikacji",
 | 
			
		||||
    "appIdMismatch": "Pobrany identyfikator pakietu nie pasuje do istniejącego identyfikatora aplikacji",
 | 
			
		||||
    "appIdMismatch": "Pobrane ID pakietu nie pasuje do istniejącego ID aplikacji",
 | 
			
		||||
    "functionNotImplemented": "Ta klasa nie zaimplementowała tej funkcji",
 | 
			
		||||
    "placeholder": "Placeholder",
 | 
			
		||||
    "someErrors": "Wystąpiły pewne błędy",
 | 
			
		||||
@@ -230,7 +230,7 @@
 | 
			
		||||
    "autoApkFilterByArch": "Spróbuj filtrować pliki APK według architektury procesora, jeśli to możliwe",
 | 
			
		||||
    "overrideSource": "Nadpisz źródło",
 | 
			
		||||
    "dontShowAgain": "Nie pokazuj tego ponownie",
 | 
			
		||||
    "dontShowTrackOnlyWarnings": "Nie wyświetlaj ostrzeżeń „Tylko obserwowana”",
 | 
			
		||||
    "dontShowTrackOnlyWarnings": "Nie pokazuj ostrzeżeń „Tylko obserwowana”",
 | 
			
		||||
    "dontShowAPKOriginWarnings": "Nie pokazuj ostrzeżeń o pochodzeniu APK",
 | 
			
		||||
    "moveNonInstalledAppsToBottom": "Przenieś niezainstalowane aplikacje na dół widoku aplikacji",
 | 
			
		||||
    "gitlabPATLabel": "Osobisty token dostępu GitLab\n(Umożliwia wyszukiwanie i lepsze wykrywanie APK)",
 | 
			
		||||
@@ -241,8 +241,9 @@
 | 
			
		||||
    "removeOnExternalUninstall": "Automatyczne usuń odinstalowane zewnętrznie aplikacje",
 | 
			
		||||
    "pickHighestVersionCode": "Automatycznie wybierz najwyższy kod wersji APK",
 | 
			
		||||
    "checkUpdateOnDetailPage": "Sprawdzaj aktualizacje podczas otwierania strony szczegółów aplikacji",
 | 
			
		||||
    "disablePageTransitions": "Disable page transition animations",
 | 
			
		||||
    "reversePageTransitions": "Reverse page transition animations",
 | 
			
		||||
    "disablePageTransitions": "Wyłącz animacje przejścia między stronami",
 | 
			
		||||
    "reversePageTransitions": "Odwróć animacje przejścia pomiędzy stronami",
 | 
			
		||||
    "minStarCount": "Minimum Star Count",
 | 
			
		||||
    "removeAppQuestion": {
 | 
			
		||||
        "one": "Usunąć aplikację?",
 | 
			
		||||
        "other": "Usunąć aplikacje?"
 | 
			
		||||
 
 | 
			
		||||
@@ -116,9 +116,9 @@
 | 
			
		||||
    "selectURLs": "Выбрать URL-адреса",
 | 
			
		||||
    "pick": "Выбрать",
 | 
			
		||||
    "theme": "Тема",
 | 
			
		||||
    "dark": "Темный",
 | 
			
		||||
    "light": "Светлый",
 | 
			
		||||
    "followSystem": "Следовать системе",
 | 
			
		||||
    "dark": "Темная",
 | 
			
		||||
    "light": "Светлая",
 | 
			
		||||
    "followSystem": "Как в системе",
 | 
			
		||||
    "obtainium": "Obtainium",
 | 
			
		||||
    "materialYou": "Material You",
 | 
			
		||||
    "useBlackTheme": "Использовать чёрную тему",
 | 
			
		||||
@@ -183,7 +183,7 @@
 | 
			
		||||
    "appId": "ID приложения",
 | 
			
		||||
    "appWithIdOrNameNotFound": "Приложение с таким ID или названием не было найдено",
 | 
			
		||||
    "reposHaveMultipleApps": "В хранилище может быть несколько приложений",
 | 
			
		||||
    "fdroidThirdPartyRepo": "Репозитории F-Droid сторонних разработчиков",
 | 
			
		||||
    "fdroidThirdPartyRepo": "Сторонние репозитории F-Droid",
 | 
			
		||||
    "steam": "Steam",
 | 
			
		||||
    "steamMobile": "Steam Mobile",
 | 
			
		||||
    "steamChat": "Steam Chat",
 | 
			
		||||
@@ -219,7 +219,7 @@
 | 
			
		||||
    "releaseDateAsVersionExplanation": "Этот параметр следует использовать только для приложений, в которых определение версии не работает правильно, но имеется дата выпуска.",
 | 
			
		||||
    "changes": "Изменения",
 | 
			
		||||
    "releaseDate": "Дата выпуска",
 | 
			
		||||
    "importFromURLsInFile": "Импорт из URL-адресов в файле (например, OPML)",
 | 
			
		||||
    "importFromURLsInFile": "Импорт URL-адресов из файла (например, OPML)",
 | 
			
		||||
    "versionDetection": "Определение версии",
 | 
			
		||||
    "standardVersionDetection": "Стандартное определение версии",
 | 
			
		||||
    "groupByCategory": "Группировать по категориям",
 | 
			
		||||
@@ -237,8 +237,9 @@
 | 
			
		||||
    "removeOnExternalUninstall": "Автоматически убирать из списка удаленные извне приложения",
 | 
			
		||||
    "pickHighestVersionCode": "Автовыбор кода наивысшей версии APK",
 | 
			
		||||
    "checkUpdateOnDetailPage": "Проверять наличие обновлений при открытии страницы представления приложения",
 | 
			
		||||
    "disablePageTransitions": "Disable page transition animations",
 | 
			
		||||
    "reversePageTransitions": "Reverse page transition animations",
 | 
			
		||||
    "disablePageTransitions": "Отключить анимацию перехода между страницами",
 | 
			
		||||
    "reversePageTransitions": "Реверс анимации перехода между страницами",
 | 
			
		||||
    "minStarCount": "Минимальное количество звёзд",
 | 
			
		||||
    "removeAppQuestion": {
 | 
			
		||||
        "one": "Удалить приложение?",
 | 
			
		||||
        "other": "Удалить приложения?"
 | 
			
		||||
 
 | 
			
		||||
@@ -234,11 +234,12 @@
 | 
			
		||||
    "requiresCredentialsInSettings": "此功能需要额外的凭据(在“设置”中添加)",
 | 
			
		||||
    "checkOnStart": "启动时进行一次检查",
 | 
			
		||||
    "tryInferAppIdFromCode": "尝试从源代码推断应用 ID",
 | 
			
		||||
    "removeOnExternalUninstall": "Automatically remove externally uninstalled Apps",
 | 
			
		||||
    "pickHighestVersionCode": "Auto-select highest version code APK",
 | 
			
		||||
    "checkUpdateOnDetailPage": "Check for updates on opening an App detail page",
 | 
			
		||||
    "disablePageTransitions": "Disable page transition animations",
 | 
			
		||||
    "reversePageTransitions": "Reverse page transition animations",
 | 
			
		||||
    "removeOnExternalUninstall": "自动删除已卸载的外部应用",
 | 
			
		||||
    "pickHighestVersionCode": "自动选择版本号最高的 APK 文件",
 | 
			
		||||
    "checkUpdateOnDetailPage": "打开应用详情页时检查更新",
 | 
			
		||||
    "disablePageTransitions": "禁用页面过渡动画效果",
 | 
			
		||||
    "reversePageTransitions": "反转页面过渡动画效果",
 | 
			
		||||
    "minStarCount": "最小星标数",
 | 
			
		||||
    "removeAppQuestion": {
 | 
			
		||||
        "one": "是否删除应用?",
 | 
			
		||||
        "other": "是否删除应用?"
 | 
			
		||||
 
 | 
			
		||||
@@ -5,6 +5,7 @@ import 'package:obtainium/custom_errors.dart';
 | 
			
		||||
import 'package:obtainium/providers/source_provider.dart';
 | 
			
		||||
 | 
			
		||||
class Codeberg extends AppSource {
 | 
			
		||||
  GitHub gh = GitHub();
 | 
			
		||||
  Codeberg() {
 | 
			
		||||
    host = 'codeberg.org';
 | 
			
		||||
 | 
			
		||||
@@ -32,10 +33,9 @@ class Codeberg extends AppSource {
 | 
			
		||||
    ];
 | 
			
		||||
 | 
			
		||||
    canSearch = true;
 | 
			
		||||
    searchQuerySettingFormItems = gh.searchQuerySettingFormItems;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  var gh = GitHub();
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  String sourceSpecificStandardizeURL(String url) {
 | 
			
		||||
    RegExp standardUrlRegEx = RegExp('^https?://$host/[^/]+/[^/]+');
 | 
			
		||||
@@ -68,10 +68,12 @@ class Codeberg extends AppSource {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  Future<Map<String, List<String>>> search(String query) async {
 | 
			
		||||
  Future<Map<String, List<String>>> search(String query,
 | 
			
		||||
      {Map<String, dynamic> querySettings = const {}}) async {
 | 
			
		||||
    return gh.searchCommon(
 | 
			
		||||
        query,
 | 
			
		||||
        'https://$host/api/v1/repos/search?q=${Uri.encodeQueryComponent(query)}&limit=100',
 | 
			
		||||
        'data');
 | 
			
		||||
        'data',
 | 
			
		||||
        querySettings: querySettings);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -72,7 +72,8 @@ class FDroid extends AppSource {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  Future<Map<String, List<String>>> search(String query) async {
 | 
			
		||||
  Future<Map<String, List<String>>> search(String query,
 | 
			
		||||
      {Map<String, dynamic> querySettings = const {}}) async {
 | 
			
		||||
    Response res = await sourceRequest(
 | 
			
		||||
        'https://search.$host/?q=${Uri.encodeQueryComponent(query)}');
 | 
			
		||||
    if (res.statusCode == 200) {
 | 
			
		||||
 
 | 
			
		||||
@@ -79,6 +79,21 @@ class GitHub extends AppSource {
 | 
			
		||||
    ];
 | 
			
		||||
 | 
			
		||||
    canSearch = true;
 | 
			
		||||
    searchQuerySettingFormItems = [
 | 
			
		||||
      GeneratedFormTextField('minStarCount',
 | 
			
		||||
          label: tr('minStarCount'),
 | 
			
		||||
          defaultValue: '0',
 | 
			
		||||
          additionalValidators: [
 | 
			
		||||
            (value) {
 | 
			
		||||
              try {
 | 
			
		||||
                int.parse(value ?? '0');
 | 
			
		||||
              } catch (e) {
 | 
			
		||||
                return tr('invalidInput');
 | 
			
		||||
              }
 | 
			
		||||
              return null;
 | 
			
		||||
            }
 | 
			
		||||
          ])
 | 
			
		||||
    ];
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
@@ -211,8 +226,8 @@ class GitHub extends AppSource {
 | 
			
		||||
                (nameA as String).substring(matchA!.start, matchA.end),
 | 
			
		||||
                (nameB as String).substring(matchB!.start, matchB.end));
 | 
			
		||||
          } else {
 | 
			
		||||
            return getReleaseDateFromRelease(a)!
 | 
			
		||||
                .compareTo(getReleaseDateFromRelease(b)!);
 | 
			
		||||
            return (getReleaseDateFromRelease(a) ?? DateTime(1))
 | 
			
		||||
                .compareTo(getReleaseDateFromRelease(b) ?? DateTime(0));
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
      });
 | 
			
		||||
@@ -310,11 +325,16 @@ class GitHub extends AppSource {
 | 
			
		||||
 | 
			
		||||
  Future<Map<String, List<String>>> searchCommon(
 | 
			
		||||
      String query, String requestUrl, String rootProp,
 | 
			
		||||
      {Function(Response)? onHttpErrorCode}) async {
 | 
			
		||||
      {Function(Response)? onHttpErrorCode,
 | 
			
		||||
      Map<String, dynamic> querySettings = const {}}) async {
 | 
			
		||||
    Response res = await sourceRequest(requestUrl);
 | 
			
		||||
    if (res.statusCode == 200) {
 | 
			
		||||
      int minStarCount = querySettings['minStarCount'] != null
 | 
			
		||||
          ? int.parse(querySettings['minStarCount'])
 | 
			
		||||
          : 0;
 | 
			
		||||
      Map<String, List<String>> urlsWithDescriptions = {};
 | 
			
		||||
      for (var e in (jsonDecode(res.body)[rootProp] as List<dynamic>)) {
 | 
			
		||||
        if ((e['stargazers_count'] ?? e['stars_count'] ?? 0) >= minStarCount) {
 | 
			
		||||
          urlsWithDescriptions.addAll({
 | 
			
		||||
            e['html_url'] as String: [
 | 
			
		||||
              e['full_name'] as String,
 | 
			
		||||
@@ -325,6 +345,7 @@ class GitHub extends AppSource {
 | 
			
		||||
            ]
 | 
			
		||||
          });
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
      return urlsWithDescriptions;
 | 
			
		||||
    } else {
 | 
			
		||||
      if (onHttpErrorCode != null) {
 | 
			
		||||
@@ -335,13 +356,14 @@ class GitHub extends AppSource {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  Future<Map<String, List<String>>> search(String query) async {
 | 
			
		||||
  Future<Map<String, List<String>>> search(String query,
 | 
			
		||||
      {Map<String, dynamic> querySettings = const {}}) async {
 | 
			
		||||
    return searchCommon(
 | 
			
		||||
        query,
 | 
			
		||||
        '${await getAPIHost()}/search/repositories?q=${Uri.encodeQueryComponent(query)}&per_page=100',
 | 
			
		||||
        'items', onHttpErrorCode: (Response res) {
 | 
			
		||||
      rateLimitErrorCheck(res);
 | 
			
		||||
    });
 | 
			
		||||
    }, querySettings: querySettings);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  rateLimitErrorCheck(Response res) {
 | 
			
		||||
 
 | 
			
		||||
@@ -69,7 +69,8 @@ class GitLab extends AppSource {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  Future<Map<String, List<String>>> search(String query) async {
 | 
			
		||||
  Future<Map<String, List<String>>> search(String query,
 | 
			
		||||
      {Map<String, dynamic> querySettings = const {}}) async {
 | 
			
		||||
    String? PAT = await getPATIfAny();
 | 
			
		||||
    if (PAT == null) {
 | 
			
		||||
      throw CredsNeededError(name);
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,6 @@
 | 
			
		||||
import 'dart:math';
 | 
			
		||||
 | 
			
		||||
import 'package:hsluv/hsluv.dart';
 | 
			
		||||
import 'package:easy_localization/easy_localization.dart';
 | 
			
		||||
import 'package:flutter/material.dart';
 | 
			
		||||
import 'package:obtainium/components/generated_form_modal.dart';
 | 
			
		||||
@@ -132,19 +133,19 @@ class GeneratedForm extends StatefulWidget {
 | 
			
		||||
  State<GeneratedForm> createState() => _GeneratedFormState();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Generates a random light color
 | 
			
		||||
// Courtesy of ChatGPT 😭 (with a bugfix 🥳)
 | 
			
		||||
// Generates a color in the HSLuv (Pastel) color space
 | 
			
		||||
// https://pub.dev/documentation/hsluv/latest/hsluv/Hsluv/hpluvToRgb.html
 | 
			
		||||
Color generateRandomLightColor() {
 | 
			
		||||
  // Create a random number generator
 | 
			
		||||
  final Random random = Random();
 | 
			
		||||
 | 
			
		||||
  // Generate random hue, saturation, and value values
 | 
			
		||||
  final double hue = random.nextDouble() * 360;
 | 
			
		||||
  final double saturation = 0.5 + random.nextDouble() * 0.5;
 | 
			
		||||
  final double value = 0.9 + random.nextDouble() * 0.1;
 | 
			
		||||
 | 
			
		||||
  // Create a HSV color with the random values
 | 
			
		||||
  return HSVColor.fromAHSV(1.0, hue, saturation, value).toColor();
 | 
			
		||||
  final randomSeed = Random().nextInt(120);
 | 
			
		||||
  // https://en.wikipedia.org/wiki/Golden_angle
 | 
			
		||||
  final goldenAngle = 180 * (3 - sqrt(5));
 | 
			
		||||
  // Generate next golden angle hue
 | 
			
		||||
  final double hue = randomSeed * goldenAngle;
 | 
			
		||||
  // Map from HPLuv color space to RGB, use constant saturation=100, lightness=70
 | 
			
		||||
  final List<double> rgbValuesDbl = Hsluv.hpluvToRgb([hue, 100, 70]);
 | 
			
		||||
  // Map RBG values from 0-1 to 0-255:
 | 
			
		||||
  final List<int> rgbValues = rgbValuesDbl.map((rgb) => (rgb * 255).toInt()).toList();
 | 
			
		||||
  return Color.fromARGB(255, rgbValues[0], rgbValues[1], rgbValues[2]);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class _GeneratedFormState extends State<GeneratedForm> {
 | 
			
		||||
@@ -368,6 +369,36 @@ class _GeneratedFormState extends State<GeneratedForm> {
 | 
			
		||||
                          ));
 | 
			
		||||
                    }) ??
 | 
			
		||||
                    [const SizedBox.shrink()],
 | 
			
		||||
                (values[widget.items[r][e].key]
 | 
			
		||||
                as Map<String, MapEntry<int, bool>>?)
 | 
			
		||||
                    ?.values
 | 
			
		||||
                    .where((e) => e.value)
 | 
			
		||||
                    .length == 1
 | 
			
		||||
                    ? Padding(
 | 
			
		||||
                    padding: const EdgeInsets.symmetric(horizontal: 4),
 | 
			
		||||
                    child: IconButton(
 | 
			
		||||
                      onPressed: () {
 | 
			
		||||
                        setState(() {
 | 
			
		||||
                          var temp = values[widget.items[r][e].key]
 | 
			
		||||
                              as Map<String, MapEntry<int, bool>>;
 | 
			
		||||
                          // get selected category str where bool is true
 | 
			
		||||
                          final oldEntry = temp.entries.firstWhere((entry) => entry.value.value);
 | 
			
		||||
                          // generate new color, ensure it is not the same
 | 
			
		||||
                          int newColor = oldEntry.value.key;
 | 
			
		||||
                          while(oldEntry.value.key == newColor) {
 | 
			
		||||
                            newColor = generateRandomLightColor().value;
 | 
			
		||||
                          }
 | 
			
		||||
                          // Update entry with new color, remain selected
 | 
			
		||||
                          temp.update(oldEntry.key, (old) => MapEntry(newColor, old.value));
 | 
			
		||||
                          values[widget.items[r][e].key] = temp;
 | 
			
		||||
                          someValueChanged();
 | 
			
		||||
                        });
 | 
			
		||||
                      },
 | 
			
		||||
                      icon: const Icon(Icons.format_color_fill_rounded),
 | 
			
		||||
                      visualDensity: VisualDensity.compact,
 | 
			
		||||
                      tooltip: tr('colour'),
 | 
			
		||||
                    ))
 | 
			
		||||
                    : const SizedBox.shrink(),
 | 
			
		||||
                (values[widget.items[r][e].key]
 | 
			
		||||
                                as Map<String, MapEntry<int, bool>>?)
 | 
			
		||||
                            ?.values
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,7 @@
 | 
			
		||||
import 'package:android_package_installer/android_package_installer.dart';
 | 
			
		||||
import 'package:easy_localization/easy_localization.dart';
 | 
			
		||||
import 'package:flutter/material.dart';
 | 
			
		||||
import 'package:flutter/services.dart';
 | 
			
		||||
import 'package:obtainium/providers/logs_provider.dart';
 | 
			
		||||
import 'package:provider/provider.dart';
 | 
			
		||||
 | 
			
		||||
@@ -101,7 +102,14 @@ showError(dynamic e, BuildContext context) {
 | 
			
		||||
            title: Text(e is MultiAppMultiError
 | 
			
		||||
                ? tr('someErrors')
 | 
			
		||||
                : tr('unexpectedError')),
 | 
			
		||||
            content: Text(e.toString()),
 | 
			
		||||
            content: GestureDetector(
 | 
			
		||||
                onLongPress: () {
 | 
			
		||||
                  Clipboard.setData(ClipboardData(text: e.toString()));
 | 
			
		||||
                  ScaffoldMessenger.of(context).showSnackBar(SnackBar(
 | 
			
		||||
                    content: Text(tr('copiedToClipboard')),
 | 
			
		||||
                  ));
 | 
			
		||||
                },
 | 
			
		||||
                child: Text(e.toString())),
 | 
			
		||||
            actions: [
 | 
			
		||||
              TextButton(
 | 
			
		||||
                  onPressed: () {
 | 
			
		||||
 
 | 
			
		||||
@@ -21,7 +21,7 @@ import 'package:easy_localization/src/easy_localization_controller.dart';
 | 
			
		||||
// ignore: implementation_imports
 | 
			
		||||
import 'package:easy_localization/src/localization.dart';
 | 
			
		||||
 | 
			
		||||
const String currentVersion = '0.13.18';
 | 
			
		||||
const String currentVersion = '0.13.21';
 | 
			
		||||
const String currentReleaseTag =
 | 
			
		||||
    'v$currentVersion-beta'; // KEEP THIS IN SYNC WITH GITHUB RELEASES
 | 
			
		||||
 | 
			
		||||
@@ -39,6 +39,7 @@ List<MapEntry<Locale, String>> supportedLocales = const [
 | 
			
		||||
  MapEntry(Locale('es'), 'Español'),
 | 
			
		||||
  MapEntry(Locale('pl'), 'Polski'),
 | 
			
		||||
  MapEntry(Locale('ru'), 'Русский язык'),
 | 
			
		||||
  MapEntry(Locale('bs'), 'Bosanski'),
 | 
			
		||||
];
 | 
			
		||||
const fallbackLocale = Locale('en');
 | 
			
		||||
const localeDir = 'assets/translations';
 | 
			
		||||
 
 | 
			
		||||
@@ -182,7 +182,8 @@ class _ImportExportPageState extends State<ImportExportPage> {
 | 
			
		||||
                  [
 | 
			
		||||
                    GeneratedFormTextField('searchQuery',
 | 
			
		||||
                        label: tr('searchQuery'))
 | 
			
		||||
                  ]
 | 
			
		||||
                  ],
 | 
			
		||||
                  ...source.searchQuerySettingFormItems.map((e) => [e])
 | 
			
		||||
                ],
 | 
			
		||||
              );
 | 
			
		||||
            });
 | 
			
		||||
@@ -191,8 +192,8 @@ class _ImportExportPageState extends State<ImportExportPage> {
 | 
			
		||||
          setState(() {
 | 
			
		||||
            importInProgress = true;
 | 
			
		||||
          });
 | 
			
		||||
          var urlsWithDescriptions =
 | 
			
		||||
              await source.search(values['searchQuery'] as String);
 | 
			
		||||
          var urlsWithDescriptions = await source
 | 
			
		||||
              .search(values['searchQuery'] as String, querySettings: values);
 | 
			
		||||
          if (urlsWithDescriptions.isNotEmpty) {
 | 
			
		||||
            var selectedUrls =
 | 
			
		||||
                // ignore: use_build_context_synchronously
 | 
			
		||||
 
 | 
			
		||||
@@ -1,5 +1,3 @@
 | 
			
		||||
import 'dart:math';
 | 
			
		||||
 | 
			
		||||
import 'package:easy_localization/easy_localization.dart';
 | 
			
		||||
import 'package:flutter/material.dart';
 | 
			
		||||
import 'package:obtainium/components/custom_app_bar.dart';
 | 
			
		||||
@@ -21,21 +19,6 @@ class SettingsPage extends StatefulWidget {
 | 
			
		||||
  State<SettingsPage> createState() => _SettingsPageState();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Generates a random light color
 | 
			
		||||
// Courtesy of ChatGPT 😭 (with a bugfix 🥳)
 | 
			
		||||
Color generateRandomLightColor() {
 | 
			
		||||
  // Create a random number generator
 | 
			
		||||
  final Random random = Random();
 | 
			
		||||
 | 
			
		||||
  // Generate random hue, saturation, and value values
 | 
			
		||||
  final double hue = random.nextDouble() * 360;
 | 
			
		||||
  final double saturation = 0.5 + random.nextDouble() * 0.5;
 | 
			
		||||
  final double value = 0.9 + random.nextDouble() * 0.1;
 | 
			
		||||
 | 
			
		||||
  // Create a HSV color with the random values
 | 
			
		||||
  return HSVColor.fromAHSV(1.0, hue, saturation, value).toColor();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class _SettingsPageState extends State<SettingsPage> {
 | 
			
		||||
  @override
 | 
			
		||||
  Widget build(BuildContext context) {
 | 
			
		||||
 
 | 
			
		||||
@@ -144,6 +144,24 @@ class AppsProvider with ChangeNotifier {
 | 
			
		||||
    }();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  Future<File> downloadFileWithRetry(
 | 
			
		||||
      String url, String fileNameNoExt, Function? onProgress,
 | 
			
		||||
      {bool useExisting = true,
 | 
			
		||||
      Map<String, String>? headers,
 | 
			
		||||
      int retries = 3}) async {
 | 
			
		||||
    try {
 | 
			
		||||
      return await downloadFile(url, fileNameNoExt, onProgress,
 | 
			
		||||
          useExisting: useExisting, headers: headers);
 | 
			
		||||
    } catch (e) {
 | 
			
		||||
      if (retries > 0 && e is ClientException) {
 | 
			
		||||
        return await downloadFileWithRetry(url, fileNameNoExt, onProgress,
 | 
			
		||||
            useExisting: useExisting, headers: headers, retries: (retries - 1));
 | 
			
		||||
      } else {
 | 
			
		||||
        rethrow;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  Future<File> downloadFile(
 | 
			
		||||
      String url, String fileNameNoExt, Function? onProgress,
 | 
			
		||||
      {bool useExisting = true, Map<String, String>? headers}) async {
 | 
			
		||||
@@ -236,8 +254,9 @@ class AppsProvider with ChangeNotifier {
 | 
			
		||||
      notificationsProvider?.cancel(notif.id);
 | 
			
		||||
      int? prevProg;
 | 
			
		||||
      var fileNameNoExt = '${app.id}-${downloadUrl.hashCode}';
 | 
			
		||||
      var downloadedFile = await downloadFile(downloadUrl, fileNameNoExt,
 | 
			
		||||
          headers: source.requestHeaders, (double? progress) {
 | 
			
		||||
      var downloadedFile = await downloadFileWithRetry(
 | 
			
		||||
          downloadUrl, fileNameNoExt, headers: source.requestHeaders,
 | 
			
		||||
          (double? progress) {
 | 
			
		||||
        int? prog = progress?.ceil();
 | 
			
		||||
        if (apps[app.id] != null) {
 | 
			
		||||
          apps[app.id]!.downloadProgress = progress;
 | 
			
		||||
@@ -346,7 +365,7 @@ class AppsProvider with ChangeNotifier {
 | 
			
		||||
    // If 0 APKs installed, throw the first install error encountered
 | 
			
		||||
    try {
 | 
			
		||||
      var somethingInstalled = false;
 | 
			
		||||
      var firstError = null;
 | 
			
		||||
      Object? firstError;
 | 
			
		||||
      for (var file in dir.extracted
 | 
			
		||||
          .listSync(recursive: true, followLinks: false)
 | 
			
		||||
          .whereType<File>()) {
 | 
			
		||||
@@ -366,7 +385,7 @@ class AppsProvider with ChangeNotifier {
 | 
			
		||||
      }
 | 
			
		||||
      if (somethingInstalled) {
 | 
			
		||||
        dir.file.delete(recursive: true);
 | 
			
		||||
      } else if (firstError) {
 | 
			
		||||
      } else if (firstError != null) {
 | 
			
		||||
        throw firstError;
 | 
			
		||||
      }
 | 
			
		||||
    } finally {
 | 
			
		||||
 
 | 
			
		||||
@@ -436,7 +436,9 @@ abstract class AppSource {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  bool canSearch = false;
 | 
			
		||||
  Future<Map<String, List<String>>> search(String query) {
 | 
			
		||||
  List<GeneratedFormItem> searchQuerySettingFormItems = [];
 | 
			
		||||
  Future<Map<String, List<String>>> search(String query,
 | 
			
		||||
      {Map<String, dynamic> querySettings = const {}}) {
 | 
			
		||||
    throw NotImplementedError();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@@ -447,8 +449,11 @@ abstract class AppSource {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
ObtainiumError getObtainiumHttpError(Response res) {
 | 
			
		||||
  return ObtainiumError(res.reasonPhrase ??
 | 
			
		||||
      tr('errorWithHttpStatusCode', args: [res.statusCode.toString()]));
 | 
			
		||||
  return ObtainiumError((res.reasonPhrase != null &&
 | 
			
		||||
          res.reasonPhrase != null &&
 | 
			
		||||
          res.reasonPhrase!.isNotEmpty)
 | 
			
		||||
      ? res.reasonPhrase!
 | 
			
		||||
      : tr('errorWithHttpStatusCode', args: [res.statusCode.toString()]));
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
abstract class MassAppUrlSource {
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										36
									
								
								pubspec.lock
									
									
									
									
									
								
							
							
						
						
									
										36
									
								
								pubspec.lock
									
									
									
									
									
								
							@@ -222,10 +222,10 @@ packages:
 | 
			
		||||
    dependency: "direct main"
 | 
			
		||||
    description:
 | 
			
		||||
      name: file_picker
 | 
			
		||||
      sha256: b1729fc96627dd44012d0a901558177418818d6bd428df59dcfeb594e5f66432
 | 
			
		||||
      sha256: "21145c9c268d54b1f771d8380c195d2d6f655e0567dc1ca2f9c134c02c819e0a"
 | 
			
		||||
      url: "https://pub.dev"
 | 
			
		||||
    source: hosted
 | 
			
		||||
    version: "5.3.2"
 | 
			
		||||
    version: "5.3.3"
 | 
			
		||||
  flutter:
 | 
			
		||||
    dependency: "direct main"
 | 
			
		||||
    description: flutter
 | 
			
		||||
@@ -243,10 +243,10 @@ packages:
 | 
			
		||||
    dependency: "direct main"
 | 
			
		||||
    description:
 | 
			
		||||
      name: flutter_fgbg
 | 
			
		||||
      sha256: d37511eef6afb7e2e3f2278ec6498bb12c650ed517c81bcd09452d910e8b9174
 | 
			
		||||
      sha256: "08c4d2fd229e3df26083d5aecc3dea9ff4f2d188f8cd57aaf2b3f047bd08a047"
 | 
			
		||||
      url: "https://pub.dev"
 | 
			
		||||
    source: hosted
 | 
			
		||||
    version: "0.2.2"
 | 
			
		||||
    version: "0.3.0"
 | 
			
		||||
  flutter_launcher_icons:
 | 
			
		||||
    dependency: "direct dev"
 | 
			
		||||
    description:
 | 
			
		||||
@@ -326,6 +326,14 @@ packages:
 | 
			
		||||
      url: "https://pub.dev"
 | 
			
		||||
    source: hosted
 | 
			
		||||
    version: "8.2.2"
 | 
			
		||||
  hsluv:
 | 
			
		||||
    dependency: "direct main"
 | 
			
		||||
    description:
 | 
			
		||||
      name: hsluv
 | 
			
		||||
      sha256: f33e63b0c24ceee0f6492874424aa8edc671ef9a20cc889e4b969284d8f02eb1
 | 
			
		||||
      url: "https://pub.dev"
 | 
			
		||||
    source: hosted
 | 
			
		||||
    version: "1.1.3"
 | 
			
		||||
  html:
 | 
			
		||||
    dependency: "direct main"
 | 
			
		||||
    description:
 | 
			
		||||
@@ -530,10 +538,10 @@ packages:
 | 
			
		||||
    dependency: transitive
 | 
			
		||||
    description:
 | 
			
		||||
      name: permission_handler_android
 | 
			
		||||
      sha256: c0c9754479a4c4b1c1f3862ddc11930c9b3f03bef2816bb4ea6eed1e13551d6f
 | 
			
		||||
      sha256: "2ffaf52a21f64ac9b35fe7369bb9533edbd4f698e5604db8645b1064ff4cf221"
 | 
			
		||||
      url: "https://pub.dev"
 | 
			
		||||
    source: hosted
 | 
			
		||||
    version: "10.3.2"
 | 
			
		||||
    version: "10.3.3"
 | 
			
		||||
  permission_handler_apple:
 | 
			
		||||
    dependency: transitive
 | 
			
		||||
    description:
 | 
			
		||||
@@ -578,10 +586,10 @@ packages:
 | 
			
		||||
    dependency: transitive
 | 
			
		||||
    description:
 | 
			
		||||
      name: plugin_platform_interface
 | 
			
		||||
      sha256: "6a2128648c854906c53fa8e33986fc0247a1116122f9534dd20e3ab9e16a32bc"
 | 
			
		||||
      sha256: "43798d895c929056255600343db8f049921cbec94d31ec87f1dc5c16c01935dd"
 | 
			
		||||
      url: "https://pub.dev"
 | 
			
		||||
    source: hosted
 | 
			
		||||
    version: "2.1.4"
 | 
			
		||||
    version: "2.1.5"
 | 
			
		||||
  pointycastle:
 | 
			
		||||
    dependency: transitive
 | 
			
		||||
    description:
 | 
			
		||||
@@ -775,10 +783,10 @@ packages:
 | 
			
		||||
    dependency: transitive
 | 
			
		||||
    description:
 | 
			
		||||
      name: url_launcher_android
 | 
			
		||||
      sha256: "15f5acbf0dce90146a0f5a2c4a002b1814a6303c4c5c075aa2623b2d16156f03"
 | 
			
		||||
      sha256: "78cb6dea3e93148615109e58e42c35d1ffbf5ef66c44add673d0ab75f12ff3af"
 | 
			
		||||
      url: "https://pub.dev"
 | 
			
		||||
    source: hosted
 | 
			
		||||
    version: "6.0.36"
 | 
			
		||||
    version: "6.0.37"
 | 
			
		||||
  url_launcher_ios:
 | 
			
		||||
    dependency: transitive
 | 
			
		||||
    description:
 | 
			
		||||
@@ -855,10 +863,10 @@ packages:
 | 
			
		||||
    dependency: transitive
 | 
			
		||||
    description:
 | 
			
		||||
      name: webview_flutter_android
 | 
			
		||||
      sha256: "27ad6a99c4b2d5e1ffd2b993a10f738b6b4979f139b4d64c34ac511595fcd748"
 | 
			
		||||
      sha256: "8587d0b4991bd0f223f4b4957101c2c7449f905601571315f4967072498dd3fb"
 | 
			
		||||
      url: "https://pub.dev"
 | 
			
		||||
    source: hosted
 | 
			
		||||
    version: "3.9.0"
 | 
			
		||||
    version: "3.9.1"
 | 
			
		||||
  webview_flutter_platform_interface:
 | 
			
		||||
    dependency: transitive
 | 
			
		||||
    description:
 | 
			
		||||
@@ -871,10 +879,10 @@ packages:
 | 
			
		||||
    dependency: transitive
 | 
			
		||||
    description:
 | 
			
		||||
      name: webview_flutter_wkwebview
 | 
			
		||||
      sha256: "369fdf6160944a7db660ff15fa048c2bd681b09557907beaef1f95e8557d21dc"
 | 
			
		||||
      sha256: "3e36a8f564809cb7c257ff4278502b185e2191349df0ddee98837f91805c74b8"
 | 
			
		||||
      url: "https://pub.dev"
 | 
			
		||||
    source: hosted
 | 
			
		||||
    version: "3.7.0"
 | 
			
		||||
    version: "3.7.1"
 | 
			
		||||
  win32:
 | 
			
		||||
    dependency: transitive
 | 
			
		||||
    description:
 | 
			
		||||
 
 | 
			
		||||
@@ -17,7 +17,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev
 | 
			
		||||
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
 | 
			
		||||
# In Windows, build-name is used as the major, minor, and patch parts
 | 
			
		||||
# of the product and file versions while build-number is used as the build suffix.
 | 
			
		||||
version: 0.13.18+182 # When changing this, update the tag in main() accordingly
 | 
			
		||||
version: 0.13.21+185 # When changing this, update the tag in main() accordingly
 | 
			
		||||
 | 
			
		||||
environment:
 | 
			
		||||
  sdk: '>=2.18.2 <3.0.0'
 | 
			
		||||
@@ -37,7 +37,7 @@ dependencies:
 | 
			
		||||
  # Use with the CupertinoIcons class for iOS style icons.
 | 
			
		||||
  cupertino_icons: ^1.0.5
 | 
			
		||||
  path_provider: ^2.0.11
 | 
			
		||||
  flutter_fgbg: ^0.2.0 # Try removing reliance on this
 | 
			
		||||
  flutter_fgbg: ^0.3.0 # Try removing reliance on this
 | 
			
		||||
  flutter_local_notifications: ^15.1.0+1
 | 
			
		||||
  provider: ^6.0.3
 | 
			
		||||
  http: ^1.0.0
 | 
			
		||||
@@ -64,7 +64,7 @@ dependencies:
 | 
			
		||||
  android_intent_plus: ^4.0.0
 | 
			
		||||
  flutter_markdown: ^0.6.14
 | 
			
		||||
  flutter_archive: ^5.0.0
 | 
			
		||||
 | 
			
		||||
  hsluv: ^1.1.3
 | 
			
		||||
 | 
			
		||||
dev_dependencies:
 | 
			
		||||
  flutter_test:
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user