Compare commits

...

21 Commits

Author SHA1 Message Date
ab43856b90 attempted switch to dio (still using http for partial dnwloads/headers) 2024-03-17 01:51:53 -04:00
9dae24ace6 Merge pull request #1462 from ImranR98/dev
Removed outdated translations related to #1459
2024-03-09 14:25:50 -05:00
b6e6568500 Increment version, upgrade Flutter + packages 2024-03-09 14:24:13 -05:00
a8eae7f04b Merge remote-tracking branch 'origin/main' into dev 2024-03-09 14:18:36 -05:00
4902e0ef06 Removed outdated translations related to #1459 2024-03-09 14:17:18 -05:00
e6926a714f Merge pull request #1442 from gidano/main
Update hu.json
2024-03-09 14:08:15 -05:00
c9eee4331d Merge pull request #1444 from rollsicecream/main
Fix 'F-Droid' translation' in fr.json
2024-03-09 14:08:09 -05:00
9a8cc2e5c3 Merge pull request #1445 from LilligantMatsuri/main
Update Chinese translation
2024-03-09 14:07:54 -05:00
a7c9cd0f27 Merge pull request #1448 from dik08razz/dev
add ukrainian
2024-03-09 14:07:49 -05:00
efc6846c1c Merge pull request #1459 from akramer-zibra/improve-gitlab-apk-retrieval
Improve gitlab .apk retrieval (#1450, #1381, #1380)
2024-03-09 14:07:36 -05:00
89edddd38c Merge pull request #1461 from ImranR98/dev
Fix bug in GitHub's 'verify latest' option (#1449)
2024-03-09 14:06:24 -05:00
e7c2112f41 Bugfix from prev. commit 2024-03-09 13:50:22 -05:00
d8cd3b6c92 Remove unused package import 2024-03-09 10:17:27 +01:00
bb1dd4ecfd Inject Private Access Token as optional request param
It turned out: If the parameter `private_token` is given but empty, the Gitlab REST API does expect a valid access token otherwise responds with an authorization error
2024-03-09 10:16:54 +01:00
3824b386d7 Regroup functions by their intention
This may prepare for later refactorings and code simplification
2024-03-09 10:11:54 +01:00
a9159fc8a0 Remove .apk retrieval from tags-feed
So this code does focus on the official Gitlab REST API. With this the retrieval logic changes.
2024-03-09 09:23:48 +01:00
7f4cf6e681 Fix bug in GitHub's 'verify latest' option (#1449) 2024-03-05 10:37:36 -05:00
215f05fbc2 add ukrainian 2024-03-04 18:14:37 +02:00
6d6afe9e69 Fix 'F-Droid' translation' in fr.json 2024-03-03 18:30:48 +01:00
66122f1608 Update zh.json
- Translate new strings
- Correct inaccurate translations

Signed-off-by: Matsuri <matsuri@vmoe.info>
2024-03-03 23:58:18 +08:00
0ad9bbdd8e Update hu.json 2024-03-03 09:32:21 +01:00
47 changed files with 787 additions and 333 deletions

View File

@ -218,7 +218,7 @@
"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)",
"gitlabPATLabel": "GitLab token za lični pristup",
"about": "O nama",
"requiresCredentialsInSettings": "{}: Za ovo su potrebni dodatni akreditivi (u Postavkama)",
"checkOnStart": "Provjerite ima li novosti pri pokretanju",
@ -232,7 +232,6 @@
"addInfoBelow": "Dodajte ove informacije ispod.",
"addInfoInSettings": "Dodajte ove informacije u Postavkama.",
"githubSourceNote": "GitHub ograničavanje se može izbjeći korišćenjem tokena za lični pristup.",
"gitlabSourceNote": "GitLab APK preuzimanje možda neće raditi bez tokena za lični pristup.",
"sortByLastLinkSegment": "Sortiraj samo po zadnjem segmentu veze",
"filterReleaseNotesByRegEx": "Filtirajte promjene u izdanju po regularnom izrazu",
"customLinkFilterRegex": "Prilagođeni APK link filtrira se po regularnom izrazu (Zadano '.apk$')",

View File

@ -218,7 +218,7 @@
"dontShowTrackOnlyWarnings": "Nezobrazovat varování pro 'Jen sledované'",
"dontShowAPKOriginWarnings": "Nezobrazovat varování pro původ APK",
"moveNonInstalledAppsToBottom": "Přesunout nenainstalované aplikace na konec zobrazení Aplikace",
"gitlabPATLabel": "GitLab Personal Access Token\n(Umožňuje vyhledávání a lepší zjišťování APK)",
"gitlabPATLabel": "GitLab Personal Access Token",
"about": "O",
"requiresCredentialsInSettings": "{}: Vyžaduje další pověření (v nastavení)",
"checkOnStart": "Zkontrolovat jednou při spuštění",
@ -232,7 +232,6 @@
"addInfoBelow": "Přidat tuto informaci na konec stránky.",
"addInfoInSettings": "Přidat tuto informaci do nastavení.",
"githubSourceNote": "Omezení rychlosti GitHub lze obejít pomocí klíče API.",
"gitlabSourceNote": "Extrakce GitLab APK nemusí fungovat bez klíče API",
"sortByLastLinkSegment": "Seřadit pouze podle poslední části odkazu",
"filterReleaseNotesByRegEx": "Filtrovat poznámky k vydání podle regulárního výrazu",
"customLinkFilterRegex": "Vlastní filtr odkazů APK podle regulárního výrazu (výchozí '.apk$')",

View File

@ -218,7 +218,7 @@
"dontShowTrackOnlyWarnings": "Warnung für 'Nur Nachverfolgen' nicht anzeigen",
"dontShowAPKOriginWarnings": "Warnung für APK-Herkunft nicht anzeigen",
"moveNonInstalledAppsToBottom": "Nicht installierte Apps ans Ende der Apps Ansicht verschieben",
"gitlabPATLabel": "GitLab Personal Access Token\n(Aktiviert Suche und bessere APK Entdeckung)",
"gitlabPATLabel": "GitLab Personal Access Token",
"about": "Über",
"requiresCredentialsInSettings": "{}: Benötigt zusätzliche Anmeldedaten (in den Einstellungen)",
"checkOnStart": "Überprüfe einmalig beim Start",
@ -232,7 +232,6 @@
"addInfoBelow": "Fügen Sie diese Informationen unten hinzu.",
"addInfoInSettings": "Fügen Sie diese Info in den Einstellungen hinzu.",
"githubSourceNote": "Die GitHub-Ratenbegrenzung kann mit einem API-Schlüssel umgangen werden.",
"gitlabSourceNote": "GitLab APK-Extraktion funktioniert möglicherweise nicht ohne API-Schlüssel",
"sortByLastLinkSegment": "Sortiere nur nach dem letzten Teil des Links",
"filterReleaseNotesByRegEx": "Versionshinweise nach regulärem Ausdruck filtern",
"customLinkFilterRegex": "Benutzerdefinierter APK Link Filter nach Regulärem Ausdruck (Standard '.apk$')",

View File

@ -218,7 +218,7 @@
"dontShowTrackOnlyWarnings": "Don't show 'Track-Only' warnings",
"dontShowAPKOriginWarnings": "Don't show APK origin warnings",
"moveNonInstalledAppsToBottom": "Move non-installed Apps to bottom of Apps view",
"gitlabPATLabel": "GitLab Personal Access Token\n(Enables Search and Better APK Discovery)",
"gitlabPATLabel": "GitLab Personal Access Token",
"about": "About",
"requiresCredentialsInSettings": "{} needs additional credentials (in Settings)",
"checkOnStart": "Check for updates on startup",
@ -232,7 +232,6 @@
"addInfoBelow": "Add this info below.",
"addInfoInSettings": "Add this info in the Settings.",
"githubSourceNote": "GitHub rate limiting can be avoided using an API key.",
"gitlabSourceNote": "GitLab APK extraction may not work without an API key.",
"sortByLastLinkSegment": "Sort by only the last segment of the link",
"filterReleaseNotesByRegEx": "Filter Release Notes by Regular Expression",
"customLinkFilterRegex": "Custom APK Link Filter by Regular Expression (Default '.apk$')",

View File

@ -218,7 +218,7 @@
"dontShowTrackOnlyWarnings": "No mostrar avisos sobre apps en 'solo seguimiento'",
"dontShowAPKOriginWarnings": "No mostrar avisos sobre las fuentes de las APKs",
"moveNonInstalledAppsToBottom": "Mover Apps no instaladas al final",
"gitlabPATLabel": "Token de acceso personal a GitLab\n(habilita la búsqueda y mejor detección de APKs)",
"gitlabPATLabel": "Token de acceso personal a GitLab",
"about": "Acerca",
"requiresCredentialsInSettings": "{}: Esto requiere credenciales adicionales (en ajustes)",
"checkOnStart": "Comprobar actualizaciones al inicio",
@ -232,7 +232,6 @@
"addInfoBelow": "Añadir esta información debajo.",
"addInfoInSettings": "Puede añadir esta información en Ajustes.",
"githubSourceNote": "La limitación de velocidad de GitHub puede evitarse con un 'token de acceso personal'.",
"gitlabSourceNote": "La extracción de APK de GitLab podría no funcionar sin un 'token de acceso personal'.",
"sortByLastLinkSegment": "Ordenar sólo por el último segmento del enlace",
"filterReleaseNotesByRegEx": "Filtrar por notas de versión (release notes)",
"customLinkFilterRegex": "Filtro personalizado de enlace APK (por defecto '.apk$')",

View File

@ -218,7 +218,7 @@
"dontShowTrackOnlyWarnings": "هشدار 'فقط ردیابی' را نشان ندهید",
"dontShowAPKOriginWarnings": "هشدارهای منبع APK را نشان ندهید",
"moveNonInstalledAppsToBottom": "برنامه های نصب نشده را به نمای پایین برنامه ها منتقل کنید",
"gitlabPATLabel": "رمز دسترسی شخصی GitLab\n(جستجو و کشف بهتر APK را فعال میکند)",
"gitlabPATLabel": "رمز دسترسی شخصی GitLab",
"about": "درباره",
"requiresCredentialsInSettings": "{}: این به اعتبارنامه های اضافی نیاز دارد (در تنظیمات)",
"checkOnStart": "بررسی در شروع",
@ -232,7 +232,6 @@
"addInfoBelow": "این اطلاعات را در زیر اضافه کنید",
"addInfoInSettings": "این اطلاعات را در تنظیمات اضافه کنید.",
"githubSourceNote": "با استفاده از کلید API می توان از محدودیت نرخ GitHub جلوگیری کرد.",
"gitlabSourceNote": "استخراج APK GitLab ممکن است بدون کلید API کار نکند.",
"sortByLastLinkSegment": "فقط بر اساس آخرین بخش پیوند مرتب کنید",
"filterReleaseNotesByRegEx": "یادداشت های انتشار را با بیان منظم فیلتر کنید",
"customLinkFilterRegex": "فیلتر پیوند سفارشی بر اساس عبارت منظم (پیش‌فرض '.apk$')",

View File

@ -167,7 +167,7 @@
"lastUpdateCheckX": "Vérification de la dernière mise à jour : {}",
"remove": "Retirer",
"yesMarkUpdated": "Oui, marquer comme mis à jour",
"fdroid": "F-Droïde Officiel",
"fdroid": "F-Droid Officiel",
"appIdOrName": "ID ou nom de l'application",
"appId": "ID de l'application",
"appWithIdOrNameNotFound": "Aucune application n'a été trouvée avec cet identifiant ou ce nom",
@ -218,7 +218,7 @@
"dontShowTrackOnlyWarnings": "Don't Show the 'Track-Only' Warning",
"dontShowAPKOriginWarnings": "Ne pas afficher les avertissements sur l'origine de l'APK",
"moveNonInstalledAppsToBottom": "Déplacer les applications non installées vers le bas de la vue Applications",
"gitlabPATLabel": "Jeton d'accès personnel GitLab\\n (permet la recherche et une meilleure découverte d'APK)",
"gitlabPATLabel": "Jeton d'accès personnel GitLab",
"about": "À propos de",
"requiresCredentialsInSettings": "{}: This needs additional credentials (in Settings)",
"checkOnStart": "Vérifier les mises à jour au démarrage",
@ -232,7 +232,6 @@
"addInfoBelow": "Ajoutez ces informations ci-dessous.",
"addInfoInSettings": "Ajoutez ces informations dans les paramètres.",
"githubSourceNote": "La limitation du débit GitHub peut être évitée à l'aide d'une clé API.",
"gitlabSourceNote": "L'extraction d'APK GitLab peut ne pas fonctionner sans clé API.",
"sortByLastLinkSegment": "Trier uniquement sur le dernier segment du lien",
"filterReleaseNotesByRegEx": "Filtrer les notes de version par expression régulière",
"customLinkFilterRegex": "Filtre de lien APK personnalisé par expression régulière (par défaut '.apk$')",

View File

@ -218,7 +218,7 @@
"dontShowTrackOnlyWarnings": "Ne jelenítsen meg 'Csak nyomon követés' figyelmeztetést",
"dontShowAPKOriginWarnings": "Ne jelenítsen meg az APK eredetére vonatkozó figyelmeztetéseket",
"moveNonInstalledAppsToBottom": "Helyezze át a nem telepített appokat az App nézet aljára",
"gitlabPATLabel": "GitLab Personal Access Token\n(Engedélyezi a Keresést és jobb APK felfedezés)",
"gitlabPATLabel": "GitLab Personal Access Token",
"about": "Rólunk",
"requiresCredentialsInSettings": "{}: Ehhez további hitelesítő adatokra van szükség (a Beállításokban)",
"checkOnStart": "Egyszer az alkalmazás indításakor is",
@ -232,7 +232,6 @@
"addInfoBelow": "Adja hozzá ezt az infót alább.",
"addInfoInSettings": "Adja hozzá ezt az infót a Beállításokban.",
"githubSourceNote": "A GitHub sebességkorlátozás elkerülhető API-kulcs használatával.",
"gitlabSourceNote": "Előfordulhat, hogy a GitLab APK kibontása nem működik API-kulcs nélkül.",
"sortByLastLinkSegment": "Rendezés csak a link utolsó szegmense szerint",
"filterReleaseNotesByRegEx": "Kiadási megjegyzések szűrése reguláris kifejezéssel",
"customLinkFilterRegex": "Egyéni APK hivatkozásszűrő reguláris kifejezéssel (Alapérték '.apk$')",
@ -282,14 +281,14 @@
"parallelDownloads": "Párhuzamos letöltéseket enged",
"installMethod": "Telepítési mód",
"normal": "Normál",
"root": "Gyökér",
"root": "Root",
"shizukuBinderNotFound": "A Shizuku nem fut",
"useSystemFont": "Használja a rendszer betűtípusát",
"systemFontError": "Hiba a rendszer betűtípusának betöltésekor: {}",
"useVersionCodeAsOSVersion": "Az app verziókód használata a rendszer által észlelt verzióként",
"requestHeader": "Kérelem fejléc",
"useLatestAssetDateAsReleaseDate": "Használja a legújabb tartalomfeltöltést megjelenési dátumként",
"defaultPseudoVersioningMethod": "Alapértelmezett álversziós módszer",
"defaultPseudoVersioningMethod": "Alapértelmezett álverziós módszer",
"partialAPKHash": "Részleges APK Hash",
"APKLinkHash": "APK Link Hash",
"directAPKLink": "Közvetlen APK Link",

View File

@ -218,7 +218,7 @@
"dontShowTrackOnlyWarnings": "Non mostrare gli avvisi 'Solo-Monitoraggio'",
"dontShowAPKOriginWarnings": "Non mostrare gli avvisi di origine dell'APK",
"moveNonInstalledAppsToBottom": "Sposta le app non installate in fondo alla lista",
"gitlabPATLabel": "GitLab Personal Access Token\n(attiva la ricerca e migliora la rilevazione di apk)",
"gitlabPATLabel": "GitLab Personal Access Token",
"about": "Informazioni",
"requiresCredentialsInSettings": "{}: Servono credenziali aggiuntive (in Impostazioni)",
"checkOnStart": "Controlla una volta all'avvio",
@ -232,7 +232,6 @@
"addInfoBelow": "Aggiungi questa info sotto.",
"addInfoInSettings": "Aggiungi questa info nelle impostazioni.",
"githubSourceNote": "Il limite di ricerca GitHub può essere evitato usando una chiave API.",
"gitlabSourceNote": "L'estrazione di APK da GitLab potrebbe non funzionare senza chiave API.",
"sortByLastLinkSegment": "Ordina solo in base all'ultimo segmento del collegamento",
"filterReleaseNotesByRegEx": "Filtra le note di rilascio con espressione regolare",
"customLinkFilterRegex": "Filtra link APK personalizzato con espressione regolare (predefinito '.apk$')",

View File

@ -218,7 +218,7 @@
"dontShowTrackOnlyWarnings": "「追跡のみ」の警告を表示しない",
"dontShowAPKOriginWarnings": "APKのダウンロード元の警告を表示しない",
"moveNonInstalledAppsToBottom": "未インストールのアプリをアプリ一覧の下部に移動させる",
"gitlabPATLabel": "GitLab パーソナルアクセストークン\n(検索とより良いAPK検出の有効化)",
"gitlabPATLabel": "GitLab パーソナルアクセストークン",
"about": "概要",
"requiresCredentialsInSettings": "{}: これには追加の認証が必要です (設定にて)",
"checkOnStart": "起動時にアップデートを確認する",
@ -232,7 +232,6 @@
"addInfoBelow": "下部でこの情報を追加してください。",
"addInfoInSettings": "設定でこの情報を追加してください。",
"githubSourceNote": "GitHubのレート制限はAPIキーを使うことで回避できます。",
"gitlabSourceNote": "GitLabのAPK抽出はAPIキーがないと動作しない場合があります。",
"sortByLastLinkSegment": "リンクの最後のセグメントのみでソートする",
"filterReleaseNotesByRegEx": "正規表現でリリースノートをフィルタリングする",
"customLinkFilterRegex": "正規表現によるカスタムリンクフィルター (デフォルト '.apk$')",

View File

@ -218,7 +218,7 @@
"dontShowTrackOnlyWarnings": "Geen waarschuwingen voor 'Track-Only' weergeven",
"dontShowAPKOriginWarnings": "APK-herkomstwaarschuwingen niet weergeven",
"moveNonInstalledAppsToBottom": "Verplaats niet-geïnstalleerde apps naar de onderkant van de apps-weergave",
"gitlabPATLabel": "GitLab Personal Access Token\n(Maakt het mogelijk beter te zoeken naar APK's)",
"gitlabPATLabel": "GitLab Personal Access Token",
"about": "Over",
"requiresCredentialsInSettings": "{}: Dit vereist aanvullende referenties (in Instellingen)",
"checkOnStart": "Controleren op updates bij opstarten",
@ -232,7 +232,6 @@
"addInfoBelow": "Voeg deze informatie hieronder toe.",
"addInfoInSettings": "Voeg deze informatie toe in de instellingen.",
"githubSourceNote": "Beperkingen van GitHub kunnen worden vermeden door het gebruik van een API-sleutel.",
"gitlabSourceNote": "GitLab APK-extractie werkt mogelijk niet zonder een API-sleutel.",
"sortByLastLinkSegment": "Sorteren op alleen het laatste segment van de link",
"filterReleaseNotesByRegEx": "Filter release-opmerkingen met een reguliere expressie.",
"customLinkFilterRegex": "Aangepaste APK-linkfilter met een reguliere expressie (Standaard '.apk$').",

View File

@ -218,7 +218,7 @@
"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)",
"gitlabPATLabel": "Osobisty token dostępu GitLab",
"about": "Więcej informacji",
"requiresCredentialsInSettings": "{}: Wymaga to dodatkowych poświadczeń (w Ustawieniach)",
"checkOnStart": "Sprawdź aktualizacje przy uruchomieniu",
@ -232,7 +232,6 @@
"addInfoBelow": "Dodaj tę informację poniżej.",
"addInfoInSettings": "Dodaj tę informację w Ustawieniach.",
"githubSourceNote": "Limit żądań GitHub można ominąć za pomocą klucza API.",
"gitlabSourceNote": "Pozyskiwanie pliku APK z GitLab może nie działać bez klucza API.",
"sortByLastLinkSegment": "Sortuj tylko według ostatniego segmentu łącza",
"filterReleaseNotesByRegEx": "Filtruj informacje o wersji według wyrażenia regularnego",
"customLinkFilterRegex": "Filtruj linki APK według wyrażenia regularnego (domyślnie \".apk$\")",

View File

@ -218,7 +218,7 @@
"dontShowTrackOnlyWarnings": "Não mostrar avisos 'Apenas monitorar'",
"dontShowAPKOriginWarnings": "Não mostrar avisos de origem da APK",
"moveNonInstalledAppsToBottom": "Mover aplicativos não instalados para o fundo da lista de aplicativos",
"gitlabPATLabel": "Token de acesso pessoal do Gitlab\n(Ativa pesquisa e melhora a descoberta de APKs)",
"gitlabPATLabel": "Token de acesso pessoal do Gitlab",
"about": "Sobre",
"requiresCredentialsInSettings": "{}: Isso requer credenciais adicionais (em Configurações)",
"checkOnStart": "Verificar se há atualizações ao iniciar",
@ -232,7 +232,6 @@
"addInfoBelow": "Adicionar essa informação abaixo.",
"addInfoInSettings": "Adicionar essa informação nas configurações.",
"githubSourceNote": "A limitação de taxa do GitHub pode ser evitada usando uma chave de API.",
"gitlabSourceNote": "A extração de endereço de download do APK no GitLab provavelmente não funcione sem que seja fornecido uma chave de API.",
"sortByLastLinkSegment": "Ordenar apenas usando o último segmento do link",
"filterReleaseNotesByRegEx": "Filtrar notas de versão usando Regex",
"customLinkFilterRegex": "Filtro de link personalizado usando expressão regular (Padrão '.apk$')",

View File

@ -218,7 +218,7 @@
"dontShowTrackOnlyWarnings": "Не показывать предупреждения о только отслеживаемых приложениях",
"dontShowAPKOriginWarnings": "Не показывать предупреждения об отличающемся источнике APK-файлов",
"moveNonInstalledAppsToBottom": "Отображать неустановленные приложения внизу списка",
"gitlabPATLabel": "Персональный токен доступа GitLab\n(включает поиск и улучшает обнаружение APK)",
"gitlabPATLabel": "Персональный токен доступа GitLab",
"about": "Описание",
"requiresCredentialsInSettings": "{}: Для этого требуются дополнительные учетные данные (в настройках)",
"checkOnStart": "Проверять наличие обновлений при запуске",
@ -232,7 +232,6 @@
"addInfoBelow": "Добавьте эту информацию ниже",
"addInfoInSettings": "Добавьте эту информацию в Настройки",
"githubSourceNote": "Используя ключ API можно обойти лимит запросов GitHub",
"gitlabSourceNote": "Без ключа API может не работать извлечение APK с GitLab",
"sortByLastLinkSegment": "Сортировать только по последнему сегменту ссылки",
"filterReleaseNotesByRegEx": "Фильтровать примечания к выпуску\n(регулярное выражение)",
"customLinkFilterRegex": "Пользовательский фильтр ссылок APK\n(регулярное выражение, по умолчанию: '.apk$')",

View File

@ -218,7 +218,7 @@
"dontShowTrackOnlyWarnings": "Visa inte 'Följ-Endast' varningar",
"dontShowAPKOriginWarnings": "Visa inte APK-ursprung varningar",
"moveNonInstalledAppsToBottom": "Flytta icke-installerade appar till botten av appvyn",
"gitlabPATLabel": "GitLab Personal Access Token\\n(Möjliggör sökning och bättre APK-upptäckt)",
"gitlabPATLabel": "GitLab Personal Access Token",
"about": "Om",
"requiresCredentialsInSettings": "{}: This needs additional credentials (in Settings)",
"checkOnStart": "Kolla efter uppdateringar vid start",
@ -232,7 +232,6 @@
"addInfoBelow": "Lägg till denna information nedanför.",
"addInfoInSettings": "Lägg till denna information i Inställningar.",
"githubSourceNote": "GitHub-hastighetsbegränsning kan undvikas med en API-nyckel.",
"gitlabSourceNote": "GitLab APK-extraktion kanske inte fungerar utan en API-nyckel.",
"sortByLastLinkSegment": "Sortera endast efter det sista segmentet av länken",
"filterReleaseNotesByRegEx": "Filtrera versionskommentarer efter reguljärt uttryck",
"customLinkFilterRegex": "Anpassad APK-länkfiltrera efter reguljärt uttryck (standard '.apk$')",

View File

@ -218,7 +218,7 @@
"dontShowTrackOnlyWarnings": "'Yalnızca Takip Edilen' uyarılarını gösterme",
"dontShowAPKOriginWarnings": "APK kaynağı uyarılarını gösterme",
"moveNonInstalledAppsToBottom": "Yüklenmemiş Uygulamaları Uygulamalar Görünümünün Altına Taşı",
"gitlabPATLabel": "GitLab Kişisel Erişim Belirteci\n(Arama ve Daha İyi APK Keşfi İçin)",
"gitlabPATLabel": "GitLab Kişisel Erişim Belirteci",
"about": "Hakkında",
"requiresCredentialsInSettings": "{}: Bu, ek kimlik bilgilerine ihtiyaç duyar (Ayarlar'da)",
"checkOnStart": "Başlangıçta güncellemeleri kontrol et",
@ -232,7 +232,6 @@
"addInfoBelow": "Bu bilgiyi aşağıya ekle.",
"addInfoInSettings": "Bu bilgiyi Ayarlar'da ekleyin.",
"githubSourceNote": "GitHub hız sınırlaması bir API anahtarı kullanılarak atlanabilir.",
"gitlabSourceNote": "GitLab APK çıkarma işlemi bir API anahtarı olmadan çalışmayabilir.",
"sortByLastLinkSegment": "Bağlantının yalnızca son bölümüne göre sırala",
"filterReleaseNotesByRegEx": "Sürüm Notlarını Düzenli İfade ile Filtrele",
"customLinkFilterRegex": "Özel APK Bağlantı Filtresi Düzenli İfade ile (Varsayılan '.apk$')",

359
assets/translations/uk.json Normal file
View File

@ -0,0 +1,359 @@
{
"invalidURLForSource": "Неправильна URL-адреса для джерела застосунку {}",
"noReleaseFound": "Не вдалося знайти відповідне видання",
"noVersionFound": "Не вдалося визначити версію видання",
"urlMatchesNoSource": "URL не відповідає відомому джерелу",
"cantInstallOlderVersion": "Не можна встановити старішу версію застосунку",
"appIdMismatch": "Ідентифікатор пакета, завантажений, не відповідає ідентифікатору існуючого застосунку",
"functionNotImplemented": "Цей клас не реалізував цю функцію",
"placeholder": "Заповнювач",
"someErrors": "Виникла деяка помилка",
"unexpectedError": "Неочікувана помилка",
"ok": "Добре",
"and": "та",
"githubPATLabel": "Персональний ключ доступу GitHub (збільшує обмеження на швидкість)",
"includePrereleases": "Включити попередні видання",
"fallbackToOlderReleases": "Повернутися до старіших видань",
"filterReleaseTitlesByRegEx": "Фільтрувати заголовки видань за допомогою регулярного виразу",
"invalidRegEx": "Неприпустимий регулярний вираз",
"noDescription": "Немає опису",
"cancel": "Скасувати",
"continue": "Продовжити",
"requiredInBrackets": "(Обов'язково)",
"dropdownNoOptsError": "ПОМИЛКА: В ВИПАДАЮЧОМУ СПИСКУ МАЄ БУТИ ХОЧА Б ОДИН ЕЛЕМЕНТ",
"colour": "Колір",
"githubStarredRepos": "Відзначені репозиторії GitHub",
"uname": "Ім'я користувача",
"wrongArgNum": "Надано неправильну кількість аргументів",
"xIsTrackOnly": "{} - тільки відстежування",
"source": "Джерело",
"app": "застосунок",
"appsFromSourceAreTrackOnly": "Додатки з цього джерела є лише для відстежування.",
"youPickedTrackOnly": "Ви вибрали опцію лише для відстежування.",
"trackOnlyAppDescription": "Застосунок буде відстежуватися для оновлень, але Obtainium не зможе його завантажити або встановити.",
"cancelled": "Скасовано",
"appAlreadyAdded": "Застосунок вже додано",
"alreadyUpToDateQuestion": "Застосунок вже оновлено?",
"addApp": "Додати Застосунок",
"appSourceURL": "URL-адреса джерела застосунку",
"error": "Помилка",
"add": "Додати",
"searchSomeSourcesLabel": "Пошук (Лише деякі джерела)",
"search": "Пошук",
"additionalOptsFor": "Додаткові опції для {}",
"supportedSources": "Підтримувані джерела",
"trackOnlyInBrackets": "(Тільки для відстеження)",
"searchableInBrackets": "(Можливий пошук)",
"appsString": "Додатки",
"noApps": "Додатків немає",
"noAppsForFilter": "Додатків для фільтрації немає",
"byX": "За {}",
"percentProgress": "Прогрес: {}%",
"pleaseWait": "Будь ласка, зачекайте",
"updateAvailable": "Доступно оновлення",
"notInstalled": "Не встановлено",
"pseudoVersion": "псевдо-версія",
"selectAll": "Вибрати все",
"deselectX": "Скасувати вибір {}",
"xWillBeRemovedButRemainInstalled": "{} буде видалено з Obtainium, але залишиться встановленим на пристрої.",
"removeSelectedAppsQuestion": "Видалити вибрані додатки?",
"removeSelectedApps": "Видалити вибрані додатки",
"updateX": "Оновити {}",
"installX": "Встановити {}",
"markXTrackOnlyAsUpdated": "Позначити {}\n(Тільки відстежування)\nяк оновлено",
"changeX": "Змінити {}",
"installUpdateApps": "Встановити/Оновити додатки",
"installUpdateSelectedApps": "Встановити/Оновити вибрані додатки",
"markXSelectedAppsAsUpdated": "Позначити {} вибрані додатки як оновлені?",
"no": "Ні",
"yes": "Так",
"markSelectedAppsUpdated": "Позначити вибрані додатки як оновлені",
"pinToTop": "Закріпити угорі",
"unpinFromTop": "Відкріпити зверху",
"resetInstallStatusForSelectedAppsQuestion": "Скинути статус встановлення для вибраних додатків?",
"installStatusOfXWillBeResetExplanation": "Статус встановлення будь-яких вибраних додатків буде скинутий.\n\nЦе може допомогти, коли версія застосунку, відображена в Obtainium, є неправильною через невдалі оновлення або інші проблеми.",
"customLinkMessage": "Ці посилання працюють на пристроях з встановленим Obtainium",
"shareAppConfigLinks": "Поділитися посиланнями на конфігурацію Застосунку як HTML",
"shareSelectedAppURLs": "Поділитися вибраними URL-адресами додатків",
"resetInstallStatus": "Скинути статус встановлення",
"more": "Більше",
"removeOutdatedFilter": "Видалити фільтр застарілих додатків",
"showOutdatedOnly": "Показати лише застарілі додатки",
"filter": "Фільтр",
"filterApps": "Фільтрувати додатки",
"appName": "Назва застосунку",
"author": "Автор",
"upToDateApps": "Актуальні додатки",
"nonInstalledApps": "Невстановлені додатки",
"importExport": "Імпорт/Експорт",
"settings": "Налаштування",
"exportedTo": "Експортовано в {}",
"obtainiumExport": "Експорт з Obtainium",
"invalidInput": "Недійсний ввід",
"importedX": "Імпортовано {}",
"obtainiumImport": "Імпорт в Obtainium",
"importFromURLList": "Імпорт зі списку URL-адрес",
"searchQuery": "Пошуковий запит",
"appURLList": "Список URL-адрес додатків",
"line": "Лінія",
"searchX": "Пошук {}",
"noResults": "Результати відсутні",
"importX": "Імпорт {}",
"importedAppsIdDisclaimer": "Імпортовані додатки можуть неправильно відображатися як \"Не встановлені\".\nДля виправлення цього перевстановіть їх через Obtainium.\nЦе не повинно вплинути на дані додатків.\n\nПов'язано лише з URL-адресами та імпортом від третіх сторін.",
"importErrors": "Помилки імпорту",
"importedXOfYApps": "Імпортовано {} з {} додатків.",
"followingURLsHadErrors": "Помилки в наступних URL-адресах:",
"selectURL": "Вибрати URL",
"selectURLs": "Вибрати URL-адреси",
"pick": "Вибрати",
"theme": "Тема",
"dark": "Темна",
"light": "Світла",
"followSystem": "Дотримуватися системи",
"useBlackTheme": "Використовувати чисто чорну темну тему",
"appSortBy": "Сортувати додатки за",
"authorName": "Автор/Назва",
"nameAuthor": "Назва/Автор",
"asAdded": "За додаванням",
"appSortOrder": "Порядок сортування додатків",
"ascending": "За зростанням",
"descending": "За спаданням",
"bgUpdateCheckInterval": "Інтервал перевірки оновлень у фоновому режимі",
"neverManualOnly": "Ніколи - Тільки вручну",
"appearance": "Вигляд",
"showWebInAppView": "Показати джерело застосунку у вигляді веб-сторінки",
"pinUpdates": "Закріпити оновлення у верхній частині вигляду додатків",
"updates": "Оновлення",
"sourceSpecific": "Певне джерело",
"appSource": "Джерело застосунку",
"noLogs": "Немає логів",
"appLogs": "Лог застосунку",
"close": "Закрити",
"share": "Поділитися",
"appNotFound": "Застосунок не знайдено",
"obtainiumExportHyphenatedLowercase": "експорт з Obtainium",
"pickAnAPK": "Вибрати APK",
"appHasMoreThanOnePackage": "{} має більше одного пакету:",
"deviceSupportsXArch": "Ваш пристрій підтримує архітектуру процесора {}.",
"deviceSupportsFollowingArchs": "Ваш пристрій підтримує наступні архітектури процесора:",
"warning": "Попередження",
"sourceIsXButPackageFromYPrompt": "Джерело застосунку - '{}' але пакет випуску походить з '{}'. Продовжити?",
"updatesAvailable": "Доступні оновлення",
"updatesAvailableNotifDescription": "Повідомляє користувача, що доступні оновлення для одного чи декількох додатків, які відстежує Obtainium",
"noNewUpdates": "Немає нових оновлень.",
"xHasAnUpdate": "{} має оновлення.",
"appsUpdated": "Додатки оновлено",
"appsUpdatedNotifDescription": "Повідомляє користувача, що оновлення одного чи декількох додатків було застосовано в фоновому режимі",
"xWasUpdatedToY": "{} було оновлено до {}.",
"errorCheckingUpdates": "Помилка перевірки оновлень",
"errorCheckingUpdatesNotifDescription": "Повідомлення, яке з'являється, коли перевірка оновлень в фоновому режимі завершується невдачею",
"appsRemoved": "Додатки видалено",
"appsRemovedNotifDescription": "Повідомляє користувача, що один чи декілька додатків були видалені через помилки при завантаженні",
"xWasRemovedDueToErrorY": "{} було видалено через цю помилку: {}",
"completeAppInstallation": "Завершення установки застосунку",
"obtainiumMustBeOpenToInstallApps": "Для встановлення додатків Obtainium має бути відкритий",
"completeAppInstallationNotifDescription": "Прохання користувача повернутися до Obtainium для завершення установки застосунку",
"checkingForUpdates": "Перевірка оновлень",
"checkingForUpdatesNotifDescription": "Тимчасове повідомлення, яке з'являється при перевірці оновлень",
"pleaseAllowInstallPerm": "Будь ласка, дозвольте Obtainium встановлювати додатки",
"trackOnly": "Тільки відстеження",
"errorWithHttpStatusCode": "Помилка {} HTTP-коду",
"versionCorrectionDisabled": "Виправлення версії вимкнено (здається, плагін не працює)",
"unknown": "Невідомо",
"none": "Нічого",
"never": "Ніколи",
"latestVersionX": "Остання версія: {}",
"installedVersionX": "Встановлено: {}",
"lastUpdateCheckX": "Остання перевірка оновлень: {}",
"remove": "Видалити",
"yesMarkUpdated": "Так, позначити як оновлене",
"fdroid": "F-Droid Офіційний",
"appIdOrName": "Ідентифікатор або назва застосунку",
"appId": "Ідентифікатор застосунку",
"appWithIdOrNameNotFound": "Застосунок з таким ідентифікатором або назвою не знайдено",
"reposHaveMultipleApps": "Сховища можуть містити кілька додатків",
"fdroidThirdPartyRepo": "F-Droid Стороннє сховище",
"steamMobile": "Мобільний Steam",
"steamChat": "Чат Steam",
"install": "Встановити",
"markInstalled": "Позначити як встановлене",
"update": "Оновити",
"markUpdated": "Позначити як оновлене",
"additionalOptions": "Додаткові опції",
"disableVersionDetection": "Вимкнути визначення версії",
"noVersionDetectionExplanation": "Цю опцію слід використовувати лише для додатків, де визначення версії працює неправильно.",
"downloadingX": "Завантаження {}",
"downloadNotifDescription": "Повідомляє користувача про прогрес завантаження застосунку",
"noAPKFound": "APK не знайдено",
"noVersionDetection": "Визначення версії відключено",
"categorize": "Категоризувати",
"categories": "Категорії",
"category": "Категорія",
"noCategory": "Без категорії",
"noCategories": "Немає категорій",
"deleteCategoriesQuestion": "Видалити категорії?",
"categoryDeleteWarning": "Усі додатки у видалених категоріях будуть переведені у некатегоризовані.",
"addCategory": "Додати категорію",
"label": "Мітка",
"language": "Мова",
"copiedToClipboard": "Скопійовано в буфер обміну",
"storagePermissionDenied": "Відмовлено у дозволі на доступ до сховища",
"selectedCategorizeWarning": "Це замінить будь-які існуючі налаштування категорій для вибраних додатків.",
"filterAPKsByRegEx": "Фільтрувати APK за регулярним виразом",
"removeFromObtainium": "Видалити з Obtainium",
"uninstallFromDevice": "Видалити з пристрою",
"onlyWorksWithNonVersionDetectApps": "Працює лише з застосунками з вимкненим визначенням версії.",
"releaseDateAsVersion": "Використовувати дату випуску як рядок версії",
"releaseDateAsVersionExplanation": "Цю опцію слід використовувати лише для додатків, де визначення версії працює неправильно, але є дата випуску.",
"changes": "Зміни",
"releaseDate": "Дата випуску",
"importFromURLsInFile": "Імпорт з URL-адрес у файлі (наприклад, OPML)",
"versionDetectionExplanation": "Порівняти рядок версії з версією, визначеною операційною системою",
"versionDetection": "Визначення версії",
"standardVersionDetection": "Стандартне визначення версії",
"groupByCategory": "Групувати за категоріями",
"autoApkFilterByArch": "Спробувати фільтрувати APK за архітектурою ЦП, якщо можливо",
"overrideSource": "Перевизначити джерело",
"dontShowAgain": "Не показувати це знову",
"dontShowTrackOnlyWarnings": "Не показувати попередження про 'Тільки відстеження'",
"dontShowAPKOriginWarnings": "Не показувати попередження про походження APK",
"moveNonInstalledAppsToBottom": "Перемістити невстановлені додатки вниз у перегляді додатків",
"gitlabPATLabel": "Особистий токен GitLab (Увімкнення пошуку та краще виявлення APK)",
"about": "Про програму",
"requiresCredentialsInSettings": "{} потребує додаткових облікових даних (у налаштуваннях)",
"checkOnStart": "Перевірити наявність оновлень при запуску",
"tryInferAppIdFromCode": "Спробувати вивести ідентифікатор застосунку з вихідного коду",
"removeOnExternalUninstall": "Автоматично видаляти додатки, які було видалено зовнішнім чином",
"pickHighestVersionCode": "Автоматично вибрати APK з найвищим кодом версії",
"checkUpdateOnDetailPage": "Перевіряти наявність оновлень при відкритті сторінки деталей застосунку",
"disablePageTransitions": "Вимкнути анімації переходів між сторінками",
"reversePageTransitions": "Зворотні анімації переходів між сторінками",
"minStarCount": "Мінімальна кількість зірок",
"addInfoBelow": "Додати цю інформацію нижче.",
"addInfoInSettings": "Додати цю інформацію у налаштуваннях.",
"githubSourceNote": "Лімітування швидкості GitHub можна уникнути, використовуючи ключ API.",
"gitlabSourceNote": "Вилучення APK з GitLab може не працювати без ключа API.",
"sortByLastLinkSegment": "Сортувати лише за останнім сегментом посилання",
"filterReleaseNotesByRegEx": "Фільтрувати примітки до релізу за регулярним виразом",
"customLinkFilterRegex": "Фільтр кастомного посилання на APK за регулярним виразом (за замовчуванням '.apk$')",
"appsPossiblyUpdated": "Оновлення додатків спробовано",
"appsPossiblyUpdatedNotifDescription": "Повідомляє користувача, що оновлення одного або декількох додатків можливо були застосовані в фоновому режимі",
"xWasPossiblyUpdatedToY": "{} можливо було оновлено до {}.",
"enableBackgroundUpdates": "Увімкнути оновлення в фоновому режимі",
"backgroundUpdateReqsExplanation": "Оновлення в фоновому режимі може бути неможливим для всіх додатків.",
"backgroundUpdateLimitsExplanation": "Успіх фонової установки може бути визначений лише після відкриття Obtainium.",
"verifyLatestTag": "Перевірити тег 'latest'",
"intermediateLinkRegex": "Фільтр для 'Проміжного' Посилання для Відвідування",
"filterByLinkText": "Фільтрувати посилання за текстом посилання",
"intermediateLinkNotFound": "Проміжне посилання не знайдено",
"intermediateLink": "Проміжне посилання",
"exemptFromBackgroundUpdates": "Виключено з фонових оновлень (якщо ввімкнено)",
"bgUpdatesOnWiFiOnly": "Вимкнути фонові оновлення поза Wi-Fi",
"autoSelectHighestVersionCode": "Автоматичний вибір APK з найвищим кодом версії",
"versionExtractionRegEx": "Регулярний вираз для вилучення рядка версії",
"matchGroupToUse": "Група співпадінь для використання в регулярному виразі вилучення версії",
"highlightTouchTargets": "Підсвічувати менш очевидні області дотику",
"pickExportDir": "Вибрати каталог експорту",
"autoExportOnChanges": "Автоматичний експорт при змінах",
"includeSettings": "Включити налаштування",
"filterVersionsByRegEx": "Фільтрувати версії за регулярним виразом",
"trySelectingSuggestedVersionCode": "Спробуйте вибрати запропонований код версії APK",
"dontSortReleasesList": "Зберігати порядок випуску з API",
"reverseSort": "Зворотне сортування",
"takeFirstLink": "Вибрати перше посилання",
"skipSort": "Пропустити сортування",
"debugMenu": "Меню налагодження",
"bgTaskStarted": "Запущено фонове завдання - перевірте журнали.",
"runBgCheckNow": "Запустити перевірку оновлень в фоновому режимі зараз",
"versionExtractWholePage": "Застосувати регулярний вираз вилучення версії до всієї сторінки",
"installing": "Встановлення",
"skipUpdateNotifications": "Пропустити сповіщення про оновлення",
"updatesAvailableNotifChannel": "Доступні оновлення",
"appsUpdatedNotifChannel": "Додатки оновлені",
"appsPossiblyUpdatedNotifChannel": "Спроба оновлення додатків",
"errorCheckingUpdatesNotifChannel": "Помилка перевірки оновлень",
"appsRemovedNotifChannel": "Додатки видалені",
"downloadingXNotifChannel": "Завантаження {}",
"completeAppInstallationNotifChannel": "Завершення встановлення застосунку",
"checkingForUpdatesNotifChannel": "Перевірка оновлень",
"onlyCheckInstalledOrTrackOnlyApps": "Перевіряти лише встановлені та додатки, які відстежуються для оновлень",
"supportFixedAPKURL": "Підтримка фіксованих посилань на APK",
"selectX": "Вибрати {}",
"parallelDownloads": "Дозволити паралельні завантаження",
"installMethod": "Метод встановлення",
"normal": "Звичайний",
"root": "Root",
"shizukuBinderNotFound": "Сумісний сервіс Shizuku не було знайдено",
"useSystemFont": "Використовувати системний шрифт",
"systemFontError": "Помилка завантаження системного шрифту: {}",
"useVersionCodeAsOSVersion": "Використовувати код версії застосунку як версію, визначену операційною системою",
"requestHeader": "Заголовок запиту",
"useLatestAssetDateAsReleaseDate": "Використовувати останню дату завантаження ресурсу як дату випуску",
"defaultPseudoVersioningMethod": "Метод за замовчуванням псевдо-версіонування",
"partialAPKHash": "Хеш часткового APK",
"APKLinkHash": "Хеш посилання на APK",
"directAPKLink": "Пряме посилання на APK",
"pseudoVersionInUse": "Використовується псевдо-версія",
"installed": "Встановлено",
"latest": "Остання",
"invertRegEx": "Інвертувати регулярний вираз",
"note": "Примітка",
"selfHostedNote": "Випадаючий список \"{}\" може використовуватися для доступу до власних/призначених для самостійного використання екземплярів будь-якого джерела.",
"badDownload": "APK не вдалося розпарсити (несумісний або часткове завантаження)",
"removeAppQuestion": {
"one": "Видалити Застосунок?",
"other": "Видалити додатки?"
},
"tooManyRequestsTryAgainInMinutes": {
"one": "Забагато запитів (обмеження швидкості) - повторіть спробу через {} хвилину",
"other": "Забагато запитів (обмеження швидкості) - повторіть спробу через {} хвилин"
},
"bgUpdateGotErrorRetryInMinutes": {
"one": "Помилка перевірки оновлень у фоновому режимі - спробую знову через {} хвилину",
"other": "Помилка перевірки оновлень у фоновому режимі - спробую знову через {} хвилин"
},
"bgCheckFoundUpdatesWillNotifyIfNeeded": {
"one": "Фонова перевірка оновлень знайшла {} оновлення - сповістить користувача, якщо це необхідно",
"other": "Фонова перевірка оновлень знайшла {} оновлень - сповістить користувача, якщо це необхідно"
},
"apps": {
"one": "{} Застосунок",
"other": "{} Додатки"
},
"url": {
"one": "{} URL",
"other": "{} URL-адреси"
},
"minute": {
"one": "{} Хвилина",
"other": "{} Хвилин"
},
"hour": {
"one": "{} Година",
"other": "{} Годин"
},
"day": {
"one": "{} День",
"other": "{} Днів"
},
"clearedNLogsBeforeXAfterY": {
"one": "Очищено {n} журнал (до = {before}, після = {after})",
"other": "Очищено {n} журналів (до = {before}, після = {after})"
},
"xAndNMoreUpdatesAvailable": {
"one": "{} і 1 інше Застосунок мають оновлення.",
"other": "{} і {} інших додатки мають оновлення."
},
"xAndNMoreUpdatesInstalled": {
"one": "{} і 1 інше Застосунок було оновлено.",
"other": "{} і {} інших додатків було оновлено."
},
"xAndNMoreUpdatesPossiblyInstalled": {
"one": "{} і 1 інше Застосунок можливо було оновлено.",
"other": "{} і {} інших додатків можливо було оновлено."
},
"apk": {
"one": "{} APK",
"other": "{} APK-файли"
}
}

View File

@ -218,7 +218,7 @@
"dontShowTrackOnlyWarnings": "Không hiển thị cảnh báo 'Chỉ theo dõi'",
"dontShowAPKOriginWarnings": "Không hiển thị cảnh báo nguồn gốc APK",
"moveNonInstalledAppsToBottom": "Chuyển Ứng dụng chưa được cài đặt xuống cuối danh sách",
"gitlabPATLabel": "GitLab Token\n(Cho phép tìm kiếm và lọc APK tốt hơn)",
"gitlabPATLabel": "GitLab Token",
"about": "Giới thiệu",
"requiresCredentialsInSettings": "{}: Điều này cần thông tin xác thực bổ sung (trong Thiết đặt)",
"checkOnStart": "Kiểm tra các bản cập nhật khi khởi động",
@ -232,7 +232,6 @@
"addInfoBelow": "Thêm thông tin này vào bên dưới.",
"addInfoInSettings": "Thêm thông tin này vào Thiết đặt.",
"githubSourceNote": "Có thể tránh được việc giới hạn tốc độ GitHub bằng cách sử dụng khóa API.",
"gitlabSourceNote": "Trích xuất APK GitLab có thể không hoạt động nếu không có khóa API.",
"sortByLastLinkSegment": "Chỉ sắp xếp theo đoạn cuối của liên kết",
"filterReleaseNotesByRegEx": "Lọc ghi chú phát hành theo biểu thức chính quy",
"customLinkFilterRegex": "Bộ lọc liên kết APK tùy chỉnh theo biểu thức chính quy (Mặc định '.apk$')",

View File

@ -52,7 +52,7 @@
"pleaseWait": "请稍候",
"updateAvailable": "更新可用",
"notInstalled": "未安装",
"pseudoVersion": "版本",
"pseudoVersion": "虚拟版本",
"selectAll": "全选",
"deselectX": "取消选择 {}",
"xWillBeRemovedButRemainInstalled": "“{}”将从 Obtainium 中删除,但仍安装在您的设备中。",
@ -208,7 +208,7 @@
"changes": "更新日志",
"releaseDate": "发行日期",
"importFromURLsInFile": "从文件中的 URL 导入(如 OPML",
"versionDetectionExplanation": "将版本字符串与操作系统检测到的版本进行协调",
"versionDetectionExplanation": "使发行版本号与应用定义的版本号一致",
"versionDetection": "版本检测",
"standardVersionDetection": "常规版本检测",
"groupByCategory": "按类别分组显示",
@ -218,13 +218,13 @@
"dontShowTrackOnlyWarnings": "忽略“仅追踪”模式警告",
"dontShowAPKOriginWarnings": "忽略 APK 文件来源警告",
"moveNonInstalledAppsToBottom": "将未安装应用置底",
"gitlabPATLabel": "GitLab 个人访问令牌(启用搜索功能并增强 APK 发现)",
"gitlabPATLabel": "GitLab 个人访问令牌",
"about": "相关文档",
"requiresCredentialsInSettings": "{}:此功能需要额外的凭据(在“设置”中添加)",
"checkOnStart": "启动时进行一次检查",
"tryInferAppIdFromCode": "尝试从源代码推断应用 ID",
"removeOnExternalUninstall": "自动删除列表中已卸载的应用",
"pickHighestVersionCode": "自动选版本号最高的 APK 文件",
"pickHighestVersionCode": "自动选取内部版本号最高的 APK 文件",
"checkUpdateOnDetailPage": "打开应用详情页时进行检查",
"disablePageTransitions": "禁用页面过渡动画效果",
"reversePageTransitions": "反转页面过渡动画效果",
@ -232,7 +232,6 @@
"addInfoBelow": "在下方添加此凭据。",
"addInfoInSettings": "在“设置”中添加此凭据。",
"githubSourceNote": "使用访问令牌可避免触发 GitHub 的 API 请求限制。",
"gitlabSourceNote": "未使用访问令牌时可能无法从 GitLab 获取 APK 文件。",
"sortByLastLinkSegment": "仅根据链接的末尾部分进行筛选",
"filterReleaseNotesByRegEx": "筛选发行说明(正则表达式)",
"customLinkFilterRegex": "筛选自定义来源的 APK 文件链接\n正则表达式默认匹配模式为“.apk$”)",
@ -249,7 +248,7 @@
"intermediateLink": "中转链接",
"exemptFromBackgroundUpdates": "禁用后台更新(如果已经全局启用)",
"bgUpdatesOnWiFiOnly": "未连接 Wi-Fi 时禁用后台更新",
"autoSelectHighestVersionCode": "自动选择版本号最高的 APK 文件",
"autoSelectHighestVersionCode": "自动选择内部版本号最高的 APK 文件",
"versionExtractionRegEx": "版本号提取规则(正则表达式)",
"matchGroupToUse": "引用的捕获组",
"highlightTouchTargets": "突出展示不明显的触摸区域",
@ -286,20 +285,20 @@
"shizukuBinderNotFound": "未发现兼容的 Shizuku 服务",
"useSystemFont": "使用系统字体",
"systemFontError": "加载系统字体出错:{}",
"useVersionCodeAsOSVersion": "使用应用程序版本代码作为操作系统检测到的版本",
"useVersionCodeAsOSVersion": "使用内部版本号代替应用定义的版本",
"requestHeader": "请求标头",
"useLatestAssetDateAsReleaseDate": "使用最新资产上传作为发日期",
"defaultPseudoVersioningMethod": "默认伪版本控制方法",
"partialAPKHash": "部分 APK 哈希值",
"APKLinkHash": "APK 链接哈希",
"directAPKLink": "直接 APK 链",
"pseudoVersionInUse": "伪版本正在使用",
"installed": "已安装",
"latest": "最新",
"invertRegEx": "反转正则表达式",
"useLatestAssetDateAsReleaseDate": "使用最近文件上传时间作为发日期",
"defaultPseudoVersioningMethod": "默认虚拟版本方案",
"partialAPKHash": "APK 文件散列值片段",
"APKLinkHash": "APK 文件链接散列值",
"directAPKLink": "APK 文件直链",
"pseudoVersionInUse": "正在使用虚拟版本号",
"installed": "当前版本",
"latest": "最新版本",
"invertRegEx": "反转匹配",
"note": "备注",
"selfHostedNote": "{}\"下拉菜单可用于访问任何来源的自托管/自定义实例。",
"badDownload": "无法解析 APK不兼容或部分下载",
"selfHostedNote": "可以通过“{}”下拉菜单来指向任意来源的自托管/自定义实例。",
"badDownload": "无法解析 APK 文件(不兼容或文件不完整",
"removeAppQuestion": {
"one": "是否删除应用?",
"other": "是否删除应用?"

View File

@ -45,7 +45,7 @@ class APKCombo extends AppSource {
if (res.statusCode != 200) {
throw getObtainiumHttpError(res);
}
var html = parse(res.body);
var html = parse(res.data);
return html
.querySelectorAll('#variants-tab > div > ul > li')
.map((e) {
@ -96,7 +96,7 @@ class APKCombo extends AppSource {
if (preres.statusCode != 200) {
throw getObtainiumHttpError(preres);
}
var res = parse(preres.body);
var res = parse(preres.data);
String? version = res.querySelector('div.version')?.text.trim();
if (version == null) {
throw NoVersionError();

View File

@ -1,8 +1,8 @@
import 'dart:io';
import 'package:dio/dio.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:html/parser.dart';
import 'package:http/http.dart';
import 'package:obtainium/components/generated_form.dart';
import 'package:obtainium/custom_errors.dart';
import 'package:obtainium/providers/source_provider.dart';
@ -62,7 +62,7 @@ class APKMirror extends AppSource {
: null;
Response res = await sourceRequest('$standardUrl/feed', additionalSettings);
if (res.statusCode == 200) {
var items = parse(res.body).querySelectorAll('item');
var items = parse(res.data).querySelectorAll('item');
dynamic targetRelease;
for (int i = 0; i < items.length; i++) {
if (!fallbackToOlderReleases && i > 0) break;

View File

@ -61,8 +61,8 @@ class APKPure extends AppSource {
var res = await sourceRequest('$standardUrl/download', additionalSettings);
var resChangelog = await sourceRequest(standardUrl, additionalSettings);
if (res.statusCode == 200 && resChangelog.statusCode == 200) {
var html = parse(res.body);
var htmlChangelog = parse(resChangelog.body);
var html = parse(res.data);
var htmlChangelog = parse(resChangelog.data);
String? version = html.querySelector('span.info-sdk span')?.text.trim();
if (version == null) {
throw NoVersionError();

View File

@ -38,10 +38,10 @@ class Aptoide extends AppSource {
if (res.statusCode != 200) {
throw getObtainiumHttpError(res);
}
var idMatch = RegExp('"app":{"id":[0-9]+').firstMatch(res.body);
var idMatch = RegExp('"app":{"id":[0-9]+').firstMatch(res.data);
String? id;
if (idMatch != null) {
id = res.body.substring(idMatch.start + 12, idMatch.end);
id = res.data.substring(idMatch.start + 12, idMatch.end);
} else {
throw NoReleasesError();
}
@ -50,7 +50,7 @@ class Aptoide extends AppSource {
if (res2.statusCode != 200) {
throw getObtainiumHttpError(res);
}
return jsonDecode(res2.body)?['nodes']?['meta']?['data'];
return jsonDecode(res2.data)?['nodes']?['meta']?['data'];
}
@override

View File

@ -1,8 +1,8 @@
import 'dart:convert';
import 'package:dio/dio.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:html/parser.dart';
import 'package:http/http.dart';
import 'package:obtainium/app_sources/github.dart';
import 'package:obtainium/app_sources/gitlab.dart';
import 'package:obtainium/components/generated_form.dart';
@ -82,7 +82,7 @@ class FDroid extends AppSource {
var res = await sourceRequest(
'https://gitlab.com/fdroid/fdroiddata/-/raw/master/metadata/$appId.yml',
additionalSettings);
var lines = res.body.split('\n');
var lines = res.data.split('\n');
var authorLines = lines.where((l) => l.startsWith('AuthorName: '));
if (authorLines.isNotEmpty) {
details.names.author =
@ -112,7 +112,7 @@ class FDroid extends AppSource {
details.changeLog = (await sourceRequest(
details.changeLog!.replaceFirst('/blob/', '/raw/'),
additionalSettings))
.body;
.data;
}
}
} catch (e) {
@ -132,7 +132,7 @@ class FDroid extends AppSource {
'https://search.${hosts[0]}/?q=${Uri.encodeQueryComponent(query)}', {});
if (res.statusCode == 200) {
Map<String, List<String>> urlsWithDescriptions = {};
parse(res.body).querySelectorAll('.package-header').forEach((e) {
parse(res.data).querySelectorAll('.package-header').forEach((e) {
String? url = e.attributes['href'];
if (url != null) {
try {
@ -172,7 +172,7 @@ class FDroid extends AppSource {
? additionalSettings['apkFilterRegEx']
: null;
if (res.statusCode == 200) {
var response = jsonDecode(res.body);
var response = jsonDecode(res.data);
List<dynamic> releases = response['packages'] ?? [];
if (apkFilterRegEx != null) {
releases = releases.where((rel) {

View File

@ -1,6 +1,6 @@
import 'package:dio/dio.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:html/parser.dart';
import 'package:http/http.dart';
import 'package:obtainium/components/generated_form.dart';
import 'package:obtainium/custom_errors.dart';
import 'package:obtainium/providers/source_provider.dart';
@ -61,9 +61,10 @@ class FDroidRepo extends AppSource {
throw NoReleasesError();
}
url = removeQueryParamsFromUrl(standardizeUrl(url));
var res = await sourceRequestWithURLVariants(url, {});
var ress = await sourceRequestWithURLVariants(url, {});
var res = ress.value;
if (res.statusCode == 200) {
var body = parse(res.body);
var body = parse(res.data);
Map<String, List<String>> results = {};
body.querySelectorAll('application').toList().forEach((app) {
String appId = app.attributes['id']!;
@ -74,7 +75,7 @@ class FDroidRepo extends AppSource {
appName.contains(query) ||
appDesc.contains(query)) {
results[
'${res.request!.url.toString().split('/').reversed.toList().sublist(1).reversed.join('/')}?appId=$appId'] = [
'${ress.value.toString().split('/').reversed.toList().sublist(1).reversed.join('/')}?appId=$appId'] = [
appName,
appDesc
];
@ -107,24 +108,24 @@ class FDroidRepo extends AppSource {
return app;
}
Future<Response> sourceRequestWithURLVariants(
Future<MapEntry<String, Response>> sourceRequestWithURLVariants(
String url,
Map<String, dynamic> additionalSettings,
) async {
var res = await sourceRequest(
'$url${url.endsWith('/index.xml') ? '' : '/index.xml'}',
additionalSettings);
var finalUrl = '$url${url.endsWith('/index.xml') ? '' : '/index.xml'}';
var res = await sourceRequest(finalUrl, additionalSettings);
if (res.statusCode != 200) {
var base = url.endsWith('/index.xml')
? url.split('/').reversed.toList().sublist(1).reversed.join('/')
: url;
res = await sourceRequest('$base/repo/index.xml', additionalSettings);
finalUrl = '$base/repo/index.xml';
res = await sourceRequest(finalUrl, additionalSettings);
if (res.statusCode != 200) {
res = await sourceRequest(
'$base/fdroid/repo/index.xml', additionalSettings);
finalUrl = '$base/fdroid/repo/index.xml';
res = await sourceRequest(finalUrl, additionalSettings);
}
}
return res;
return MapEntry(finalUrl, res);
}
@override
@ -142,10 +143,11 @@ class FDroidRepo extends AppSource {
if (appIdOrName == null) {
throw NoReleasesError();
}
var res =
var ress =
await sourceRequestWithURLVariants(standardUrl, additionalSettings);
var res = ress.value;
if (res.statusCode == 200) {
var body = parse(res.body);
var body = parse(res.data);
var foundApps = body.querySelectorAll('application').where((element) {
return element.attributes['id'] == appIdOrName;
}).toList();
@ -193,7 +195,7 @@ class FDroidRepo extends AppSource {
}
List<String> apkUrls = latestVersionReleases
.map((e) =>
'${res.request!.url.toString().split('/').reversed.toList().sublist(1).reversed.join('/')}/${e.querySelector('apkname')!.innerHtml}')
'${ress.value.toString().split('/').reversed.toList().sublist(1).reversed.join('/')}/${e.querySelector('apkname')!.innerHtml}')
.toList();
return APKDetails(latestVersion, getApkUrlsFromUrls(apkUrls),
AppNames(authorName, appName),

View File

@ -1,8 +1,8 @@
import 'dart:convert';
import 'dart:io';
import 'package:dio/dio.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:http/http.dart';
import 'package:obtainium/app_sources/html.dart';
import 'package:obtainium/components/generated_form.dart';
import 'package:obtainium/custom_errors.dart';
@ -112,12 +112,12 @@ class GitHub extends AppSource {
];
for (var path in possibleBuildGradleLocations) {
try {
var res = await sourceRequest(
'${await convertStandardUrlToAPIUrl(standardUrl, additionalSettings)}/contents/$path',
additionalSettings);
var finalUrl =
'${await convertStandardUrlToAPIUrl(standardUrl, additionalSettings)}/contents/$path';
var res = await sourceRequest(finalUrl, additionalSettings);
if (res.statusCode == 200) {
try {
var body = jsonDecode(res.body);
var body = jsonDecode(res.data);
var trimmedLines = utf8
.decode(base64
.decode(body['content'].toString().split('\n').join('')))
@ -143,7 +143,7 @@ class GitHub extends AppSource {
}
} catch (err) {
LogsProvider().add(
'Error parsing build.gradle from ${res.request!.url.toString()}: ${err.toString()}');
'Error parsing build.gradle from $finalUrl: ${err.toString()}');
}
}
} catch (err) {
@ -256,11 +256,11 @@ class GitHub extends AppSource {
}
throw getObtainiumHttpError(res);
}
latestRelease = jsonDecode(res.body);
latestRelease = jsonDecode(res.data);
}
Response res = await sourceRequest(requestUrl, additionalSettings);
if (res.statusCode == 200) {
var releases = jsonDecode(res.body) as List<dynamic>;
var releases = jsonDecode(res.data) as List<dynamic>;
if (latestRelease != null) {
var latestTag = latestRelease['tag_name'] ?? latestRelease['name'];
if (releases
@ -344,12 +344,14 @@ class GitHub extends AppSource {
});
}
if (latestRelease != null &&
(latestRelease['tag_name'] ?? latestRelease['name']) != null &&
releases.isNotEmpty &&
latestRelease !=
(releases[releases.length - 1]['tag_name'] ??
releases[0]['name'])) {
var ind = releases.indexWhere((element) =>
latestRelease == (element['tag_name'] ?? element['name']));
(latestRelease['tag_name'] ?? latestRelease['name']) ==
(element['tag_name'] ?? element['name']));
if (ind >= 0) {
releases.add(releases.removeAt(ind));
}
@ -464,7 +466,7 @@ class GitHub extends AppSource {
? int.parse(querySettings['minStarCount'])
: 0;
Map<String, List<String>> urlsWithDescriptions = {};
for (var e in (jsonDecode(res.body)[rootProp] as List<dynamic>)) {
for (var e in (jsonDecode(res.data)[rootProp] as List<dynamic>)) {
if ((e['stargazers_count'] ?? e['stars_count'] ?? 0) >= minStarCount) {
urlsWithDescriptions.addAll({
e['html_url'] as String: [
@ -498,11 +500,13 @@ class GitHub extends AppSource {
}
rateLimitErrorCheck(Response res) {
if (res.headers['x-ratelimit-remaining'] == '0') {
String? rateLimitHeader;
if (res.headers.map['x-ratelimit-remaining']?.isNotEmpty == true) {
rateLimitHeader = res.headers.map['x-ratelimit-remaining']![0];
}
if (rateLimitHeader == '0') {
throw RateLimitError(
(int.parse(res.headers['x-ratelimit-reset'] ?? '1800000000') /
60000000)
.round());
(int.parse(rateLimitHeader ?? '1800000000') / 60000000).round());
}
}
}

View File

@ -1,9 +1,8 @@
import 'dart:convert';
import 'dart:io';
import 'package:dio/dio.dart';
import 'package:flutter/material.dart';
import 'package:html/parser.dart';
import 'package:http/http.dart';
import 'package:obtainium/app_sources/github.dart';
import 'package:obtainium/custom_errors.dart';
import 'package:obtainium/providers/settings_provider.dart';
@ -73,14 +72,6 @@ class GitLab extends AppSource {
return creds != null && creds.isNotEmpty ? creds : null;
}
@override
Future<String?> getSourceNote() async {
if ((await getPATIfAny({})) == null) {
return '${tr('gitlabSourceNote')} ${hostChanged ? tr('addInfoBelow') : tr('addInfoInSettings')}';
}
return null;
}
@override
Future<Map<String, List<String>>> search(String query,
{Map<String, dynamic> querySettings = const {}}) async {
@ -90,7 +81,7 @@ class GitLab extends AppSource {
if (res.statusCode != 200) {
throw getObtainiumHttpError(res);
}
var json = jsonDecode(res.body) as List<dynamic>;
var json = jsonDecode(res.data) as List<dynamic>;
Map<String, List<String>> results = {};
for (var element in json) {
results['https://${hosts[0]}/${element['path_with_namespace']}'] = [
@ -125,100 +116,67 @@ class GitLab extends AppSource {
String standardUrl,
Map<String, dynamic> additionalSettings,
) async {
// Prepare request params
var names = GitHub().getAppNames(standardUrl);
String? PAT = await getPATIfAny(hostChanged ? additionalSettings : {});
String optionalAuth = (PAT != null) ? 'private_token=$PAT' : '';
// Request data from REST API
Response res = await sourceRequest(
'https://${hosts[0]}/api/v4/projects/${names.author}%2F${names.name}/releases?$optionalAuth',
additionalSettings);
if (res.statusCode != 200) {
throw getObtainiumHttpError(res);
}
// Extract .apk details from received data
Iterable<APKDetails> apkDetailsList = [];
var json = jsonDecode(res.data) as List<dynamic>;
apkDetailsList = json.map((e) {
var apkUrlsFromAssets = (e['assets']?['links'] as List<dynamic>? ?? [])
.map((e) {
return (e['direct_asset_url'] ?? e['url'] ?? '') as String;
})
.where((s) => s.isNotEmpty)
.toList();
List<String> uploadedAPKsFromDescription =
((e['description'] ?? '') as String)
.split('](')
.join('\n')
.split('.apk)')
.join('.apk\n')
.split('\n')
.where((s) => s.startsWith('/uploads/') && s.endsWith('apk'))
.map((s) => '$standardUrl$s')
.toList();
var apkUrlsSet = apkUrlsFromAssets.toSet();
apkUrlsSet.addAll(uploadedAPKsFromDescription);
var releaseDateString = e['released_at'] ?? e['created_at'];
DateTime? releaseDate =
releaseDateString != null ? DateTime.parse(releaseDateString) : null;
return APKDetails(
e['tag_name'] ?? e['name'],
getApkUrlsFromUrls(apkUrlsSet.toList()),
GitHub().getAppNames(standardUrl),
releaseDate: releaseDate);
});
if (apkDetailsList.isEmpty) {
throw NoReleasesError();
}
// Fallback procedure
bool fallbackToOlderReleases =
additionalSettings['fallbackToOlderReleases'] == true;
String? PAT = await getPATIfAny(hostChanged ? additionalSettings : {});
Iterable<APKDetails> apkDetailsList = [];
if (PAT != null) {
var names = GitHub().getAppNames(standardUrl);
Response res = await sourceRequest(
'https://${hosts[0]}/api/v4/projects/${names.author}%2F${names.name}/releases?private_token=$PAT',
additionalSettings);
if (res.statusCode != 200) {
throw getObtainiumHttpError(res);
}
var json = jsonDecode(res.body) as List<dynamic>;
apkDetailsList = json.map((e) {
var apkUrlsFromAssets = (e['assets']?['links'] as List<dynamic>? ?? [])
.map((e) {
return (e['direct_asset_url'] ?? e['url'] ?? '') as String;
})
.where((s) => s.isNotEmpty)
.toList();
List<String> uploadedAPKsFromDescription =
((e['description'] ?? '') as String)
.split('](')
.join('\n')
.split('.apk)')
.join('.apk\n')
.split('\n')
.where((s) => s.startsWith('/uploads/') && s.endsWith('apk'))
.map((s) => '$standardUrl$s')
.toList();
var apkUrlsSet = apkUrlsFromAssets.toSet();
apkUrlsSet.addAll(uploadedAPKsFromDescription);
var releaseDateString = e['released_at'] ?? e['created_at'];
DateTime? releaseDate = releaseDateString != null
? DateTime.parse(releaseDateString)
: null;
return APKDetails(
e['tag_name'] ?? e['name'],
getApkUrlsFromUrls(apkUrlsSet.toList()),
GitHub().getAppNames(standardUrl),
releaseDate: releaseDate);
});
} else {
Response res = await sourceRequest(
'$standardUrl/-/tags?format=atom', additionalSettings);
if (res.statusCode != 200) {
throw getObtainiumHttpError(res);
}
var standardUri = Uri.parse(standardUrl);
var parsedHtml = parse(res.body);
apkDetailsList = parsedHtml.querySelectorAll('entry').map((entry) {
var entryContent = parse(
parseFragment(entry.querySelector('content')!.innerHtml).text);
var apkUrls = [
...getLinksFromParsedHTML(
entryContent,
RegExp(
'^${standardUri.path.replaceAllMapped(RegExp(r'[.*+?^${}()|[\]\\]'), (x) {
return '\\${x[0]}';
})}/uploads/[^/]+/[^/]+\\.apk\$',
caseSensitive: false),
standardUri.origin),
// GitLab releases may contain links to externally hosted APKs
...getLinksFromParsedHTML(entryContent,
RegExp('/[^/]+\\.apk\$', caseSensitive: false), '')
.where((element) => Uri.parse(element).host != '')
];
var entryId = entry.querySelector('id')?.innerHtml;
var version =
entryId == null ? null : Uri.parse(entryId).pathSegments.last;
var releaseDateString = entry.querySelector('updated')?.innerHtml;
DateTime? releaseDate = releaseDateString != null
? DateTime.parse(releaseDateString)
: null;
if (version == null) {
throw NoVersionError();
}
return APKDetails(version, getApkUrlsFromUrls(apkUrls),
GitHub().getAppNames(standardUrl),
releaseDate: releaseDate);
});
}
if (apkDetailsList.isEmpty) {
throw NoReleasesError(note: tr('gitlabSourceNote'));
}
if (fallbackToOlderReleases) {
if (additionalSettings['trackOnly'] != true) {
apkDetailsList =
apkDetailsList.where((e) => e.apkUrls.isNotEmpty).toList();
}
if (apkDetailsList.isEmpty) {
throw NoReleasesError(note: tr('gitlabSourceNote'));
throw NoReleasesError();
}
}
return apkDetailsList.first;
}
}

View File

@ -1,6 +1,6 @@
import 'package:dio/dio.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:html/parser.dart';
import 'package:http/http.dart';
import 'package:obtainium/components/generated_form.dart';
import 'package:obtainium/custom_errors.dart';
import 'package:obtainium/providers/apps_provider.dart';
@ -213,11 +213,11 @@ class HTML extends AppSource {
// Given an HTTP response, grab some links according to the common additional settings
// (those that apply to intermediate and final steps)
Future<List<MapEntry<String, String>>> grabLinksCommon(
Response res, Map<String, dynamic> additionalSettings) async {
Response res, Uri url, Map<String, dynamic> additionalSettings) async {
if (res.statusCode != 200) {
throw getObtainiumHttpError(res);
}
var html = parse(res.body);
var html = parse(res.data);
List<MapEntry<String, String>> allLinks = html
.querySelectorAll('a')
.map((element) => MapEntry(
@ -226,13 +226,12 @@ class HTML extends AppSource {
? element.text
: (element.attributes['href'] ?? '').split('/').last))
.where((element) => element.key.isNotEmpty)
.map((e) =>
MapEntry(ensureAbsoluteUrl(e.key, res.request!.url), e.value))
.map((e) => MapEntry(ensureAbsoluteUrl(e.key, url), e.value))
.toList();
if (allLinks.isEmpty) {
allLinks = RegExp(
r'(http|ftp|https)://([\w_-]+(?:(?:\.[\w_-]+)+))([\w.,@?^=%&:/~+#-]*[\w@?^=%&/~+#-])?')
.allMatches(res.body)
.allMatches(res.data)
.map((match) =>
MapEntry(match.group(0)!, match.group(0)?.split('/').last ?? ''))
.toList();
@ -285,6 +284,7 @@ class HTML extends AppSource {
for (int i = 0; i < (additionalSettings['intermediateLink'].length); i++) {
var intLinks = await grabLinksCommon(
await sourceRequest(currentUrl, additionalSettings),
Uri.parse(currentUrl),
additionalSettings['intermediateLink'][i]);
if (intLinks.isEmpty) {
throw NoReleasesError();
@ -298,8 +298,9 @@ class HTML extends AppSource {
if (additionalSettings['directAPKLink'] != true) {
Response res = await sourceRequest(currentUrl, additionalSettings);
versionExtractionWholePageString =
res.body.split('\r\n').join('\n').split('\n').join('\\n');
links = await grabLinksCommon(res, additionalSettings);
res.data.split('\r\n').join('\n').split('\n').join('\\n');
links =
await grabLinksCommon(res, Uri.parse(currentUrl), additionalSettings);
links = filterApks(links, additionalSettings['apkFilterRegEx'],
additionalSettings['invertAPKFilter']);
if (links.isEmpty) {

View File

@ -1,5 +1,5 @@
import 'package:dio/dio.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:http/http.dart';
import 'package:obtainium/custom_errors.dart';
import 'package:obtainium/providers/source_provider.dart';
@ -57,8 +57,8 @@ class HuaweiAppGallery extends AppSource {
{Map<String, dynamic> additionalSettings = const {}}) async {
String dlUrl = getDlUrl(standardUrl);
Response res = await requestAppdlRedirect(dlUrl, additionalSettings);
return res.headers['location'] != null
? appIdFromRedirectDlUrl(res.headers['location']!)
return res.headers.map['location']?.isNotEmpty == true
? appIdFromRedirectDlUrl(res.headers.map['location']![0])
: null;
}
@ -72,9 +72,12 @@ class HuaweiAppGallery extends AppSource {
if (res.headers['location'] == null) {
throw NoReleasesError();
}
String appId = appIdFromRedirectDlUrl(res.headers['location']!);
var relDateStr =
res.headers['location']?.split('?')[0].split('.').reversed.toList()[1];
String appId = appIdFromRedirectDlUrl(res.headers.map['location']![0]);
var relDateStr = res.headers.map['location']?[0]
.split('?')[0]
.split('.')
.reversed
.toList()[1];
var relDateStrAdj = relDateStr?.split('');
var tempLen = relDateStrAdj?.length ?? 0;
var i = 2;

View File

@ -1,6 +1,6 @@
import 'dart:convert';
import 'package:http/http.dart';
import 'package:dio/dio.dart';
import 'package:obtainium/custom_errors.dart';
import 'package:obtainium/providers/source_provider.dart';
@ -33,7 +33,7 @@ class Jenkins extends AppSource {
Response res = await sourceRequest(
'$standardUrl/lastSuccessfulBuild/api/json', additionalSettings);
if (res.statusCode == 200) {
var json = jsonDecode(res.body);
var json = jsonDecode(res.data);
var releaseDate = json['timestamp'] == null
? null
: DateTime.fromMillisecondsSinceEpoch(json['timestamp'] as int);

View File

@ -1,5 +1,5 @@
import 'package:dio/dio.dart';
import 'package:html/parser.dart';
import 'package:http/http.dart';
import 'package:obtainium/app_sources/github.dart';
import 'package:obtainium/custom_errors.dart';
import 'package:obtainium/providers/source_provider.dart';
@ -33,7 +33,7 @@ class Mullvad extends AppSource {
Response res = await sourceRequest(
'$standardUrl/en/download/android', additionalSettings);
if (res.statusCode == 200) {
var versions = parse(res.body)
var versions = parse(res.data)
.querySelectorAll('p')
.map((e) => e.innerHtml)
.where((p) => p.contains('Latest version: '))

View File

@ -1,5 +1,5 @@
import 'package:dio/dio.dart';
import 'package:html/parser.dart';
import 'package:http/http.dart';
import 'package:obtainium/custom_errors.dart';
import 'package:obtainium/providers/source_provider.dart';
@ -83,7 +83,7 @@ class NeutronCode extends AppSource {
) async {
Response res = await sourceRequest(standardUrl, additionalSettings);
if (res.statusCode == 200) {
var http = parse(res.body);
var http = parse(res.data);
var name = http.querySelector('.pd-title')?.innerHtml;
var filename = http.querySelector('.pd-filename .pd-float')?.innerHtml;
if (filename == null) {

View File

@ -1,5 +1,5 @@
import 'dart:convert';
import 'package:http/http.dart';
import 'package:dio/dio.dart';
import 'package:obtainium/custom_errors.dart';
import 'package:obtainium/providers/source_provider.dart';
@ -21,7 +21,7 @@ class Signal extends AppSource {
Response res = await sourceRequest(
'https://updates.${hosts[0]}/android/latest.json', additionalSettings);
if (res.statusCode == 200) {
var json = jsonDecode(res.body);
var json = jsonDecode(res.data);
String? apkUrl = json['url'];
List<String> apkUrls = apkUrl == null ? [] : [apkUrl];
String? version = json['versionName'];

View File

@ -1,5 +1,5 @@
import 'package:dio/dio.dart';
import 'package:html/parser.dart';
import 'package:http/http.dart';
import 'package:obtainium/custom_errors.dart';
import 'package:obtainium/providers/source_provider.dart';
@ -49,7 +49,7 @@ class SourceForge extends AppSource {
'${standardUri.origin}/${standardUri.pathSegments.sublist(0, 2).join('/')}/rss?path=/',
additionalSettings);
if (res.statusCode == 200) {
var parsedHtml = parse(res.body);
var parsedHtml = parse(res.data);
var allDownloadLinks = parsedHtml
.querySelectorAll('guid')
.map((e) => e.innerHtml)

View File

@ -1,5 +1,5 @@
import 'package:dio/dio.dart';
import 'package:html/parser.dart';
import 'package:http/http.dart';
import 'package:obtainium/app_sources/html.dart';
import 'package:obtainium/custom_errors.dart';
import 'package:obtainium/providers/source_provider.dart';
@ -55,7 +55,7 @@ class SourceHut extends AppSource {
Response res =
await sourceRequest('$standardUrl/refs/rss.xml', additionalSettings);
if (res.statusCode == 200) {
var parsedHtml = parse(res.body);
var parsedHtml = parse(res.data);
List<APKDetails> apkDetailsList = [];
int ind = 0;
@ -85,7 +85,7 @@ class SourceHut extends AppSource {
var res2 = await sourceRequest(releasePage, additionalSettings);
List<MapEntry<String, String>> apkUrls = [];
if (res2.statusCode == 200) {
apkUrls = getApkUrlsFromUrls(parse(res2.body)
apkUrls = getApkUrlsFromUrls(parse(res2.data)
.querySelectorAll('a')
.map((e) => e.attributes['href'] ?? '')
.where((e) => e.toLowerCase().endsWith('.apk'))

View File

@ -1,6 +1,6 @@
import 'package:dio/dio.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:html/parser.dart';
import 'package:http/http.dart';
import 'package:obtainium/components/generated_form.dart';
import 'package:obtainium/custom_errors.dart';
import 'package:obtainium/providers/source_provider.dart';
@ -38,7 +38,7 @@ class SteamMobile extends AppSource {
}
String apkInURLRegexPattern =
'/$apkNamePrefix-([0-9]+\\.)*[0-9]+\\.apk\$';
var links = parse(res.body)
var links = parse(res.data)
.querySelectorAll('a')
.map((e) => e.attributes['href'] ?? '')
.where((e) => RegExp('https://.*$apkInURLRegexPattern').hasMatch(e))

View File

@ -1,6 +1,6 @@
import 'package:dio/dio.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:html/parser.dart';
import 'package:http/http.dart';
import 'package:obtainium/custom_errors.dart';
import 'package:obtainium/providers/source_provider.dart';
@ -23,7 +23,7 @@ class TelegramApp extends AppSource {
Response res =
await sourceRequest('https://t.me/s/TAndroidAPK', additionalSettings);
if (res.statusCode == 200) {
var http = parse(res.body);
var http = parse(res.data);
var messages =
http.querySelectorAll('.tgme_widget_message_text.js-message_text');
var version = messages.isNotEmpty

View File

@ -37,7 +37,7 @@ class Uptodown extends AppSource {
if (res.statusCode != 200) {
throw getObtainiumHttpError(res);
}
var html = parse(res.body);
var html = parse(res.data);
String? version = html.querySelector('div.version')?.innerHtml;
String? apkUrl =
'${standardUrl.split('/').reversed.toList().sublist(1).reversed.join('/')}/post-download';
@ -94,7 +94,7 @@ class Uptodown extends AppSource {
if (res.statusCode != 200) {
throw getObtainiumHttpError(res);
}
var html = parse(res.body);
var html = parse(res.data);
var finalUrlKey =
html.querySelector('.post-download')?.attributes['data-url'];
if (finalUrlKey == null) {

View File

@ -1,7 +1,8 @@
import 'package:dio/dio.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:html/parser.dart';
import 'package:http/http.dart';
import 'package:obtainium/custom_errors.dart';
import 'package:obtainium/main.dart';
import 'package:obtainium/providers/source_provider.dart';
class VLC extends AppSource {
@ -29,7 +30,7 @@ class VLC extends AppSource {
String standardUrl, Map<String, dynamic> additionalSettings) async {
Response res = await sourceRequest(dwUrlBase, additionalSettings);
if (res.statusCode == 200) {
var dwLinks = parse(res.body)
var dwLinks = parse(res.data)
.querySelectorAll('a')
.where((element) => element.attributes['href'] != 'last/')
.map((e) => e.attributes['href']?.split('/')[0])
@ -49,11 +50,11 @@ class VLC extends AppSource {
String standardUrl,
Map<String, dynamic> additionalSettings,
) async {
Response res = await get(
Uri.parse('https://www.videolan.org/vlc/download-android.html'));
Response res =
await dio.get('https://www.videolan.org/vlc/download-android.html');
if (res.statusCode == 200) {
var dwUrlBase = 'get.videolan.org/vlc-android';
var dwLinks = parse(res.body)
var dwLinks = parse(res.data)
.querySelectorAll('a')
.where((element) =>
element.attributes['href']?.contains(dwUrlBase) ?? false)
@ -84,14 +85,14 @@ class VLC extends AppSource {
Response res = await sourceRequest(apkUrl, additionalSettings);
if (res.statusCode == 200) {
String? apkUrl =
parse(res.body).querySelector('#alt_link')?.attributes['href'];
parse(res.data).querySelector('#alt_link')?.attributes['href'];
if (apkUrl == null) {
throw NoAPKError();
}
return apkUrl;
} else if (res.statusCode == 500 &&
res.body.toLowerCase().indexOf('mirror') > 0) {
var html = parse(res.body);
res.data.toLowerCase().indexOf('mirror') > 0) {
var html = parse(res.data);
var err = '';
html.body?.nodes.forEach((element) {
if (element.text != null) {

View File

@ -1,5 +1,5 @@
import 'package:dio/dio.dart';
import 'package:html/parser.dart';
import 'package:http/http.dart';
import 'package:obtainium/custom_errors.dart';
import 'package:obtainium/providers/source_provider.dart';
@ -20,7 +20,7 @@ class WhatsApp extends AppSource {
Response res =
await sourceRequest('$standardUrl/android', additionalSettings);
if (res.statusCode == 200) {
var targetLinks = parse(res.body)
var targetLinks = parse(res.data)
.querySelectorAll('a')
.map((e) => e.attributes['href'] ?? '')
.where((e) => e.isNotEmpty)

View File

@ -18,6 +18,7 @@ import 'package:easy_localization/easy_localization.dart';
import 'package:easy_localization/src/easy_localization_controller.dart';
// ignore: implementation_imports
import 'package:easy_localization/src/localization.dart';
import 'package:dio/dio.dart';
List<MapEntry<Locale, String>> supportedLocales = const [
MapEntry(Locale('en'), 'English'),
@ -38,6 +39,7 @@ List<MapEntry<Locale, String>> supportedLocales = const [
MapEntry(Locale('nl'), 'Nederlands'),
MapEntry(Locale('vi'), 'Tiếng Việt'),
MapEntry(Locale('tr'), 'Türkçe'),
MapEntry(Locale('uk'), 'Українська'),
];
const fallbackLocale = Locale('en');
const localeDir = 'assets/translations';
@ -45,6 +47,9 @@ var fdroid = false;
final globalNavigatorKey = GlobalKey<NavigatorState>();
final dio = Dio(BaseOptions(
responseType: ResponseType.plain, receiveDataWhenStatusError: true));
Future<void> loadTranslations() async {
// See easy_localization/issues/210
await EasyLocalizationController.initEasyLocation();

View File

@ -1,9 +1,10 @@
import 'dart:convert';
import 'package:dio/dio.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:http/http.dart';
import 'package:obtainium/app_sources/github.dart';
import 'package:obtainium/custom_errors.dart';
import 'package:obtainium/main.dart';
import 'package:obtainium/providers/source_provider.dart';
class GitHubStars implements MassAppUrlSource {
@ -15,13 +16,12 @@ class GitHubStars implements MassAppUrlSource {
Future<Map<String, List<String>>> getOnePageOfUserStarredUrlsWithDescriptions(
String username, int page) async {
Response res = await get(
Uri.parse(
'https://api.github.com/users/$username/starred?per_page=100&page=$page'),
headers: await GitHub().getRequestHeaders({}));
Response res = await dio.get(
'https://api.github.com/users/$username/starred?per_page=100&page=$page',
options: Options(headers: await GitHub().getRequestHeaders({})));
if (res.statusCode == 200) {
Map<String, List<String>> urlsWithDescriptions = {};
for (var e in (jsonDecode(res.body) as List<dynamic>)) {
for (var e in (jsonDecode(res.data) as List<dynamic>)) {
urlsWithDescriptions.addAll({
e['html_url'] as String: [
e['full_name'] as String,

View File

@ -5,14 +5,15 @@ import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'dart:math';
import 'package:http/http.dart' as http;
import 'package:crypto/crypto.dart';
import 'package:http/http.dart' as http;
import 'package:android_intent_plus/flag.dart';
import 'package:android_package_installer/android_package_installer.dart';
import 'package:android_package_manager/android_package_manager.dart';
import 'package:connectivity_plus/connectivity_plus.dart';
import 'package:device_info_plus/device_info_plus.dart';
import 'package:dio/dio.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
@ -28,7 +29,6 @@ import 'package:provider/provider.dart';
import 'package:path_provider/path_provider.dart';
import 'package:flutter_fgbg/flutter_fgbg.dart';
import 'package:obtainium/providers/source_provider.dart';
import 'package:http/http.dart';
import 'package:android_intent_plus/android_intent.dart';
import 'package:flutter_archive/flutter_archive.dart';
import 'package:shared_storage/shared_storage.dart' as saf;
@ -146,10 +146,10 @@ Future<File> downloadFileWithRetry(
Map<String, String>? headers,
int retries = 3}) async {
try {
return await downloadFile(url, fileNameNoExt, onProgress, destDir,
return await downloadApk(url, fileNameNoExt, onProgress, destDir,
useExisting: useExisting, headers: headers);
} catch (e) {
if (retries > 0 && e is ClientException) {
if (retries > 0 && e is DioException) {
await Future.delayed(const Duration(seconds: 5));
return await downloadFileWithRetry(
url, fileNameNoExt, onProgress, destDir,
@ -183,9 +183,162 @@ Future<String> checkPartialDownloadHashDynamic(String url,
throw NoVersionError();
}
Future<File> downloadApk(
String url, String fileNameNoExt, Function? onProgress, String destDir,
{bool useExisting = true, Map<String, String>? headers}) async {
var resHeaders = await getHeaders(url, headers: headers);
String ext = resHeaders['content-disposition']?.split('.').last ?? 'apk';
if (ext.endsWith('"') || ext.endsWith("other")) {
ext = ext.substring(0, ext.length - 1);
}
if (url.toLowerCase().endsWith('.apk') && ext != 'apk') {
ext = 'apk';
}
File file = File('$destDir/$fileNameNoExt.$ext');
final contentLength = await getContentLengthIfRangeSupported(resHeaders);
if (useExisting && file.existsSync()) {
var length = file.lengthSync();
if (contentLength == null) {
return file;
} else {
if (length == contentLength) {
return file;
}
if (length > contentLength) {
useExisting = false;
}
}
}
double progress = -1;
try {
if (contentLength == null) {
Response response = await dio.download(
url,
file.path,
options: Options(headers: headers),
onReceiveProgress: (count, total) {
progress = (total > 0 ? count / total * 100 : 30);
if (onProgress != null) {
onProgress(progress);
}
},
);
if ((response.statusCode ?? 200) < 200 ||
(response.statusCode ?? 200) > 299) {
throw response.statusMessage ?? tr('unexpectedError');
}
} else {
var targetFileLength =
useExisting && file.existsSync() ? file.lengthSync() : null;
int bufferSize = 1024 * 1024; // 1 Megabyte
final sink = file.openWrite(
mode: useExisting ? FileMode.writeOnlyAppend : FileMode.writeOnly,
);
int rangeStart = targetFileLength ?? 0;
int rangeEnd = min(
rangeStart + bufferSize - 1,
contentLength - 1,
);
if (onProgress != null) {
progress = ((rangeStart / contentLength) * 100);
onProgress(progress);
}
while (true) {
var headersCurrent = headers ?? {};
headersCurrent['range'] = 'bytes=$rangeStart-$rangeEnd';
Response response = await dio.get(
url,
onReceiveProgress: (count, total) {
if (onProgress != null) {
final newProgress =
(((rangeStart + count) / contentLength) * 100);
if (newProgress != progress) {
progress = newProgress;
onProgress(progress);
}
}
},
options: Options(
headers: headersCurrent,
responseType: ResponseType.bytes,
),
);
if ((response.statusCode ?? 200) < 200 ||
(response.statusCode ?? 200) > 299) {
throw response.statusMessage ?? tr('unexpectedError');
}
final Uint8List data = response.data;
sink.add(data);
if (rangeEnd == contentLength - 1) {
break;
}
rangeStart = rangeEnd + 1;
rangeEnd = min(
rangeStart + bufferSize - 1,
contentLength - 1,
);
}
await sink.flush();
await sink.close();
}
} finally {
if (onProgress != null) {
onProgress(null);
}
}
return file;
}
Future<int?> getContentLengthIfRangeSupported(
Map<String, String> headers) async {
try {
int? contentLength;
{
var contentLengthHeaderValue = headers['content-length'];
if (contentLengthHeaderValue?.isNotEmpty == true) {
contentLength = int.tryParse(contentLengthHeaderValue!);
}
}
bool rangeFeatureEnabled = false;
if (headers['accept-ranges']?.isNotEmpty == true) {
rangeFeatureEnabled =
headers['accept-ranges']!.trim().toLowerCase() == 'bytes';
}
if (!rangeFeatureEnabled) {
contentLength = null;
}
return contentLength;
} catch (e) {
return null;
}
}
Future<Map<String, String>> getHeaders(String url,
{Map<String, String>? headers}) async {
var req = http.Request('GET', Uri.parse(url));
if (headers != null) {
req.headers.addAll(headers);
}
var client = http.Client();
var response = await client.send(req);
if (response.statusCode < 200 || response.statusCode > 299) {
throw ObtainiumError(response.reasonPhrase ?? tr('unexpectedError'));
}
var returnHeaders = response.headers;
client.close();
return returnHeaders;
}
Future<String> checkPartialDownloadHash(String url, int bytesToGrab,
{Map<String, String>? headers}) async {
var req = Request('GET', Uri.parse(url));
var req = http.Request('GET', Uri.parse(url));
if (headers != null) {
req.headers.addAll(headers);
}
@ -199,58 +352,41 @@ Future<String> checkPartialDownloadHash(String url, int bytesToGrab,
return hashListOfLists(bytes);
}
Future<File> downloadFile(
String url, String fileNameNoExt, Function? onProgress, String destDir,
{bool useExisting = true, Map<String, String>? headers}) async {
var req = Request('GET', Uri.parse(url));
if (headers != null) {
req.headers.addAll(headers);
}
var client = http.Client();
StreamedResponse response = await client.send(req);
String ext =
response.headers['content-disposition']?.split('.').last ?? 'apk';
if (ext.endsWith('"') || ext.endsWith("other")) {
ext = ext.substring(0, ext.length - 1);
}
if (url.toLowerCase().endsWith('.apk') && ext != 'apk') {
ext = 'apk';
}
File downloadedFile = File('$destDir/$fileNameNoExt.$ext');
if (!(downloadedFile.existsSync() && useExisting)) {
File tempDownloadedFile = File('${downloadedFile.path}.part');
if (tempDownloadedFile.existsSync()) {
tempDownloadedFile.deleteSync(recursive: true);
}
var length = response.contentLength;
var received = 0;
double? progress;
var sink = tempDownloadedFile.openWrite();
await response.stream.map((s) {
received += s.length;
progress = (length != null ? received / length * 100 : 30);
if (onProgress != null) {
onProgress(progress);
}
return s;
}).pipe(sink);
await sink.close();
progress = null;
if (onProgress != null) {
onProgress(progress);
}
if (response.statusCode != 200) {
tempDownloadedFile.deleteSync(recursive: true);
throw response.reasonPhrase ?? tr('unexpectedError');
}
if (tempDownloadedFile.existsSync()) {
tempDownloadedFile.renameSync(downloadedFile.path);
}
} else {
client.close();
}
return downloadedFile;
}
// Future<File> downloadFile(
// String url, String fileNameNoExt, Function? onProgress, String destDir,
// {bool useExisting = true, Map<String, String>? headers}) async {
// var resHead = await dio.head(url);
// String ext =
// resHead.headers.map['content-disposition']?[0].split('.').last ?? 'apk';
// if (ext.endsWith('"') || ext.endsWith("other")) {
// ext = ext.substring(0, ext.length - 1);
// }
// if (url.toLowerCase().endsWith('.apk') && ext != 'apk') {
// ext = 'apk';
// }
// File downloadedFile = File('$destDir/$fileNameNoExt.$ext');
// if (!(downloadedFile.existsSync() && useExisting)) {
// double? progress;
// var response = await dio.download(
// url,
// downloadedFile.path,
// options: Options(headers: headers),
// onReceiveProgress: (count, total) {
// progress = (total > 0 ? count / total * 100 : 30);
// if (onProgress != null) {
// onProgress(progress);
// }
// },
// );
// if (onProgress != null) {
// onProgress(null);
// }
// if (response.statusCode != 200) {
// throw response.statusMessage ?? tr('unexpectedError');
// }
// }
// return downloadedFile;
// }
Future<PackageInfo?> getInstalledInfo(String? packageName,
{bool printErr = true}) async {
@ -1621,7 +1757,7 @@ Future<void> bgUpdateCheck(String taskId, Map<String, dynamic>? params) async {
// Next task interval is based on the error with the longest retry time
int minRetryIntervalForThisApp = err is RateLimitError
? (err.remainingMinutes * 60)
: e is ClientException
: e is DioException
? (15 * 60)
: (toCheckApp.value + 1);
if (minRetryIntervalForThisApp > maxRetryWaitSeconds) {

View File

@ -4,9 +4,9 @@
import 'dart:convert';
import 'package:device_info_plus/device_info_plus.dart';
import 'package:dio/dio.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:html/dom.dart';
import 'package:http/http.dart';
import 'package:obtainium/app_sources/apkmirror.dart';
import 'package:obtainium/app_sources/apkpure.dart';
import 'package:obtainium/app_sources/aptoide.dart';
@ -31,6 +31,7 @@ import 'package:obtainium/app_sources/vlc.dart';
import 'package:obtainium/app_sources/whatsapp.dart';
import 'package:obtainium/components/generated_form.dart';
import 'package:obtainium/custom_errors.dart';
import 'package:obtainium/main.dart';
import 'package:obtainium/mass_app_sources/githubstars.dart';
import 'package:obtainium/providers/settings_provider.dart';
@ -442,16 +443,9 @@ abstract class AppSource {
String url, Map<String, dynamic> additionalSettings,
{bool followRedirects = true}) async {
var requestHeaders = await getRequestHeaders(additionalSettings);
if (requestHeaders != null || followRedirects == false) {
var req = Request('GET', Uri.parse(url));
req.followRedirects = followRedirects;
if (requestHeaders != null) {
req.headers.addAll(requestHeaders);
}
return Response.fromStream(await Client().send(req));
} else {
return get(Uri.parse(url));
}
return await dio.get(url,
options:
Options(headers: requestHeaders, followRedirects: followRedirects));
}
String sourceSpecificStandardizeURL(String url) {
@ -618,10 +612,10 @@ abstract class AppSource {
}
ObtainiumError getObtainiumHttpError(Response res) {
return ObtainiumError((res.reasonPhrase != null &&
res.reasonPhrase != null &&
res.reasonPhrase!.isNotEmpty)
? res.reasonPhrase!
return ObtainiumError((res.statusMessage != null &&
res.statusMessage != null &&
res.statusMessage!.isNotEmpty)
? res.statusMessage!
: tr('errorWithHttpStatusCode', args: [res.statusCode.toString()]));
}

View File

@ -38,10 +38,10 @@ packages:
dependency: "direct main"
description:
name: app_links
sha256: "4e392b5eba997df356ca6021f28431ce1cfeb16758699553a94b13add874a3bb"
sha256: "3ced568a5d9e309e99af71285666f1f3117bddd0bd5b3317979dccc1a40cada4"
url: "https://pub.dev"
source: hosted
version: "3.5.0"
version: "3.5.1"
archive:
dependency: transitive
description:
@ -70,10 +70,10 @@ packages:
dependency: "direct main"
description:
name: background_fetch
sha256: "1a7868d9bd165eb177f039ff8244cfa7952340b18f7caabf322b26e712b438a3"
sha256: eb3af263d390d7e68ecb90f2ae984d2bfd96dceb4c7b4f72418dd5383b49de0a
url: "https://pub.dev"
source: hosted
version: "1.2.3"
version: "1.2.4"
boolean_selector:
dependency: transitive
description:
@ -202,6 +202,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "7.0.0"
dio:
dependency: "direct main"
description:
name: dio
sha256: "49af28382aefc53562459104f64d16b9dfd1e8ef68c862d5af436cc8356ce5a8"
url: "https://pub.dev"
source: hosted
version: "5.4.1"
dynamic_color:
dependency: "direct main"
description:
@ -254,10 +262,10 @@ packages:
dependency: "direct main"
description:
name: file_picker
sha256: "4e42aacde3b993c5947467ab640882c56947d9d27342a5b6f2895b23956954a6"
sha256: caa6bc229eab3e32eb2f37b53a5f9d22a6981474afd210c512a7546c1e1a04f6
url: "https://pub.dev"
source: hosted
version: "6.1.1"
version: "6.2.0"
fixnum:
dependency: transitive
description:
@ -307,10 +315,10 @@ packages:
dependency: "direct main"
description:
name: flutter_local_notifications
sha256: c18f1de98fe0bb9dd5ba91e1330d4febc8b6a7de6aae3ffe475ef423723e72f3
sha256: "55b9b229307a10974b26296ff29f2e132256ba4bd74266939118eaefa941cb00"
url: "https://pub.dev"
source: hosted
version: "16.3.2"
version: "16.3.3"
flutter_local_notifications_linux:
dependency: transitive
description:
@ -336,10 +344,10 @@ packages:
dependency: "direct main"
description:
name: flutter_markdown
sha256: a64c5323ac83ed2b7940d2b6288d160aa1753ff271ba9d9b2a86770414aa3eab
sha256: cb44f7831b23a6bdd0f501718b0d2e8045cbc625a15f668af37ddb80314821db
url: "https://pub.dev"
source: hosted
version: "0.6.20+1"
version: "0.6.21"
flutter_plugin_android_lifecycle:
dependency: transitive
description:
@ -474,10 +482,10 @@ packages:
dependency: transitive
description:
name: markdown
sha256: "1b134d9f8ff2da15cb298efe6cd8b7d2a78958c1b00384ebcbdf13fe340a6c90"
sha256: ef2a1298144e3f985cc736b22e0ccdaf188b5b3970648f2d9dc13efd1d9df051
url: "https://pub.dev"
source: hosted
version: "7.2.1"
version: "7.2.2"
matcher:
dependency: transitive
description:
@ -943,10 +951,10 @@ packages:
dependency: transitive
description:
name: web
sha256: "1d9158c616048c38f712a6646e317a3426da10e884447626167240d45209cbad"
sha256: "97da13628db363c635202ad97068d47c5b8aa555808e7a9411963c533b449b27"
url: "https://pub.dev"
source: hosted
version: "0.5.0"
version: "0.5.1"
webview_flutter:
dependency: "direct main"
description:
@ -983,10 +991,10 @@ packages:
dependency: transitive
description:
name: win32
sha256: "464f5674532865248444b4c3daca12bd9bf2d7c47f759ce2617986e7229494a8"
sha256: "8cb58b45c47dcb42ab3651533626161d6b67a2921917d8d429791f76972b3480"
url: "https://pub.dev"
source: hosted
version: "5.2.0"
version: "5.3.0"
win32_registry:
dependency: transitive
description:

View File

@ -17,7 +17,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
# 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: 1.0.4+2254 # When changing this, update the tag in main() accordingly
version: 1.0.5+2255 # When changing this, update the tag in main() accordingly
environment:
sdk: '>=3.0.0 <4.0.0'
@ -68,6 +68,7 @@ dependencies:
crypto: ^3.0.3
app_links: ^3.5.0
background_fetch: ^1.2.1
dio: ^5.4.1
dev_dependencies:
flutter_test: