Merge pull request #1826 from ImranR98/dev

- Improved APKPure compatibility by prioritizing APK when available (over XAPK)
- Auto-select direct APK source for URLs ending in '.apk' (#1820)
- Handle install failures more gracefully on apps page (#1782)
- Enabled ZH-TW and EO languages
- Added "allow insecure request" option (#1825)
This commit is contained in:
Imran
2024-09-08 04:00:49 -04:00
committed by GitHub
51 changed files with 166 additions and 83 deletions

View File

@ -314,6 +314,7 @@
"appVerifierInstructionToast": "Dijeli sa AppVerifier-om, zatim se vratite kada ste spremni.",
"wiki": "Pomoć/Wiki",
"crowdsourcedConfigsLabel": "Konfiguracije aplikacije obezbeđene pomoću velikog broja ljudi (crowdsourcing) (koristite na svoju odgovornost)",
"allowInsecure": "Allow insecure HTTP requests",
"removeAppQuestion": {
"one": "Želite li ukloniti aplikaciju?",
"other": "Želite li ukloniti aplikacije?"

View File

@ -314,6 +314,7 @@
"appVerifierInstructionToast": "Sdílejte do aplikace AppVerifier a po dokončení se sem vraťte.",
"wiki": "Nápověda/Wiki",
"crowdsourcedConfigsLabel": "Konfigurace aplikací s využitím crowdsourcingu (použití na vlastní nebezpečí)",
"allowInsecure": "Povolení nezabezpečených požadavků HTTP",
"removeAppQuestion": {
"one": "Odstranit Apku?",
"other": "Odstranit Apky?"

View File

@ -314,6 +314,7 @@
"appVerifierInstructionToast": "Del til AppVerifier, og vend tilbage hertil, når du er klar.",
"wiki": "Hjælp/Wiki",
"crowdsourcedConfigsLabel": "Crowdsourcede app-konfigurationer (brug på egen risiko)",
"allowInsecure": "Tillad usikre HTTP-anmodninger",
"removeAppQuestion": {
"one": "Fjern app?",
"other": "Fjern apps?"

View File

@ -314,6 +314,7 @@
"appVerifierInstructionToast": "Geben Sie die Daten an AppVerifier weiter und kehren Sie dann hierher zurück, wenn Sie fertig sind.",
"wiki": "Hilfe/Wiki",
"crowdsourcedConfigsLabel": "Crowdsourced App-Konfigurationen (Verwendung auf eigene Gefahr)",
"allowInsecure": "Unsichere HTTP-Anfragen zulassen",
"removeAppQuestion": {
"one": "App entfernen?",
"other": "Apps entfernen?"

View File

@ -102,7 +102,7 @@
"searchX": "Serĉi {}",
"noResults": "Neniu rezulto",
"importX": "Importi {}",
"importedAppsIdDisclaimer": "La importitaj apoj povas montriĝi malĝuste kiel \"Neinstalitaj\".\nPor solvi tion, reinstalu ilin per Obtainium.\Tiu ne afekcios la apodatumoj.\n\nAkefcias nur la URL-ajn lak triajn importmetodojn.",
"importedAppsIdDisclaimer": "La importitaj apoj povas montriĝi malĝuste kiel \"Neinstalitaj\".\nPor solvi tion, reinstalu ilin per Obtainium.\nTiu ne afekcios la apodatumoj.\n\nAkefcias nur la URL-ajn lak triajn importmetodojn.",
"importErrors": "Eraroj de importado",
"importedXOfYApps": "{} apoj el {} importitaj.",
"followingURLsHadErrors": "La sekvantaj URLj havis erarojn:",
@ -314,6 +314,7 @@
"appVerifierInstructionToast": "Diskonigu kun AppVerifier, poste revenu ĉi tie kiam preta.",
"wiki": "Helpo/Vikio",
"crowdsourcedConfigsLabel": "Komunumaj apo-agordoj (uzu kun singardo)",
"allowInsecure": "Allow insecure HTTP requests",
"removeAppQuestion": {
"one": "Forigi la aplikaĵon?",
"other": "Forigi la aplikaĵojn?"

View File

@ -314,6 +314,7 @@
"appVerifierInstructionToast": "Share to AppVerifier, then return here when ready.",
"wiki": "Help/Wiki",
"crowdsourcedConfigsLabel": "Crowdsourced App Configurations (use at your own risk)",
"allowInsecure": "Allow insecure HTTP requests",
"removeAppQuestion": {
"one": "Remove App?",
"other": "Remove Apps?"

View File

@ -314,6 +314,7 @@
"appVerifierInstructionToast": "Comparta con AppVerifier y vuelva aquí cuando esté listo.",
"wiki": "Ayuda/Wiki",
"crowdsourcedConfigsLabel": "Crowdsourced App Configurations (uso bajo su propia responsabilidad)",
"allowInsecure": "Permitir peticiones HTTP inseguras",
"removeAppQuestion": {
"one": "¿Eliminar aplicación?",
"other": "¿Eliminar aplicaciones?"

View File

@ -314,6 +314,7 @@
"appVerifierInstructionToast": "در AppVerifier به اشتراک بگذارید، سپس پس از آماده شدن به اینجا برگردید.",
"wiki": "راهنما/ویکی",
"crowdsourcedConfigsLabel": "تنظیمات برنامه Crowdsourced (با مسئولیت خود استفاده کنید)",
"allowInsecure": "Allow insecure HTTP requests",
"removeAppQuestion": {
"one": "برنامه حذف شود؟",
"other": "برنامه ها حذف شوند؟"

View File

@ -314,6 +314,7 @@
"appVerifierInstructionToast": "Partagez avec AppVerifier, puis revenez ici lorsque tout est prêt.",
"wiki": "Aide/Wiki",
"crowdsourcedConfigsLabel": "Configurations d'applications par la communauté (à utiliser à vos risques et périls)",
"allowInsecure": "Autoriser les requêtes HTTP non sécurisées",
"removeAppQuestion": {
"one": "Supprimer l'application?",
"other": "Supprimer les applications?"

View File

@ -314,6 +314,7 @@
"appVerifierInstructionToast": "Ossza meg az AppVerifierrel, majd térjen vissza ide, ha kész.",
"wiki": "Súgó/Wiki",
"crowdsourcedConfigsLabel": "Crowdsourced App Configurations (használat saját felelősségre)",
"allowInsecure": "Bizonytalan HTTP-kérések engedélyezése",
"removeAppQuestion": {
"one": "Eltávolítja az alkalmazást?",
"other": "Eltávolítja az alkalmazásokat?"

View File

@ -314,6 +314,7 @@
"appVerifierInstructionToast": "Condividete con AppVerifier, quindi tornate qui quando siete pronti.",
"wiki": "Aiuto/Wiki",
"crowdsourcedConfigsLabel": "Configurazioni di app in crowdsourcing (uso a proprio rischio)",
"allowInsecure": "Consentire le richieste HTTP non sicure",
"removeAppQuestion": {
"one": "Rimuovere l'app?",
"other": "Rimuovere le app?"

View File

@ -314,6 +314,7 @@
"appVerifierInstructionToast": "AppVerifierに共有し、準備ができたらここに戻ってください。",
"wiki": "ヘルプ/ウィキ",
"crowdsourcedConfigsLabel": "クラウドソーシングによるアプリの設定(利用は自己責任で)",
"allowInsecure": "安全でないHTTPリクエストを許可する",
"removeAppQuestion": {
"one": "アプリを削除しますか?",
"other": "アプリを削除しますか?"

View File

@ -314,6 +314,7 @@
"appVerifierInstructionToast": "Deel het met AppVerifier en keer daarna hier terug.",
"wiki": "Help/Wiki",
"crowdsourcedConfigsLabel": "Crowdsourced App-configuraties (gebruik op eigen risico)",
"allowInsecure": "Onveilige HTTP-verzoeken toestaan",
"removeAppQuestion": {
"one": "App verwijderen?",
"other": "Apps verwijderen?"

View File

@ -314,6 +314,7 @@
"appVerifierInstructionToast": "Udostępnij w AppVerifier, a następnie wróć tutaj, gdy będziesz gotowy.",
"wiki": "Pomoc/Wiki",
"crowdsourcedConfigsLabel": "Konfiguracje aplikacji pochodzące z crowdsourcingu (korzystanie na własne ryzyko)",
"allowInsecure": "Zezwalaj na niezabezpieczone żądania HTTP",
"removeAppQuestion": {
"one": "Usunąć aplikację?",
"few": "Usunąć aplikacje?",

View File

@ -314,6 +314,7 @@
"appVerifierInstructionToast": "Partilhe com o AppVerifier e, em seguida, regresse aqui quando estiver pronto.",
"wiki": "Ajuda/Wiki",
"crowdsourcedConfigsLabel": "Configurações de aplicações de crowdsourcing (utilização por sua conta e risco)",
"allowInsecure": "Permitir pedidos HTTP inseguros",
"removeAppQuestion": {
"one": "Remover aplicativo?",
"other": "Remover aplicativos?"

View File

@ -314,6 +314,7 @@
"appVerifierInstructionToast": "Поделитесь с AppVerifier, а затем вернитесь сюда, когда будете готовы.",
"wiki": "Помощь/Вики",
"crowdsourcedConfigsLabel": "Конфигурации приложений на основе краудсорсинга (используйте на свой страх и риск)",
"allowInsecure": "Разрешить небезопасные HTTP-запросы",
"removeAppQuestion": {
"one": "Удалить приложение?",
"other": "Удалить приложения?"

View File

@ -314,6 +314,7 @@
"appVerifierInstructionToast": "Dela till AppVerifier och återvänd sedan hit när du är klar.",
"wiki": "Hjälp/Wiki",
"crowdsourcedConfigsLabel": "Crowdsourcade appkonfigurationer (använd på egen risk)",
"allowInsecure": "Tillåt osäkra HTTP-förfrågningar",
"removeAppQuestion": {
"one": "Ta Bort App?",
"other": "Ta Bort Appar?"

View File

@ -314,6 +314,7 @@
"appVerifierInstructionToast": "AppVerifier ile paylaşın, hazır olduğunuzda buraya dönün.",
"wiki": "Yardım/Wiki",
"crowdsourcedConfigsLabel": "Kitle Kaynaklı Uygulama Yapılandırmaları (riski size ait olmak üzere kullanın)",
"allowInsecure": "Güvensiz HTTP isteklerine izin ver",
"removeAppQuestion": {
"one": "Uygulamayı Kaldır?",
"other": "Uygulamaları Kaldır?"

View File

@ -314,6 +314,7 @@
"appVerifierInstructionToast": "Надішліть на AppVerifier, а потім поверніться сюди, коли будете готові.",
"wiki": "Довідка/Вікі",
"crowdsourcedConfigsLabel": "Краудсорсингові конфігурації додатків (використовуйте на свій страх і ризик)",
"allowInsecure": "Дозволити незахищені HTTP-запити",
"removeAppQuestion": {
"one": "Видалити застосунок?",
"other": "Видалити застосунки?"

View File

@ -314,6 +314,7 @@
"appVerifierInstructionToast": "Chia sẻ lên AppVerifier, sau đó quay lại đây khi sẵn sàng.",
"wiki": "Trợ giúp/Wiki",
"crowdsourcedConfigsLabel": "Crowdsourced App Configurations (use at your own risk)",
"allowInsecure": "Allow insecure HTTP requests",
"removeAppQuestion": {
"one": "Gỡ ứng dụng?",
"other": "Gỡ ứng dụng?"

View File

@ -314,6 +314,7 @@
"appVerifierInstructionToast": "分享至 AppVerifier然後準備好時回到此處。",
"wiki": "幫助/維基",
"crowdsourcedConfigsLabel": "群眾外包的應用程式設定(使用風險自負)",
"allowInsecure": "Allow insecure HTTP requests",
"removeAppQuestion": {
"one": "移除應用程式?",
"other": "移除應用程式?"

View File

@ -314,6 +314,7 @@
"appVerifierInstructionToast": "分享至 AppVerifier完成后返回此处。",
"wiki": "帮助/Wiki",
"crowdsourcedConfigsLabel": "众包应用程序配置(使用风险自负)",
"allowInsecure": "允许不安全的 HTTP 请求",
"removeAppQuestion": {
"one": "是否删除应用?",
"other": "是否删除应用?"

View File

@ -10,7 +10,7 @@ class APKCombo extends AppSource {
}
@override
String sourceSpecificStandardizeURL(String url) {
String sourceSpecificStandardizeURL(String url, {bool forSelection = false}) {
RegExp standardUrlRegEx = RegExp(
'^https?://(www\\.)?${getSourceRegex(hosts)}/+[^/]+/+[^/]+',
caseSensitive: false);

View File

@ -32,7 +32,7 @@ class APKMirror extends AppSource {
}
@override
String sourceSpecificStandardizeURL(String url) {
String sourceSpecificStandardizeURL(String url, {bool forSelection = false}) {
RegExp standardUrlRegEx = RegExp(
'^https?://(www\\.)?${getSourceRegex(hosts)}/apk/[^/]+/[^/]+',
caseSensitive: false);

View File

@ -29,7 +29,7 @@ class APKPure extends AppSource {
}
@override
String sourceSpecificStandardizeURL(String url) {
String sourceSpecificStandardizeURL(String url, {bool forSelection = false}) {
RegExp standardUrlRegExB = RegExp(
'^https?://m.${getSourceRegex(hosts)}(/+[^/]{2})?/+[^/]+/+[^/]+',
caseSensitive: false);
@ -109,11 +109,16 @@ class APKPure extends AppSource {
'')
?.group(0)
?.trim();
String? type = apkInfo
?.querySelector('div.info-top span.tag')
?.text
.trim() ??
'APK';
var types = apkInfo
?.querySelectorAll('div.info-top span.tag')
.map((e) => e.text.trim())
.map((t) => t == 'APKs' ? 'APK' : t) ??
[];
String type = types.isEmpty
? 'APK'
: types.length == 1
? types.first
: types.last;
String? dateString = apkInfo
?.querySelector('div.info-bottom span.time')
?.text

View File

@ -14,7 +14,7 @@ class Aptoide extends AppSource {
}
@override
String sourceSpecificStandardizeURL(String url) {
String sourceSpecificStandardizeURL(String url, {bool forSelection = false}) {
RegExp standardUrlRegEx = RegExp(
'^https?://([^\\.]+\\.){2,}${getSourceRegex(hosts)}',
caseSensitive: false);

View File

@ -16,7 +16,7 @@ class Codeberg extends AppSource {
}
@override
String sourceSpecificStandardizeURL(String url) {
String sourceSpecificStandardizeURL(String url, {bool forSelection = false}) {
RegExp standardUrlRegEx = RegExp(
'^https?://(www\\.)?${getSourceRegex(hosts)}/[^/]+/[^/]+',
caseSensitive: false);

View File

@ -1,12 +1,12 @@
import 'package:easy_localization/easy_localization.dart';
import 'package:obtainium/app_sources/html.dart';
import 'package:obtainium/custom_errors.dart';
import 'package:obtainium/providers/source_provider.dart';
class DirectAPKLink extends AppSource {
HTML html = HTML();
DirectAPKLink() {
neverAutoSelect = true;
name = tr('directAPKLink');
additionalSourceAppSpecificSettingFormItems = html
.additionalSourceAppSpecificSettingFormItems
@ -24,6 +24,19 @@ class DirectAPKLink extends AppSource {
];
}
@override
String sourceSpecificStandardizeURL(String url, {bool forSelection = false}) {
if (!forSelection) {
return url;
}
RegExp standardUrlRegExA = RegExp('.+\\.apk\$', caseSensitive: false);
var match = standardUrlRegExA.firstMatch(url);
if (match == null) {
throw InvalidURLError(name);
}
return match.group(0)!;
}
@override
Future<Map<String, String>?> getRequestHeaders(
Map<String, dynamic> additionalSettings,

View File

@ -38,7 +38,7 @@ class FDroid extends AppSource {
}
@override
String sourceSpecificStandardizeURL(String url) {
String sourceSpecificStandardizeURL(String url, {bool forSelection = false}) {
RegExp standardUrlRegExB = RegExp(
'^https?://(www\\.)?${getSourceRegex(hosts)}/+[^/]+/+packages/+[^/]+',
caseSensitive: false);

View File

@ -43,7 +43,7 @@ class FDroidRepo extends AppSource {
}
@override
String sourceSpecificStandardizeURL(String url) {
String sourceSpecificStandardizeURL(String url, {bool forSelection = false}) {
var standardUri = Uri.parse(url);
var pathSegments = standardUri.pathSegments;
if (pathSegments.isNotEmpty && pathSegments.last == 'index.xml') {

View File

@ -154,7 +154,7 @@ class GitHub extends AppSource {
}
@override
String sourceSpecificStandardizeURL(String url) {
String sourceSpecificStandardizeURL(String url, {bool forSelection = false}) {
RegExp standardUrlRegEx = RegExp(
'^https?://(www\\.)?${getSourceRegex(hosts)}/[^/]+/[^/]+',
caseSensitive: false);

View File

@ -52,7 +52,7 @@ class GitLab extends AppSource {
}
@override
String sourceSpecificStandardizeURL(String url) {
String sourceSpecificStandardizeURL(String url, {bool forSelection = false}) {
RegExp standardUrlRegEx = RegExp(
'^https?://(www\\.)?${getSourceRegex(hosts)}/[^/]+/[^/]+',
caseSensitive: false);

View File

@ -288,7 +288,7 @@ class HTML extends AppSource {
}
@override
String sourceSpecificStandardizeURL(String url) {
String sourceSpecificStandardizeURL(String url, {bool forSelection = false}) {
return url;
}
@ -350,7 +350,8 @@ class HTML extends AppSource {
? rel.hashCode.toString()
: (await checkPartialDownloadHashDynamic(rel,
headers: await getRequestHeaders(additionalSettings,
forAPKDownload: true)))
forAPKDownload: true),
allowInsecure: additionalSettings['allowInsecure'] == true))
.toString();
return APKDetails(version, [rel].map((e) => MapEntry(e, e)).toList(),
AppNames(uri.host, tr('app')));

View File

@ -12,7 +12,7 @@ class HuaweiAppGallery extends AppSource {
}
@override
String sourceSpecificStandardizeURL(String url) {
String sourceSpecificStandardizeURL(String url, {bool forSelection = false}) {
RegExp standardUrlRegEx = RegExp(
'^https?://(www\\.)?${getSourceRegex(hosts)}(/#)?/(app|appdl)/[^/]+',
caseSensitive: false);

View File

@ -14,7 +14,7 @@ class IzzyOnDroid extends AppSource {
}
@override
String sourceSpecificStandardizeURL(String url) {
String sourceSpecificStandardizeURL(String url, {bool forSelection = false}) {
RegExp standardUrlRegExA = RegExp(
'^https?://android.${getSourceRegex(hosts)}/repo/apk/[^/]+',
caseSensitive: false);

View File

@ -10,7 +10,7 @@ class Mullvad extends AppSource {
}
@override
String sourceSpecificStandardizeURL(String url) {
String sourceSpecificStandardizeURL(String url, {bool forSelection = false}) {
RegExp standardUrlRegEx = RegExp(
'^https?://(www\\.)?${getSourceRegex(hosts)}',
caseSensitive: false);

View File

@ -10,7 +10,7 @@ class NeutronCode extends AppSource {
}
@override
String sourceSpecificStandardizeURL(String url) {
String sourceSpecificStandardizeURL(String url, {bool forSelection = false}) {
RegExp standardUrlRegEx = RegExp(
'^https?://(www\\.)?${getSourceRegex(hosts)}/downloads/file/[^/]+',
caseSensitive: false);

View File

@ -9,7 +9,7 @@ class Signal extends AppSource {
}
@override
String sourceSpecificStandardizeURL(String url) {
String sourceSpecificStandardizeURL(String url, {bool forSelection = false}) {
return 'https://${hosts[0]}';
}

View File

@ -9,7 +9,7 @@ class SourceForge extends AppSource {
}
@override
String sourceSpecificStandardizeURL(String url) {
String sourceSpecificStandardizeURL(String url, {bool forSelection = false}) {
var sourceRegex = getSourceRegex(hosts);
RegExp standardUrlRegExC =
RegExp('^https?://(www\\.)?$sourceRegex/p/.+', caseSensitive: false);

View File

@ -20,7 +20,7 @@ class SourceHut extends AppSource {
}
@override
String sourceSpecificStandardizeURL(String url) {
String sourceSpecificStandardizeURL(String url, {bool forSelection = false}) {
RegExp standardUrlRegEx = RegExp(
'^https?://(www\\.)?${getSourceRegex(hosts)}/[^/]+/[^/]+',
caseSensitive: false);

View File

@ -20,7 +20,7 @@ class SteamMobile extends AppSource {
final apks = {'steam': tr('steamMobile'), 'steam-chat-app': tr('steamChat')};
@override
String sourceSpecificStandardizeURL(String url) {
String sourceSpecificStandardizeURL(String url, {bool forSelection = false}) {
return 'https://${hosts[0]}';
}

View File

@ -11,7 +11,7 @@ class TelegramApp extends AppSource {
}
@override
String sourceSpecificStandardizeURL(String url) {
String sourceSpecificStandardizeURL(String url, {bool forSelection = false}) {
return 'https://${hosts[0]}';
}

View File

@ -14,7 +14,7 @@ class Uptodown extends AppSource {
}
@override
String sourceSpecificStandardizeURL(String url) {
String sourceSpecificStandardizeURL(String url, {bool forSelection = false}) {
RegExp standardUrlRegEx = RegExp(
'^https?://([^\\.]+\\.){2,}${getSourceRegex(hosts)}',
caseSensitive: false);

View File

@ -21,7 +21,7 @@ class VLC extends AppSource {
}
@override
String sourceSpecificStandardizeURL(String url) {
String sourceSpecificStandardizeURL(String url, {bool forSelection = false}) {
return 'https://${hosts[0]}';
}

View File

@ -10,7 +10,7 @@ class WhatsApp extends AppSource {
}
@override
String sourceSpecificStandardizeURL(String url) {
String sourceSpecificStandardizeURL(String url, {bool forSelection = false}) {
return 'https://${hosts[0]}';
}

View File

@ -23,6 +23,7 @@ import 'package:easy_localization/src/localization.dart';
List<MapEntry<Locale, String>> supportedLocales = const [
MapEntry(Locale('en'), 'English'),
MapEntry(Locale('zh'), '简体中文'),
MapEntry(Locale('zh_Hant_TW'), '臺灣話'),
MapEntry(Locale('it'), 'Italiano'),
MapEntry(Locale('ja'), '日本語'),
MapEntry(Locale('hu'), 'Magyar'),
@ -41,6 +42,8 @@ List<MapEntry<Locale, String>> supportedLocales = const [
MapEntry(Locale('tr'), 'Türkçe'),
MapEntry(Locale('uk'), 'Українська'),
MapEntry(Locale('da'), 'Dansk'),
MapEntry(Locale('en', 'EO'),
'Esperanto'), // https://github.com/aissat/easy_localization/issues/220#issuecomment-846035493
];
const fallbackLocale = Locale('en');
const localeDir = 'assets/translations';
@ -245,15 +248,17 @@ class _ObtainiumState extends State<Obtainium> {
colorScheme: settingsProvider.theme == ThemeSettings.dark
? darkColorScheme
: lightColorScheme,
fontFamily:
settingsProvider.useSystemFont ? 'SystemFont' : 'Wix-Madefor-Display'),
fontFamily: settingsProvider.useSystemFont
? 'SystemFont'
: 'Wix-Madefor-Display'),
darkTheme: ThemeData(
useMaterial3: true,
colorScheme: settingsProvider.theme == ThemeSettings.light
? lightColorScheme
: darkColorScheme,
fontFamily:
settingsProvider.useSystemFont ? 'SystemFont' : 'Wix-Madefor-Display'),
fontFamily: settingsProvider.useSystemFont
? 'SystemFont'
: 'Wix-Madefor-Display'),
home: Shortcuts(shortcuts: <LogicalKeySet, Intent>{
LogicalKeySet(LogicalKeyboardKey.select): const ActivateIntent(),
}, child: const HomePage()));

View File

@ -17,6 +17,7 @@ import 'package:device_info_plus/device_info_plus.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:http/io_client.dart';
import 'package:obtainium/components/generated_form.dart';
import 'package:obtainium/components/generated_form_modal.dart';
import 'package:obtainium/custom_errors.dart';
@ -146,17 +147,23 @@ Future<File> downloadFileWithRetry(String url, String fileName,
bool fileNameHasExt, Function? onProgress, String destDir,
{bool useExisting = true,
Map<String, String>? headers,
int retries = 3}) async {
int retries = 3,
bool allowInsecure = false}) async {
try {
return await downloadFile(
url, fileName, fileNameHasExt, onProgress, destDir,
useExisting: useExisting, headers: headers);
useExisting: useExisting,
headers: headers,
allowInsecure: allowInsecure);
} catch (e) {
if (retries > 0 && e is ClientException) {
await Future.delayed(const Duration(seconds: 5));
return await downloadFileWithRetry(
url, fileName, fileNameHasExt, onProgress, destDir,
useExisting: useExisting, headers: headers, retries: (retries - 1));
useExisting: useExisting,
headers: headers,
retries: (retries - 1),
allowInsecure: allowInsecure);
} else {
rethrow;
}
@ -173,11 +180,14 @@ String hashListOfLists(List<List<int>> data) {
Future<String> checkPartialDownloadHashDynamic(String url,
{int startingSize = 1024,
int lowerLimit = 128,
Map<String, String>? headers}) async {
Map<String, String>? headers,
bool allowInsecure = false}) async {
for (int i = startingSize; i >= lowerLimit; i -= 256) {
List<String> ab = await Future.wait([
checkPartialDownloadHash(url, i, headers: headers),
checkPartialDownloadHash(url, i, headers: headers)
checkPartialDownloadHash(url, i,
headers: headers, allowInsecure: allowInsecure),
checkPartialDownloadHash(url, i,
headers: headers, allowInsecure: allowInsecure)
]);
if (ab[0] == ab[1]) {
return ab[0];
@ -187,13 +197,13 @@ Future<String> checkPartialDownloadHashDynamic(String url,
}
Future<String> checkPartialDownloadHash(String url, int bytesToGrab,
{Map<String, String>? headers}) async {
{Map<String, String>? headers, bool allowInsecure = false}) async {
var req = Request('GET', Uri.parse(url));
if (headers != null) {
req.headers.addAll(headers);
}
req.headers[HttpHeaders.rangeHeader] = 'bytes=0-$bytesToGrab';
var client = http.Client();
var client = IOClient(createHttpClient(allowInsecure));
var response = await client.send(req);
if (response.statusCode < 200 || response.statusCode > 299) {
throw ObtainiumError(response.reasonPhrase ?? tr('unexpectedError'));
@ -204,12 +214,14 @@ Future<String> checkPartialDownloadHash(String url, int bytesToGrab,
Future<File> downloadFile(String url, String fileName, bool fileNameHasExt,
Function? onProgress, String destDir,
{bool useExisting = true, Map<String, String>? headers}) async {
{bool useExisting = true,
Map<String, String>? headers,
bool allowInsecure = false}) async {
// Send the initial request but cancel it as soon as you have the headers
var reqHeaders = headers ?? {};
var req = Request('GET', Uri.parse(url));
req.headers.addAll(reqHeaders);
var client = http.Client();
var client = IOClient(createHttpClient(allowInsecure));
StreamedResponse response = await client.send(req);
var resHeaders = response.headers;
@ -275,7 +287,7 @@ Future<File> downloadFile(String url, String fileName, bool fileNameHasExt,
IOSink? sink;
if (rangeFeatureEnabled && fullContentLength != null && rangeStart > 0) {
client.close();
client = http.Client();
client = IOClient(createHttpClient(allowInsecure));
req = Request('GET', Uri.parse(url));
req.headers.addAll(reqHeaders);
req.headers.addAll({'range': 'bytes=$rangeStart-${fullContentLength - 1}'});
@ -318,12 +330,12 @@ Future<File> downloadFile(String url, String fileName, bool fileNameHasExt,
}
Future<Map<String, String>> getHeaders(String url,
{Map<String, String>? headers}) async {
{Map<String, String>? headers, bool allowInsecure = false}) async {
var req = http.Request('GET', Uri.parse(url));
if (headers != null) {
req.headers.addAll(headers);
}
var client = http.Client();
var client = IOClient(createHttpClient(allowInsecure));
var response = await client.send(req);
if (response.statusCode < 200 || response.statusCode > 299) {
throw ObtainiumError(response.reasonPhrase ?? tr('unexpectedError'));
@ -468,7 +480,9 @@ class AppsProvider with ChangeNotifier {
notificationsProvider?.notify(notif);
}
prevProg = prog;
}, APKDir.path, useExisting: useExisting);
}, APKDir.path,
useExisting: useExisting,
allowInsecure: app.additionalSettings['allowInsecure'] == true);
// Set to 90 for remaining steps, will make null in 'finally'
if (apps[app.id] != null) {
apps[app.id]!.downloadProgress = -1;
@ -967,11 +981,16 @@ class AppsProvider with ChangeNotifier {
}
for (var res in downloadResults) {
if (!errors.appIdNames.containsKey(res['id'])) {
await installFn(
res['id'] as String,
res['willBeSilent'] as bool,
res['downloadedFile'] as DownloadedApk?,
res['downloadedDir'] as DownloadedXApkDir?);
try {
await installFn(
res['id'] as String,
res['willBeSilent'] as bool,
res['downloadedFile'] as DownloadedApk?,
res['downloadedDir'] as DownloadedXApkDir?);
} catch (e) {
var id = res['id'] as String;
errors.add(id, e, appName: apps[id]?.name);
}
}
}
@ -1031,7 +1050,8 @@ class AppsProvider with ChangeNotifier {
.getRequestHeaders(app.additionalSettings,
forAPKDownload:
fileUrl.key.endsWith('.apk') ? true : false),
useExisting: false);
useExisting: false,
allowInsecure: app.additionalSettings['allowInsecure'] == true);
notificationsProvider
.notify(DownloadedNotification(fileUrl.key, fileUrl.value));
} catch (e) {

View File

@ -2,11 +2,13 @@
// AppSource is an abstract class with a concrete implementation for each source
import 'dart:convert';
import 'dart:io';
import 'package:device_info_plus/device_info_plus.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:html/dom.dart';
import 'package:http/http.dart';
import 'package:http/io_client.dart';
import 'package:obtainium/app_sources/apkmirror.dart';
import 'package:obtainium/app_sources/apkpure.dart';
import 'package:obtainium/app_sources/aptoide.dart';
@ -399,6 +401,15 @@ getSourceRegex(List<String> hosts) {
return '(${hosts.join('|').replaceAll('.', '\\.')})';
}
HttpClient createHttpClient(bool insecure) {
final client = HttpClient();
if (insecure) {
client.badCertificateCallback =
(X509Certificate cert, String host, int port) => true;
}
return client;
}
abstract class AppSource {
List<String> hosts = [];
bool hostChanged = false;
@ -462,7 +473,9 @@ abstract class AppSource {
if (requestHeaders != null) {
req.headers.addAll(requestHeaders);
}
return Response.fromStream(await Client().send(req));
return Response.fromStream(await IOClient(
createHttpClient(additionalSettings['allowInsecure'] == true))
.send(req));
} else {
return get(Uri.parse(url));
}
@ -472,7 +485,7 @@ abstract class AppSource {
//
}
String sourceSpecificStandardizeURL(String url) {
String sourceSpecificStandardizeURL(String url, {bool forSelection = false}) {
throw NotImplementedError();
}
@ -538,6 +551,10 @@ abstract class AppSource {
GeneratedFormSwitch('shizukuPretendToBeGooglePlay',
label: tr('shizukuPretendToBeGooglePlay'), defaultValue: false)
],
[
GeneratedFormSwitch('allowInsecure',
label: tr('allowInsecure'), defaultValue: false)
],
[
GeneratedFormSwitch('exemptFromBackgroundUpdates',
label: tr('exemptFromBackgroundUpdates'))
@ -809,7 +826,7 @@ class SourceProvider {
for (var s in sources.where(
(element) => element.hosts.isEmpty && !element.neverAutoSelect)) {
try {
s.sourceSpecificStandardizeURL(url);
s.sourceSpecificStandardizeURL(url, forSelection: true);
source = s;
break;
} catch (e) {

View File

@ -47,10 +47,10 @@ packages:
dependency: "direct main"
description:
name: app_links
sha256: "4acba851087b25136e8f6e32a53bd4536eb3bec69947ddb66e7b9a5792ceb0c7"
sha256: ad1a6d598e7e39b46a34f746f9a8b011ee147e4c275d407fa457e7a62f84dd99
url: "https://pub.dev"
source: hosted
version: "6.2.0"
version: "6.3.2"
app_links_linux:
dependency: transitive
description:
@ -103,10 +103,10 @@ packages:
dependency: "direct main"
description:
name: background_fetch
sha256: b5c298c911bc2ce41152668bc72eb0488f0665d75bc6d1e69e7d8367763eddcd
sha256: f910c1c7c67a55f242daf78e9e9835d26eb01d39fc7f5d77f57dd84d009a6bab
url: "https://pub.dev"
source: hosted
version: "1.3.5"
version: "1.3.6"
boolean_selector:
dependency: transitive
description:
@ -311,10 +311,10 @@ packages:
dependency: transitive
description:
name: flex_seed_scheme
sha256: cc08c81879ecfd2ab840664ce4770980da0b8a319e35f51bcf763849b7f7596b
sha256: "7d97ba5c20f0e5cb1e3e2c17c865e1f797d129de31fc1f75d2dcce9470d6373c"
url: "https://pub.dev"
source: hosted
version: "3.1.2"
version: "3.3.0"
flutter:
dependency: "direct main"
description: flutter
@ -441,10 +441,10 @@ packages:
dependency: transitive
description:
name: flutter_plugin_android_lifecycle
sha256: "9d98bd47ef9d34e803d438f17fd32b116d31009f534a6fa5ce3a1167f189a6de"
sha256: "9ee02950848f61c4129af3d6ec84a1cfc0e47931abc746b03e7a3bc3e8ff6eda"
url: "https://pub.dev"
source: hosted
version: "2.0.21"
version: "2.0.22"
flutter_test:
dependency: "direct dev"
description: flutter
@ -611,10 +611,10 @@ packages:
dependency: transitive
description:
name: mime
sha256: "2e123074287cc9fd6c09de8336dae606d1ddb88d9ac47358826db698c176a1f2"
sha256: "801fd0b26f14a4a58ccb09d5892c3fbdeff209594300a542492cf13fba9d247a"
url: "https://pub.dev"
source: hosted
version: "1.0.5"
version: "1.0.6"
nested:
dependency: transitive
description:
@ -715,10 +715,10 @@ packages:
dependency: transitive
description:
name: permission_handler_html
sha256: d220eb8476b466d58b161e10b3001d93999010a26228a3fb89c4280db1249546
sha256: af26edbbb1f2674af65a8f4b56e1a6f526156bc273d0e65dd8075fab51c78851
url: "https://pub.dev"
source: hosted
version: "0.1.3+1"
version: "0.1.3+2"
permission_handler_platform_interface:
dependency: transitive
description:
@ -827,10 +827,10 @@ packages:
dependency: transitive
description:
name: shared_preferences_android
sha256: a7e8467e9181cef109f601e3f65765685786c1a738a83d7fbbde377589c0d974
sha256: "480ba4345773f56acda9abf5f50bd966f581dac5d514e5fc4a18c62976bbba7e"
url: "https://pub.dev"
source: hosted
version: "2.3.1"
version: "2.3.2"
shared_preferences_foundation:
dependency: transitive
description:
@ -1001,10 +1001,10 @@ packages:
dependency: transitive
description:
name: url_launcher_android
sha256: f0c73347dfcfa5b3db8bc06e1502668265d39c08f310c29bff4e28eea9699f79
sha256: e35a698ac302dd68e41f73250bd9517fe3ab5fa4f18fe4647a0872db61bacbab
url: "https://pub.dev"
source: hosted
version: "6.3.9"
version: "6.3.10"
url_launcher_ios:
dependency: transitive
description:
@ -1057,10 +1057,10 @@ packages:
dependency: transitive
description:
name: uuid
sha256: "83d37c7ad7aaf9aa8e275490669535c8080377cfa7a7004c24dfac53afffaa90"
sha256: f33d6bb662f0e4f79dcd7ada2e6170f3b3a2530c28fc41f49a411ddedd576a77
url: "https://pub.dev"
source: hosted
version: "4.4.2"
version: "4.5.0"
vector_math:
dependency: transitive
description:
@ -1073,10 +1073,10 @@ packages:
dependency: transitive
description:
name: vm_service
sha256: f652077d0bdf60abe4c1f6377448e8655008eef28f128bc023f7b5e8dfeb48fc
sha256: "5c5f338a667b4c644744b661f309fb8080bb94b18a7e91ef1dbd343bed00ed6d"
url: "https://pub.dev"
source: hosted
version: "14.2.4"
version: "14.2.5"
web:
dependency: transitive
description:
@ -1089,18 +1089,18 @@ packages:
dependency: "direct main"
description:
name: webview_flutter
sha256: "6869c8786d179f929144b4a1f86e09ac0eddfe475984951ea6c634774c16b522"
sha256: ec81f57aa1611f8ebecf1d2259da4ef052281cb5ad624131c93546c79ccc7736
url: "https://pub.dev"
source: hosted
version: "4.8.0"
version: "4.9.0"
webview_flutter_android:
dependency: transitive
description:
name: webview_flutter_android
sha256: c66651fba15f9d7ddd31daec42da8d6bce46c85610a7127e3ebcb39a4395c3c9
sha256: "6e64fcb1c19d92024da8f33503aaeeda35825d77142c01d0ea2aa32edc79fdc8"
url: "https://pub.dev"
source: hosted
version: "3.16.6"
version: "3.16.7"
webview_flutter_platform_interface:
dependency: transitive
description:

View File

@ -17,7 +17,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
# In Windows, build-name is used as the major, minor, and patch parts
# of the product and file versions while build-number is used as the build suffix.
version: 1.1.20+2277
version: 1.1.21+2278
environment:
sdk: '>=3.0.0 <4.0.0'