Compare commits

...

37 Commits

Author SHA1 Message Date
5e869b7e03 Update release.yml 2024-01-11 21:58:48 -05:00
34fee36132 Merge pull request #1277 from ImranR98/dev
- Fix APKPure downloads (#1250)
- F-Droid bugfix (#1264)
- Allow versionCode to be used as the version for comparison (#1269)
- Add custom header support to HTML (#709, #1272)
- Disabled Mullvad source (#1276)
- Bugfixes: Don't make URLs lowercase, never auto-select Jenkins
2024-01-11 21:44:36 -05:00
ea09bc36a5 Merge branch 'main' into dev 2024-01-11 21:44:24 -05:00
f7e0678cb2 Remove unused variable 2024-01-11 21:39:41 -05:00
223ae378a9 Update packages, increment version 2024-01-11 21:39:26 -05:00
6f52a48991 Merge remote-tracking branch 'origin/main' into dev 2024-01-11 21:36:15 -05:00
0280935955 Merge pull request #1268 from bluefly000/japanese-translation
Update ja.json
2024-01-11 21:36:03 -05:00
eebc7d9ab3 Merge pull request #1271 from pmtpro/main
update vi.json
2024-01-11 21:35:53 -05:00
aa7b5652ff Fix APKPure downloads (#1250) 2024-01-11 21:34:46 -05:00
ec6683a198 Bugfixes: Don't make URLs lowercase, never auto-select Jenkins 2024-01-11 21:28:15 -05:00
05372924a0 F-Droid bugfix (#1264) 2024-01-11 21:16:25 -05:00
e63c1399dd Allow versionCode to be used as the version for comparison (#1269) 2024-01-11 20:10:20 -05:00
4fcad92e1d Disabled Mullvad source (#1276) 2024-01-11 19:50:51 -05:00
e18e7298bc GeneratedFormSubForm UI improvements 2024-01-11 19:43:17 -05:00
76a6a509cd Header usage bugfix 2024-01-11 19:23:59 -05:00
7a03561ff6 Add custom header support to HTML (#709, #1272) 2024-01-11 19:01:17 -05:00
70e54ce14a F-Droid small fix 2024-01-10 18:51:22 -05:00
b5f86f0e79 update vi.json 2024-01-10 10:20:59 +07:00
4ebca49ef7 Update ja.json 2024-01-09 22:26:11 +09:00
49cfa95463 Merge pull request #1265 from ImranR98/dev
- Multi-host support + add '.net' host to APKPure source (#1250)
- HTML link parsing bugfix (#1259)
- APKMirror version extraction bugfix (#1264)
2024-01-08 19:28:31 -05:00
4600ab0593 Increment version 2024-01-08 19:20:08 -05:00
7f2ca98bde Multi-host support + add '.net' host to APKPure source (#1250) 2024-01-08 19:17:50 -05:00
6511485bcf APKMirror version extraction bugfix (#1264) 2024-01-08 18:33:24 -05:00
fd22113e44 HTML link parsing bugfix (#1259) 2024-01-08 18:18:57 -05:00
e8580dc1d5 Merge pull request #1257 from ImranR98/dev
Add F-Droid changelogs (#1255), Fix corrupt pt.json (#1256)
2024-01-07 21:59:17 -05:00
daffff7eb0 Add F-Droid changelogs (#1255) 2024-01-07 21:57:23 -05:00
751fda5e37 Increment version 2024-01-07 21:45:41 -05:00
1e38abc500 Fix corrupt pt.json (#1256) 2024-01-07 21:40:56 -05:00
8e7137815b Merge pull request #1254 from ImranR98/dev
URL parsing bugfix for HTML source (#1253)
2024-01-07 12:37:51 -05:00
4226ee453f Merge remote-tracking branch 'origin/main' into dev 2024-01-07 12:37:14 -05:00
568a110443 Merge pull request #1249 from LilligantMatsuri/main
Update Chinese translation
2024-01-07 12:37:00 -05:00
7fd7a6ca8d Merge pull request #1252 from jont4/main
Translation: many fixes in PT translation
2024-01-07 12:36:47 -05:00
3092c854ff Increment version 2024-01-07 12:36:20 -05:00
a9566f4b23 Remove redundant function call 2024-01-07 12:34:09 -05:00
7a5aa3c11d URL parsing bugfix for HTML source (#1253) 2024-01-07 12:33:06 -05:00
f2454bb028 Translation: many fixes in PT translation 2024-01-07 12:28:24 -03:00
2c1023a6fd Update zh.json
- Add & translate new strings
- Correct inaccurate translations

Signed-off-by: Matsuri <matsuri@vmoe.info>
2024-01-07 16:55:00 +08:00
53 changed files with 654 additions and 471 deletions

View File

@ -37,7 +37,7 @@ jobs:
for apk in ./build/app/outputs/flutter-apk/*-release*.apk; do
unsignedFn=${apk/-release/-unsigned}
mv "$apk" "$unsignedFn"
${ANDROID_HOME}/build-tools/30.0.2/apksigner sign --ks apksign.keystore --ks-pass pass:"${KEYSTORE_PASSWORD}" --out "${apk}" "${unsignedFn}"
${ANDROID_HOME}/build-tools/$(ls ${ANDROID_HOME}/build-tools/ | tail -1)/apksigner sign --ks apksign.keystore --ks-pass pass:"${KEYSTORE_PASSWORD}" --out "${apk}" "${unsignedFn}"
sha256sum ${apk} | cut -d " " -f 1 > "$apk".sha256
gpg --batch --pinentry-mode loopback --passphrase "${PGP_PASSPHRASE}" --sign --detach-sig "$apk".sha256
done

View File

@ -21,7 +21,7 @@ Currently supported App sources:
- [SourceForge](https://sourceforge.net/)
- [SourceHut](https://git.sr.ht/)
- Other - General:
- [APKPure](https://apkpure.com/)
- [APKPure](https://apkpure.net/)
- [Aptoide](https://aptoide.com/)
- [Uptodown](https://uptodown.com/)
- [APKMirror](https://apkmirror.com/) (Track-Only)

View File

@ -287,6 +287,8 @@
"shizuku": "Shizuku",
"root": "Root",
"shizukuBinderNotFound": "Shizuku is not running",
"useVersionCodeAsOSVersion": "Use app versionCode as OS-detected version",
"requestHeader": "Request header",
"removeAppQuestion": {
"one": "Želite li ukloniti aplikaciju?",
"other": "Želite li ukloniti aplikacije?"

View File

@ -287,6 +287,8 @@
"shizuku": "Shizuku",
"root": "Správce",
"shizukuBinderNotFound": "Shizuku neběží",
"useVersionCodeAsOSVersion": "Use app versionCode as OS-detected version",
"requestHeader": "Request header",
"removeAppQuestion": {
"one": "Odstranit Apku?",
"other": "Odstranit Apky?"

View File

@ -287,6 +287,8 @@
"shizuku": "Shizuku",
"root": "Root",
"shizukuBinderNotFound": "Shizuku läuft nicht",
"useVersionCodeAsOSVersion": "Use app versionCode as OS-detected version",
"requestHeader": "Request header",
"removeAppQuestion": {
"one": "App entfernen?",
"other": "Apps entfernen?"

View File

@ -289,6 +289,8 @@
"shizukuBinderNotFound": "Сompatible Shizuku service wasn't found",
"useSystemFont": "Use the system font",
"systemFontError": "Error loading the system font: {}",
"useVersionCodeAsOSVersion": "Use app versionCode as OS-detected version",
"requestHeader": "Request header",
"removeAppQuestion": {
"one": "Remove App?",
"other": "Remove Apps?"

View File

@ -287,6 +287,8 @@
"shizuku": "Shizuku",
"root": "Root",
"shizukuBinderNotFound": "Shizuku no está operativo",
"useVersionCodeAsOSVersion": "Use app versionCode as OS-detected version",
"requestHeader": "Request header",
"removeAppQuestion": {
"one": "¿Eliminar Aplicación?",
"other": "¿Eliminar Aplicaciones?"

View File

@ -287,6 +287,8 @@
"shizuku": "Shizuku",
"root": "Root",
"shizukuBinderNotFound": "Shizuku is not running",
"useVersionCodeAsOSVersion": "Use app versionCode as OS-detected version",
"requestHeader": "Request header",
"removeAppQuestion": {
"one": "برنامه حذف شود؟",
"other": "برنامه ها حذف شوند؟"

View File

@ -287,6 +287,8 @@
"shizuku": "Shizuku",
"root": "Root",
"shizukuBinderNotFound": "Shizuku is not running",
"useVersionCodeAsOSVersion": "Use app versionCode as OS-detected version",
"requestHeader": "Request header",
"removeAppQuestion": {
"one": "Supprimer l'application ?",
"other": "Supprimer les applications ?"

View File

@ -287,6 +287,8 @@
"shizuku": "Shizuku",
"root": "Root",
"shizukuBinderNotFound": "Shizuku is not running",
"useVersionCodeAsOSVersion": "Use app versionCode as OS-detected version",
"requestHeader": "Request header",
"removeAppQuestion": {
"one": "Eltávolítja az alkalmazást?",
"other": "Eltávolítja az alkalmazást?"

View File

@ -287,6 +287,8 @@
"shizuku": "Shizuku",
"root": "Root",
"shizukuBinderNotFound": "Shizuku non è in esecuzione",
"useVersionCodeAsOSVersion": "Use app versionCode as OS-detected version",
"requestHeader": "Request header",
"removeAppQuestion": {
"one": "Rimuovere l'app?",
"other": "Rimuovere le app?"

View File

@ -287,6 +287,10 @@
"shizuku": "Shizuku",
"root": "Root",
"shizukuBinderNotFound": "Shizukuが起動していません",
"useSystemFont": "システムフォントを使用する",
"systemFontError": "システムフォントの読み込みエラー: {}",
"useVersionCodeAsOSVersion": "Use app versionCode as OS-detected version",
"requestHeader": "Request header",
"removeAppQuestion": {
"one": "アプリを削除しますか?",
"other": "アプリを削除しますか?"

View File

@ -287,6 +287,8 @@
"shizuku": "Shizuku",
"root": "Root",
"shizukuBinderNotFound": "Shizuku is not running",
"useVersionCodeAsOSVersion": "Use app versionCode as OS-detected version",
"requestHeader": "Request header",
"removeAppQuestion": {
"one": "App verwijderen?",
"other": "Apps verwijderen?"

View File

@ -287,6 +287,8 @@
"shizuku": "Shizuku",
"root": "Root",
"shizukuBinderNotFound": "Shizuku is not running",
"useVersionCodeAsOSVersion": "Use app versionCode as OS-detected version",
"requestHeader": "Request header",
"removeAppQuestion": {
"one": "Usunąć aplikację?",
"few": "Usunąć aplikacje?",

View File

@ -1,21 +1,21 @@
{
"invalidURLForSource": "URL {} inválida",
"noReleaseFound": "Não foi possivel encontrar uma versão adequada",
"noVersionFound": "Não foi possivel encontrar uma versão lançada",
"noReleaseFound": "Não foi possível encontrar uma versão adequada",
"noVersionFound": "Não foi possível encontrar uma versão",
"urlMatchesNoSource": "URL não corresponde a uma fonte conhecida",
"cantInstallOlderVersion": "Não pode instalar uma versão anterior de um App",
"appIdMismatch": "ID do pacote baixado não é igual ao ID do App instalado",
"cantInstallOlderVersion": "Não é permitido instalar uma versão anterior de um aplicativo",
"appIdMismatch": "ID do pacote baixado não é igual ao ID do aplicativo instalado",
"functionNotImplemented": "Esta classe não implementou essa função",
"placeholder": "Espaço Reservado",
"someErrors": "Alguns Erros Ocorreram",
"unexpectedError": "Erro Inesperado",
"ok": "Ok",
"placeholder": "Espaço reservado",
"someErrors": "Alguns erros ocorreram",
"unexpectedError": "Erro inesperado",
"ok": "OK",
"and": "e",
"githubPATLabel": "Token de Acceso Pessoal do GitHub (Reduz tempos de espera)",
"githubPATLabel": "Token de acesso pessoal do GitHub (Reduz tempos de espera)",
"includePrereleases": "Incluir pré-lançamentos",
"fallbackToOlderReleases": "Retornar para versões anteriores",
"filterReleaseTitlesByRegEx": "Filtrar Titulos de Versões por Expressão Regular",
"invalidRegEx": "Expressão Regular Inválida",
"filterReleaseTitlesByRegEx": "Filtrar tulos de versões por expressão regular",
"invalidRegEx": "Expressão regular inválida",
"noDescription": "Sem descrição",
"cancel": "Cancelar",
"continue": "Continuar",
@ -25,66 +25,66 @@
"githubStarredRepos": "Favoritados no GitHub",
"uname": "Nome de usuário",
"wrongArgNum": "Número de argumentos errado",
"xIsTrackOnly": "{} é 'Apenas Seguir'",
"xIsTrackOnly": "{} é 'Apenas monitorar'",
"source": "Fonte",
"app": "App",
"appsFromSourceAreTrackOnly": "Os apps desta fonte são 'Apenas Seguir'.",
"youPickedTrackOnly": "Você selecionou a opção 'Apenas Seguir'.",
"trackOnlyAppDescription": "Esse App vai ser seguido por atualizações, mais o Obtainium não poderá baixa-lo ou instala-lo.",
"app": "Aplicativo",
"appsFromSourceAreTrackOnly": "Os aplicativos desta fonte são 'Apenas monitorar'.",
"youPickedTrackOnly": "Você selecionou a opção 'Apenas monitorar'.",
"trackOnlyAppDescription": "As atualizações desse aplicativo serão monitoradas, mas o Obtainium não poderá baixá-lo ou instalá-lo.",
"cancelled": "Cancelado",
"appAlreadyAdded": "App já adicionado",
"alreadyUpToDateQuestion": "App já atualizado?",
"addApp": "Adicionar App",
"appSourceURL": "URL de origem do App",
"appAlreadyAdded": "Aplicativo já adicionado",
"alreadyUpToDateQuestion": "Aplicativo já foi atualizado?",
"addApp": "Adicionar aplicativo",
"appSourceURL": "URL de origem do aplicativo",
"error": "Erro",
"add": "Adicionar",
"searchSomeSourcesLabel": "Procurar (Apenas Algumas Fontes)",
"searchSomeSourcesLabel": "Procurar (Apenas algumas fontes)",
"search": "Procurar",
"additionalOptsFor": "Opções Adicionais para {}",
"supportedSources": "Fontes Compatíveis",
"trackOnlyInBrackets": "(Apenas Seguir)",
"additionalOptsFor": "Opções adicionais para {}",
"supportedSources": "Fontes compatíveis",
"trackOnlyInBrackets": "(Apenas monitorar)",
"searchableInBrackets": "(Pesquisável)",
"appsString": "Apps",
"noApps": "Sem Apps",
"noAppsForFilter": "Sem Apps para Filtrar",
"appsString": "Aplicativos",
"noApps": "Não há aplicativos",
"noAppsForFilter": "Sem aplicativos para filtrar",
"byX": "Por {}",
"percentProgress": "Progresso: {}%",
"pleaseWait": "Por Favor Espere",
"updateAvailable": "Atualização Disponível",
"pleaseWait": "Por favor, espere",
"updateAvailable": "Atualização disponível",
"estimateInBracketsShort": "(Aprox.)",
"notInstalled": "Não Instalado",
"notInstalled": "Não instalado",
"estimateInBrackets": "(Aproximado)",
"selectAll": "Selecionar All",
"selectAll": "Selecionar todos",
"deselectX": "Deselecionar {}",
"xWillBeRemovedButRemainInstalled": "{} sera removido do Obtainium mais permanecerá instalado no dispositivo.",
"removeSelectedAppsQuestion": "Remover Apps Selecionados?",
"removeSelectedApps": "Remover Apps Selecionados",
"xWillBeRemovedButRemainInstalled": "{} será removido do Obtainium mais permanecerá instalado no dispositivo.",
"removeSelectedAppsQuestion": "Remover aplicativos selecionados?",
"removeSelectedApps": "Remover aplicativos selecionados",
"updateX": "Atualizar {}",
"installX": "Instalar {}",
"markXTrackOnlyAsUpdated": "Marcar {}\n(Apenas Seguir)\ncomo Atualizado",
"markXTrackOnlyAsUpdated": "Marcar {}\n(Apenas monitorar)\ncomo Atualizado",
"changeX": "Mudar {}",
"installUpdateApps": "Instalar/Atualizar Apps",
"installUpdateSelectedApps": "Instalar/Atualizar Apps Selecionados",
"markXSelectedAppsAsUpdated": "Marcar {} Apps Delecionados como Atualizados?",
"installUpdateApps": "Instalar/Atualizar aplicativos",
"installUpdateSelectedApps": "Instalar/Atualizar aplicativos selecionados",
"markXSelectedAppsAsUpdated": "Marcar {} aplicativos selecionados como atualizados?",
"no": "Não",
"yes": "Sim",
"markSelectedAppsUpdated": "Marcar Apps Selecionados como Atualizados",
"markSelectedAppsUpdated": "Marcar aplicativos selecionados como Atualizados",
"pinToTop": "Fixar no topo",
"unpinFromTop": "Desafixar do topo",
"resetInstallStatusForSelectedAppsQuestion": "Reiniciar Status de Instalação para Apps Seleciondos?",
"installStatusOfXWillBeResetExplanation": "O status de instalação de qualquer app selecionado sera reiniciado.\n\nIsso pode ajudar quando uma versão de um App mostrada no Obtainium é incorreta devido a falhas ao atualizar ou outros problemas.",
"shareSelectedAppURLs": "Compartilhar URLs de Apps Selecionados",
"resetInstallStatus": "Reiniciar Status de Instalação",
"resetInstallStatusForSelectedAppsQuestion": "Reiniciar status de instalação para aplicativos selecionados?",
"installStatusOfXWillBeResetExplanation": "O status de instalação de qualquer aplicativo selecionado será reiniciado.\n\nIsso pode ajudar quando uma versão de um aplicativo mostrada no Obtainium é incorreta devido a falhas ao atualizar ou outros problemas.",
"shareSelectedAppURLs": "Compartilhar URLs de aplicativos selecionados",
"resetInstallStatus": "Reiniciar status de Iistalação",
"more": "Mais",
"removeOutdatedFilter": "Remover Filtro de Apps Desatualizados",
"showOutdatedOnly": "Mostrar Apenas Apps Desatualizados",
"removeOutdatedFilter": "Remover filtro de aplicativos desatualizados",
"showOutdatedOnly": "Mostrar apenas aplicativos desatualizados",
"filter": "Filtro",
"filterActive": "Filtro *",
"filterApps": "Filtrar Apps",
"appName": "Nome do App",
"filterApps": "Filtrar aplicativos",
"appName": "Nome do aplicativo",
"author": "Autor",
"upToDateApps": "Apps Atualizados",
"nonInstalledApps": "Apps Não Instalados",
"upToDateApps": "Aplicativos tualizados",
"nonInstalledApps": "Aplicativos não instalados",
"importExport": "Importar/Exportar",
"settings": "Configurações",
"exportedTo": "Exportado para {}",
@ -92,16 +92,16 @@
"invalidInput": "Input Inválido",
"importedX": "Importado {}",
"obtainiumImport": "Importar Obtainium",
"importFromURLList": "Importar de Lista de URLs",
"importFromURLList": "Importar de lista de URLs",
"searchQuery": "Pesquisa",
"appURLList": "Lista de URLs de Apps",
"appURLList": "Lista de URLs de aplicativos",
"line": "Linha",
"searchX": "Pesquisa {}",
"noResults": "Nenhum resultado encontrado",
"importX": "Importar {}",
"importedAppsIdDisclaimer": "Apps Importados podem ser mostrados incorretamente como \"Não Instalado\".\nPara consertar, reinstale-os usando o Obtainium.\nIsso não deve afetar dados do App.\n\nAfeta apenas métodos de importação de URL e de terceiros.",
"importErrors": "Erros de Importação",
"importedXOfYApps": "{} de {} Apps importados.",
"importedAppsIdDisclaimer": "Aplicativos Importados podem ser mostrados incorretamente como \"Não Instalado\".\nPara consertar, reinstale-os usando o Obtainium.\nIsso não deve afetar dados do aplicativo.\n\nAfeta apenas métodos de importação de URL e de terceiros.",
"importErrors": "Erros de importação",
"importedXOfYApps": "{} de {} aplicativos importados.",
"followingURLsHadErrors": "As seguintes URLs apresentaram erros:",
"selectURL": "Selecionar URL",
"selectURLs": "Selecionar URLs",
@ -109,125 +109,125 @@
"theme": "Tema",
"dark": "Escuro",
"light": "Claro",
"followSystem": "Seguir o Sistema",
"followSystem": "Seguir o sistema",
"obtainium": "Obtainium",
"materialYou": "Material You",
"useBlackTheme": "Usar tema preto completamente escuro",
"appSortBy": "Classificar App por",
"appSortBy": "Classificar aplicativo por",
"authorName": "Autor/Nome",
"nameAuthor": "Nome/Autor",
"asAdded": "Como Adicionado",
"appSortOrder": "Ordem de classificação de Apps",
"asAdded": "Como adicionado",
"appSortOrder": "Ordem de classificação de aplicativos",
"ascending": "Ascendente",
"descending": "Descendente",
"bgUpdateCheckInterval": "Intervalo de verificação de atualizações em segundo plano",
"neverManualOnly": "Nunca - Apenas Manual",
"bgUpdateCheckInterval": "Intervalo de verificação de atualizações em segundo-plano",
"neverManualOnly": "Nunca - apenas manual",
"appearance": "Aparência",
"showWebInAppView": "Mostrar páginas da internet em App view",
"pinUpdates": "Fixar atualizações no topo da visão de Apps",
"showWebInAppView": "Mostrar página da internet em informações do aplicativo",
"pinUpdates": "Fixar atualizações no topo da janela de aplicativos",
"updates": "Atualizações",
"sourceSpecific": "Específico a fonte",
"appSource": "Fonte do App",
"noLogs": "Sem Logs",
"appLogs": "Logs do App",
"appSource": "Fonte do aplicativo",
"noLogs": "Sem logs",
"appLogs": "Logs do aplicativo",
"close": "Fechar",
"share": "Compartilhar",
"appNotFound": "App não encontrado",
"appNotFound": "Aplicativo não encontrado",
"obtainiumExportHyphenatedLowercase": "obtainium-export",
"pickAnAPK": "Selecionar um APK",
"appHasMoreThanOnePackage": "{} tem mais de um pacote:",
"deviceSupportsXArch": "Seu dispositivo suporta a arquitetura de CPU {}.",
"deviceSupportsFollowingArchs": "Seu dispositivo suporta as seguintes arquiteturas de CPU:",
"warning": "Aviso",
"sourceIsXButPackageFromYPrompt": "A Fonte do App é '{}' mais o pacote lançado vem de '{}'. Continuar?",
"updatesAvailable": "Atualizações Disponíveis",
"updatesAvailableNotifDescription": "Notifica o usuário quando atualizações estão disponíveis um ou mais Apps seguidos pelo Obtainium",
"sourceIsXButPackageFromYPrompt": "A fonte do aplicativo é '{}' mas a origem do pacote é '{}'. Continuar?",
"updatesAvailable": "Atualizações disponíveis",
"updatesAvailableNotifDescription": "Notifica o usuário quando atualizações de um ou mais aplicativos monitorados pelo Obtainium estão disponíveis",
"noNewUpdates": "Sem novas atualizações.",
"xHasAnUpdate": "{} tem uma atualização.",
"appsUpdated": "Apps Atualizados",
"appsUpdatedNotifDescription": "Notifica o usuário quando atualizações para um ou mais Apps foram aplicadas em segundo plano",
"appsUpdated": "Aplicativos atualizados",
"appsUpdatedNotifDescription": "Notifica o usuário quando atualizações foram aplicadas em segundo-plano para um ou mais aplicativos ",
"xWasUpdatedToY": "{} foi atualizado para {}.",
"errorCheckingUpdates": "Erro ao Procurar por Atualizações",
"errorCheckingUpdatesNotifDescription": "Uma notificação que mostra quando a checagem por atualizações em segundo plano falha",
"appsRemoved": "Apps Removidos",
"appsRemovedNotifDescription": "Notifica o usuário quando um ou mais Apps foram removidos devido a erros ao carregá-los",
"errorCheckingUpdates": "Erro ao procurar por atualizações",
"errorCheckingUpdatesNotifDescription": "Uma notificação que mostra quando a checagem por atualizações em segundo-plano falha",
"appsRemoved": "Aplicativos removidos",
"appsRemovedNotifDescription": "Notifica o usuário quando um ou mais aplicativos foram removidos devido a erros de carregamento",
"xWasRemovedDueToErrorY": "{} foi removido devido a este erro: {}",
"completeAppInstallation": "Instalação completa do App",
"obtainiumMustBeOpenToInstallApps": "Obtainium deve estar aberto para instalar Apps",
"completeAppInstallationNotifDescription": "Pede ao usuário que retorne ao Obtainium para finalizar a instalação de um App",
"checkingForUpdates": "Checando por Atualizações",
"checkingForUpdatesNotifDescription": "Notificação transiente que aparece quando checando por atualizações",
"pleaseAllowInstallPerm": "Por favor, permita o Obtainium instalar Apps",
"trackOnly": "Apenas Seguir",
"completeAppInstallation": "Instalação do aplicativo completa",
"obtainiumMustBeOpenToInstallApps": "Obtainium deve estar aberto para instalar os aplicativos",
"completeAppInstallationNotifDescription": "Pede ao usuário que retorne ao Obtainium para finalizar a instalação de um aplicativo",
"checkingForUpdates": "Verificando atualizações",
"checkingForUpdatesNotifDescription": "Notificação transiente que aparece quando o Obtainium está verificando se há atualizações",
"pleaseAllowInstallPerm": "Por favor, permita que o Obtainium possa instalar aplicativos",
"trackOnly": "Apenas monitorar",
"errorWithHttpStatusCode": "Erro {}",
"versionCorrectionDisabled": "Correção de versão desativada (plugin parece não funcionar)",
"unknown": "Desconhecido",
"none": "Nenhum",
"never": "Nunca",
"latestVersionX": "Última versão: {}",
"installedVersionX": "Versão Instalada: {}",
"lastUpdateCheckX": "Última Checagem por Atualização: {}",
"installedVersionX": "Versão instalada: {}",
"lastUpdateCheckX": "Última verificação de atualizações: {}",
"remove": "Remover",
"yesMarkUpdated": "Sim, Marcar como Atualizado",
"yesMarkUpdated": "Sim, marcar como Atualizado",
"fdroid": "F-Droid Official",
"appIdOrName": "ID do App ou Nome",
"appId": "ID do App",
"appWithIdOrNameNotFound": "Nenhum App foi encontrado com esse ID ou nome",
"reposHaveMultipleApps": "Repositórios podem conter multiplos Apps",
"appIdOrName": "ID do aplicativo ou nome",
"appId": "ID do aplicativo",
"appWithIdOrNameNotFound": "Nenhum aplicativo foi encontrado com esse ID ou nome",
"reposHaveMultipleApps": "Repositórios podem conter multiplos aplicativos",
"fdroidThirdPartyRepo": "Repositórios de terceiros F-Droid",
"steam": "Steam",
"steamMobile": "Steam Mobile",
"steamChat": "Steam Chat",
"install": "Instalar",
"markInstalled": "Marcar Instalado",
"markInstalled": "Marcar instalado",
"update": "Atualizar",
"markUpdated": "Marcar Atualizado",
"additionalOptions": "Opções Adicionais",
"disableVersionDetection": "Desativar Detecção de Versão",
"noVersionDetectionExplanation": "Essa opção deve apenas ser usada por Apps onde detecção de versão não funciona corretamente.",
"markUpdated": "Marcar como atualizado",
"additionalOptions": "Opções adicionais",
"disableVersionDetection": "Desativar detecção de versão",
"noVersionDetectionExplanation": "Essa opção deve apenas ser usada por aplicativos onde a detecção de versão não funciona corretamente.",
"downloadingX": "Baixando {}",
"downloadNotifDescription": "Notifica o usuário do progresso ao baixar um App",
"downloadNotifDescription": "Notifica o usuário o progresso do download de um aplicativo",
"noAPKFound": "APK não encontrado",
"noVersionDetection": "Sem Detecção de versão",
"noVersionDetection": "Sem detecção de versão",
"categorize": "Categorizar",
"categories": "Categorias",
"category": "Categoria",
"noCategory": "Sem Categoria",
"noCategories": "Sem Categoria",
"deleteCategoriesQuestion": "Deletar Categorias?",
"categoryDeleteWarning": "Todos os Apps em categorias removidas serão descategorizados.",
"addCategory": "Adicionar Categoria",
"noCategory": "Sem categoria",
"noCategories": "Sem categoria",
"deleteCategoriesQuestion": "Deletar categorias?",
"categoryDeleteWarning": "Todos os aplicativos em categorias removidas serão descategorizados.",
"addCategory": "Adicionar categoria",
"label": "Etiqueta",
"language": "Linguagem",
"copiedToClipboard": "Copiado para a área de transferência",
"storagePermissionDenied": "Permição ao armazenamento negada",
"selectedCategorizeWarning": "Isso vai substituir qualquer confirução de categoria para os Apps selecionados.",
"filterAPKsByRegEx": "Filtrar APKs por Expressão Regular",
"storagePermissionDenied": "Permissão de armazenamento negada",
"selectedCategorizeWarning": "Isso vai substituir qualquer configuração de categoria para os aplicativos selecionados.",
"filterAPKsByRegEx": "Filtrar APKs por expressão regular",
"removeFromObtainium": "Remover do Obtainium",
"uninstallFromDevice": "Desinstalar do dispositivo",
"onlyWorksWithNonVersionDetectApps": "Apenas funciona para Apps com detecção de versão desativada.",
"releaseDateAsVersion": "Usar Data de Lançamento como Versão",
"onlyWorksWithNonVersionDetectApps": "Apenas funciona para aplicativos com detecção de versão desativada.",
"releaseDateAsVersion": "Usar data de lançamento como versão",
"releaseDateAsVersionExplanation": "Esta opção só deve ser usada para aplicativos onde a detecção de versão não funciona corretamente, mas há uma data de lançamento disponível.",
"changes": "Mudanças",
"releaseDate": "Data de Lançamento",
"importFromURLsInFile": "Importar de URLs em Arquivo (como OPML)",
"releaseDate": "Data de lançamento",
"importFromURLsInFile": "Importar de URLs em arquivo (como OPML)",
"versionDetection": "Detecção de Versão",
"standardVersionDetection": "Detecção de versão padrão",
"groupByCategory": "Agroupar por Categoria",
"groupByCategory": "Agroupar por categoria",
"autoApkFilterByArch": "Tente filtrar APKs por arquitetura de CPU, se possível",
"overrideSource": "Substituir Fonte",
"overrideSource": "Substituir fonte",
"dontShowAgain": "Não mostrar isso novamente",
"dontShowTrackOnlyWarnings": "Não mostrar avisos 'Apenas Seguir'",
"dontShowTrackOnlyWarnings": "Não mostrar avisos 'Apenas Monitorar'",
"dontShowAPKOriginWarnings": "Não mostrar avisos de origem da APK",
"moveNonInstalledAppsToBottom": "Mover Apps não instalados para o fundo da visão de Apps",
"moveNonInstalledAppsToBottom": "Mover aplicativos não instalados para o fundo da lista de aplicativos",
"gitlabPATLabel": "Token de Acceso Pessoal do Gitlab\n(Ativa Pesquisa e Melhor Descoberta de APKs)",
"about": "Sobre",
"requiresCredentialsInSettings": "{}: Isso requer credenciais adicionais (em Configurações)",
"checkOnStart": "Checar por atualizações ao iniciar ",
"tryInferAppIdFromCode": "Tente inferir o ID do App pelo código fonte",
"removeOnExternalUninstall": "Remover automaticamente Apps desinstalados externamente",
"tryInferAppIdFromCode": "Tente inferir o ID do aplicativo pelo código-fonte",
"removeOnExternalUninstall": "Remover automaticamente aplicativos desinstalados externamente",
"pickHighestVersionCode": "Auto-selecionar o maior numero de versão do APK",
"checkUpdateOnDetailPage": "Checar por atualizações ao abrir a pagina de detalhes de um App",
"checkUpdateOnDetailPage": "Checar por atualizações ao abrir a página de detalhes de um aplicativo",
"disablePageTransitions": "Desativar animações de transição de pagina",
"reversePageTransitions": "Reverter animações de transição de pagina",
"minStarCount": "Contagem Minima de Estrelas",
@ -238,19 +238,19 @@
"sortByLastLinkSegment": "Sort by only the last segment of the link",
"filterReleaseNotesByRegEx": "Filtrar Notas de Lançamento por Expressão Regular",
"customLinkFilterRegex": "Filtro de Link Personalizado por Expressão Regular (Padrão '.apk$')",
"appsPossiblyUpdated": "Tentativas de atualização de Apps",
"appsPossiblyUpdatedNotifDescription": "Notifica o usuário de que atualizações de um ou mais Apps foram potencialmente aplicadas em segundo plano",
"appsPossiblyUpdated": "Tentativas de atualização de aplicativos",
"appsPossiblyUpdatedNotifDescription": "Notifica o usuário de que atualizações de um ou mais aplicativos foram potencialmente aplicadas em segundo-plano",
"xWasPossiblyUpdatedToY": "{} pode ter sido atualizado para {}.",
"enableBackgroundUpdates": "Ativar atualizações em segundo plano",
"backgroundUpdateReqsExplanation": "Atualizações em segundo plano podem não ser possíveis para todos os Apps.",
"backgroundUpdateLimitsExplanation": "O sucesso de uma instalação em segundo plano só pode ser determinado quando o Obtainium é aberto.",
"enableBackgroundUpdates": "Ativar atualizações em segundo-plano",
"backgroundUpdateReqsExplanation": "Atualizações em segundo-plano podem não ser possíveis para todos os aplicativos.",
"backgroundUpdateLimitsExplanation": "O sucesso de uma instalação em segundo-plano só pode ser determinado quando o Obtainium é aberto.",
"verifyLatestTag": "Verifique a 'ultima' etiqueta",
"intermediateLinkRegex": "Filter for an 'Intermediate' Link to Visit",
"filterByLinkText": "Filter links by link text",
"intermediateLinkNotFound": "Link intermediário não encontrado",
"intermediateLink": "Intermediate link",
"exemptFromBackgroundUpdates": "Isento de atualizações em segundo plano (se ativadas)",
"bgUpdatesOnWiFiOnly": "Desative atualizações em segundo plano quando não estiver em WiFi",
"exemptFromBackgroundUpdates": "Isento de atualizações em segundo-plano (se ativadas)",
"bgUpdatesOnWiFiOnly": "Desative atualizações em segundo-plano quando não estiver em WiFi",
"autoSelectHighestVersionCode": "Auto-selecionar o maior codigo de versão",
"versionExtractionRegEx": "RegEx para Extração de Versão",
"matchGroupToUse": "Grupo de Seleção para Usar",
@ -265,18 +265,18 @@
"takeFirstLink": "Take first link",
"skipSort": "Skip sorting",
"debugMenu": "Menu Debug",
"bgTaskStarted": "Tarefa em segundo plano iniciada - verifique os logs.",
"runBgCheckNow": "Execute a verificação de atualização em segundo plano agora",
"bgTaskStarted": "Tarefa em segundo-plano iniciada - verifique os logs.",
"runBgCheckNow": "Execute a verificação de atualização em segundo-plano agora",
"versionExtractWholePage": "Aplicar Regex de Extração de Versão à Página Inteira",
"installing": "Instalando",
"skipUpdateNotifications": "Pular notificações de update",
"updatesAvailableNotifChannel": "Atualizações Disponíveis",
"appsUpdatedNotifChannel": "Apps Atualizados",
"appsPossiblyUpdatedNotifChannel": "Tentativas de atualização de Apps",
"appsUpdatedNotifChannel": "Aplicativos Atualizados",
"appsPossiblyUpdatedNotifChannel": "Tentativas de atualização de aplicativos",
"errorCheckingUpdatesNotifChannel": "Erro ao Procurar por Atualizações",
"appsRemovedNotifChannel": "Apps Removidos",
"appsRemovedNotifChannel": "Aplicativos Removidos",
"downloadingXNotifChannel": "Baixando {}",
"completeAppInstallationNotifChannel": "Instalação completa do App",
"completeAppInstallationNotifChannel": "Instalação completa do aplicativo",
"checkingForUpdatesNotifChannel": "Checando por Atualizações",
"onlyCheckInstalledOrTrackOnlyApps": "Apenas checar apps instalados e 'Apenas Seguir' por updates",
"supportFixedAPKURL": "Suporte APK com URLs fixas",
@ -286,26 +286,28 @@
"normal": "Normal",
"shizuku": "Shizuku",
"root": "Root",
"shizukuBinderNotFound": "Shizuku não esta rodando",
"shizukuBinderNotFound": "Shizuku não está rodando",
"useVersionCodeAsOSVersion": "Use app versionCode as OS-detected version",
"requestHeader": "Request header",
"removeAppQuestion": {
"one": "Remover App?",
"other": "Remover Apps?"
"one": "Remover aplicativo?",
"other": "Remover aplicativos?"
},
"tooManyRequestsTryAgainInMinutes": {
"one": "Muitas solicitações (taxa limitada) - tente novamente em {} minuto",
"other": "Muitas solicitações (taxa limitada) - tente novamente em {} minutos"
},
"bgUpdateGotErrorRetryInMinutes": {
"one": "A verificação de atualizações em segundo plano encontrou um {}, agendada uma nova verificação em {} minuto",
"other": "A verificação de atualizações em segundo plano encontrou um {}, agendada uma nova verificação em {} minutos"
"one": "A verificação de atualizações em segundo-plano encontrou um {}, agendada uma nova verificação em {} minuto",
"other": "A verificação de atualizações em segundo-plano encontrou um {}, agendada uma nova verificação em {} minutos"
},
"bgCheckFoundUpdatesWillNotifyIfNeeded": {
"one": "A verificação de atualizações em segundo plano encontrou {} atualização, o usuário sera notificado caso necessário",
"other": "A verificação de atualizações em segundo plano encontrou {} atualizações, o usuário sera notificado caso necessário"
"one": "A verificação de atualizações em segundo-plano encontrou {} atualização, o usuário sera notificado caso necessário",
"other": "A verificação de atualizações em segundo-plano encontrou {} atualizações, o usuário sera notificado caso necessário"
},
"apps": {
"one": "{} App",
"other": "{} Apps"
"one": "{} Aplicativo",
"other": "{} Aplicativos"
},
"url": {
"one": "{} URL",

View File

@ -289,6 +289,8 @@
"shizukuBinderNotFound": "Совместимый сервис Shizuku не найден",
"useSystemFont": "Использовать системный шрифт",
"systemFontError": "Ошибка загрузки системного шрифта: {}",
"useVersionCodeAsOSVersion": "Use app versionCode as OS-detected version",
"requestHeader": "Request header",
"removeAppQuestion": {
"one": "Удалить приложение?",
"other": "Удалить приложения?"

View File

@ -273,6 +273,8 @@
"shizuku": "Shizuku",
"root": "Root",
"shizukuBinderNotFound": "Shizuku is not running",
"useVersionCodeAsOSVersion": "Use app versionCode as OS-detected version",
"requestHeader": "Request header",
"removeAppQuestion": {
"one": "Ta Bort App?",
"other": "Ta Bort Appar?"

View File

@ -287,6 +287,8 @@
"shizuku": "Shizuku",
"root": "Root",
"shizukuBinderNotFound": "Shizuku is not running",
"useVersionCodeAsOSVersion": "Use app versionCode as OS-detected version",
"requestHeader": "Request header",
"removeAppQuestion": {
"one": "Uygulamayı Kaldır?",
"other": "Uygulamaları Kaldır?"

View File

@ -11,7 +11,7 @@
"unexpectedError": "Lỗi không mong đợi",
"ok": "OK",
"and": "và",
"githubPATLabel": "Mã thông báo truy cập cá nhân GitHub (Tăng tốc độ giới hạn)",
"githubPATLabel": "GitHub Token (Tăng tốc độ, giới hạn)",
"includePrereleases": "Bao gồm các bản phát hành trước",
"fallbackToOlderReleases": "Dự phòng về bản phát hành cũ hơn",
"filterReleaseTitlesByRegEx": "Lọc tiêu đề bản phát hành theo biểu thức chính quy",
@ -34,7 +34,7 @@
"cancelled": "Đã hủy",
"appAlreadyAdded": "Ứng dụng được thêm rồi",
"alreadyUpToDateQuestion": "Ứng dụng đã được cập nhật?",
"addApp": "Thêm ứng dụng",
"addApp": "Thêm",
"appSourceURL": "URL nguồn ứng dụng",
"error": "Lỗi",
"add": "Thêm",
@ -48,7 +48,7 @@
"noApps": "Không có ứng dụng",
"noAppsForFilter": "Không có ứng dụng cho bộ lọc",
"byX": "Bởi {}",
"percentProgress": "Tiến triển: {}%",
"percentProgress": "Đang tải {}%",
"pleaseWait": "Vui lòng chờ",
"updateAvailable": "Có sẵn bản cập nhật",
"estimateInBracketsShort": "(Ước lượng.)",
@ -88,10 +88,10 @@
"importExport": "Nhập/Xuất",
"settings": "Cài đặt",
"exportedTo": "Đã xuất sang {}",
"obtainiumExport": "Xuất Obtainium",
"obtainiumExport": "Xuất",
"invalidInput": "Đầu vào không hợp lệ",
"importedX": "Đã nhập {}",
"obtainiumImport": "Nhập Obtainium",
"obtainiumImport": "Nhập",
"importFromURLList": "Nhập từ danh sách URL",
"searchQuery": "Truy vấn tìm kiếm",
"appURLList": "Danh sách URL ứng dụng",
@ -120,13 +120,13 @@
"appSortOrder": "Thứ tự sắp xếp",
"ascending": "Tăng dần",
"descending": "Giảm dần",
"bgUpdateCheckInterval": "Khoảng thời gian kiểm tra cập nhật nền",
"neverManualOnly": "Không bao giờ - Chỉ thủ công",
"bgUpdateCheckInterval": "Thời gian tự động kiểm tra cập nhật",
"neverManualOnly": "Không bao giờ",
"appearance": "Hiển thị",
"showWebInAppView": "Hiển thị trang web Nguồn trong chế độ xem Ứng dụng",
"pinUpdates": "Ghim nội dung cập nhật lên đầu chế độ xem Ứng dụng",
"showWebInAppView": "Hiển thị trang web Nguồn trong chế độ xem chi tiết Ứng dụng",
"pinUpdates": "Chuyển ứng dng có phiên bản mới lên đầu danh sách",
"updates": "Cập nhật",
"sourceSpecific": "Nguồn cụ thể",
"sourceSpecific": "Cài đặt Nguồn",
"appSource": "Nguồn ứng dụng",
"noLogs": "Không có nhật ký",
"appLogs": "Nhật ký ứng dụng",
@ -219,8 +219,8 @@
"dontShowAgain": "Đừng hiển thị thông tin này nữa",
"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": "Di chuyển Ứng dụng chưa được cài đặt xuống cuối chế độ xem Ứng dụng",
"gitlabPATLabel": "Mã thông báo truy cập cá nhân GitLab\n(Cho phép tìm kiếm và khám phá APK tốt hơn)",
"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)",
"about": "Giới thiệu",
"requiresCredentialsInSettings": "{}: Điều này cần thông tin xác thực bổ sung (trong Cài đặt)",
"checkOnStart": "Kiểm tra các bản cập nhật khi khởi động",
@ -241,9 +241,9 @@
"appsPossiblyUpdated": "Đã cố gắng cập nhật ứng dụng",
"appsPossiblyUpdatedNotifDescription": "Thông báo cho người dùng rằng các bản cập nhật cho một hoặc nhiều Ứng dụng có khả năng được áp dụng trong nền",
"xWasPossiblyUpdatedToY": "{} có thể đã được cập nhật thành {}.",
"enableBackgroundUpdates": "Bật cập nhật nền",
"backgroundUpdateReqsExplanation": "Có thể không thực hiện được cập nhật nền cho tất cả ứng dụng.",
"backgroundUpdateLimitsExplanation": "Sự thành công của cài đặt nền chỉ có thể được xác định khi mở Obtainium.",
"enableBackgroundUpdates": "Tự động cập nhật trong nền",
"backgroundUpdateReqsExplanation": "Có thể không thực hiện được cập nhật trong nền cho tất cả ứng dụng.",
"backgroundUpdateLimitsExplanation": "Sự thành công của cài đặt trong nền chỉ có thể được xác định khi mở Obtainium.",
"verifyLatestTag": "Xác minh thẻ 'mới nhất'",
"intermediateLinkRegex": "Filter for an 'Intermediate' Link to Visit",
"filterByLinkText": "Filter links by link text",
@ -256,8 +256,8 @@
"matchGroupToUse": "Nhóm đối sánh để sử dụng cho Regex trích xuất phiên bản",
"highlightTouchTargets": "Đánh dấu các mục tiêu cảm ứng ít rõ ràng hơn",
"pickExportDir": "Chọn thư mục xuất",
"autoExportOnChanges": "Tự động xuất khi thay đổi",
"includeSettings": "Include settings",
"autoExportOnChanges": "Tự động xuất",
"includeSettings": "Bao gồm cài đặt ứng dụng",
"filterVersionsByRegEx": "Lọc phiên bản theo biểu thức chính quy",
"trySelectingSuggestedVersionCode": "Thử chọn APK Mã phiên bản được đề xuất",
"dontSortReleasesList": "Giữ lại thứ tự phát hành từ API",
@ -278,12 +278,12 @@
"downloadingXNotifChannel": "Đang tải xuống {}",
"completeAppInstallationNotifChannel": "Hoàn tất cài đặt ứng dụng",
"checkingForUpdatesNotifChannel": "Đang kiểm tra cập nhật",
"onlyCheckInstalledOrTrackOnlyApps": "Chỉ kiểm tra các ứng dụng đã cài đặt và Chỉ-Theo dõi để biết các bản cập nhật",
"onlyCheckInstalledOrTrackOnlyApps": "Chỉ kiểm tra cập nhật các ứng dụng đã cài đặt và Chỉ-Theo dõi",
"supportFixedAPKURL": "Support fixed APK URLs",
"selectX": "Select {}",
"parallelDownloads": "Allow parallel downloads",
"installMethod": "Installation method",
"normal": "Normal",
"parallelDownloads": "Cho phép tải đa luồng",
"installMethod": "Phương thức cài đặt",
"normal": "Mặc định",
"shizuku": "Shizuku",
"root": "Root",
"shizukuBinderNotFound": "Shizuku chưa khởi động",

View File

@ -22,11 +22,11 @@
"requiredInBrackets": "(必填)",
"dropdownNoOptsError": "错误:下拉菜单必须包含至少一个选项",
"colour": "配色",
"githubStarredRepos": "GitHub 已星标仓库",
"githubStarredRepos": "已星标的 GitHub 仓库",
"uname": "用户名",
"wrongArgNum": "参数数量错误",
"xIsTrackOnly": "{}为“仅追踪”模式",
"source": "源代码",
"xIsTrackOnly": "{}为“仅追踪”模式",
"source": "源",
"app": "应用",
"appsFromSourceAreTrackOnly": "此来源的应用为“仅追踪”模式。",
"youPickedTrackOnly": "您选择了“仅追踪”。",
@ -56,12 +56,12 @@
"estimateInBrackets": "(推测)",
"selectAll": "全选",
"deselectX": "取消选择 {}",
"xWillBeRemovedButRemainInstalled": "{} 将从 Obtainium 中删除,但仍安装在您的设备中。",
"xWillBeRemovedButRemainInstalled": "{}将从 Obtainium 中删除,但仍安装在您的设备中。",
"removeSelectedAppsQuestion": "是否删除选中的应用?",
"removeSelectedApps": "删除选中的应用",
"updateX": "更新 {}",
"installX": "安装 {}",
"markXTrackOnlyAsUpdated": "将 {}\n仅追踪\n标记为已更新",
"markXTrackOnlyAsUpdated": "将{}\n仅追踪\n标记为已更新",
"changeX": "更改 {}",
"installUpdateApps": "安装/更新应用",
"installUpdateSelectedApps": "安装/更新选中的应用",
@ -88,17 +88,17 @@
"importExport": "导入/导出",
"settings": "设置",
"exportedTo": "已导出至 {}",
"obtainiumExport": "Obtainium 导出",
"obtainiumExport": "导出 Obtainium",
"invalidInput": "无效的输入",
"importedX": "已导入 {}",
"obtainiumImport": "Obtainium 导入",
"obtainiumImport": "导入 Obtainium",
"importFromURLList": "从 URL 列表导入",
"searchQuery": "搜索查询",
"appURLList": "应用 URL 列表",
"line": "行",
"searchX": "搜索 {}",
"searchX": "搜索{}",
"noResults": "无结果",
"importX": "导入 {}",
"importX": "导入{}",
"importedAppsIdDisclaimer": "导入的应用可能会错误地显示为“未安装”状态。\n请通过 Obtainium 重新安装这些应用来解决此问题。",
"importErrors": "导入错误",
"importedXOfYApps": "已导入 {} 中的 {} 个应用。",
@ -123,7 +123,7 @@
"bgUpdateCheckInterval": "后台更新检查间隔",
"neverManualOnly": "手动",
"appearance": "外观",
"showWebInAppView": "应用详情页显示来源网页",
"showWebInAppView": "应用详情页显示来源网页",
"pinUpdates": "将待更新应用置顶",
"updates": "更新",
"sourceSpecific": "来源",
@ -135,7 +135,7 @@
"appNotFound": "未找到应用",
"obtainiumExportHyphenatedLowercase": "obtainium-export",
"pickAnAPK": "选择一个 APK 文件",
"appHasMoreThanOnePackage": "{} 有多个架构可用:",
"appHasMoreThanOnePackage": "{}有多个架构可用:",
"deviceSupportsXArch": "您的设备支持 {} 架构。",
"deviceSupportsFollowingArchs": "您的设备支持下列架构:",
"warning": "警告",
@ -143,15 +143,15 @@
"updatesAvailable": "更新可用",
"updatesAvailableNotifDescription": "Obtainium 追踪的应用有更新时发送通知",
"noNewUpdates": "全部应用已是最新。",
"xHasAnUpdate": "{} 可以更新了。",
"xHasAnUpdate": "{}可以更新了。",
"appsUpdated": "应用已更新",
"appsUpdatedNotifDescription": "当应用在后台安装更新时发送通知",
"xWasUpdatedToY": "{} 已更新至 {}。",
"xWasUpdatedToY": "{}已更新至 {}。",
"errorCheckingUpdates": "检查更新出错",
"errorCheckingUpdatesNotifDescription": "当后台检查更新失败时显示的通知",
"appsRemoved": "应用已删除",
"appsRemovedNotifDescription": "当应用因加载出错而被删除时发送通知",
"xWasRemovedDueToErrorY": "{} 由于以下错误被删除:{}",
"xWasRemovedDueToErrorY": "{}由于以下错误被删除:{}",
"completeAppInstallation": "完成应用安装",
"obtainiumMustBeOpenToInstallApps": "必须启动 Obtainium 才能安装应用",
"completeAppInstallationNotifDescription": "提示返回 Obtainium 以完成应用的安装",
@ -159,7 +159,7 @@
"checkingForUpdatesNotifDescription": "检查更新时短暂显示的通知",
"pleaseAllowInstallPerm": "请授予 Obtainium 安装应用的权限",
"trackOnly": "仅追踪",
"errorWithHttpStatusCode": "错误 {}",
"errorWithHttpStatusCode": "{} 错误",
"versionCorrectionDisabled": "禁用版本号更正(插件似乎未起作用)",
"unknown": "未知",
"none": "无",
@ -185,11 +185,11 @@
"additionalOptions": "附加选项",
"disableVersionDetection": "禁用版本检测",
"noVersionDetectionExplanation": "此选项应该仅用于无法进行版本检测的应用。",
"downloadingX": "正在下载{}",
"downloadingX": "正在下载{}",
"downloadNotifDescription": "提示应用的下载进度",
"noAPKFound": "未找到 APK 文件",
"noVersionDetection": "禁用版本检测",
"categorize": "类",
"categorize": "类",
"categories": "类别",
"category": "类别",
"noCategory": "无类别",
@ -217,17 +217,17 @@
"autoApkFilterByArch": "如果可能,尝试按设备支持的 CPU 架构筛选 APK 文件",
"overrideSource": "覆盖来源",
"dontShowAgain": "不再显示",
"dontShowTrackOnlyWarnings": "不显示“仅追踪”模式警告",
"dontShowAPKOriginWarnings": "不显示 APK 文件来源警告",
"dontShowTrackOnlyWarnings": "忽略“仅追踪”模式警告",
"dontShowAPKOriginWarnings": "忽略 APK 文件来源警告",
"moveNonInstalledAppsToBottom": "将未安装应用置底",
"gitlabPATLabel": "GitLab 个人访问令牌(启用搜索功能并增强 APK 发现)",
"about": "相关文档",
"requiresCredentialsInSettings": "{}:此功能需要额外的凭据(在“设置”中添加)",
"checkOnStart": "启动时进行一次检查",
"tryInferAppIdFromCode": "尝试从源代码推断应用 ID",
"removeOnExternalUninstall": "自动删除已卸载的外部应用",
"removeOnExternalUninstall": "自动删除列表中已卸载的应用",
"pickHighestVersionCode": "自动选择版本号最高的 APK 文件",
"checkUpdateOnDetailPage": "打开应用详情页时检查更新",
"checkUpdateOnDetailPage": "打开应用详情页时进行检查",
"disablePageTransitions": "禁用页面过渡动画效果",
"reversePageTransitions": "反转页面过渡动画效果",
"minStarCount": "最小星标数",
@ -275,18 +275,22 @@
"appsPossiblyUpdatedNotifChannel": "已尝试更新应用",
"errorCheckingUpdatesNotifChannel": "检查更新出错",
"appsRemovedNotifChannel": "应用已删除",
"downloadingXNotifChannel": "正在下载{}",
"downloadingXNotifChannel": "正在下载{}",
"completeAppInstallationNotifChannel": "完成应用安装",
"checkingForUpdatesNotifChannel": "正在检查更新",
"onlyCheckInstalledOrTrackOnlyApps": "只已安装和“仅追踪”的应用进行更新检查",
"onlyCheckInstalledOrTrackOnlyApps": "只检查已安装和“仅追踪”的应用",
"supportFixedAPKURL": "支持固定的 APK 文件链接",
"selectX": "选择 {}",
"selectX": "选择{}",
"parallelDownloads": "启用并行下载",
"installMethod": "安装方式",
"normal": "常规",
"shizuku": "Shizuku",
"root": "Root",
"shizukuBinderNotFound": "Shizuku 服务未运行",
"shizukuBinderNotFound": "未发现兼容的 Shizuku 服务",
"useSystemFont": "使用系统字体",
"systemFontError": "加载系统字体出错:{}",
"useVersionCodeAsOSVersion": "Use app versionCode as OS-detected version",
"requestHeader": "Request header",
"removeAppQuestion": {
"one": "是否删除应用?",
"other": "是否删除应用?"
@ -328,15 +332,15 @@
"other": "清除了 {n} 个日志({before} 之前,{after} 之后)"
},
"xAndNMoreUpdatesAvailable": {
"one": "{} 和另外 1 个应用可以更新了。",
"other": "{} 和另外 {} 个应用可以更新了。"
"one": "{}和另外 1 个应用可以更新了。",
"other": "{}和另外 {} 个应用可以更新了。"
},
"xAndNMoreUpdatesInstalled": {
"one": "{} 和另外 1 个应用已更新。",
"other": "{} 和另外 {} 个应用已更新。"
"other": "{}和另外 {} 个应用已更新。"
},
"xAndNMoreUpdatesPossiblyInstalled": {
"one": "{} 和另外 1 个应用已尝试更新。",
"other": "{} 和另外 {} 个应用已尝试更新。"
"other": "{}和另外 {} 个应用已尝试更新。"
}
}

View File

@ -5,17 +5,19 @@ import 'package:obtainium/providers/source_provider.dart';
class APKCombo extends AppSource {
APKCombo() {
host = 'apkcombo.com';
hosts = ['apkcombo.com'];
}
@override
String sourceSpecificStandardizeURL(String url) {
RegExp standardUrlRegEx = RegExp('^https?://(www\\.)?$host/+[^/]+/+[^/]+');
var match = standardUrlRegEx.firstMatch(url.toLowerCase());
RegExp standardUrlRegEx = RegExp(
'^https?://(www\\.)?${getSourceRegex(hosts)}/+[^/]+/+[^/]+',
caseSensitive: false);
var match = standardUrlRegEx.firstMatch(url);
if (match == null) {
throw InvalidURLError(name);
}
return url.substring(0, match.end);
return match.group(0)!;
}
@override
@ -26,18 +28,19 @@ class APKCombo extends AppSource {
@override
Future<Map<String, String>?> getRequestHeaders(
{Map<String, dynamic> additionalSettings = const <String, dynamic>{},
bool forAPKDownload = false}) async {
Map<String, dynamic> additionalSettings,
{bool forAPKDownload = false}) async {
return {
"User-Agent": "curl/8.0.1",
"Accept": "*/*",
"Connection": "keep-alive",
"Host": "$host"
"Host": hosts[0]
};
}
Future<List<MapEntry<String, String>>> getApkUrls(String standardUrl) async {
var res = await sourceRequest('$standardUrl/download/apk');
Future<List<MapEntry<String, String>>> getApkUrls(
String standardUrl, Map<String, dynamic> additionalSettings) async {
var res = await sourceRequest('$standardUrl/download/apk', {});
if (res.statusCode != 200) {
throw getObtainiumHttpError(res);
}
@ -70,9 +73,9 @@ class APKCombo extends AppSource {
}
@override
Future<String> apkUrlPrefetchModifier(
String apkUrl, String standardUrl) async {
var freshURLs = await getApkUrls(standardUrl);
Future<String> apkUrlPrefetchModifier(String apkUrl, String standardUrl,
Map<String, dynamic> additionalSettings) async {
var freshURLs = await getApkUrls(standardUrl, additionalSettings);
var path2Match = Uri.parse(apkUrl).path;
for (var url in freshURLs) {
if (Uri.parse(url.value).path == path2Match) {
@ -88,7 +91,7 @@ class APKCombo extends AppSource {
Map<String, dynamic> additionalSettings,
) async {
String appId = (await tryInferringAppId(standardUrl))!;
var preres = await sourceRequest(standardUrl);
var preres = await sourceRequest(standardUrl, additionalSettings);
if (preres.statusCode != 200) {
throw getObtainiumHttpError(preres);
}
@ -112,7 +115,9 @@ class APKCombo extends AppSource {
}
}
return APKDetails(
version, await getApkUrls(standardUrl), AppNames(author, appName),
version,
await getApkUrls(standardUrl, additionalSettings),
AppNames(author, appName),
releaseDate: releaseDate);
}
}

View File

@ -9,7 +9,7 @@ import 'package:obtainium/providers/source_provider.dart';
class APKMirror extends AppSource {
APKMirror() {
host = 'apkmirror.com';
hosts = ['apkmirror.com'];
enforceTrackOnly = true;
additionalSourceAppSpecificSettingFormItems = [
@ -32,13 +32,14 @@ class APKMirror extends AppSource {
@override
String sourceSpecificStandardizeURL(String url) {
RegExp standardUrlRegEx =
RegExp('^https?://(www\\.)?$host/apk/[^/]+/[^/]+');
RegExpMatch? match = standardUrlRegEx.firstMatch(url.toLowerCase());
RegExp standardUrlRegEx = RegExp(
'^https?://(www\\.)?${getSourceRegex(hosts)}/apk/[^/]+/[^/]+',
caseSensitive: false);
RegExpMatch? match = standardUrlRegEx.firstMatch(url);
if (match == null) {
throw InvalidURLError(name);
}
return url.substring(0, match.end);
return match.group(0)!;
}
@override
@ -58,7 +59,7 @@ class APKMirror extends AppSource {
true
? additionalSettings['filterReleaseTitlesByRegEx']
: null;
Response res = await sourceRequest('$standardUrl/feed');
Response res = await sourceRequest('$standardUrl/feed', additionalSettings);
if (res.statusCode == 200) {
var items = parse(res.body).querySelectorAll('item');
dynamic targetRelease;
@ -84,7 +85,7 @@ class APKMirror extends AppSource {
dateString != null ? HttpDate.parse('$dateString GMT') : null;
String? version = titleString
?.substring(RegExp('[0-9]').firstMatch(titleString)?.start ?? 0,
RegExp(' by ').firstMatch(titleString)?.start ?? 0)
RegExp(' by ').allMatches(titleString).last.start)
.trim();
if (version == null || version.isEmpty) {
version = titleString;

View File

@ -20,26 +20,28 @@ parseDateTimeMMMddCommayyyy(String? dateString) {
class APKPure extends AppSource {
APKPure() {
host = 'apkpure.com';
hosts = ['apkpure.net', 'apkpure.com'];
allowSubDomains = true;
naiveStandardVersionDetection = true;
}
@override
String sourceSpecificStandardizeURL(String url) {
RegExp standardUrlRegExB =
RegExp('^https?://m.$host/+[^/]+/+[^/]+(/+[^/]+)?');
RegExpMatch? match = standardUrlRegExB.firstMatch(url.toLowerCase());
RegExp standardUrlRegExB = RegExp(
'^https?://m.${getSourceRegex(hosts)}/+[^/]+/+[^/]+(/+[^/]+)?',
caseSensitive: false);
RegExpMatch? match = standardUrlRegExB.firstMatch(url);
if (match != null) {
url = 'https://$host${Uri.parse(url).path}';
url = 'https://${getSourceRegex(hosts)}${Uri.parse(url).path}';
}
RegExp standardUrlRegExA =
RegExp('^https?://(www\\.)?$host/+[^/]+/+[^/]+(/+[^/]+)?');
match = standardUrlRegExA.firstMatch(url.toLowerCase());
RegExp standardUrlRegExA = RegExp(
'^https?://(www\\.)?${getSourceRegex(hosts)}/+[^/]+/+[^/]+(/+[^/]+)?',
caseSensitive: false);
match = standardUrlRegExA.firstMatch(url);
if (match == null) {
throw InvalidURLError(name);
}
return url.substring(0, match.end);
return match.group(0)!;
}
@override
@ -55,8 +57,8 @@ class APKPure extends AppSource {
) async {
String appId = (await tryInferringAppId(standardUrl))!;
String host = Uri.parse(standardUrl).host;
var res = await sourceRequest('$standardUrl/download');
var resChangelog = await sourceRequest(standardUrl);
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);
@ -69,7 +71,8 @@ class APKPure extends AppSource {
DateTime? releaseDate = parseDateTimeMMMddCommayyyy(dateString);
String type = html.querySelector('a.info-tag')?.text.trim() ?? 'APK';
List<MapEntry<String, String>> apkUrls = [
MapEntry('$appId.apk', 'https://d.$host/b/$type/$appId?version=latest')
MapEntry('$appId.apk',
'https://d.${hosts.contains(host) ? 'cdnpure.com' : host}/b/$type/$appId?version=latest')
];
String author = html
.querySelector('span.info-sdk')

View File

@ -6,7 +6,7 @@ import 'package:obtainium/providers/source_provider.dart';
class Aptoide extends AppSource {
Aptoide() {
host = 'aptoide.com';
hosts = ['aptoide.com'];
name = 'Aptoide';
allowSubDomains = true;
naiveStandardVersionDetection = true;
@ -14,22 +14,26 @@ class Aptoide extends AppSource {
@override
String sourceSpecificStandardizeURL(String url) {
RegExp standardUrlRegEx = RegExp('^https?://([^\\.]+\\.){2,}$host');
RegExpMatch? match = standardUrlRegEx.firstMatch(url.toLowerCase());
RegExp standardUrlRegEx = RegExp(
'^https?://([^\\.]+\\.){2,}${getSourceRegex(hosts)}',
caseSensitive: false);
RegExpMatch? match = standardUrlRegEx.firstMatch(url);
if (match == null) {
throw InvalidURLError(name);
}
return url.substring(0, match.end);
return match.group(0)!;
}
@override
Future<String?> tryInferringAppId(String standardUrl,
{Map<String, dynamic> additionalSettings = const {}}) async {
return (await getAppDetailsJSON(standardUrl))['package'];
return (await getAppDetailsJSON(
standardUrl, additionalSettings))['package'];
}
Future<Map<String, dynamic>> getAppDetailsJSON(String standardUrl) async {
var res = await sourceRequest(standardUrl);
Future<Map<String, dynamic>> getAppDetailsJSON(
String standardUrl, Map<String, dynamic> additionalSettings) async {
var res = await sourceRequest(standardUrl, additionalSettings);
if (res.statusCode != 200) {
throw getObtainiumHttpError(res);
}
@ -40,8 +44,8 @@ class Aptoide extends AppSource {
} else {
throw NoReleasesError();
}
var res2 =
await sourceRequest('https://ws2.aptoide.com/api/7/getApp/app_id/$id');
var res2 = await sourceRequest(
'https://ws2.aptoide.com/api/7/getApp/app_id/$id', additionalSettings);
if (res2.statusCode != 200) {
throw getObtainiumHttpError(res);
}
@ -53,7 +57,7 @@ class Aptoide extends AppSource {
String standardUrl,
Map<String, dynamic> additionalSettings,
) async {
var appDetails = await getAppDetailsJSON(standardUrl);
var appDetails = await getAppDetailsJSON(standardUrl, additionalSettings);
String appName = appDetails['name'] ?? tr('app');
String author = appDetails['developer']?['name'] ?? name;
String? dateStr = appDetails['updated'];

View File

@ -5,7 +5,7 @@ import 'package:obtainium/providers/source_provider.dart';
class Codeberg extends AppSource {
GitHub gh = GitHub();
Codeberg() {
host = 'codeberg.org';
hosts = ['codeberg.org'];
additionalSourceAppSpecificSettingFormItems =
gh.additionalSourceAppSpecificSettingFormItems;
@ -16,12 +16,14 @@ class Codeberg extends AppSource {
@override
String sourceSpecificStandardizeURL(String url) {
RegExp standardUrlRegEx = RegExp('^https?://(www\\.)?$host/[^/]+/[^/]+');
RegExpMatch? match = standardUrlRegEx.firstMatch(url.toLowerCase());
RegExp standardUrlRegEx = RegExp(
'^https?://(www\\.)?${getSourceRegex(hosts)}/[^/]+/[^/]+',
caseSensitive: false);
RegExpMatch? match = standardUrlRegEx.firstMatch(url);
if (match == null) {
throw InvalidURLError(name);
}
return url.substring(0, match.end);
return match.group(0)!;
}
@override
@ -35,7 +37,7 @@ class Codeberg extends AppSource {
) async {
return await gh.getLatestAPKDetailsCommon2(standardUrl, additionalSettings,
(bool useTagUrl) async {
return 'https://$host/api/v1/repos${standardUrl.substring('https://$host'.length)}/${useTagUrl ? 'tags' : 'releases'}?per_page=100';
return 'https://${hosts[0]}/api/v1/repos${standardUrl.substring('https://${hosts[0]}'.length)}/${useTagUrl ? 'tags' : 'releases'}?per_page=100';
}, null);
}
@ -50,7 +52,7 @@ class Codeberg extends AppSource {
{Map<String, dynamic> querySettings = const {}}) async {
return gh.searchCommon(
query,
'https://$host/api/v1/repos/search?q=${Uri.encodeQueryComponent(query)}&limit=100',
'https://${hosts[0]}/api/v1/repos/search?q=${Uri.encodeQueryComponent(query)}&limit=100',
'data',
querySettings: querySettings);
}

View File

@ -9,7 +9,7 @@ import 'package:obtainium/providers/source_provider.dart';
class FDroid extends AppSource {
FDroid() {
host = 'f-droid.org';
hosts = ['f-droid.org'];
name = tr('fdroid');
naiveStandardVersionDetection = true;
canSearch = true;
@ -37,20 +37,22 @@ class FDroid extends AppSource {
@override
String sourceSpecificStandardizeURL(String url) {
RegExp standardUrlRegExB =
RegExp('^https?://(www\\.)?$host/+[^/]+/+packages/+[^/]+');
RegExpMatch? match = standardUrlRegExB.firstMatch(url.toLowerCase());
RegExp standardUrlRegExB = RegExp(
'^https?://(www\\.)?${getSourceRegex(hosts)}/+[^/]+/+packages/+[^/]+',
caseSensitive: false);
RegExpMatch? match = standardUrlRegExB.firstMatch(url);
if (match != null) {
url =
'https://${Uri.parse(url.substring(0, match.end)).host}/packages/${Uri.parse(url).pathSegments.last}';
'https://${Uri.parse(match.group(0)!).host}/packages/${Uri.parse(url).pathSegments.last}';
}
RegExp standardUrlRegExA =
RegExp('^https?://(www\\.)?$host/+packages/+[^/]+');
match = standardUrlRegExA.firstMatch(url.toLowerCase());
RegExp standardUrlRegExA = RegExp(
'^https?://(www\\.)?${getSourceRegex(hosts)}/+packages/+[^/]+',
caseSensitive: false);
match = standardUrlRegExA.firstMatch(url);
if (match == null) {
throw InvalidURLError(name);
}
return url.substring(0, match.end);
return match.group(0)!;
}
@override
@ -67,7 +69,8 @@ class FDroid extends AppSource {
String? appId = await tryInferringAppId(standardUrl);
String host = Uri.parse(standardUrl).host;
var details = getAPKUrlsFromFDroidPackagesAPIResponse(
await sourceRequest('https://$host/api/v1/packages/$appId'),
await sourceRequest(
'https://$host/api/v1/packages/$appId', additionalSettings),
'https://$host/repo/$appId',
standardUrl,
name,
@ -84,18 +87,32 @@ class FDroid extends AppSource {
if (!hostChanged) {
try {
var res = await sourceRequest(
'https://gitlab.com/fdroid/fdroiddata/-/raw/master/metadata/$appId.yml');
String author = res.body
.split('\n')
.where((l) => l.startsWith('AuthorName: '))
.first
.split(': ')
.sublist(1)
.join(': ');
details.names.author = author;
'https://gitlab.com/fdroid/fdroiddata/-/raw/master/metadata/$appId.yml',
additionalSettings);
var lines = res.body.split('\n');
var authorLines = lines.where((l) => l.startsWith('AuthorName: '));
if (authorLines.isNotEmpty) {
details.names.author =
authorLines.first.split(': ').sublist(1).join(': ');
}
var changelogUrls = lines.where((l) => l.startsWith('Changelog: '));
if (changelogUrls.isNotEmpty) {
details.changeLog = changelogUrls.first;
details.changeLog = (await sourceRequest(
details.changeLog!
.split(': ')
.sublist(1)
.join(': ')
.replaceFirst('/blob/', '/raw/'),
additionalSettings))
.body;
}
} catch (e) {
// Fail silently
}
if ((details.changeLog?.length ?? 0) > 2048) {
details.changeLog = '${details.changeLog!.substring(0, 2048)}...';
}
}
return details;
}
@ -104,7 +121,7 @@ class FDroid extends AppSource {
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)}');
'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) {

View File

@ -59,7 +59,7 @@ class FDroidRepo extends AppSource {
throw NoReleasesError();
}
url = removeQueryParamsFromUrl(standardizeUrl(url));
var res = await sourceRequest('$url/index.xml');
var res = await sourceRequest('$url/index.xml', {});
if (res.statusCode == 200) {
var body = parse(res.body);
Map<String, List<String>> results = {};
@ -117,7 +117,8 @@ class FDroidRepo extends AppSource {
throw NoReleasesError();
}
var res = await sourceRequest(
'$standardUrl${standardUrl.endsWith('/index.xml') ? '' : '/index.xml'}');
'$standardUrl${standardUrl.endsWith('/index.xml') ? '' : '/index.xml'}',
additionalSettings);
if (res.statusCode == 200) {
var body = parse(res.body);
var foundApps = body.querySelectorAll('application').where((element) {

View File

@ -14,7 +14,7 @@ import 'package:url_launcher/url_launcher_string.dart';
class GitHub extends AppSource {
GitHub() {
host = 'github.com';
hosts = ['github.com'];
appIdInferIsOptional = true;
sourceConfigSettingFormItems = [
@ -108,7 +108,8 @@ class GitHub extends AppSource {
for (var path in possibleBuildGradleLocations) {
try {
var res = await sourceRequest(
'${await convertStandardUrlToAPIUrl(standardUrl, additionalSettings)}/contents/$path');
'${await convertStandardUrlToAPIUrl(standardUrl, additionalSettings)}/contents/$path',
additionalSettings);
if (res.statusCode == 200) {
try {
var body = jsonDecode(res.body);
@ -149,18 +150,20 @@ class GitHub extends AppSource {
@override
String sourceSpecificStandardizeURL(String url) {
RegExp standardUrlRegEx = RegExp('^https?://(www\\.)?$host/[^/]+/[^/]+');
RegExpMatch? match = standardUrlRegEx.firstMatch(url.toLowerCase());
RegExp standardUrlRegEx = RegExp(
'^https?://(www\\.)?${getSourceRegex(hosts)}/[^/]+/[^/]+',
caseSensitive: false);
RegExpMatch? match = standardUrlRegEx.firstMatch(url);
if (match == null) {
throw InvalidURLError(name);
}
return url.substring(0, match.end);
return match.group(0)!;
}
@override
Future<Map<String, String>?> getRequestHeaders(
{Map<String, dynamic> additionalSettings = const <String, dynamic>{},
bool forAPKDownload = false}) async {
Map<String, dynamic> additionalSettings,
{bool forAPKDownload = false}) async {
var token = await getTokenIfAny(additionalSettings);
var headers = <String, String>{};
if (token != null) {
@ -203,11 +206,11 @@ class GitHub extends AppSource {
}
Future<String> getAPIHost(Map<String, dynamic> additionalSettings) async =>
'https://api.$host';
'https://api.${hosts[0]}';
Future<String> convertStandardUrlToAPIUrl(
String standardUrl, Map<String, dynamic> additionalSettings) async =>
'${await getAPIHost(additionalSettings)}/repos${standardUrl.substring('https://$host'.length)}';
'${await getAPIHost(additionalSettings)}/repos${standardUrl.substring('https://${hosts[0]}'.length)}';
@override
String? changeLogPageFromStandardUrl(String standardUrl) =>
@ -238,7 +241,8 @@ class GitHub extends AppSource {
if (verifyLatestTag) {
var temp = requestUrl.split('?');
Response res = await sourceRequest(
'${temp[0]}/latest${temp.length > 1 ? '?${temp.sublist(1).join('?')}' : ''}');
'${temp[0]}/latest${temp.length > 1 ? '?${temp.sublist(1).join('?')}' : ''}',
additionalSettings);
if (res.statusCode != 200) {
if (onHttpErrorCode != null) {
onHttpErrorCode(res);
@ -247,7 +251,7 @@ class GitHub extends AppSource {
}
latestRelease = jsonDecode(res.body);
}
Response res = await sourceRequest(requestUrl);
Response res = await sourceRequest(requestUrl, additionalSettings);
if (res.statusCode == 200) {
var releases = jsonDecode(res.body) as List<dynamic>;
if (latestRelease != null) {
@ -424,7 +428,7 @@ class GitHub extends AppSource {
String query, String requestUrl, String rootProp,
{Function(Response)? onHttpErrorCode,
Map<String, dynamic> querySettings = const {}}) async {
Response res = await sourceRequest(requestUrl);
Response res = await sourceRequest(requestUrl, {});
if (res.statusCode == 200) {
int minStarCount = querySettings['minStarCount'] != null
? int.parse(querySettings['minStarCount'])

View File

@ -13,7 +13,7 @@ import 'package:url_launcher/url_launcher_string.dart';
class GitLab extends AppSource {
GitLab() {
host = 'gitlab.com';
hosts = ['gitlab.com'];
canSearch = true;
sourceConfigSettingFormItems = [
@ -52,12 +52,14 @@ class GitLab extends AppSource {
@override
String sourceSpecificStandardizeURL(String url) {
RegExp standardUrlRegEx = RegExp('^https?://(www\\.)?$host/[^/]+/[^/]+');
RegExpMatch? match = standardUrlRegEx.firstMatch(url.toLowerCase());
RegExp standardUrlRegEx = RegExp(
'^https?://(www\\.)?${getSourceRegex(hosts)}/[^/]+/[^/]+',
caseSensitive: false);
RegExpMatch? match = standardUrlRegEx.firstMatch(url);
if (match == null) {
throw InvalidURLError(name);
}
return url.substring(0, match.end);
return match.group(0)!;
}
Future<String?> getPATIfAny(Map<String, dynamic> additionalSettings) async {
@ -81,15 +83,15 @@ class GitLab extends AppSource {
Future<Map<String, List<String>>> search(String query,
{Map<String, dynamic> querySettings = const {}}) async {
var url =
'https://$host/api/v4/projects?search=${Uri.encodeQueryComponent(query)}';
var res = await sourceRequest(url);
'https://${hosts[0]}/api/v4/projects?search=${Uri.encodeQueryComponent(query)}';
var res = await sourceRequest(url, {});
if (res.statusCode != 200) {
throw getObtainiumHttpError(res);
}
var json = jsonDecode(res.body) as List<dynamic>;
Map<String, List<String>> results = {};
for (var element in json) {
results['https://$host/${element['path_with_namespace']}'] = [
results['https://${hosts[0]}/${element['path_with_namespace']}'] = [
element['name_with_namespace'],
element['description'] ?? tr('noDescription')
];
@ -113,7 +115,8 @@ class GitLab extends AppSource {
if (PAT != null) {
var names = GitHub().getAppNames(standardUrl);
Response res = await sourceRequest(
'https://$host/api/v4/projects/${names.author}%2F${names.name}/releases?private_token=$PAT');
'https://${hosts[0]}/api/v4/projects/${names.author}%2F${names.name}/releases?private_token=$PAT',
additionalSettings);
if (res.statusCode != 200) {
throw getObtainiumHttpError(res);
}
@ -148,7 +151,8 @@ class GitLab extends AppSource {
releaseDate: releaseDate);
});
} else {
Response res = await sourceRequest('$standardUrl/-/tags?format=atom');
Response res = await sourceRequest(
'$standardUrl/-/tags?format=atom', additionalSettings);
if (res.statusCode != 200) {
throw getObtainiumHttpError(res);
}

View File

@ -17,13 +17,19 @@ String ensureAbsoluteUrl(String ambiguousUrl, Uri referenceAbsoluteUrl) {
.split('/')
.where((element) => element.trim().isNotEmpty)
.toList();
String absoluteUrl;
if (ambiguousUrl.startsWith('/') || currPathSegments.isEmpty) {
return '${referenceAbsoluteUrl.origin}/$ambiguousUrl';
absoluteUrl = '${referenceAbsoluteUrl.origin}$ambiguousUrl';
} else if (currPathSegments.isEmpty) {
absoluteUrl = '${referenceAbsoluteUrl.origin}/$ambiguousUrl';
} else if (ambiguousUrl.split('/').where((e) => e.isNotEmpty).length == 1) {
return '${referenceAbsoluteUrl.origin}/${currPathSegments.join('/')}/$ambiguousUrl';
absoluteUrl =
'${referenceAbsoluteUrl.origin}/${currPathSegments.join('/')}/$ambiguousUrl';
} else {
return '${referenceAbsoluteUrl.origin}/${currPathSegments.sublist(0, currPathSegments.length - (currPathSegments.last.contains('.') ? 1 : 0)).join('/')}/$ambiguousUrl';
absoluteUrl =
'${referenceAbsoluteUrl.origin}/${currPathSegments.sublist(0, currPathSegments.length - (currPathSegments.last.contains('.') ? 1 : 0)).join('/')}/$ambiguousUrl';
}
return Uri.parse(absoluteUrl).toString();
}
int compareAlphaNumeric(String a, String b) {
@ -135,7 +141,37 @@ class HTML extends AppSource {
],
finalStepFormitems[0],
...commonFormItems,
...finalStepFormitems.sublist(1)
...finalStepFormitems.sublist(1),
[
GeneratedFormSubForm(
'requestHeader',
[
[
GeneratedFormTextField('requestHeader',
label: tr('requestHeader'),
additionalValidators: [
(value) {
if ((value ?? 'empty:valid')
.split(':')
.map((e) => e.trim())
.where((e) => e.isNotEmpty)
.length <
2) {
return tr('invalidInput');
}
return null;
}
])
]
],
label: tr('requestHeader'),
defaultValue: [
{
'requestHeader':
'User-Agent: Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Mobile Safari/537.36'
}
])
]
];
overrideVersionDetectionFormDefault('noVersionDetection',
disableStandard: false, disableRelDate: true);
@ -143,12 +179,25 @@ class HTML extends AppSource {
@override
Future<Map<String, String>?> getRequestHeaders(
{Map<String, dynamic> additionalSettings = const <String, dynamic>{},
bool forAPKDownload = false}) async {
return {
"User-Agent":
"Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Mobile Safari/537.36"
};
Map<String, dynamic> additionalSettings,
{bool forAPKDownload = false}) async {
if (additionalSettings.isNotEmpty) {
if (additionalSettings['requestHeader']?.isNotEmpty != true) {
additionalSettings['requestHeader'] = [];
}
additionalSettings['requestHeader'] = additionalSettings['requestHeader']
.where((l) => l['requestHeader'].isNotEmpty == true)
.toList();
Map<String, String> requestHeaders = {};
for (int i = 0; i < (additionalSettings['requestHeader'].length); i++) {
var temp =
(additionalSettings['requestHeader'][i]['requestHeader'] as String)
.split(':');
requestHeaders[temp[0].trim()] = temp.sublist(1).join(':').trim();
}
return requestHeaders;
}
return null;
}
@override
@ -172,6 +221,8 @@ 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))
.toList();
if (allLinks.isEmpty) {
allLinks = RegExp(
@ -227,7 +278,8 @@ class HTML extends AppSource {
.where((l) => l['customLinkFilterRegex'].isNotEmpty == true)
.toList();
for (int i = 0; i < (additionalSettings['intermediateLink'].length); i++) {
var intLinks = await grabLinksCommon(await sourceRequest(currentUrl),
var intLinks = await grabLinksCommon(
await sourceRequest(currentUrl, additionalSettings),
additionalSettings['intermediateLink'][i]);
if (intLinks.isEmpty) {
throw NoReleasesError();
@ -237,7 +289,7 @@ class HTML extends AppSource {
}
var uri = Uri.parse(currentUrl);
Response res = await sourceRequest(currentUrl);
Response res = await sourceRequest(currentUrl, additionalSettings);
var links = await grabLinksCommon(res, additionalSettings);
if ((additionalSettings['apkFilterRegEx'] as String?)?.isNotEmpty == true) {
@ -258,7 +310,6 @@ class HTML extends AppSource {
additionalSettings['versionExtractWholePage'] == true
? res.body.split('\r\n').join('\n').split('\n').join('\\n')
: rel);
rel = ensureAbsoluteUrl(rel, uri);
version ??= (await checkDownloadHash(rel)).toString();
return APKDetails(version, [rel].map((e) => MapEntry(e, e)).toList(),
AppNames(uri.host, tr('app')));

View File

@ -6,26 +6,30 @@ import 'package:obtainium/providers/source_provider.dart';
class HuaweiAppGallery extends AppSource {
HuaweiAppGallery() {
name = 'Huawei AppGallery';
host = 'appgallery.huawei.com';
hosts = ['appgallery.huawei.com'];
overrideVersionDetectionFormDefault('releaseDateAsVersion',
disableStandard: true);
}
@override
String sourceSpecificStandardizeURL(String url) {
RegExp standardUrlRegEx = RegExp('^https?://(www\\.)?$host/app/[^/]+');
RegExpMatch? match = standardUrlRegEx.firstMatch(url.toLowerCase());
RegExp standardUrlRegEx = RegExp(
'^https?://(www\\.)?${getSourceRegex(hosts)}/app/[^/]+',
caseSensitive: false);
RegExpMatch? match = standardUrlRegEx.firstMatch(url);
if (match == null) {
throw InvalidURLError(name);
}
return url.substring(0, match.end);
return match.group(0)!;
}
getDlUrl(String standardUrl) =>
'https://${host!.replaceAll('appgallery.', 'appgallery.cloud.')}/appdl/${standardUrl.split('/').last}';
'https://${hosts[0].replaceAll('appgallery.', 'appgallery.cloud.')}/appdl/${standardUrl.split('/').last}';
requestAppdlRedirect(String dlUrl) async {
Response res = await sourceRequest(dlUrl, followRedirects: false);
requestAppdlRedirect(
String dlUrl, Map<String, dynamic> additionalSettings) async {
Response res =
await sourceRequest(dlUrl, additionalSettings, followRedirects: false);
if (res.statusCode == 200 ||
res.statusCode == 302 ||
res.statusCode == 304) {
@ -52,7 +56,7 @@ class HuaweiAppGallery extends AppSource {
Future<String?> tryInferringAppId(String standardUrl,
{Map<String, dynamic> additionalSettings = const {}}) async {
String dlUrl = getDlUrl(standardUrl);
Response res = await requestAppdlRedirect(dlUrl);
Response res = await requestAppdlRedirect(dlUrl, additionalSettings);
return res.headers['location'] != null
? appIdFromRedirectDlUrl(res.headers['location']!)
: null;
@ -64,7 +68,7 @@ class HuaweiAppGallery extends AppSource {
Map<String, dynamic> additionalSettings,
) async {
String dlUrl = getDlUrl(standardUrl);
Response res = await requestAppdlRedirect(dlUrl);
Response res = await requestAppdlRedirect(dlUrl, additionalSettings);
if (res.headers['location'] == null) {
throw NoReleasesError();
}

View File

@ -6,7 +6,7 @@ class IzzyOnDroid extends AppSource {
late FDroid fd;
IzzyOnDroid() {
host = 'izzysoft.de';
hosts = ['izzysoft.de'];
fd = FDroid();
additionalSourceAppSpecificSettingFormItems =
fd.additionalSourceAppSpecificSettingFormItems;
@ -15,17 +15,20 @@ class IzzyOnDroid extends AppSource {
@override
String sourceSpecificStandardizeURL(String url) {
RegExp standardUrlRegExA = RegExp('^https?://android.$host/repo/apk/[^/]+');
RegExpMatch? match = standardUrlRegExA.firstMatch(url.toLowerCase());
RegExp standardUrlRegExA = RegExp(
'^https?://android.${getSourceRegex(hosts)}/repo/apk/[^/]+',
caseSensitive: false);
RegExpMatch? match = standardUrlRegExA.firstMatch(url);
if (match == null) {
RegExp standardUrlRegExB =
RegExp('^https?://apt.$host/fdroid/index/apk/[^/]+');
match = standardUrlRegExB.firstMatch(url.toLowerCase());
RegExp standardUrlRegExB = RegExp(
'^https?://apt.${getSourceRegex(hosts)}/fdroid/index/apk/[^/]+',
caseSensitive: false);
match = standardUrlRegExB.firstMatch(url);
}
if (match == null) {
throw InvalidURLError(name);
}
return url.substring(0, match.end);
return match.group(0)!;
}
@override
@ -42,7 +45,8 @@ class IzzyOnDroid extends AppSource {
String? appId = await tryInferringAppId(standardUrl);
return fd.getAPKUrlsFromFDroidPackagesAPIResponse(
await sourceRequest(
'https://apt.izzysoft.de/fdroid/api/v1/packages/$appId'),
'https://apt.izzysoft.de/fdroid/api/v1/packages/$appId',
additionalSettings),
'https://android.izzysoft.de/frepo/$appId',
standardUrl,
name,

View File

@ -8,6 +8,7 @@ class Jenkins extends AppSource {
Jenkins() {
overrideVersionDetectionFormDefault('releaseDateAsVersion',
disableStandard: true);
neverAutoSelect = true;
}
String trimJobUrl(String url) {
@ -16,7 +17,7 @@ class Jenkins extends AppSource {
if (match == null) {
throw InvalidURLError(name);
}
return url.substring(0, match.end);
return match.group(0)!;
}
@override
@ -29,8 +30,8 @@ class Jenkins extends AppSource {
Map<String, dynamic> additionalSettings,
) async {
standardUrl = trimJobUrl(standardUrl);
Response res =
await sourceRequest('$standardUrl/lastSuccessfulBuild/api/json');
Response res = await sourceRequest(
'$standardUrl/lastSuccessfulBuild/api/json', additionalSettings);
if (res.statusCode == 200) {
var json = jsonDecode(res.body);
var releaseDate = json['timestamp'] == null

View File

@ -6,17 +6,19 @@ import 'package:obtainium/providers/source_provider.dart';
class Mullvad extends AppSource {
Mullvad() {
host = 'mullvad.net';
hosts = ['mullvad.net'];
}
@override
String sourceSpecificStandardizeURL(String url) {
RegExp standardUrlRegEx = RegExp('^https?://(www\\.)?$host');
RegExpMatch? match = standardUrlRegEx.firstMatch(url.toLowerCase());
RegExp standardUrlRegEx = RegExp(
'^https?://(www\\.)?${getSourceRegex(hosts)}',
caseSensitive: false);
RegExpMatch? match = standardUrlRegEx.firstMatch(url);
if (match == null) {
throw InvalidURLError(name);
}
return url.substring(0, match.end);
return match.group(0)!;
}
@override
@ -28,7 +30,8 @@ class Mullvad extends AppSource {
String standardUrl,
Map<String, dynamic> additionalSettings,
) async {
Response res = await sourceRequest('$standardUrl/en/download/android');
Response res = await sourceRequest(
'$standardUrl/en/download/android', additionalSettings);
if (res.statusCode == 200) {
var versions = parse(res.body)
.querySelectorAll('p')

View File

@ -5,18 +5,19 @@ import 'package:obtainium/providers/source_provider.dart';
class NeutronCode extends AppSource {
NeutronCode() {
host = 'neutroncode.com';
hosts = ['neutroncode.com'];
}
@override
String sourceSpecificStandardizeURL(String url) {
RegExp standardUrlRegEx =
RegExp('^https?://(www\\.)?$host/downloads/file/[^/]+');
RegExpMatch? match = standardUrlRegEx.firstMatch(url.toLowerCase());
RegExp standardUrlRegEx = RegExp(
'^https?://(www\\.)?${getSourceRegex(hosts)}/downloads/file/[^/]+',
caseSensitive: false);
RegExpMatch? match = standardUrlRegEx.firstMatch(url);
if (match == null) {
throw InvalidURLError(name);
}
return url.substring(0, match.end);
return match.group(0)!;
}
@override
@ -79,7 +80,7 @@ class NeutronCode extends AppSource {
String standardUrl,
Map<String, dynamic> additionalSettings,
) async {
Response res = await sourceRequest(standardUrl);
Response res = await sourceRequest(standardUrl, additionalSettings);
if (res.statusCode == 200) {
var http = parse(res.body);
var name = http.querySelector('.pd-title')?.innerHtml;
@ -92,7 +93,7 @@ class NeutronCode extends AppSource {
if (version == null) {
throw NoVersionError();
}
String? apkUrl = 'https://$host/download/$filename';
String? apkUrl = 'https://${hosts[0]}/download/$filename';
var dateStringOriginal =
http.querySelector('.pd-date-txt')?.nextElementSibling?.innerHtml;
var dateString = dateStringOriginal != null

View File

@ -5,12 +5,12 @@ import 'package:obtainium/providers/source_provider.dart';
class Signal extends AppSource {
Signal() {
host = 'signal.org';
hosts = ['signal.org'];
}
@override
String sourceSpecificStandardizeURL(String url) {
return 'https://$host';
return 'https://${hosts[0]}';
}
@override
@ -18,8 +18,8 @@ class Signal extends AppSource {
String standardUrl,
Map<String, dynamic> additionalSettings,
) async {
Response res =
await sourceRequest('https://updates.$host/android/latest.json');
Response res = await sourceRequest(
'https://updates.${hosts[0]}/android/latest.json', additionalSettings);
if (res.statusCode == 200) {
var json = jsonDecode(res.body);
String? apkUrl = json['url'];

View File

@ -5,24 +5,27 @@ import 'package:obtainium/providers/source_provider.dart';
class SourceForge extends AppSource {
SourceForge() {
host = 'sourceforge.net';
hosts = ['sourceforge.net'];
}
@override
String sourceSpecificStandardizeURL(String url) {
RegExp standardUrlRegExB = RegExp('^https?://(www\\.)?$host/p/[^/]+');
RegExpMatch? match = standardUrlRegExB.firstMatch(url.toLowerCase());
RegExp standardUrlRegExB = RegExp(
'^https?://(www\\.)?${getSourceRegex(hosts)}/p/[^/]+',
caseSensitive: false);
RegExpMatch? match = standardUrlRegExB.firstMatch(url);
if (match != null) {
url =
'https://${Uri.parse(url.substring(0, match.end)).host}/projects/${url.substring(Uri.parse(url.substring(0, match.end)).host.length + '/projects/'.length + 1)}';
'https://${Uri.parse(match.group(0)!).host}/projects/${url.substring(Uri.parse(match.group(0)!).host.length + '/projects/'.length + 1)}';
}
RegExp standardUrlRegExA =
RegExp('^https?://(www\\.)?$host/projects/[^/]+');
match = standardUrlRegExA.firstMatch(url.toLowerCase());
RegExp standardUrlRegExA = RegExp(
'^https?://(www\\.)?${getSourceRegex(hosts)}/projects/[^/]+',
caseSensitive: false);
match = standardUrlRegExA.firstMatch(url);
if (match == null) {
throw InvalidURLError(name);
}
return url.substring(0, match.end);
return match.group(0)!;
}
@override
@ -30,7 +33,8 @@ class SourceForge extends AppSource {
String standardUrl,
Map<String, dynamic> additionalSettings,
) async {
Response res = await sourceRequest('$standardUrl/rss?path=/');
Response res =
await sourceRequest('$standardUrl/rss?path=/', additionalSettings);
if (res.statusCode == 200) {
var parsedHtml = parse(res.body);
var allDownloadLinks =

View File

@ -8,7 +8,7 @@ import 'package:easy_localization/easy_localization.dart';
class SourceHut extends AppSource {
SourceHut() {
host = 'git.sr.ht';
hosts = ['git.sr.ht'];
additionalSourceAppSpecificSettingFormItems = [
[
@ -20,12 +20,14 @@ class SourceHut extends AppSource {
@override
String sourceSpecificStandardizeURL(String url) {
RegExp standardUrlRegEx = RegExp('^https?://(www\\.)?$host/[^/]+/[^/]+');
RegExpMatch? match = standardUrlRegEx.firstMatch(url.toLowerCase());
RegExp standardUrlRegEx = RegExp(
'^https?://(www\\.)?${getSourceRegex(hosts)}/[^/]+/[^/]+',
caseSensitive: false);
RegExpMatch? match = standardUrlRegEx.firstMatch(url);
if (match == null) {
throw InvalidURLError(name);
}
return url.substring(0, match.end);
return match.group(0)!;
}
@override
@ -40,7 +42,8 @@ class SourceHut extends AppSource {
String appName = standardUri.pathSegments.last;
bool fallbackToOlderReleases =
additionalSettings['fallbackToOlderReleases'] == true;
Response res = await sourceRequest('$standardUrl/refs/rss.xml');
Response res =
await sourceRequest('$standardUrl/refs/rss.xml', additionalSettings);
if (res.statusCode == 200) {
var parsedHtml = parse(res.body);
List<APKDetails> apkDetailsList = [];
@ -69,7 +72,7 @@ class SourceHut extends AppSource {
} catch (e) {
// ignore
}
var res2 = await sourceRequest(releasePage);
var res2 = await sourceRequest(releasePage, additionalSettings);
List<MapEntry<String, String>> apkUrls = [];
if (res2.statusCode == 200) {
apkUrls = getApkUrlsFromUrls(parse(res2.body)

View File

@ -7,7 +7,7 @@ import 'package:obtainium/providers/source_provider.dart';
class SteamMobile extends AppSource {
SteamMobile() {
host = 'store.steampowered.com';
hosts = ['store.steampowered.com'];
name = tr('steam');
additionalSourceAppSpecificSettingFormItems = [
[
@ -21,7 +21,7 @@ class SteamMobile extends AppSource {
@override
String sourceSpecificStandardizeURL(String url) {
return 'https://$host';
return 'https://${hosts[0]}';
}
@override
@ -29,7 +29,8 @@ class SteamMobile extends AppSource {
String standardUrl,
Map<String, dynamic> additionalSettings,
) async {
Response res = await sourceRequest('https://$host/mobile');
Response res =
await sourceRequest('https://${hosts[0]}/mobile', additionalSettings);
if (res.statusCode == 200) {
var apkNamePrefix = additionalSettings['app'] as String?;
if (apkNamePrefix == null) {

View File

@ -6,13 +6,13 @@ import 'package:obtainium/providers/source_provider.dart';
class TelegramApp extends AppSource {
TelegramApp() {
host = 'telegram.org';
hosts = ['telegram.org'];
name = 'Telegram ${tr('app')}';
}
@override
String sourceSpecificStandardizeURL(String url) {
return 'https://$host';
return 'https://${hosts[0]}';
}
@override
@ -20,7 +20,8 @@ class TelegramApp extends AppSource {
String standardUrl,
Map<String, dynamic> additionalSettings,
) async {
Response res = await sourceRequest('https://t.me/s/TAndroidAPK');
Response res =
await sourceRequest('https://t.me/s/TAndroidAPK', additionalSettings);
if (res.statusCode == 200) {
var http = parse(res.body);
var messages =

View File

@ -6,29 +6,33 @@ import 'package:obtainium/providers/source_provider.dart';
class Uptodown extends AppSource {
Uptodown() {
host = 'uptodown.com';
hosts = ['uptodown.com'];
allowSubDomains = true;
naiveStandardVersionDetection = true;
}
@override
String sourceSpecificStandardizeURL(String url) {
RegExp standardUrlRegEx = RegExp('^https?://([^\\.]+\\.){2,}$host');
RegExpMatch? match = standardUrlRegEx.firstMatch(url.toLowerCase());
RegExp standardUrlRegEx = RegExp(
'^https?://([^\\.]+\\.){2,}${getSourceRegex(hosts)}',
caseSensitive: false);
RegExpMatch? match = standardUrlRegEx.firstMatch(url);
if (match == null) {
throw InvalidURLError(name);
}
return '${url.substring(0, match.end)}/android/download';
return '${match.group(0)!}/android/download';
}
@override
Future<String?> tryInferringAppId(String standardUrl,
{Map<String, dynamic> additionalSettings = const {}}) async {
return (await getAppDetailsFromPage(standardUrl))['appId'];
return (await getAppDetailsFromPage(
standardUrl, additionalSettings))['appId'];
}
Future<Map<String, String?>> getAppDetailsFromPage(String standardUrl) async {
var res = await sourceRequest(standardUrl);
Future<Map<String, String?>> getAppDetailsFromPage(
String standardUrl, Map<String, dynamic> additionalSettings) async {
var res = await sourceRequest(standardUrl, additionalSettings);
if (res.statusCode != 200) {
throw getObtainiumHttpError(res);
}
@ -56,7 +60,8 @@ class Uptodown extends AppSource {
String standardUrl,
Map<String, dynamic> additionalSettings,
) async {
var appDetails = await getAppDetailsFromPage(standardUrl);
var appDetails =
await getAppDetailsFromPage(standardUrl, additionalSettings);
var version = appDetails['version'];
var apkUrl = appDetails['apkUrl'];
var appId = appDetails['appId'];
@ -82,9 +87,9 @@ class Uptodown extends AppSource {
}
@override
Future<String> apkUrlPrefetchModifier(
String apkUrl, String standardUrl) async {
var res = await sourceRequest(apkUrl);
Future<String> apkUrlPrefetchModifier(String apkUrl, String standardUrl,
Map<String, dynamic> additionalSettings) async {
var res = await sourceRequest(apkUrl, additionalSettings);
if (res.statusCode != 200) {
throw getObtainiumHttpError(res);
}
@ -94,6 +99,6 @@ class Uptodown extends AppSource {
if (finalUrlKey == null) {
throw NoAPKError();
}
return 'https://dw.$host/dwn/$finalUrlKey';
return 'https://dw.${hosts[0]}/dwn/$finalUrlKey';
}
}

View File

@ -1,31 +1,33 @@
import 'package:easy_localization/easy_localization.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';
class VLC extends AppSource {
VLC() {
host = 'videolan.org';
hosts = ['videolan.org'];
}
get dwUrlBase => 'https://get.$host/vlc-android/';
get dwUrlBase => 'https://get.${hosts[0]}/vlc-android/';
@override
Future<Map<String, String>?> getRequestHeaders(
{Map<String, dynamic> additionalSettings = const <String, dynamic>{},
bool forAPKDownload = false}) =>
HTML().getRequestHeaders(
additionalSettings: additionalSettings,
forAPKDownload: forAPKDownload);
Map<String, dynamic> additionalSettings,
{bool forAPKDownload = false}) async {
return {
"User-Agent":
"Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Mobile Safari/537.36"
};
}
@override
String sourceSpecificStandardizeURL(String url) {
return 'https://$host';
return 'https://${hosts[0]}';
}
Future<String?> getLatestVersion(String standardUrl) async {
Response res = await sourceRequest(dwUrlBase);
Future<String?> getLatestVersion(
String standardUrl, Map<String, dynamic> additionalSettings) async {
Response res = await sourceRequest(dwUrlBase, additionalSettings);
if (res.statusCode == 200) {
var dwLinks = parse(res.body)
.querySelectorAll('a')
@ -77,9 +79,9 @@ class VLC extends AppSource {
}
@override
Future<String> apkUrlPrefetchModifier(
String apkUrl, String standardUrl) async {
Response res = await sourceRequest(apkUrl);
Future<String> apkUrlPrefetchModifier(String apkUrl, String standardUrl,
Map<String, dynamic> additionalSettings) async {
Response res = await sourceRequest(apkUrl, additionalSettings);
if (res.statusCode == 200) {
String? apkUrl =
parse(res.body).querySelector('#alt_link')?.attributes['href'];

View File

@ -5,20 +5,21 @@ import 'package:obtainium/providers/source_provider.dart';
class WhatsApp extends AppSource {
WhatsApp() {
host = 'whatsapp.com';
hosts = ['whatsapp.com'];
overrideVersionDetectionFormDefault('noVersionDetection',
disableStandard: true, disableRelDate: true);
}
@override
String sourceSpecificStandardizeURL(String url) {
return 'https://$host';
return 'https://${hosts[0]}';
}
@override
Future<String> apkUrlPrefetchModifier(
String apkUrl, String standardUrl) async {
Response res = await sourceRequest('$standardUrl/android');
Future<String> apkUrlPrefetchModifier(String apkUrl, String standardUrl,
Map<String, dynamic> additionalSettings) async {
Response res =
await sourceRequest('$standardUrl/android', additionalSettings);
if (res.statusCode == 200) {
var targetLinks = parse(res.body)
.querySelectorAll('a')
@ -42,8 +43,8 @@ class WhatsApp extends AppSource {
) async {
// This is a CDN link that is consistent per version
// But it has query params that change constantly
Uri apkUri =
Uri.parse(await apkUrlPrefetchModifier(standardUrl, standardUrl));
Uri apkUri = Uri.parse(await apkUrlPrefetchModifier(
standardUrl, standardUrl, additionalSettings));
var unusableApkUrl = '${apkUri.origin}/${apkUri.path}';
// So we use the param-less URL is a pseudo-version to add the app and check for updates
// See #357 for why we can't scrape the version number directly

View File

@ -510,9 +510,10 @@ class _GeneratedFormState extends State<GeneratedForm> {
]);
} else if (widget.items[r][e] is GeneratedFormSubForm) {
List<Widget> subformColumn = [];
var formItems = (widget.items[r][e] as GeneratedFormSubForm).items;
var compact = formItems.length == 1 && formItems[0].length == 1;
for (int i = 0; i < values[fieldKey].length; i++) {
var items = (widget.items[r][e] as GeneratedFormSubForm)
.items
var items = formItems
.map((x) => x.map((y) {
y.defaultValue = values[fieldKey]?[i]?[y.key];
return y;
@ -525,14 +526,15 @@ class _GeneratedFormState extends State<GeneratedForm> {
subformColumn.add(Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Divider(),
const SizedBox(
height: 16,
),
Text(
'${(widget.items[r][e] as GeneratedFormSubForm).label} (${i + 1})',
style: const TextStyle(fontWeight: FontWeight.bold),
),
if (!compact)
const SizedBox(
height: 16,
),
if (!compact)
Text(
'${(widget.items[r][e] as GeneratedFormSubForm).label} (${i + 1})',
style: const TextStyle(fontWeight: FontWeight.bold),
),
GeneratedForm(
key: internalFormKey,
items: items,
@ -567,13 +569,12 @@ class _GeneratedFormState extends State<GeneratedForm> {
Icons.delete_outline_rounded,
))
],
),
)
],
));
}
subformColumn.add(Padding(
padding: EdgeInsets.only(
bottom: values[fieldKey].length > 0 ? 24 : 0, top: 8),
padding: const EdgeInsets.only(bottom: 0, top: 8),
child: Row(
children: [
Expanded(
@ -591,9 +592,6 @@ class _GeneratedFormState extends State<GeneratedForm> {
],
),
));
if (values[fieldKey].length > 0) {
subformColumn.add(const Divider());
}
formInputs[r][e] = Column(children: subformColumn);
}
}

View File

@ -19,12 +19,10 @@ import 'package:easy_localization/src/easy_localization_controller.dart';
// ignore: implementation_imports
import 'package:easy_localization/src/localization.dart';
const String currentVersion = '0.15.5';
const String currentVersion = '0.15.9';
const String currentReleaseTag =
'v$currentVersion-beta'; // KEEP THIS IN SYNC WITH GITHUB RELEASES
const int bgUpdateCheckAlarmId = 666;
List<MapEntry<Locale, String>> supportedLocales = const [
MapEntry(Locale('en'), 'English'),
MapEntry(Locale('zh'), '简体中文'),

View File

@ -18,7 +18,7 @@ class GitHubStars implements MassAppUrlSource {
Response res = await get(
Uri.parse(
'https://api.github.com/users/$username/starred?per_page=100&page=$page'),
headers: await GitHub().getRequestHeaders());
headers: await GitHub().getRequestHeaders({}));
if (res.statusCode == 200) {
Map<String, List<String>> urlsWithDescriptions = {};
for (var e in (jsonDecode(res.body) as List<dynamic>)) {

View File

@ -59,7 +59,9 @@ class AddAppPageState extends State<AddAppPage> {
if (updateUrlInput) {
urlInputKey++;
}
var prevHost = pickedSource?.host;
var prevHost = pickedSource?.hosts.isNotEmpty == true
? pickedSource?.hosts[0]
: null;
try {
var naturalSource =
valid ? sourceProvider.getSource(userInput) : null;
@ -77,7 +79,7 @@ class AddAppPageState extends State<AddAppPage> {
overrideSource: pickedSourceOverride)
: null;
if (pickedSource.runtimeType != source.runtimeType ||
(prevHost != null && prevHost != source?.host)) {
(prevHost != null && prevHost != source?.hosts[0])) {
pickedSource = source;
additionalSettings = source != null
? getDefaultValuesFromFormItems(
@ -508,16 +510,16 @@ class AddAppPageState extends State<AddAppPage> {
height: 16,
),
...sourceProvider.sources.map((e) => GestureDetector(
onTap: e.host != null
onTap: e.hosts.isNotEmpty
? () {
launchUrlString('https://${e.host}',
launchUrlString('https://${e.hosts[0]}',
mode: LaunchMode.externalApplication);
}
: null,
child: Text(
'${e.name}${e.enforceTrackOnly ? ' ${tr('trackOnlyInBrackets')}' : ''}${e.canSearch ? ' ${tr('searchableInBrackets')}' : ''}',
style: TextStyle(
decoration: e.host != null
decoration: e.hosts.isNotEmpty
? TextDecoration.underline
: TextDecoration.none,
fontStyle: FontStyle.italic),

View File

@ -199,10 +199,11 @@ class _ImportExportPageState extends State<ImportExportPage> {
...source.searchQuerySettingFormItems.map((e) => [e]),
[
GeneratedFormTextField('url',
label: source.host != null
label: source.hosts.isNotEmpty
? tr('overrideSource')
: plural('url', 1).substring(2),
defaultValue: source.host ?? '',
defaultValue:
source.hosts.isNotEmpty ? source.hosts[0] : '',
required: true)
],
],
@ -212,7 +213,7 @@ class _ImportExportPageState extends State<ImportExportPage> {
setState(() {
importInProgress = true;
});
if (values['url'] != source.host) {
if (values['url'] != source.hosts[0]) {
source = sourceProvider.getSource(values['url'],
overrideSource: source.runtimeType.toString());
}

View File

@ -326,13 +326,15 @@ class AppsProvider with ChangeNotifier {
AppSource source = SourceProvider()
.getSource(app.url, overrideSource: app.overrideSource);
String downloadUrl = await source.apkUrlPrefetchModifier(
app.apkUrls[app.preferredApkIndex].value, app.url);
app.apkUrls[app.preferredApkIndex].value,
app.url,
app.additionalSettings);
var notif = DownloadNotification(app.finalName, 100);
notificationsProvider?.cancel(notif.id);
int? prevProg;
var fileNameNoExt = '${app.id}-${downloadUrl.hashCode}';
var headers = await source.getRequestHeaders(
additionalSettings: app.additionalSettings, forAPKDownload: true);
var headers = await source.getRequestHeaders(app.additionalSettings,
forAPKDownload: true);
var downloadedFile = await downloadFileWithRetry(
downloadUrl, fileNameNoExt,
headers: headers, (double? progress) {
@ -796,13 +798,17 @@ class AppsProvider with ChangeNotifier {
SourceProvider()
.getSource(app.app.url, overrideSource: app.app.overrideSource)
.naiveStandardVersionDetection;
String? realInstalledVersion =
app.app.additionalSettings['useVersionCodeAsOSVersion'] == true
? app.installedInfo?.versionCode.toString()
: app.installedInfo?.versionName;
return app.app.additionalSettings['trackOnly'] != true &&
app.app.additionalSettings['versionDetection'] !=
'releaseDateAsVersion' &&
app.installedInfo?.versionName != null &&
realInstalledVersion != null &&
app.app.installedVersion != null &&
(reconcileVersionDifferences(app.installedInfo!.versionName!,
app.app.installedVersion!) !=
(reconcileVersionDifferences(
realInstalledVersion, app.app.installedVersion!) !=
null ||
naiveStandardVersionDetection);
}
@ -821,30 +827,33 @@ class AppsProvider with ChangeNotifier {
SourceProvider()
.getSource(app.url, overrideSource: app.overrideSource)
.naiveStandardVersionDetection;
String? realInstalledVersion =
app.additionalSettings['useVersionCodeAsOSVersion'] == true
? installedInfo?.versionCode.toString()
: installedInfo?.versionName;
// FIRST, COMPARE THE APP'S REPORTED AND REAL INSTALLED VERSIONS, WHERE ONE IS NULL
if (installedInfo == null && app.installedVersion != null && !trackOnly) {
// App says it's installed but isn't really (and isn't track only) - set to not installed
app.installedVersion = null;
modded = true;
} else if (installedInfo?.versionName != null &&
app.installedVersion == null) {
// App says it's not installed but really is - set to installed and use real package versionName
app.installedVersion = installedInfo!.versionName;
} else if (realInstalledVersion != null && app.installedVersion == null) {
// App says it's not installed but really is - set to installed and use real package versionName (or versionCode if chosen)
app.installedVersion = realInstalledVersion;
modded = true;
}
// SECOND, RECONCILE DIFFERENCES BETWEEN THE APP'S REPORTED AND REAL INSTALLED VERSIONS, WHERE NEITHER IS NULL
if (installedInfo?.versionName != null &&
installedInfo!.versionName != app.installedVersion &&
if (realInstalledVersion != null &&
realInstalledVersion != app.installedVersion &&
versionDetectionIsStandard) {
// App's reported version and real version don't match (and it uses standard version detection)
// If they share a standard format (and are still different under it), update the reported version accordingly
var correctedInstalledVersion = reconcileVersionDifferences(
installedInfo.versionName!, app.installedVersion!);
realInstalledVersion, app.installedVersion!);
if (correctedInstalledVersion?.key == false) {
app.installedVersion = correctedInstalledVersion!.value;
modded = true;
} else if (naiveStandardVersionDetection) {
app.installedVersion = installedInfo.versionName;
app.installedVersion = realInstalledVersion;
modded = true;
}
}
@ -1289,8 +1298,11 @@ class AppsProvider with ChangeNotifier {
await Future.delayed(const Duration(microseconds: 1));
}
for (App a in importedApps) {
var installedInfo = await getInstalledInfo(a.id, printErr: false);
a.installedVersion =
(await getInstalledInfo(a.id, printErr: false))?.versionName;
a.additionalSettings['useVersionCodeAsOSVersion'] == true
? installedInfo?.versionCode.toString()
: installedInfo?.versionName;
}
await saveApps(importedApps, onlyIfExists: false);
notifyListeners();

View File

@ -14,7 +14,7 @@ import 'package:permission_handler/permission_handler.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:shared_storage/shared_storage.dart' as saf;
String obtainiumTempId = 'imranr98_obtainium_${GitHub().host}';
String obtainiumTempId = 'imranr98_obtainium_${GitHub().hosts[0]}';
String obtainiumId = 'dev.imranr.obtainium';
enum InstallMethodSettings { normal, shizuku, root }

View File

@ -19,7 +19,6 @@ import 'package:obtainium/app_sources/huaweiappgallery.dart';
import 'package:obtainium/app_sources/izzyondroid.dart';
import 'package:obtainium/app_sources/html.dart';
import 'package:obtainium/app_sources/jenkins.dart';
import 'package:obtainium/app_sources/mullvad.dart';
import 'package:obtainium/app_sources/neutroncode.dart';
import 'package:obtainium/app_sources/signal.dart';
import 'package:obtainium/app_sources/sourceforge.dart';
@ -366,8 +365,12 @@ List<MapEntry<String, String>> getApkUrlsFromUrls(List<String> urls) =>
return MapEntry(apkSegs.isNotEmpty ? apkSegs.last : segments.last, e);
}).toList();
getSourceRegex(List<String> hosts) {
return '(${hosts.join('|').replaceAll('.', '\\.')})';
}
abstract class AppSource {
String? host;
List<String> hosts = [];
bool hostChanged = false;
late String name;
bool enforceTrackOnly = false;
@ -413,8 +416,8 @@ abstract class AppSource {
}
Future<Map<String, String>?> getRequestHeaders(
{Map<String, dynamic> additionalSettings = const <String, dynamic>{},
bool forAPKDownload = false}) async {
Map<String, dynamic> additionalSettings,
{bool forAPKDownload = false}) async {
return null;
}
@ -422,12 +425,10 @@ abstract class AppSource {
return app;
}
Future<Response> sourceRequest(String url,
{bool followRedirects = true,
Map<String, dynamic> additionalSettings =
const <String, dynamic>{}}) async {
var requestHeaders =
await getRequestHeaders(additionalSettings: additionalSettings);
Future<Response> sourceRequest(
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;
@ -484,6 +485,10 @@ abstract class AppSource {
label: tr('versionDetection'),
defaultValue: 'standardVersionDetection')
],
[
GeneratedFormSwitch('useVersionCodeAsOSVersion',
label: tr('useVersionCodeAsOSVersion'), defaultValue: false)
],
[
GeneratedFormTextField('apkFilterRegEx',
label: tr('filterAPKsByRegEx'),
@ -544,8 +549,8 @@ abstract class AppSource {
return null;
}
Future<String> apkUrlPrefetchModifier(
String apkUrl, String standardUrl) async {
Future<String> apkUrlPrefetchModifier(String apkUrl, String standardUrl,
Map<String, dynamic> additionalSettings) async {
return apkUrl;
}
@ -676,7 +681,6 @@ class SourceProvider {
APKMirror(),
HuaweiAppGallery(),
Jenkins(),
Mullvad(),
Signal(),
VLC(),
WhatsApp(),
@ -697,14 +701,14 @@ class SourceProvider {
throw UnsupportedURLError();
}
var res = srcs.first;
res.host = Uri.parse(url).host;
res.hosts = [Uri.parse(url).host];
res.hostChanged = true;
return srcs.first;
}
AppSource? source;
for (var s in sources.where((element) => element.host != null)) {
for (var s in sources.where((element) => element.hosts.isNotEmpty)) {
if (RegExp(
'://${s.allowSubDomains ? '([^\\.]+\\.)*' : '(www\\.)?'}${s.host}(/|\\z)?')
'://${s.allowSubDomains ? '([^\\.]+\\.)*' : '(www\\.)?'}(${getSourceRegex(s.hosts)})(/|\\z)?')
.hasMatch(url)) {
source = s;
break;
@ -712,7 +716,7 @@ class SourceProvider {
}
if (source == null) {
for (var s in sources.where(
(element) => element.host == null && !element.neverAutoSelect)) {
(element) => element.hosts.isEmpty && !element.neverAutoSelect)) {
try {
s.sourceSpecificStandardizeURL(url);
source = s;

View File

@ -22,10 +22,10 @@ packages:
dependency: "direct main"
description:
name: android_package_manager
sha256: b873fe5856f7c442aca9751dac05d117285be9e4de08eb15d1ffb811fd1b688d
sha256: e52ca607b9f19f95d5dae4211ed8fa93e67093f22ac570db47489c5bca512940
url: "https://pub.dev"
source: hosted
version: "0.6.0"
version: "0.7.0"
animations:
dependency: "direct main"
description:
@ -70,10 +70,10 @@ packages:
dependency: "direct main"
description:
name: background_fetch
sha256: f70b28a0f7a3156195e9742229696f004ea3bf10f74039b7bf4c78a74fbda8a4
sha256: "34550cf9b383e5a1844e7d22119aa500508c7df9421fa967c9fb4430d6cb2878"
url: "https://pub.dev"
source: hosted
version: "1.2.1"
version: "1.2.2"
boolean_selector:
dependency: transitive
description:
@ -258,6 +258,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "6.1.1"
fixnum:
dependency: transitive
description:
name: fixnum
sha256: "25517a4deb0c03aa0f32fd12db525856438902d9c16536311e76cdc57b31d7d1"
url: "https://pub.dev"
source: hosted
version: "1.1.0"
flutter:
dependency: "direct main"
description: flutter
@ -506,10 +514,10 @@ packages:
dependency: "direct main"
description:
name: path_provider
sha256: a1aa8aaa2542a6bc57e381f132af822420216c80d4781f7aa085ca3229208aaa
sha256: b27217933eeeba8ff24845c34003b003b2b22151de3c908d0e679e8fe1aa078b
url: "https://pub.dev"
source: hosted
version: "2.1.1"
version: "2.1.2"
path_provider_android:
dependency: transitive
description:
@ -538,10 +546,10 @@ packages:
dependency: transitive
description:
name: path_provider_platform_interface
sha256: "94b1e0dd80970c1ce43d5d4e050a9918fce4f4a775e6142424c30a29a363265c"
sha256: "88f5779f72ba699763fa3a3b06aa4bf6de76c8e5de842cf6f29e2e06476c2334"
url: "https://pub.dev"
source: hosted
version: "2.1.1"
version: "2.1.2"
path_provider_windows:
dependency: transitive
description:
@ -690,10 +698,10 @@ packages:
dependency: transitive
description:
name: shared_preferences_platform_interface
sha256: d4ec5fc9ebb2f2e056c617112aa75dcf92fc2e4faaf2ae999caa297473f75d8a
sha256: "22e2ecac9419b4246d7c22bfbbda589e3acf5c0351137d87dd2939d984d37c3b"
url: "https://pub.dev"
source: hosted
version: "2.3.1"
version: "2.3.2"
shared_preferences_web:
dependency: transitive
description:
@ -823,26 +831,26 @@ packages:
dependency: "direct main"
description:
name: url_launcher
sha256: e9aa5ea75c84cf46b3db4eea212523591211c3cf2e13099ee4ec147f54201c86
sha256: d25bb0ca00432a5e1ee40e69c36c85863addf7cc45e433769d61bed3fe81fd96
url: "https://pub.dev"
source: hosted
version: "6.2.2"
version: "6.2.3"
url_launcher_android:
dependency: transitive
description:
name: url_launcher_android
sha256: c0766a55ab42cefaa728cabc951e82919ab41a3a4fee0aaa96176ca82da8cc51
sha256: "507dc655b1d9cb5ebc756032eb785f114e415f91557b73bf60b7e201dfedeb2f"
url: "https://pub.dev"
source: hosted
version: "6.2.1"
version: "6.2.2"
url_launcher_ios:
dependency: transitive
description:
name: url_launcher_ios
sha256: "46b81e3109cbb2d6b81702ad3077540789a3e74e22795eb9f0b7d494dbaa72ea"
sha256: cdb7b6da34483f9b2c9f8b2b29bc468fa7271d92e2021607ca0c4d3bcb04cdd4
url: "https://pub.dev"
source: hosted
version: "6.2.2"
version: "6.2.3"
url_launcher_linux:
dependency: transitive
description:
@ -863,10 +871,10 @@ packages:
dependency: transitive
description:
name: url_launcher_platform_interface
sha256: "4aca1e060978e19b2998ee28503f40b5ba6226819c2b5e3e4d1821e8ccd92198"
sha256: a932c3a8082e118f80a475ce692fde89dc20fddb24c57360b96bc56f7035de1f
url: "https://pub.dev"
source: hosted
version: "2.3.0"
version: "2.3.1"
url_launcher_web:
dependency: transitive
description:
@ -887,10 +895,10 @@ packages:
dependency: transitive
description:
name: uuid
sha256: "22c94e5ad1e75f9934b766b53c742572ee2677c56bc871d850a57dad0f82127f"
sha256: cd210a09f7c18cbe5a02511718e0334de6559871052c90a90c0cca46a4aa81c8
url: "https://pub.dev"
source: hosted
version: "4.2.2"
version: "4.3.3"
vector_math:
dependency: transitive
description:
@ -911,10 +919,10 @@ packages:
dependency: "direct main"
description:
name: webview_flutter
sha256: "60e23976834e995c404c0b21d3b9db37ecd77d3303ef74f8b8d7a7b19947fc04"
sha256: "71e1bfaef41016c8d5954291df5e9f8c6172f1f6ff3af01b5656456ddb11f94c"
url: "https://pub.dev"
source: hosted
version: "4.4.3"
version: "4.4.4"
webview_flutter_android:
dependency: transitive
description:
@ -927,10 +935,10 @@ packages:
dependency: transitive
description:
name: webview_flutter_platform_interface
sha256: dbe745ee459a16b6fec296f7565a8ef430d0d681001d8ae521898b9361854943
sha256: "80b40ae4fb959957eef9fa8970b6c9accda9f49fc45c2b75154696a8e8996cfe"
url: "https://pub.dev"
source: hosted
version: "2.9.0"
version: "2.9.1"
webview_flutter_wkwebview:
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: 0.15.5+241 # When changing this, update the tag in main() accordingly
version: 0.15.9+245 # When changing this, update the tag in main() accordingly
environment:
sdk: '>=3.0.0 <4.0.0'
@ -55,7 +55,7 @@ dependencies:
git:
url: https://github.com/ImranR98/android_package_installer
ref: main
android_package_manager: ^0.6.0
android_package_manager: ^0.7.0
share_plus: ^7.0.0
sqflite: ^2.2.0+3
easy_localization: ^3.0.1