Merge pull request #1874 from ImranR98/dev

- App-wide "pretend to be GPlay" option (#1859)
- UI bugfixes on add app page (#1866)
- UI tweaks (#1871, #1872)
- Added "skip latest" option to APKPure (also fallback toggle)
This commit is contained in:
Imran
2024-10-01 16:20:55 -05:00
committed by GitHub
32 changed files with 205 additions and 118 deletions

View File

@@ -315,6 +315,7 @@
"wiki": "Pomoć/Wiki", "wiki": "Pomoć/Wiki",
"crowdsourcedConfigsLabel": "Konfiguracije aplikacije obezbeđene pomoću velikog broja ljudi (crowdsourcing) (koristite na svoju odgovornost)", "crowdsourcedConfigsLabel": "Konfiguracije aplikacije obezbeđene pomoću velikog broja ljudi (crowdsourcing) (koristite na svoju odgovornost)",
"allowInsecure": "Allow insecure HTTP requests", "allowInsecure": "Allow insecure HTTP requests",
"stayOneVersionBehind": "Stay one version behind latest",
"removeAppQuestion": { "removeAppQuestion": {
"one": "Želite li ukloniti aplikaciju?", "one": "Želite li ukloniti aplikaciju?",
"other": "Želite li ukloniti aplikacije?" "other": "Želite li ukloniti aplikacije?"

View File

@@ -315,6 +315,7 @@
"wiki": "Nápověda/Wiki", "wiki": "Nápověda/Wiki",
"crowdsourcedConfigsLabel": "Konfigurace aplikací s využitím crowdsourcingu (použití na vlastní nebezpečí)", "crowdsourcedConfigsLabel": "Konfigurace aplikací s využitím crowdsourcingu (použití na vlastní nebezpečí)",
"allowInsecure": "Povolení nezabezpečených požadavků HTTP", "allowInsecure": "Povolení nezabezpečených požadavků HTTP",
"stayOneVersionBehind": "Zůstaňte o jednu verzi pozadu za nejnovější",
"removeAppQuestion": { "removeAppQuestion": {
"one": "Odstranit Apku?", "one": "Odstranit Apku?",
"other": "Odstranit Apky?" "other": "Odstranit Apky?"

View File

@@ -315,6 +315,7 @@
"wiki": "Hjælp/Wiki", "wiki": "Hjælp/Wiki",
"crowdsourcedConfigsLabel": "Crowdsourcede app-konfigurationer (brug på egen risiko)", "crowdsourcedConfigsLabel": "Crowdsourcede app-konfigurationer (brug på egen risiko)",
"allowInsecure": "Tillad usikre HTTP-anmodninger", "allowInsecure": "Tillad usikre HTTP-anmodninger",
"stayOneVersionBehind": "Vær en version bagud i forhold til den nyeste",
"removeAppQuestion": { "removeAppQuestion": {
"one": "Fjern app?", "one": "Fjern app?",
"other": "Fjern apps?" "other": "Fjern apps?"

View File

@@ -315,6 +315,7 @@
"wiki": "Hilfe/Wiki", "wiki": "Hilfe/Wiki",
"crowdsourcedConfigsLabel": "Crowdsourced App-Konfigurationen (Verwendung auf eigene Gefahr)", "crowdsourcedConfigsLabel": "Crowdsourced App-Konfigurationen (Verwendung auf eigene Gefahr)",
"allowInsecure": "Unsichere HTTP-Anfragen zulassen", "allowInsecure": "Unsichere HTTP-Anfragen zulassen",
"stayOneVersionBehind": "Eine Version hinter der neuesten Version bleiben",
"removeAppQuestion": { "removeAppQuestion": {
"one": "App entfernen?", "one": "App entfernen?",
"other": "Apps entfernen?" "other": "Apps entfernen?"

View File

@@ -315,6 +315,7 @@
"wiki": "Helpo/Vikio", "wiki": "Helpo/Vikio",
"crowdsourcedConfigsLabel": "Komunumaj apo-agordoj (uzu kun singardo)", "crowdsourcedConfigsLabel": "Komunumaj apo-agordoj (uzu kun singardo)",
"allowInsecure": "Allow insecure HTTP requests", "allowInsecure": "Allow insecure HTTP requests",
"stayOneVersionBehind": "Stay one version behind latest",
"removeAppQuestion": { "removeAppQuestion": {
"one": "Forigi la aplikaĵon?", "one": "Forigi la aplikaĵon?",
"other": "Forigi la aplikaĵojn?" "other": "Forigi la aplikaĵojn?"

View File

@@ -315,6 +315,7 @@
"wiki": "Help/Wiki", "wiki": "Help/Wiki",
"crowdsourcedConfigsLabel": "Crowdsourced App Configurations (use at your own risk)", "crowdsourcedConfigsLabel": "Crowdsourced App Configurations (use at your own risk)",
"allowInsecure": "Allow insecure HTTP requests", "allowInsecure": "Allow insecure HTTP requests",
"stayOneVersionBehind": "Stay one version behind latest",
"removeAppQuestion": { "removeAppQuestion": {
"one": "Remove App?", "one": "Remove App?",
"other": "Remove Apps?" "other": "Remove Apps?"

View File

@@ -315,6 +315,7 @@
"wiki": "Ayuda/Wiki", "wiki": "Ayuda/Wiki",
"crowdsourcedConfigsLabel": "Crowdsourced App Configurations (uso bajo su propia responsabilidad)", "crowdsourcedConfigsLabel": "Crowdsourced App Configurations (uso bajo su propia responsabilidad)",
"allowInsecure": "Permitir peticiones HTTP inseguras", "allowInsecure": "Permitir peticiones HTTP inseguras",
"stayOneVersionBehind": "Mantenerse una versión por detrás de la última",
"removeAppQuestion": { "removeAppQuestion": {
"one": "¿Eliminar aplicación?", "one": "¿Eliminar aplicación?",
"other": "¿Eliminar aplicaciones?" "other": "¿Eliminar aplicaciones?"

View File

@@ -315,6 +315,7 @@
"wiki": "راهنما/ویکی", "wiki": "راهنما/ویکی",
"crowdsourcedConfigsLabel": "تنظیمات برنامه Crowdsourced (با مسئولیت خود استفاده کنید)", "crowdsourcedConfigsLabel": "تنظیمات برنامه Crowdsourced (با مسئولیت خود استفاده کنید)",
"allowInsecure": "درخواست های HTTP ناامن را مجاز کنید", "allowInsecure": "درخواست های HTTP ناامن را مجاز کنید",
"stayOneVersionBehind": "Stay one version behind latest",
"removeAppQuestion": { "removeAppQuestion": {
"one": "برنامه حذف شود؟", "one": "برنامه حذف شود؟",
"other": "برنامه ها حذف شوند؟" "other": "برنامه ها حذف شوند؟"

View File

@@ -315,6 +315,7 @@
"wiki": "Aide/Wiki", "wiki": "Aide/Wiki",
"crowdsourcedConfigsLabel": "Configurations d'applications par la communauté (à utiliser à vos risques et périls)", "crowdsourcedConfigsLabel": "Configurations d'applications par la communauté (à utiliser à vos risques et périls)",
"allowInsecure": "Autoriser les requêtes HTTP non sécurisées", "allowInsecure": "Autoriser les requêtes HTTP non sécurisées",
"stayOneVersionBehind": "Rester à une version de la dernière",
"removeAppQuestion": { "removeAppQuestion": {
"one": "Supprimer l'application?", "one": "Supprimer l'application?",
"other": "Supprimer les applications?" "other": "Supprimer les applications?"

View File

@@ -315,6 +315,7 @@
"wiki": "Súgó/Wiki", "wiki": "Súgó/Wiki",
"crowdsourcedConfigsLabel": "Crowdsource-ből származó alkalmazások beállítása (saját felelősségére használja)", "crowdsourcedConfigsLabel": "Crowdsource-ből származó alkalmazások beállítása (saját felelősségére használja)",
"allowInsecure": "Nem biztonságos HTTP-kérések engedélyezése", "allowInsecure": "Nem biztonságos HTTP-kérések engedélyezése",
"stayOneVersionBehind": "Maradjon egy verzióval a legfrissebb mögött",
"removeAppQuestion": { "removeAppQuestion": {
"one": "Eltávolítja az alkalmazást?", "one": "Eltávolítja az alkalmazást?",
"other": "Eltávolítja az alkalmazásokat?" "other": "Eltávolítja az alkalmazásokat?"

View File

@@ -204,7 +204,7 @@
"categoryDeleteWarning": "Semua aplikasi dalam kategori yang dihapus akan diatur sebagai tidak terkategori.", "categoryDeleteWarning": "Semua aplikasi dalam kategori yang dihapus akan diatur sebagai tidak terkategori.",
"addCategory": "Tambah kategori", "addCategory": "Tambah kategori",
"label": "Label", "label": "Label",
"language": "Language", "language": "Bahasa",
"copiedToClipboard": "Disalin ke papan klip", "copiedToClipboard": "Disalin ke papan klip",
"storagePermissionDenied": "Izin penyimpanan ditolak", "storagePermissionDenied": "Izin penyimpanan ditolak",
"selectedCategorizeWarning": "Ini akan mengganti pengaturan kategori yang ada untuk aplikasi terpilih.", "selectedCategorizeWarning": "Ini akan mengganti pengaturan kategori yang ada untuk aplikasi terpilih.",
@@ -315,6 +315,7 @@
"wiki": "Bantuan/Wiki", "wiki": "Bantuan/Wiki",
"crowdsourcedConfigsLabel": "Konfigurasi aplikasi Crowdsourced (risiko penggunaan ditanggung sendiri)", "crowdsourcedConfigsLabel": "Konfigurasi aplikasi Crowdsourced (risiko penggunaan ditanggung sendiri)",
"allowInsecure": "Izinkan permintaan HTTP yang tidak aman", "allowInsecure": "Izinkan permintaan HTTP yang tidak aman",
"stayOneVersionBehind": "Tetap satu versi di belakang versi terbaru",
"removeAppQuestion": { "removeAppQuestion": {
"one": "Hapus aplikasi?", "one": "Hapus aplikasi?",
"other": "Hapus aplikasi?" "other": "Hapus aplikasi?"

View File

@@ -315,6 +315,7 @@
"wiki": "Aiuto/Wiki", "wiki": "Aiuto/Wiki",
"crowdsourcedConfigsLabel": "Configurazioni di app in crowdsourcing (uso a proprio rischio)", "crowdsourcedConfigsLabel": "Configurazioni di app in crowdsourcing (uso a proprio rischio)",
"allowInsecure": "Consentire le richieste HTTP non sicure", "allowInsecure": "Consentire le richieste HTTP non sicure",
"stayOneVersionBehind": "Rimanere una versione indietro rispetto alla più recente",
"removeAppQuestion": { "removeAppQuestion": {
"one": "Rimuovere l'app?", "one": "Rimuovere l'app?",
"other": "Rimuovere le app?" "other": "Rimuovere le app?"

View File

@@ -315,6 +315,7 @@
"wiki": "ヘルプ/ウィキ", "wiki": "ヘルプ/ウィキ",
"crowdsourcedConfigsLabel": "クラウドソーシングによるアプリの設定(利用は自己責任で)", "crowdsourcedConfigsLabel": "クラウドソーシングによるアプリの設定(利用は自己責任で)",
"allowInsecure": "安全でないHTTPリクエストを許可する", "allowInsecure": "安全でないHTTPリクエストを許可する",
"stayOneVersionBehind": "最新バージョンから1つ遅れ",
"removeAppQuestion": { "removeAppQuestion": {
"one": "アプリを削除しますか?", "one": "アプリを削除しますか?",
"other": "アプリを削除しますか?" "other": "アプリを削除しますか?"

View File

@@ -315,6 +315,7 @@
"wiki": "Help/Wiki", "wiki": "Help/Wiki",
"crowdsourcedConfigsLabel": "Crowdsourced App-configuraties (gebruik op eigen risico)", "crowdsourcedConfigsLabel": "Crowdsourced App-configuraties (gebruik op eigen risico)",
"allowInsecure": "Onveilige HTTP-verzoeken toestaan", "allowInsecure": "Onveilige HTTP-verzoeken toestaan",
"stayOneVersionBehind": "Blijf een versie achter op de nieuwste",
"removeAppQuestion": { "removeAppQuestion": {
"one": "App verwijderen?", "one": "App verwijderen?",
"other": "Apps verwijderen?" "other": "Apps verwijderen?"

View File

@@ -315,6 +315,7 @@
"wiki": "Pomoc/Wiki", "wiki": "Pomoc/Wiki",
"crowdsourcedConfigsLabel": "Konfiguracje aplikacji pochodzące z crowdsourcingu (korzystanie na własne ryzyko)", "crowdsourcedConfigsLabel": "Konfiguracje aplikacji pochodzące z crowdsourcingu (korzystanie na własne ryzyko)",
"allowInsecure": "Zezwalaj na niezabezpieczone żądania HTTP", "allowInsecure": "Zezwalaj na niezabezpieczone żądania HTTP",
"stayOneVersionBehind": "Pozostań jedną wersję za najnowszą",
"removeAppQuestion": { "removeAppQuestion": {
"one": "Usunąć aplikację?", "one": "Usunąć aplikację?",
"few": "Usunąć aplikacje?", "few": "Usunąć aplikacje?",

View File

@@ -315,6 +315,7 @@
"wiki": "Ajuda/Wiki", "wiki": "Ajuda/Wiki",
"crowdsourcedConfigsLabel": "Configurações de aplicações de crowdsourcing (utilização por sua conta e risco)", "crowdsourcedConfigsLabel": "Configurações de aplicações de crowdsourcing (utilização por sua conta e risco)",
"allowInsecure": "Permitir pedidos HTTP inseguros", "allowInsecure": "Permitir pedidos HTTP inseguros",
"stayOneVersionBehind": "Manter-se uma versão atrás da mais recente",
"removeAppQuestion": { "removeAppQuestion": {
"one": "Remover aplicativo?", "one": "Remover aplicativo?",
"other": "Remover aplicativos?" "other": "Remover aplicativos?"

View File

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

View File

@@ -315,6 +315,7 @@
"wiki": "Hjälp/Wiki", "wiki": "Hjälp/Wiki",
"crowdsourcedConfigsLabel": "Crowdsourcade appkonfigurationer (använd på egen risk)", "crowdsourcedConfigsLabel": "Crowdsourcade appkonfigurationer (använd på egen risk)",
"allowInsecure": "Tillåt osäkra HTTP-förfrågningar", "allowInsecure": "Tillåt osäkra HTTP-förfrågningar",
"stayOneVersionBehind": "Håll dig en version bakom den senaste",
"removeAppQuestion": { "removeAppQuestion": {
"one": "Ta Bort App?", "one": "Ta Bort App?",
"other": "Ta Bort Appar?" "other": "Ta Bort Appar?"

View File

@@ -315,6 +315,7 @@
"wiki": "Yardım/Wiki", "wiki": "Yardım/Wiki",
"crowdsourcedConfigsLabel": "Kitle Kaynaklı Uygulama Yapılandırmaları (riski size ait olmak üzere kullanın)", "crowdsourcedConfigsLabel": "Kitle Kaynaklı Uygulama Yapılandırmaları (riski size ait olmak üzere kullanın)",
"allowInsecure": "Güvensiz HTTP isteklerine izin ver", "allowInsecure": "Güvensiz HTTP isteklerine izin ver",
"stayOneVersionBehind": "En son sürümün bir sürüm gerisinde kalın",
"removeAppQuestion": { "removeAppQuestion": {
"one": "Uygulamayı Kaldır?", "one": "Uygulamayı Kaldır?",
"other": "Uygulamaları Kaldır?" "other": "Uygulamaları Kaldır?"

View File

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

View File

@@ -315,6 +315,7 @@
"wiki": "Trợ giúp/Wiki", "wiki": "Trợ giúp/Wiki",
"crowdsourcedConfigsLabel": "Crowdsourced App Configurations (use at your own risk)", "crowdsourcedConfigsLabel": "Crowdsourced App Configurations (use at your own risk)",
"allowInsecure": "Allow insecure HTTP requests", "allowInsecure": "Allow insecure HTTP requests",
"stayOneVersionBehind": "Stay one version behind latest",
"removeAppQuestion": { "removeAppQuestion": {
"one": "Gỡ ứng dụng?", "one": "Gỡ ứng dụng?",
"other": "Gỡ ứng dụng?" "other": "Gỡ ứng dụng?"

View File

@@ -315,6 +315,7 @@
"wiki": "幫助/維基", "wiki": "幫助/維基",
"crowdsourcedConfigsLabel": "群眾外包的應用程式設定(使用風險自負)", "crowdsourcedConfigsLabel": "群眾外包的應用程式設定(使用風險自負)",
"allowInsecure": "Allow insecure HTTP requests", "allowInsecure": "Allow insecure HTTP requests",
"stayOneVersionBehind": "Stay one version behind latest",
"removeAppQuestion": { "removeAppQuestion": {
"one": "移除應用程式?", "one": "移除應用程式?",
"other": "移除應用程式?" "other": "移除應用程式?"

View File

@@ -315,6 +315,7 @@
"wiki": "帮助/Wiki", "wiki": "帮助/Wiki",
"crowdsourcedConfigsLabel": "众包应用程序配置(使用风险自负)", "crowdsourcedConfigsLabel": "众包应用程序配置(使用风险自负)",
"allowInsecure": "允许不安全的 HTTP 请求", "allowInsecure": "允许不安全的 HTTP 请求",
"stayOneVersionBehind": "比最新版本晚一个版本",
"removeAppQuestion": { "removeAppQuestion": {
"one": "是否删除应用?", "one": "是否删除应用?",
"other": "是否删除应用?" "other": "是否删除应用?"

View File

@@ -2,6 +2,7 @@ import 'package:device_info_plus/device_info_plus.dart';
import 'package:easy_localization/easy_localization.dart'; import 'package:easy_localization/easy_localization.dart';
import 'package:html/parser.dart'; import 'package:html/parser.dart';
import 'package:obtainium/app_sources/html.dart'; import 'package:obtainium/app_sources/html.dart';
import 'package:obtainium/components/generated_form.dart';
import 'package:obtainium/custom_errors.dart'; import 'package:obtainium/custom_errors.dart';
import 'package:obtainium/providers/source_provider.dart'; import 'package:obtainium/providers/source_provider.dart';
@@ -26,6 +27,16 @@ class APKPure extends AppSource {
allowSubDomains = true; allowSubDomains = true;
naiveStandardVersionDetection = true; naiveStandardVersionDetection = true;
showReleaseDateAsVersionToggle = true; showReleaseDateAsVersionToggle = true;
additionalSourceAppSpecificSettingFormItems = [
[
GeneratedFormSwitch('fallbackToOlderReleases',
label: tr('fallbackToOlderReleases'), defaultValue: true)
],
[
GeneratedFormSwitch('stayOneVersionBehind',
label: tr('stayOneVersionBehind'), defaultValue: false)
]
];
} }
@override @override
@@ -54,6 +65,97 @@ class APKPure extends AppSource {
return Uri.parse(standardUrl).pathSegments.last; return Uri.parse(standardUrl).pathSegments.last;
} }
getDetailsForVersionLink(
String standardUrl,
String appId,
String host,
List<String> supportedArchs,
String link,
Map<String, dynamic> additionalSettings) async {
var res = await sourceRequest(link, additionalSettings);
if (res.statusCode == 200) {
var html = parse(res.body);
var apksDiv =
html.querySelector('#version-list div div.show-more-content');
DateTime? topReleaseDate;
var apkUrls = apksDiv
?.querySelectorAll('div.group-title')
.map((e) {
String architectureString = e.text.trim();
if (architectureString.toLowerCase() == 'unlimited' ||
architectureString.toLowerCase() == 'universal') {
architectureString = '';
}
List<String> architectures = architectureString
.split(',')
.map((e) => e.trim())
.where((e) => e.isNotEmpty)
.toList();
// Only take the first APK for each architecture, ignore others for now, for simplicity
// Unclear why there can even be multiple APKs for the same version and arch
var apkInfo = e.nextElementSibling?.querySelector('div.info');
String? versionCode = RegExp('[0-9]+')
.firstMatch(apkInfo
?.querySelector('div.info-top span.code')
?.text ??
'')
?.group(0)
?.trim();
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
.trim();
DateTime? releaseDate = parseDateTimeMMMddCommayyyy(dateString);
if (additionalSettings['autoApkFilterByArch'] == true &&
architectures.isNotEmpty &&
architectures
.where((a) => supportedArchs.contains(a))
.isEmpty) {
return const MapEntry('', '');
}
topReleaseDate ??=
releaseDate; // Just use the release date of the first APK in the list as the release date for this version
return MapEntry(
'$appId-$versionCode-$architectureString.${type.toLowerCase()}',
'https://d.${hosts.contains(host) ? 'cdnpure.com' : host}/b/$type/$appId?versionCode=$versionCode');
})
.where((e) => e.key.isNotEmpty)
.toList() ??
[];
if (apkUrls.isEmpty) {
throw NoAPKError();
}
String version = Uri.parse(link).pathSegments.last;
String author = html
.querySelector('span.info-sdk')
?.text
.trim()
.substring(version.length + 4) ??
Uri.parse(standardUrl).pathSegments.reversed.last;
String appName =
html.querySelector('h1.info-title')?.text.trim() ?? appId;
String? changeLog = html
.querySelector('div.module.change-log')
?.innerHtml
.trim()
.replaceAll("<br>", " \n");
return APKDetails(version, apkUrls, AppNames(author, appName),
releaseDate: topReleaseDate, changeLog: changeLog);
} else {
throw getObtainiumHttpError(res);
}
}
@override @override
Future<APKDetails> getLatestAPKDetails( Future<APKDetails> getLatestAPKDetails(
String standardUrl, String standardUrl,
@@ -80,88 +182,17 @@ class APKPure extends AppSource {
for (var i = 0; i < versionLinks.length; i++) { for (var i = 0; i < versionLinks.length; i++) {
var link = versionLinks[i]; var link = versionLinks[i];
var res = await sourceRequest(link.key, additionalSettings); try {
if (res.statusCode == 200) { if (i == 0 && additionalSettings['stayOneVersionBehind'] == true) {
var html = parse(res.body); throw NoReleasesError();
var apksDiv = }
html.querySelector('#version-list div div.show-more-content'); return await getDetailsForVersionLink(standardUrl, appId, host,
DateTime? topReleaseDate; supportedArchs, link.key, additionalSettings);
var apkUrls = apksDiv } catch (e) {
?.querySelectorAll('div.group-title') if (additionalSettings['fallbackToOlderReleases'] != true ||
.map((e) { i == versionLinks.length - 1) {
String architectureString = e.text.trim(); rethrow;
if (architectureString.toLowerCase() == 'unlimited' ||
architectureString.toLowerCase() == 'universal') {
architectureString = '';
}
List<String> architectures = architectureString
.split(',')
.map((e) => e.trim())
.where((e) => e.isNotEmpty)
.toList();
// Only take the first APK for each architecture, ignore others for now, for simplicity
// Unclear why there can even be multiple APKs for the same version and arch
var apkInfo = e.nextElementSibling?.querySelector('div.info');
String? versionCode = RegExp('[0-9]+')
.firstMatch(apkInfo
?.querySelector('div.info-top span.code')
?.text ??
'')
?.group(0)
?.trim();
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
.trim();
DateTime? releaseDate =
parseDateTimeMMMddCommayyyy(dateString);
if (additionalSettings['autoApkFilterByArch'] == true &&
architectures.isNotEmpty &&
architectures
.where((a) => supportedArchs.contains(a))
.isEmpty) {
return const MapEntry('', '');
}
topReleaseDate ??=
releaseDate; // Just use the release date of the first APK in the list as the release date for this version
return MapEntry(
'$appId-$versionCode-$architectureString.${type.toLowerCase()}',
'https://d.${hosts.contains(host) ? 'cdnpure.com' : host}/b/$type/$appId?versionCode=$versionCode');
})
.where((e) => e.key.isNotEmpty)
.toList() ??
[];
if (apkUrls.isEmpty) {
continue;
} }
String version = Uri.parse(link.key).pathSegments.last;
String author = html
.querySelector('span.info-sdk')
?.text
.trim()
.substring(version.length + 4) ??
Uri.parse(standardUrl).pathSegments.reversed.last;
String appName =
html.querySelector('h1.info-title')?.text.trim() ?? appId;
String? changeLog = html
.querySelector('div.module.change-log')
?.innerHtml
.trim()
.replaceAll("<br>", " \n");
return APKDetails(version, apkUrls, AppNames(author, appName),
releaseDate: topReleaseDate, changeLog: changeLog);
} else {
throw getObtainiumHttpError(res);
} }
} }
throw NoAPKError(); throw NoAPKError();

View File

@@ -30,6 +30,7 @@ class AddAppPageState extends State<AddAppPage> {
String userInput = ''; String userInput = '';
String searchQuery = ''; String searchQuery = '';
String? pickedSourceOverride; String? pickedSourceOverride;
String? previousPickedSourceOverride;
AppSource? pickedSource; AppSource? pickedSource;
Map<String, dynamic> additionalSettings = {}; Map<String, dynamic> additionalSettings = {};
bool additionalSettingsValid = true; bool additionalSettingsValid = true;
@@ -58,6 +59,9 @@ class AddAppPageState extends State<AddAppPage> {
if (overrideSource != null) { if (overrideSource != null) {
pickedSourceOverride = overrideSource; pickedSourceOverride = overrideSource;
} }
bool overrideChanged =
pickedSourceOverride != previousPickedSourceOverride;
previousPickedSourceOverride = pickedSourceOverride;
if (updateUrlInput) { if (updateUrlInput) {
urlInputKey++; urlInputKey++;
} }
@@ -69,6 +73,7 @@ class AddAppPageState extends State<AddAppPage> {
overrideSource: pickedSourceOverride) overrideSource: pickedSourceOverride)
: null; : null;
if (pickedSource.runtimeType != source.runtimeType || if (pickedSource.runtimeType != source.runtimeType ||
overrideChanged ||
(prevHost != null && prevHost != source?.hosts[0])) { (prevHost != null && prevHost != source?.hosts[0])) {
pickedSource = source; pickedSource = source;
pickedSource?.runOnAddAppInputChange(userInput); pickedSource?.runOnAddAppInputChange(userInput);
@@ -487,7 +492,8 @@ class AddAppPageState extends State<AddAppPage> {
height: 16, height: 16,
), ),
GeneratedForm( GeneratedForm(
key: Key(pickedSource.runtimeType.toString()), key: Key(
'${pickedSource.runtimeType.toString()}-${pickedSource?.hostChanged.toString()}-${pickedSource?.hostIdenticalDespiteAnyChange.toString()}'),
items: [ items: [
...pickedSource!.combinedAppSpecificSettingFormItems, ...pickedSource!.combinedAppSpecificSettingFormItems,
...(pickedSourceOverride != null ...(pickedSourceOverride != null

View File

@@ -242,11 +242,11 @@ class _AppPageState extends State<AppPage> {
); );
} }
getFullInfoColumn() => Column( getFullInfoColumn({bool small = false}) => Column(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.stretch, crossAxisAlignment: CrossAxisAlignment.stretch,
children: [ children: [
const SizedBox(height: 20), SizedBox(height: small ? 5 : 20),
FutureBuilder( FutureBuilder(
future: future:
appsProvider.updateAppIcon(app?.app.id, ignoreCache: true), appsProvider.updateAppIcon(app?.app.id, ignoreCache: true),
@@ -261,24 +261,28 @@ class _AppPageState extends State<AppPage> {
: () => pm.openApp(app.app.id), : () => pm.openApp(app.app.id),
child: Image.memory( child: Image.memory(
app!.icon!, app!.icon!,
height: 150, height: small ? 70 : 150,
gaplessPlayback: true, gaplessPlayback: true,
), ),
) )
]) ])
: Container(); : Container();
}), }),
const SizedBox( SizedBox(
height: 25, height: small ? 10 : 25,
), ),
Text( Text(
app?.name ?? tr('app'), app?.name ?? tr('app'),
textAlign: TextAlign.center, textAlign: TextAlign.center,
style: Theme.of(context).textTheme.displayLarge, style: small
? Theme.of(context).textTheme.displaySmall
: Theme.of(context).textTheme.displayLarge,
), ),
Text(tr('byX', args: [app?.app.author ?? tr('unknown')]), Text(tr('byX', args: [app?.app.author ?? tr('unknown')]),
textAlign: TextAlign.center, textAlign: TextAlign.center,
style: Theme.of(context).textTheme.headlineMedium), style: small
? Theme.of(context).textTheme.headlineSmall
: Theme.of(context).textTheme.headlineMedium),
const SizedBox( const SizedBox(
height: 24, height: 24,
), ),
@@ -496,11 +500,8 @@ class _AppPageState extends State<AppPage> {
builder: (BuildContext ctx) { builder: (BuildContext ctx) {
return AlertDialog( return AlertDialog(
scrollable: true, scrollable: true,
content: getInfoColumn(), content: getFullInfoColumn(small: true),
title: Text( title: Text(app.name),
'${app.name} ${tr('byX', args: [
app.app.author
])}'),
actions: [ actions: [
TextButton( TextButton(
onPressed: () { onPressed: () {

View File

@@ -578,6 +578,22 @@ class _SettingsPageState extends State<SettingsPage> {
}) })
], ],
), ),
height16,
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Flexible(
child: Text(
tr('shizukuPretendToBeGooglePlay'))),
Switch(
value: settingsProvider
.shizukuPretendToBeGooglePlay,
onChanged: (value) {
settingsProvider
.shizukuPretendToBeGooglePlay = value;
})
],
),
height32, height32,
Text( Text(
tr('sourceSpecific'), tr('sourceSpecific'),

View File

@@ -879,22 +879,20 @@ class AppsProvider with ChangeNotifier {
apps[id]?.installedInfo == null ? context : null; apps[id]?.installedInfo == null ? context : null;
bool needBGWorkaround = bool needBGWorkaround =
willBeSilent && context == null && !settingsProvider.useShizuku; willBeSilent && context == null && !settingsProvider.useShizuku;
bool shizukuPretendToBeGooglePlay = settingsProvider
.shizukuPretendToBeGooglePlay ||
apps[id]!.app.additionalSettings['shizukuPretendToBeGooglePlay'] ==
true;
if (downloadedFile != null) { if (downloadedFile != null) {
if (needBGWorkaround) { if (needBGWorkaround) {
// ignore: use_build_context_synchronously // ignore: use_build_context_synchronously
installApk(downloadedFile, contextIfNewInstall, installApk(downloadedFile, contextIfNewInstall,
needsBGWorkaround: true, needsBGWorkaround: true,
shizukuPretendToBeGooglePlay: apps[id]! shizukuPretendToBeGooglePlay: shizukuPretendToBeGooglePlay);
.app
.additionalSettings['shizukuPretendToBeGooglePlay'] ==
true);
} else { } else {
// ignore: use_build_context_synchronously // ignore: use_build_context_synchronously
sayInstalled = await installApk(downloadedFile, contextIfNewInstall, sayInstalled = await installApk(downloadedFile, contextIfNewInstall,
shizukuPretendToBeGooglePlay: apps[id]! shizukuPretendToBeGooglePlay: shizukuPretendToBeGooglePlay);
.app
.additionalSettings['shizukuPretendToBeGooglePlay'] ==
true);
} }
} else { } else {
if (needBGWorkaround) { if (needBGWorkaround) {
@@ -905,10 +903,7 @@ class AppsProvider with ChangeNotifier {
// ignore: use_build_context_synchronously // ignore: use_build_context_synchronously
sayInstalled = await installXApkDir( sayInstalled = await installXApkDir(
downloadedDir!, contextIfNewInstall, downloadedDir!, contextIfNewInstall,
shizukuPretendToBeGooglePlay: apps[id]! shizukuPretendToBeGooglePlay: shizukuPretendToBeGooglePlay);
.app
.additionalSettings['shizukuPretendToBeGooglePlay'] ==
true);
} }
} }
if (willBeSilent && context == null) { if (willBeSilent && context == null) {

View File

@@ -48,7 +48,7 @@ class SettingsProvider with ChangeNotifier {
notifyListeners(); notifyListeners();
} }
bool get useShizuku{ bool get useShizuku {
return prefs?.getBool('useShizuku') ?? false; return prefs?.getBool('useShizuku') ?? false;
} }
@@ -69,8 +69,7 @@ class SettingsProvider with ChangeNotifier {
Color get themeColor { Color get themeColor {
int? colorCode = prefs?.getInt('themeColor'); int? colorCode = prefs?.getInt('themeColor');
return (colorCode != null) ? return (colorCode != null) ? Color(colorCode) : obtainiumThemeColor;
Color(colorCode) : obtainiumThemeColor;
} }
set themeColor(Color themeColor) { set themeColor(Color themeColor) {
@@ -469,4 +468,13 @@ class SettingsProvider with ChangeNotifier {
prefs?.setBool('beforeNewInstallsShareToAppVerifier', val); prefs?.setBool('beforeNewInstallsShareToAppVerifier', val);
notifyListeners(); notifyListeners();
} }
bool get shizukuPretendToBeGooglePlay {
return prefs?.getBool('shizukuPretendToBeGooglePlay') ?? false;
}
set shizukuPretendToBeGooglePlay(bool val) {
prefs?.setBool('shizukuPretendToBeGooglePlay', val);
notifyListeners();
}
} }

View File

@@ -414,6 +414,7 @@ HttpClient createHttpClient(bool insecure) {
abstract class AppSource { abstract class AppSource {
List<String> hosts = []; List<String> hosts = [];
bool hostChanged = false; bool hostChanged = false;
bool hostIdenticalDespiteAnyChange = false;
late String name; late String name;
bool enforceTrackOnly = false; bool enforceTrackOnly = false;
bool changeLogIfAnyIsMarkDown = true; bool changeLogIfAnyIsMarkDown = true;
@@ -628,9 +629,10 @@ abstract class AppSource {
SettingsProvider settingsProvider) async { SettingsProvider settingsProvider) async {
Map<String, String> results = {}; Map<String, String> results = {};
for (var e in sourceConfigSettingFormItems) { for (var e in sourceConfigSettingFormItems) {
var val = hostChanged var val = hostChanged && !hostIdenticalDespiteAnyChange
? additionalSettings[e.key] ? additionalSettings[e.key]
: settingsProvider.getSettingString(e.key); : additionalSettings[e.key] ??
settingsProvider.getSettingString(e.key);
if (val != null) { if (val != null) {
results[e.key] = val; results[e.key] = val;
} }
@@ -813,9 +815,14 @@ class SourceProvider {
throw UnsupportedURLError(); throw UnsupportedURLError();
} }
var res = srcs.first; var res = srcs.first;
res.hosts = [Uri.parse(url).host]; var originalHosts = res.hosts;
var newHost = Uri.parse(url).host;
res.hosts = [newHost];
res.hostChanged = true; res.hostChanged = true;
return srcs.first; if (originalHosts.contains(newHost)) {
res.hostIdenticalDespiteAnyChange = true;
}
return res;
} }
AppSource? source; AppSource? source;
for (var s in sources.where((element) => element.hosts.isNotEmpty)) { for (var s in sources.where((element) => element.hosts.isNotEmpty)) {

View File

@@ -1097,10 +1097,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: webview_flutter_android name: webview_flutter_android
sha256: "6e64fcb1c19d92024da8f33503aaeeda35825d77142c01d0ea2aa32edc79fdc8" sha256: ed021f27ae621bc97a6019fb601ab16331a3db4bf8afa305e9f6689bdb3edced
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.16.7" version: "3.16.8"
webview_flutter_platform_interface: webview_flutter_platform_interface:
dependency: transitive dependency: transitive
description: 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 # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
# In Windows, build-name is used as the major, minor, and patch parts # In Windows, build-name is used as the major, minor, and patch parts
# of the product and file versions while build-number is used as the build suffix. # of the product and file versions while build-number is used as the build suffix.
version: 1.1.25+2282 version: 1.1.26+2283
environment: environment:
sdk: '>=3.0.0 <4.0.0' sdk: '>=3.0.0 <4.0.0'