diff --git a/.flutter b/.flutter index a402d9a..d693b4b 160000 --- a/.flutter +++ b/.flutter @@ -1 +1 @@ -Subproject commit a402d9a4376add5bc2d6b1e33e53edaae58c07f8 +Subproject commit d693b4b9dbac2acd4477aea4555ca6dcbea44ba2 diff --git a/assets/translations/ar.json b/assets/translations/ar.json index 90f7258..1867e4c 100644 --- a/assets/translations/ar.json +++ b/assets/translations/ar.json @@ -336,6 +336,7 @@ "foregroundServiceExplanation": "استخدام خدمة مقدمة للتحقق من التحديثات (أكثر موثوقية وتستهلك طاقة أكبر)", "fgServiceNotice": "هذا الإشعار مطلوب للتحقق من التحديث في الخلفية (يمكن إخفاؤه في إعدادات نظام التشغيل)", "excludeSecrets": "استبعاد الأسرار", + "GHReqPrefix": "مثيل \"sky22333/hubproxy\" لطلبات GitHub", "removeAppQuestion": { "one": "إزالة التطبيق؟", "other": "إزالة التطبيقات؟" diff --git a/assets/translations/bs.json b/assets/translations/bs.json index 2a6143b..8641173 100644 --- a/assets/translations/bs.json +++ b/assets/translations/bs.json @@ -336,6 +336,7 @@ "foregroundServiceExplanation": "Use a foreground service for update checking (more reliable, consumes more power)", "fgServiceNotice": "This notification is required for background update checking (it can be hidden in the OS settings)", "excludeSecrets": "Exclude secrets", + "GHReqPrefix": "'sky22333/hubproxy' instance for GitHub requests", "removeAppQuestion": { "one": "Želite li ukloniti aplikaciju?", "other": "Želite li ukloniti aplikacije?" diff --git a/assets/translations/ca.json b/assets/translations/ca.json index d640ba6..4b7d5d1 100644 --- a/assets/translations/ca.json +++ b/assets/translations/ca.json @@ -336,6 +336,7 @@ "foregroundServiceExplanation": "Usa el servei d'Obtainium en primer pla per comprovar les actualitzacions (és més fiable però consumeix més bateria)", "fgServiceNotice": "Aquesta notificació és necessària per comprovar les actualitzacions en segon pla (la pots ocultar als paràmetres del Sistema Operatiu)", "excludeSecrets": "Exclude secrets", + "GHReqPrefix": "'sky22333/hubproxy' instance for GitHub requests", "removeAppQuestion": { "one": "¿Suprimeixo l'aplicació?", "other": "¿Suprimeixo les aplicacions?" diff --git a/assets/translations/cs.json b/assets/translations/cs.json index 684e8c2..5f81c07 100644 --- a/assets/translations/cs.json +++ b/assets/translations/cs.json @@ -336,6 +336,7 @@ "foregroundServiceExplanation": "Použít službu v popředí pro kontrolu aktualizací (spolehlivější, spotřebovává více energie)", "fgServiceNotice": "Toto oznámení je nutné pro kontrolu aktualizací na pozadí (lze jej skrýt v nastavení systému)", "excludeSecrets": "Vyloučit tajemství", + "GHReqPrefix": "instance 'sky22333/hubproxy' pro požadavky GitHubu", "removeAppQuestion": { "one": "Odstranit aplikaci?", "other": "Odstranit aplikace?" diff --git a/assets/translations/da.json b/assets/translations/da.json index b106a87..059a7e4 100644 --- a/assets/translations/da.json +++ b/assets/translations/da.json @@ -336,6 +336,7 @@ "foregroundServiceExplanation": "Brug en forgrundstjeneste til opdateringskontrol (mere pålidelig, bruger mere strøm)", "fgServiceNotice": "Denne meddelelse er nødvendig for baggrundsopdateringskontrol (den kan skjules i OS-indstillingerne).", "excludeSecrets": "Udeluk hemmeligheder", + "GHReqPrefix": "'sky22333/hubproxy'-instans til GitHub-anmodninger", "removeAppQuestion": { "one": "Fjern app?", "other": "Fjern apps?" diff --git a/assets/translations/de.json b/assets/translations/de.json index 26cdda9..0a85f31 100644 --- a/assets/translations/de.json +++ b/assets/translations/de.json @@ -336,6 +336,7 @@ "foregroundServiceExplanation": "Aktualisierungsprüfung im Vordergrund durchführen (zuverlässiger, verbraucht mehr Strom)", "fgServiceNotice": "Diese Benachrichtigung ist für die Prüfung von Updates im Hintergrund erforderlich (sie kann in den Betriebssystemeinstellungen ausgeblendet werden)", "excludeSecrets": "Geheimnisse ausschließen", + "GHReqPrefix": "sky22333/hubproxy'-Instanz für GitHub-Anfragen", "removeAppQuestion": { "one": "App entfernen?", "other": "Apps entfernen?" diff --git a/assets/translations/en-EO.json b/assets/translations/en-EO.json index 17702dc..060db6e 100644 --- a/assets/translations/en-EO.json +++ b/assets/translations/en-EO.json @@ -336,6 +336,7 @@ "foregroundServiceExplanation": "Use a foreground service for update checking (more reliable, consumes more power)", "fgServiceNotice": "This notification is required for background update checking (it can be hidden in the OS settings)", "excludeSecrets": "Exclude secrets", + "GHReqPrefix": "'sky22333/hubproxy' instance for GitHub requests", "removeAppQuestion": { "one": "Forigi la aplikaĵon?", "other": "Forigi la aplikaĵojn?" diff --git a/assets/translations/en.json b/assets/translations/en.json index 5d58f96..0fd891a 100644 --- a/assets/translations/en.json +++ b/assets/translations/en.json @@ -336,6 +336,7 @@ "foregroundServiceExplanation": "Use a foreground service for update checking (more reliable, consumes more power)", "fgServiceNotice": "This notification is required for background update checking (it can be hidden in the OS settings)", "excludeSecrets": "Exclude secrets", + "GHReqPrefix": "'sky22333/hubproxy' instance for GitHub requests", "removeAppQuestion": { "one": "Remove app?", "other": "Remove apps?" diff --git a/assets/translations/es.json b/assets/translations/es.json index 9136a67..bf6da72 100644 --- a/assets/translations/es.json +++ b/assets/translations/es.json @@ -336,6 +336,7 @@ "foregroundServiceExplanation": "Usar un servicio en primer plano para comprobar las actualizaciones (más fiable, consume más energía).", "fgServiceNotice": "Esta notificación es necesaria para la comprobación de actualizaciones en segundo plano (puede ocultarse en la configuración del sistema operativo).", "excludeSecrets": "Excluir secretos", + "GHReqPrefix": "Instancia \"sky22333/hubproxy\" para las solicitudes de GitHub", "removeAppQuestion": { "one": "¿Eliminar aplicación?", "other": "¿Eliminar aplicaciones?" diff --git a/assets/translations/fa.json b/assets/translations/fa.json index 7a46917..902c409 100644 --- a/assets/translations/fa.json +++ b/assets/translations/fa.json @@ -336,6 +336,7 @@ "foregroundServiceExplanation": "Use a foreground service for update checking (more reliable, consumes more power)", "fgServiceNotice": "This notification is required for background update checking (it can be hidden in the OS settings)", "excludeSecrets": "Exclude secrets", + "GHReqPrefix": "'sky22333/hubproxy' instance for GitHub requests", "removeAppQuestion": { "one": "برنامه حذف شود؟", "other": "برنامه ها حذف شوند؟" diff --git a/assets/translations/fr.json b/assets/translations/fr.json index f3675c5..6b8fc5b 100644 --- a/assets/translations/fr.json +++ b/assets/translations/fr.json @@ -336,6 +336,7 @@ "foregroundServiceExplanation": "Utiliser un service de premier plan pour la vérification des mises à jour (plus fiable, consomme plus d'énergie)", "fgServiceNotice": "Cette notification est nécessaire pour la vérification des mises à jour en arrière-plan (elle peut être masquée dans les paramètres du système d'exploitation).", "excludeSecrets": "Exclure les secrets", + "GHReqPrefix": "instance 'sky22333/hubproxy' pour les requêtes GitHub", "removeAppQuestion": { "one": "Supprimer l'application ?", "other": "Supprimer les applications ?" diff --git a/assets/translations/hu.json b/assets/translations/hu.json index 22857a0..81de3f0 100644 --- a/assets/translations/hu.json +++ b/assets/translations/hu.json @@ -336,6 +336,7 @@ "foregroundServiceExplanation": "Előtér-szolgáltatás használata a frissítések ellenőrzéséhez (megbízhatóbb, de több energiát fogyaszt)", "fgServiceNotice": "Ez az értesítés a háttérben történő frissítésellenőrzéshez szükséges (a rendszer beállításaiban elrejthető).", "excludeSecrets": "Érzékeny adatok (például: személyes hozzáférési tokenek) kihagyása", + "GHReqPrefix": "„sky22333/hubproxy” példány a GitHub lekérdezéséhez", "removeAppQuestion": { "one": "Eltávolítja az alkalmazást?", "other": "Eltávolítja az alkalmazásokat?" diff --git a/assets/translations/id.json b/assets/translations/id.json index c85da4c..ab90b0e 100644 --- a/assets/translations/id.json +++ b/assets/translations/id.json @@ -336,6 +336,7 @@ "foregroundServiceExplanation": "Gunakan layanan latar depan untuk pemeriksaan pembaruan (lebih dapat diandalkan, menghabiskan lebih banyak daya)", "fgServiceNotice": "Pemberitahuan ini diperlukan untuk pemeriksaan pembaruan latar belakang (dapat disembunyikan dalam pengaturan OS)", "excludeSecrets": "Mengecualikan rahasia", + "GHReqPrefix": "Instance 'sky22333/hubproxy' untuk permintaan GitHub", "removeAppQuestion": { "one": "Hapus aplikasi?", "other": "Hapus aplikasi?" diff --git a/assets/translations/it.json b/assets/translations/it.json index 02afc03..50c051e 100644 --- a/assets/translations/it.json +++ b/assets/translations/it.json @@ -336,6 +336,7 @@ "foregroundServiceExplanation": "Utilizzare un servizio in primo piano per il controllo degli aggiornamenti (più affidabile, consuma più energia)", "fgServiceNotice": "Questa notifica è necessaria per il controllo degli aggiornamenti in background (può essere nascosta nelle impostazioni del sistema operativo).", "excludeSecrets": "Escludere i segreti", + "GHReqPrefix": "istanza 'sky22333/hubproxy' per le richieste a GitHub", "removeAppQuestion": { "one": "Rimuovere l'app?", "other": "Rimuovere le app?" diff --git a/assets/translations/ja.json b/assets/translations/ja.json index c7d64c6..64a4997 100644 --- a/assets/translations/ja.json +++ b/assets/translations/ja.json @@ -336,6 +336,7 @@ "foregroundServiceExplanation": "アップデート確認にフォアグラウンドサービスを使用する(より信頼性が高いが、より電力を消費する)", "fgServiceNotice": "この通知は、バックグラウンドでアップデートを確認するために必要です(OSの設定で非表示にできます)。", "excludeSecrets": "シークレットを除く", + "GHReqPrefix": "GitHub リクエスト用の 'sky22333/hubproxy' インスタンス", "removeAppQuestion": { "one": "アプリを削除しますか?", "other": "アプリを削除しますか?" diff --git a/assets/translations/ko.json b/assets/translations/ko.json index f7e6b3d..6ec4cda 100644 --- a/assets/translations/ko.json +++ b/assets/translations/ko.json @@ -336,6 +336,7 @@ "foregroundServiceExplanation": "업데이트 확인을 위해 포그라운드 서비스 사용(안정성 향상, 전력 소비량 증가)", "fgServiceNotice": "이 알림은 백그라운드 업데이트 확인에 필요합니다(OS 설정에서 숨길 수 있음).", "excludeSecrets": "비밀 제외", + "GHReqPrefix": "GitHub 요청을 위한 'sky22333/hubproxy' 인스턴스", "removeAppQuestion": { "one": "앱을 제거하시겠습니까?", "other": "앱을 제거하시겠습니까?" diff --git a/assets/translations/ml.json b/assets/translations/ml.json index 932ae23..93dae6f 100644 --- a/assets/translations/ml.json +++ b/assets/translations/ml.json @@ -336,6 +336,7 @@ "foregroundServiceExplanation": "Use a foreground service for update checking (more reliable, consumes more power)", "fgServiceNotice": "This notification is required for background update checking (it can be hidden in the OS settings)", "excludeSecrets": "Exclude secrets", + "GHReqPrefix": "'sky22333/hubproxy' instance for GitHub requests", "removeAppQuestion": { "one": "ആപ്പ് നീക്കം ചെയ്യണോ?", "other": "ആപ്പുകൾ നീക്കം ചെയ്യണോ?" diff --git a/assets/translations/nl.json b/assets/translations/nl.json index ea88d61..cfe1d7d 100644 --- a/assets/translations/nl.json +++ b/assets/translations/nl.json @@ -336,6 +336,7 @@ "foregroundServiceExplanation": "Gebruik een voorgronddienst voor het controleren van updates (betrouwbaarder, verbruikt meer stroom)", "fgServiceNotice": "Deze melding is nodig voor het controleren van updates op de achtergrond (kan worden verborgen in de OS-instellingen)", "excludeSecrets": "Geheimen uitsluiten", + "GHReqPrefix": "'sky22333/hubproxy' instantie voor GitHub verzoeken", "removeAppQuestion": { "one": "App verwijderen?", "other": "Apps verwijderen?" diff --git a/assets/translations/pl.json b/assets/translations/pl.json index f792638..81e609f 100644 --- a/assets/translations/pl.json +++ b/assets/translations/pl.json @@ -336,6 +336,7 @@ "foregroundServiceExplanation": "Używanie usługi pierwszoplanowej do sprawdzania aktualizacji (bardziej niezawodne, zużywa więcej energii)", "fgServiceNotice": "To powiadomienie jest wymagane do sprawdzania aktualizacji w tle (można je ukryć w ustawieniach systemu operacyjnego).", "excludeSecrets": "Wyklucz sekrety", + "GHReqPrefix": "Instancja \"sky22333/hubproxy\" dla żądań GitHub", "removeAppQuestion": { "one": "Usunąć aplikację?", "few": "Usunąć aplikacje?", diff --git a/assets/translations/pt-BR.json b/assets/translations/pt-BR.json index 19b01a5..cb385b5 100644 --- a/assets/translations/pt-BR.json +++ b/assets/translations/pt-BR.json @@ -336,6 +336,7 @@ "foregroundServiceExplanation": "Usar um serviço em primeiro plano para verificação de atualizações (mais confiável, consome mais energia)", "fgServiceNotice": "Essa notificação é necessária para a verificação de atualizações em segundo plano (ela pode ser ocultada nas configurações do sistema operacional)", "excludeSecrets": "Excluir segredos", + "GHReqPrefix": "Instância \"sky22333/hubproxy\" para solicitações do GitHub", "removeAppQuestion": { "one": "Remover app?", "other": "Remover apps?" diff --git a/assets/translations/pt.json b/assets/translations/pt.json index a6dc72e..cb3ac7d 100644 --- a/assets/translations/pt.json +++ b/assets/translations/pt.json @@ -336,6 +336,7 @@ "foregroundServiceExplanation": "Utilizar um serviço em primeiro plano para verificação de actualizações (mais fiável, consome mais energia)", "fgServiceNotice": "Esta notificação é necessária para a verificação de actualizações em segundo plano (pode ser ocultada nas definições do SO)", "excludeSecrets": "Excluir segredos", + "GHReqPrefix": "Instância 'sky22333/hubproxy' para pedidos de GitHub", "removeAppQuestion": { "one": "Remover aplicação?", "other": "Remover aplicações?" diff --git a/assets/translations/ru.json b/assets/translations/ru.json index dd8974e..d14f5df 100644 --- a/assets/translations/ru.json +++ b/assets/translations/ru.json @@ -336,6 +336,7 @@ "foregroundServiceExplanation": "Использовать приоритетную службу для проверки обновлений (надёжнее, энергозатратнее)", "fgServiceNotice": "Это уведомление необходимо для фоновой проверки обновлений (оно может быть скрыто в настройках ОС)", "excludeSecrets": "Исключить секреты", + "GHReqPrefix": "Экземпляр 'sky22333/hubproxy' для запросов на GitHub", "removeAppQuestion": { "one": "Удалить приложение?", "other": "Удалить приложения?" diff --git a/assets/translations/sv.json b/assets/translations/sv.json index a51ed1a..83ef1f0 100644 --- a/assets/translations/sv.json +++ b/assets/translations/sv.json @@ -336,6 +336,7 @@ "foregroundServiceExplanation": "Använd en förgrundstjänst för uppdateringskontroll (mer tillförlitlig, förbrukar mer ström)", "fgServiceNotice": "Detta meddelande krävs för bakgrundsuppdateringskontroll (det kan döljas i OS-inställningarna)", "excludeSecrets": "Utesluta hemligheter", + "GHReqPrefix": "Instansen \"sky22333/hubproxy\" för GitHub-förfrågningar", "removeAppQuestion": { "one": "Ta Bort App?", "other": "Ta Bort Appar?" diff --git a/assets/translations/tr.json b/assets/translations/tr.json index b33bb18..87450de 100644 --- a/assets/translations/tr.json +++ b/assets/translations/tr.json @@ -336,6 +336,7 @@ "foregroundServiceExplanation": "Güncelleme denetimi için bir ön plan hizmeti kullanın (daha güvenilir, daha fazla güç tüketir)", "fgServiceNotice": "Bu bildirim arka planda güncelleme kontrolü için gereklidir (işletim sistemi ayarlarından gizlenebilir)", "excludeSecrets": "Sırları hariç tut", + "GHReqPrefix": "GitHub istekleri için 'sky22333/hubproxy' örneği", "removeAppQuestion": { "one": "Uygulamayı Kaldır?", "other": "Uygulamaları Kaldır?" diff --git a/assets/translations/uk.json b/assets/translations/uk.json index 4199541..f634688 100644 --- a/assets/translations/uk.json +++ b/assets/translations/uk.json @@ -336,6 +336,7 @@ "foregroundServiceExplanation": "Використовуйте службу переднього плану для перевірки оновлень (надійніша, споживає більше енергії)", "fgServiceNotice": "Це сповіщення необхідне для фонової перевірки оновлень (його можна приховати в налаштуваннях ОС)", "excludeSecrets": "Виключити секрети", + "GHReqPrefix": "екземпляр 'sky22333/hubproxy' для запитів на GitHub", "removeAppQuestion": { "one": "Видалити застосунок?", "other": "Видалити застосунки?" diff --git a/assets/translations/vi.json b/assets/translations/vi.json index 270cc72..2933d6a 100644 --- a/assets/translations/vi.json +++ b/assets/translations/vi.json @@ -336,6 +336,7 @@ "foregroundServiceExplanation": "Sử dụng dịch vụ nền trước để kiểm tra cập nhật (đáng tin cậy hơn, tiêu tốn nhiều pin hơn)", "fgServiceNotice": "Thông báo này là bắt buộc để kiểm tra cập nhật nền (có thể ẩn trong cài đặt hệ điều hành).", "excludeSecrets": "Loại trừ thông tin bí mật", + "GHReqPrefix": "Thực thể 'sky22333/hubproxy' cho các yêu cầu GitHub", "removeAppQuestion": { "one": "Gỡ ứng dụng?", "other": "Gỡ ứng dụng?" diff --git a/assets/translations/zh-Hant-TW.json b/assets/translations/zh-Hant-TW.json index a8eb1d1..da52339 100644 --- a/assets/translations/zh-Hant-TW.json +++ b/assets/translations/zh-Hant-TW.json @@ -336,6 +336,7 @@ "foregroundServiceExplanation": "Use a foreground service for update checking (more reliable, consumes more power)", "fgServiceNotice": "This notification is required for background update checking (it can be hidden in the OS settings)", "excludeSecrets": "Exclude secrets", + "GHReqPrefix": "'sky22333/hubproxy' instance for GitHub requests", "removeAppQuestion": { "one": "移除應用程式?", "other": "移除應用程式?" diff --git a/assets/translations/zh.json b/assets/translations/zh.json index 296e9c7..e94b4de 100644 --- a/assets/translations/zh.json +++ b/assets/translations/zh.json @@ -336,6 +336,7 @@ "foregroundServiceExplanation": "使用前台服务检查更新(更稳定,但也更耗电)", "fgServiceNotice": "后台检查更新时需要此通知(可在操作系统设置中隐藏)", "excludeSecrets": "排除机密", + "GHReqPrefix": "用于 GitHub 请求的 \"sky22333/hubproxy \"实例", "removeAppQuestion": { "one": "是否删除应用?", "other": "是否删除应用?" diff --git a/lib/app_sources/apkcombo.dart b/lib/app_sources/apkcombo.dart index 71fc596..37f6b2b 100644 --- a/lib/app_sources/apkcombo.dart +++ b/lib/app_sources/apkcombo.dart @@ -82,13 +82,13 @@ class APKCombo extends AppSource { } @override - Future apkUrlPrefetchModifier( - String apkUrl, + Future assetUrlPrefetchModifier( + String assetUrl, String standardUrl, Map additionalSettings, ) async { var freshURLs = await getApkUrls(standardUrl, additionalSettings); - var path2Match = Uri.parse(apkUrl).path; + var path2Match = Uri.parse(assetUrl).path; for (var url in freshURLs) { if (Uri.parse(url.value).path == path2Match) { return url.value; diff --git a/lib/app_sources/farsroid.dart b/lib/app_sources/farsroid.dart index 82754d0..c4faece 100644 --- a/lib/app_sources/farsroid.dart +++ b/lib/app_sources/farsroid.dart @@ -1,7 +1,9 @@ import 'dart:convert'; +import 'package:easy_localization/easy_localization.dart'; import 'package:html/parser.dart'; import 'package:obtainium/app_sources/html.dart'; +import 'package:obtainium/components/generated_form.dart'; import 'package:obtainium/custom_errors.dart'; import 'package:obtainium/providers/source_provider.dart'; @@ -9,6 +11,17 @@ class Farsroid extends AppSource { Farsroid() { hosts = ['farsroid.com']; name = 'Farsroid'; + naiveStandardVersionDetection = true; + + additionalSourceAppSpecificSettingFormItems = [ + [ + GeneratedFormSwitch( + 'useFirstApkOfVersion', + label: tr('useFirstApkOfVersion'), + defaultValue: true, + ), + ], + ]; } @override @@ -57,15 +70,21 @@ class Farsroid extends AppSource { if (html2.isEmpty) { throw NoAPKError(); } - var apkLinks = - (await grabLinksCommon(html2, res2.request!.url, additionalSettings)) - .map((l) => MapEntry(Uri.parse(l.key).pathSegments.last, l.key)) - .where( - (l) => l.key.toLowerCase().startsWith( - '$appName-$version'.toLowerCase(), - ), - ) - .toList(); + var apkLinks = (await grabLinksCommon( + html2, + res2.request!.url, + additionalSettings, + )).map((l) => MapEntry(Uri.parse(l.key).pathSegments.last, l.key)).toList(); + + if (additionalSettings['useFirstApkOfVersion'] == true) { + apkLinks = apkLinks + .where( + (l) => l.key.toLowerCase().startsWith( + '$appName-$version'.toLowerCase(), + ), + ) + .toList(); + } if (apkLinks.isEmpty) { throw NoAPKError(); diff --git a/lib/app_sources/github.dart b/lib/app_sources/github.dart index fa15b1a..f9c53d3 100644 --- a/lib/app_sources/github.dart +++ b/lib/app_sources/github.dart @@ -45,6 +45,46 @@ class GitHub extends AppSource { const SizedBox(height: 4), ], ), + GeneratedFormTextField( + 'GHReqPrefix', + label: tr('GHReqPrefix'), + hint: 'gh-proxy.com', + required: false, + additionalValidators: [ + (value) { + try { + if (value != null && Uri.parse(value).scheme.isNotEmpty) { + throw true; + } + if (value != null) { + Uri.parse('https://${value}/api.github.com'); + } + } catch (e) { + return tr('invalidInput'); + } + return null; + }, + ], + belowWidgets: [ + const SizedBox(height: 4), + GestureDetector( + onTap: () { + launchUrlString( + 'https://github.com/sky22333/hubproxy', + mode: LaunchMode.externalApplication, + ); + }, + child: Text( + tr('about'), + style: const TextStyle( + decoration: TextDecoration.underline, + fontSize: 12, + ), + ), + ), + const SizedBox(height: 4), + ], + ), ]; additionalSourceAppSpecificSettingFormItems = [ @@ -249,6 +289,9 @@ class GitHub extends AppSource { settingsProvider, ); String? creds = sourceConfig['github-creds']; + if ((additionalSettings['GHReqPrefix'] as String? ?? '').isNotEmpty) { + creds = null; + } if (creds != null) { var userNameEndIndex = creds.indexOf(':'); if (userNameEndIndex > 0) { @@ -270,6 +313,18 @@ class GitHub extends AppSource { return null; } + @override + Future generalReqPrefetchModifier( + String reqUrl, + Map additionalSettings, + ) async { + if ((additionalSettings['GHReqPrefix'] as String? ?? '').isNotEmpty) { + var uri = Uri.parse(reqUrl); + return 'https://${additionalSettings['GHReqPrefix']}/${uri.toString().substring('https://'.length)}'; + } + return reqUrl; + } + Future getAPIHost(Map additionalSettings) async => 'https://api.${hosts[0]}'; @@ -289,6 +344,12 @@ class GitHub extends AppSource { Map additionalSettings, { Function(Response)? onHttpErrorCode, }) async { + SettingsProvider settingsProvider = SettingsProvider(); + await settingsProvider.initializeSettings(); + var sourceConfigSettingValues = await getSourceConfigValues( + additionalSettings, + settingsProvider, + ); bool includePrereleases = additionalSettings['includePrereleases'] == true; bool fallbackToOlderReleases = additionalSettings['fallbackToOlderReleases'] == true; @@ -344,6 +405,7 @@ class GitHub extends AppSource { var url = !e['name'].toString().toLowerCase().endsWith('.apk') ? (e['browser_download_url'] ?? e['url']) : (e['url'] ?? e['browser_download_url']); + url = undoGHProxyMod(url, sourceConfigSettingValues); e['final_url'] = (e['name'] != null) && (url != null) ? MapEntry(e['name'] as String, url as String) : const MapEntry('', ''); @@ -522,7 +584,10 @@ class GitHub extends AppSource { allAssetUrls.add( MapEntry( (targetRelease['version'] ?? 'source') + '.tar.gz', - targetRelease['tarball_url'], + undoGHProxyMod( + targetRelease['tarball_url'], + sourceConfigSettingValues, + ), ), ); } @@ -530,7 +595,10 @@ class GitHub extends AppSource { allAssetUrls.add( MapEntry( (targetRelease['version'] ?? 'source') + '.zip', - targetRelease['zipball_url'], + undoGHProxyMod( + targetRelease['zipball_url'], + sourceConfigSettingValues, + ), ), ); } @@ -652,12 +720,23 @@ class GitHub extends AppSource { } } + undoGHProxyMod( + String reqUrl, + Map sourceConfigSettingValues, + ) => reqUrl.replaceFirst( + 'https://${sourceConfigSettingValues['GHReqPrefix']}/', + '', + ); + @override Future>> search( String query, { Map querySettings = const {}, }) async { - return searchCommon( + var sp = SettingsProvider(); + await sp.initializeSettings(); + var sourceConfigSettingValues = await getSourceConfigValues({}, sp); + var results = await searchCommon( query, '${await getAPIHost({})}/search/repositories?q=${Uri.encodeQueryComponent(query)}&per_page=100', 'items', @@ -666,6 +745,15 @@ class GitHub extends AppSource { }, querySettings: querySettings, ); + if ((sourceConfigSettingValues['GHReqPrefix'] ?? '').isNotEmpty) { + Map> results2 = {}; + results.forEach((k, v) { + results2[undoGHProxyMod(k, sourceConfigSettingValues)] = v; + }); + return results2; + } else { + return results; + } } void rateLimitErrorCheck(Response res) { diff --git a/lib/app_sources/gitlab.dart b/lib/app_sources/gitlab.dart index 14cc009..18617f1 100644 --- a/lib/app_sources/gitlab.dart +++ b/lib/app_sources/gitlab.dart @@ -129,14 +129,14 @@ class GitLab extends AppSource { } @override - Future apkUrlPrefetchModifier( - String apkUrl, + Future assetUrlPrefetchModifier( + String assetUrl, String standardUrl, Map additionalSettings, ) async { String? PAT = await getPATIfAny(hostChanged ? additionalSettings : {}); String optionalAuth = (PAT != null) ? 'private_token=$PAT' : ''; - return '$apkUrl${(Uri.parse(apkUrl).query.isEmpty ? '?' : '&')}$optionalAuth'; + return '$assetUrl${(Uri.parse(assetUrl).query.isEmpty ? '?' : '&')}$optionalAuth'; } @override diff --git a/lib/app_sources/html.dart b/lib/app_sources/html.dart index 22f5f3d..15874ad 100644 --- a/lib/app_sources/html.dart +++ b/lib/app_sources/html.dart @@ -146,10 +146,7 @@ Future>> grabLinksCommon( .map((e) => MapEntry(ensureAbsoluteUrl(e.key, reqUrl), e.value)) .toList(); if (allLinks.isEmpty || matchLinksOutsideATags) { - allLinks = getLinksInLines(rawBody); - } - if (allLinks.isEmpty) { - // Getting desperate + // Decode the body if the response is a JSON try { var jsonStrings = collectAllStringsFromJSONObject(jsonDecode(rawBody)); allLinks = getLinksInLines(jsonStrings.join('\n')); @@ -163,7 +160,7 @@ Future>> grabLinksCommon( ); } } catch (e) { - // + allLinks = getLinksInLines(rawBody); } } List> links = []; diff --git a/lib/app_sources/rustore.dart b/lib/app_sources/rustore.dart index 6589f96..d174ffc 100644 --- a/lib/app_sources/rustore.dart +++ b/lib/app_sources/rustore.dart @@ -80,20 +80,20 @@ class RuStore extends AppSource { } Response res1 = await sourceRequest( - 'https://backapi.rustore.ru/applicationData/download-link', + 'https://backapi.rustore.ru/applicationData/v2/download-link', additionalSettings, followRedirects: false, postBody: {"appId": appDetails['appId'], "firstInstall": true}, ); var downloadDetails = (await decodeJsonBody(res1.bodyBytes))['body']; - if (res1.statusCode != 200 || downloadDetails['apkUrl'] == null) { + if (res1.statusCode != 200 || downloadDetails['downloadUrls'][0]['url'] == null) { throw NoAPKError(); } return APKDetails( version, getApkUrlsFromUrls([ - (downloadDetails['apkUrl'] as String).replaceAll( + (downloadDetails['downloadUrls'][0]['url'] as String).replaceAll( RegExp('\\.zip\$'), '.apk', ), diff --git a/lib/app_sources/uptodown.dart b/lib/app_sources/uptodown.dart index 7315675..63b4678 100644 --- a/lib/app_sources/uptodown.dart +++ b/lib/app_sources/uptodown.dart @@ -124,12 +124,12 @@ class Uptodown extends AppSource { } @override - Future apkUrlPrefetchModifier( - String apkUrl, + Future assetUrlPrefetchModifier( + String assetUrl, String standardUrl, Map additionalSettings, ) async { - var res = await sourceRequest(apkUrl, additionalSettings); + var res = await sourceRequest(assetUrl, additionalSettings); if (res.statusCode != 200) { throw getObtainiumHttpError(res); } diff --git a/lib/app_sources/vivoappstore.dart b/lib/app_sources/vivoappstore.dart index 278ca3c..280a585 100644 --- a/lib/app_sources/vivoappstore.dart +++ b/lib/app_sources/vivoappstore.dart @@ -70,12 +70,14 @@ class VivoAppStore extends AppSource { throw NoReleasesError(); } Map> results = {}; - var resultsJson = json['data']['appSearchResponse']['value']; - for (var item in (resultsJson as List)) { - results['$appDetailUrl${item['id']}'] = [ - item['title_zh'].toString(), - item['developer'].toString(), - ]; + var resultsJson = json['data']['appSearchResponse']?['value']; + if (resultsJson != null) { + for (var item in (resultsJson as List)) { + results['$appDetailUrl${item['id']}'] = [ + item['title_zh'].toString(), + item['developer'].toString(), + ]; + } } return results; } diff --git a/lib/pages/app.dart b/lib/pages/app.dart index f67884e..a50ba70 100644 --- a/lib/pages/app.dart +++ b/lib/pages/app.dart @@ -16,9 +16,14 @@ import 'package:provider/provider.dart'; import 'package:markdown/markdown.dart' as md; class AppPage extends StatefulWidget { - const AppPage({super.key, required this.appId}); + const AppPage({ + super.key, + required this.appId, + this.showOppositeOfPreferredView = false, + }); final String appId; + final bool showOppositeOfPreferredView; @override State createState() => _AppPageState(); @@ -60,6 +65,11 @@ class _AppPageState extends State { Widget build(BuildContext context) { var appsProvider = context.watch(); var settingsProvider = context.watch(); + var showAppWebpageFinal = + (settingsProvider.showAppWebpage && + !widget.showOppositeOfPreferredView) || + (!settingsProvider.showAppWebpage && + widget.showOppositeOfPreferredView); getUpdate(String id, {bool resetVersion = false}) async { try { setState(() { @@ -565,7 +575,7 @@ class _AppPageState extends State { icon: const Icon(Icons.settings), tooltip: tr('settings'), ), - if (app != null && settingsProvider.showAppWebpage) + if (app != null && showAppWebpageFinal) IconButton( onPressed: () { showDialog( @@ -661,10 +671,10 @@ class _AppPageState extends State { ); return Scaffold( - appBar: settingsProvider.showAppWebpage ? AppBar() : appScreenAppBar(), + appBar: showAppWebpageFinal ? AppBar() : appScreenAppBar(), backgroundColor: Theme.of(context).colorScheme.surface, body: RefreshIndicator( - child: settingsProvider.showAppWebpage + child: showAppWebpageFinal ? getAppWebView() : CustomScrollView( slivers: [ diff --git a/lib/pages/apps.dart b/lib/pages/apps.dart index 3cbf7e6..71e9a73 100644 --- a/lib/pages/apps.dart +++ b/lib/pages/apps.dart @@ -451,40 +451,57 @@ class AppsPageState extends State { } getAppIcon(int appIndex) { - return FutureBuilder( - future: appsProvider.updateAppIcon(listedApps[appIndex].app.id), - builder: (ctx, val) { - return listedApps[appIndex].icon != null - ? Image.memory( - listedApps[appIndex].icon!, - gaplessPlayback: true, - opacity: AlwaysStoppedAnimation( - listedApps[appIndex].installedInfo == null ? 0.6 : 1, - ), - ) - : Row( - mainAxisSize: MainAxisSize.min, - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Transform( - alignment: Alignment.center, - transform: Matrix4.rotationZ(0.31), - child: Padding( - padding: const EdgeInsets.all(15), - child: Image( - image: const AssetImage( - 'assets/graphics/icon_small.png', + return GestureDetector( + child: FutureBuilder( + future: appsProvider.updateAppIcon(listedApps[appIndex].app.id), + builder: (ctx, val) { + return listedApps[appIndex].icon != null + ? Image.memory( + listedApps[appIndex].icon!, + gaplessPlayback: true, + opacity: AlwaysStoppedAnimation( + listedApps[appIndex].installedInfo == null ? 0.6 : 1, + ), + ) + : Row( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Transform( + alignment: Alignment.center, + transform: Matrix4.rotationZ(0.31), + child: Padding( + padding: const EdgeInsets.all(15), + child: Image( + image: const AssetImage( + 'assets/graphics/icon_small.png', + ), + color: + Theme.of(context).brightness == Brightness.dark + ? Colors.white.withOpacity(0.4) + : Colors.white.withOpacity(0.3), + colorBlendMode: BlendMode.modulate, + gaplessPlayback: true, ), - color: Theme.of(context).brightness == Brightness.dark - ? Colors.white.withOpacity(0.4) - : Colors.white.withOpacity(0.3), - colorBlendMode: BlendMode.modulate, - gaplessPlayback: true, ), ), - ), - ], - ); + ], + ); + }, + ), + onDoubleTap: () { + pm.openApp(listedApps[appIndex].app.id); + }, + onLongPress: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => AppPage( + appId: listedApps[appIndex].app.id, + showOppositeOfPreferredView: true, + ), + ), + ); }, ); } diff --git a/lib/pages/settings.dart b/lib/pages/settings.dart index 586c77f..abea2cd 100644 --- a/lib/pages/settings.dart +++ b/lib/pages/settings.dart @@ -319,13 +319,24 @@ class _SettingsPageState extends State { if (e.sourceConfigSettingFormItems.isNotEmpty) { return GeneratedForm( items: e.sourceConfigSettingFormItems.map((e) { - e.defaultValue = settingsProvider.getSettingString(e.key); + if (e is GeneratedFormSwitch) { + e.defaultValue = settingsProvider.getSettingBool(e.key); + } else { + e.defaultValue = settingsProvider.getSettingString(e.key); + } return [e]; }).toList(), onValueChanges: (values, valid, isBuilding) { if (valid && !isBuilding) { values.forEach((key, value) { - settingsProvider.setSettingString(key, value); + var formItem = e.sourceConfigSettingFormItems + .where((i) => i.key == key) + .firstOrNull; + if (formItem is GeneratedFormSwitch) { + settingsProvider.setSettingBool(key, value == true); + } else { + settingsProvider.setSettingString(key, value ?? ''); + } }); } }, diff --git a/lib/providers/apps_provider.dart b/lib/providers/apps_provider.dart index e61c89a..4c0c96b 100644 --- a/lib/providers/apps_provider.dart +++ b/lib/providers/apps_provider.dart @@ -606,10 +606,20 @@ class AppsProvider with ChangeNotifier { app.url, overrideSource: app.overrideSource, ); - String downloadUrl = await source.apkUrlPrefetchModifier( - app.apkUrls[app.preferredApkIndex].value, + var additionalSettingsPlusSourceConfig = { + ...app.additionalSettings, + ...(await source.getSourceConfigValues( + app.additionalSettings, + settingsProvider, + )), + }; + String downloadUrl = await source.assetUrlPrefetchModifier( + await source.generalReqPrefetchModifier( + app.apkUrls[app.preferredApkIndex].value, + additionalSettingsPlusSourceConfig, + ), app.url, - app.additionalSettings, + additionalSettingsPlusSourceConfig, ); var notif = DownloadNotification(app.finalName, 100); notificationsProvider?.cancel(notif.id); @@ -764,10 +774,13 @@ class AppsProvider with ChangeNotifier { int? targetSDK = (await getInstalledInfo( app.id, ))?.applicationInfo?.targetSdkVersion; + int requiredSDK = osInfo.version.sdkInt - 3; // The APK should target a new enough API // https://developer.android.com/reference/android/content/pm/PackageInstaller.SessionParams#setRequireUserAction(int) - if (!(targetSDK != null && targetSDK >= (osInfo.version.sdkInt - 3))) { - logs.add('Multiple APK URLs: ${app.id}'); + if (!(targetSDK != null && targetSDK >= requiredSDK)) { + logs.add( + 'App currently targets API ${targetSDK} which is too low for background updates (requires API ${requiredSDK}): ${app.id}', + ); return false; } @@ -1324,15 +1337,26 @@ class AppsProvider with ChangeNotifier { evenIfSingleChoice: true, ); if (tempFileUrl != null) { + var s = SourceProvider().getSource( + apps[id]!.app.url, + overrideSource: apps[id]!.app.overrideSource, + ); + var additionalSettingsPlusSourceConfig = { + ...apps[id]!.app.additionalSettings, + ...(await s.getSourceConfigValues( + apps[id]!.app.additionalSettings, + settingsProvider, + )), + }; fileUrl = MapEntry( tempFileUrl.key, - await (SourceProvider().getSource( + await s.assetUrlPrefetchModifier( + await s.generalReqPrefetchModifier( + tempFileUrl.value, + additionalSettingsPlusSourceConfig, + ), apps[id]!.app.url, - overrideSource: apps[id]!.app.overrideSource, - )).apkUrlPrefetchModifier( - tempFileUrl.value, - apps[id]!.app.url, - apps[id]!.app.additionalSettings, + additionalSettingsPlusSourceConfig, ), ); } diff --git a/lib/providers/settings_provider.dart b/lib/providers/settings_provider.dart index 121ad1e..43f4b70 100644 --- a/lib/providers/settings_provider.dart +++ b/lib/providers/settings_provider.dart @@ -249,6 +249,15 @@ class SettingsProvider with ChangeNotifier { notifyListeners(); } + bool? getSettingBool(String settingId) { + return prefs?.getBool(settingId) ?? false; + } + + void setSettingBool(String settingId, bool value) { + prefs?.setBool(settingId, value); + notifyListeners(); + } + Map get categories => Map.from(jsonDecode(prefs?.getString('categories') ?? '{}')); diff --git a/lib/providers/source_provider.dart b/lib/providers/source_provider.dart index aa338f3..b7dc317 100644 --- a/lib/providers/source_provider.dart +++ b/lib/providers/source_provider.dart @@ -686,14 +686,27 @@ abstract class AppSource { bool followRedirects = true, Object? postBody, }) async { + var sp = SettingsProvider(); + await sp.initializeSettings(); + getSourceConfigValues(additionalSettings, sp); + var additionalSettingsPlusSourceConfig = { + ...additionalSettings, + ...(await getSourceConfigValues(additionalSettings, sp)), + }; + url = await generalReqPrefetchModifier( + url, + additionalSettingsPlusSourceConfig, + ); var method = postBody == null ? 'GET' : 'POST'; - var requestHeaders = await getRequestHeaders(additionalSettings); + var requestHeaders = await getRequestHeaders( + additionalSettingsPlusSourceConfig, + ); var streamedResponseUrlWithResponseAndClient = await sourceRequestStreamResponse( method, url, requestHeaders, - additionalSettings, + additionalSettingsPlusSourceConfig, followRedirects: followRedirects, postBody: postBody, ); @@ -911,12 +924,19 @@ abstract class AppSource { return null; } - Future apkUrlPrefetchModifier( - String apkUrl, + Future assetUrlPrefetchModifier( + String assetUrl, String standardUrl, Map additionalSettings, ) async { - return apkUrl; + return assetUrl; + } + + Future generalReqPrefetchModifier( + String reqUrl, + Map additionalSettings, + ) async { + return reqUrl; } bool canSearch = false; diff --git a/pubspec.lock b/pubspec.lock index 3a0ed90..05247eb 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -5,10 +5,10 @@ packages: dependency: "direct main" description: name: android_intent_plus - sha256: "2329378af63f49b985cb2e110ac784d08374f1e2b1984be77ba9325b1c8cce11" + sha256: "14a9f94c5825a528e8c38ee89a33dbeba947efbbf76f066c174f4f3ae4f48feb" url: "https://pub.dev" source: hosted - version: "5.3.1" + version: "6.0.0" android_package_installer: dependency: "direct main" description: @@ -104,10 +104,10 @@ packages: dependency: "direct main" description: name: battery_plus - sha256: "03d5a6bb36db9d2b977c548f6b0262d5a84c4d5a4cfee2edac4a91d57011b365" + sha256: ad16fcb55b7384be6b4bbc763d5e2031ac7ea62b2d9b6b661490c7b9741155bf url: "https://pub.dev" source: hosted - version: "6.2.3" + version: "7.0.0" battery_plus_platform_interface: dependency: transitive description: @@ -160,10 +160,10 @@ packages: dependency: "direct main" description: name: connectivity_plus - sha256: b5e72753cf63becce2c61fd04dfe0f1c430cc5278b53a1342dc5ad839eab29ec + sha256: "33bae12a398f841c6cda09d1064212957265869104c478e5ad51e2fb26c3973c" url: "https://pub.dev" source: hosted - version: "6.1.5" + version: "7.0.0" connectivity_plus_platform_interface: dependency: transitive description: @@ -216,10 +216,10 @@ packages: dependency: "direct main" description: name: device_info_plus - sha256: "98f28b42168cc509abc92f88518882fd58061ea372d7999aecc424345c7bff6a" + sha256: "49413c8ca514dea7633e8def233b25efdf83ec8522955cc2c0e3ad802927e7c6" url: "https://pub.dev" source: hosted - version: "11.5.0" + version: "12.1.0" device_info_plus_platform_interface: dependency: transitive description: @@ -508,10 +508,10 @@ packages: dependency: "direct main" description: name: fluttertoast - sha256: "25e51620424d92d3db3832464774a6143b5053f15e382d8ffbfd40b6e795dcf1" + sha256: "144ddd74d49c865eba47abe31cbc746c7b311c82d6c32e571fd73c4264b740e2" url: "https://pub.dev" source: hosted - version: "8.2.12" + version: "9.0.0" fraction: dependency: transitive description: @@ -828,10 +828,10 @@ packages: dependency: "direct main" description: name: share_plus - sha256: d7dc0630a923883c6328ca31b89aa682bacbf2f8304162d29f7c6aaff03a27a1 + sha256: "3424e9d5c22fd7f7590254ba09465febd6f8827c8b19a44350de4ac31d92d3a6" url: "https://pub.dev" source: hosted - version: "11.1.0" + version: "12.0.0" share_plus_platform_interface: dependency: transitive description: @@ -852,10 +852,10 @@ packages: dependency: transitive description: name: shared_preferences_android - sha256: a2608114b1ffdcbc9c120eb71a0e207c71da56202852d4aab8a5e30a82269e74 + sha256: bd14436108211b0d4ee5038689a56d4ae3620fd72fd6036e113bf1345bc74d9e url: "https://pub.dev" source: hosted - version: "2.4.12" + version: "2.4.13" shared_preferences_foundation: dependency: transitive description: @@ -1059,10 +1059,10 @@ packages: dependency: transitive description: name: url_launcher_android - sha256: "07cffecb7d68cbc6437cd803d5f11a86fe06736735c3dfe46ff73bcb0f958eed" + sha256: "199bc33e746088546a39cc5f36bac5a278c5e53b40cb3196f99e7345fdcfae6b" url: "https://pub.dev" source: hosted - version: "6.3.21" + version: "6.3.22" url_launcher_ios: dependency: transitive description: @@ -1171,10 +1171,10 @@ packages: dependency: transitive description: name: webview_flutter_wkwebview - sha256: fb46db8216131a3e55bcf44040ca808423539bc6732e7ed34fb6d8044e3d512f + sha256: fea63576b3b7e02b2df8b78ba92b48ed66caec2bb041e9a0b1cbd586d5d80bfd url: "https://pub.dev" source: hosted - version: "3.23.0" + version: "3.23.1" win32: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index fedb6b0..0fd184c 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -16,7 +16,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.2.4+2320 +version: 1.2.6+2322 environment: sdk: ^3.8.1 @@ -45,8 +45,8 @@ dependencies: shared_preferences: ^2.5.3 url_launcher: ^6.3.1 permission_handler: ^12.0.0+1 - fluttertoast: ^8.2.12 - device_info_plus: ^11.4.0 + fluttertoast: ^9.0.0 + device_info_plus: ^12.1.0 file_picker: ^10.1.9 animations: ^2.0.11 android_package_installer: # TODO: See if PR will be accepted (dev may not be active), else remove this comment @@ -57,14 +57,14 @@ dependencies: git: url: https://github.com/ImranR98/android_package_manager ref: master - share_plus: ^11.0.0 + share_plus: ^12.0.0 sqflite: ^2.4.2 easy_localization: ^3.0.7+1 - android_intent_plus: ^5.3.0 + android_intent_plus: ^6.0.0 flutter_markdown: ^0.7.7+1 flutter_archive: ^6.0.3 hsluv: ^1.1.3 - connectivity_plus: ^6.1.4 + connectivity_plus: ^7.0.0 shared_storage: # TODO: Is this maintained? git: url: https://github.com/AlexBacich/shared-storage @@ -85,7 +85,7 @@ dependencies: ref: master markdown: ^7.3.0 flutter_typeahead: ^5.2.0 - battery_plus: ^6.2.1 + battery_plus: ^7.0.0 flutter_charset_detector: ^5.0.0 # The "flutter_lints" package below contains a set of recommended lints to