Merge pull request #2551 from ImranR98/dev

- Add 'sky22333/hubproxy' support for GitHub (#2513)
- Fix error in Vivo Store search when no result found (#2494)
- Correct a log string (#2519)
- Loose auto-filtering for farsroid.com (#2441)
- Re-enable auto-filtering for farsroid.com w/ toggle to disable it (#2441)
- Enable long-press and double-tap gestures on app icon in apps list (#2444, #2448)
This commit is contained in:
Imran
2025-09-21 14:14:37 -04:00
committed by GitHub
43 changed files with 328 additions and 104 deletions

View File

@@ -336,6 +336,7 @@
"foregroundServiceExplanation": "استخدام خدمة مقدمة للتحقق من التحديثات (أكثر موثوقية وتستهلك طاقة أكبر)",
"fgServiceNotice": "هذا الإشعار مطلوب للتحقق من التحديث في الخلفية (يمكن إخفاؤه في إعدادات نظام التشغيل)",
"excludeSecrets": "استبعاد الأسرار",
"GHReqPrefix": "مثيل \"sky22333/hubproxy\" لطلبات GitHub",
"removeAppQuestion": {
"one": "إزالة التطبيق؟",
"other": "إزالة التطبيقات؟"

View File

@@ -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?"

View File

@@ -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?"

View File

@@ -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?"

View File

@@ -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?"

View File

@@ -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?"

View File

@@ -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?"

View File

@@ -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?"

View File

@@ -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?"

View File

@@ -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": "برنامه ها حذف شوند؟"

View File

@@ -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 ?"

View File

@@ -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 kérésekhez",
"removeAppQuestion": {
"one": "Eltávolítja az alkalmazást?",
"other": "Eltávolítja az alkalmazásokat?"

View File

@@ -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?"

View File

@@ -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?"

View File

@@ -336,6 +336,7 @@
"foregroundServiceExplanation": "アップデート確認にフォアグラウンドサービスを使用する(より信頼性が高いが、より電力を消費する)",
"fgServiceNotice": "この通知は、バックグラウンドでアップデートを確認するために必要ですOSの設定で非表示にできます。",
"excludeSecrets": "シークレットを除く",
"GHReqPrefix": "GitHub リクエスト用の 'sky22333/hubproxy' インスタンス",
"removeAppQuestion": {
"one": "アプリを削除しますか?",
"other": "アプリを削除しますか?"

View File

@@ -336,6 +336,7 @@
"foregroundServiceExplanation": "업데이트 확인을 위해 포그라운드 서비스 사용(안정성 향상, 전력 소비량 증가)",
"fgServiceNotice": "이 알림은 백그라운드 업데이트 확인에 필요합니다(OS 설정에서 숨길 수 있음).",
"excludeSecrets": "비밀 제외",
"GHReqPrefix": "GitHub 요청을 위한 'sky22333/hubproxy' 인스턴스",
"removeAppQuestion": {
"one": "앱을 제거하시겠습니까?",
"other": "앱을 제거하시겠습니까?"

View File

@@ -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": "ആപ്പുകൾ നീക്കം ചെയ്യണോ?"

View File

@@ -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?"

View File

@@ -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?",

View File

@@ -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?"

View File

@@ -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?"

View File

@@ -336,6 +336,7 @@
"foregroundServiceExplanation": "Использовать приоритетную службу для проверки обновлений (надёжнее, энергозатратнее)",
"fgServiceNotice": "Это уведомление необходимо для фоновой проверки обновлений (оно может быть скрыто в настройках ОС)",
"excludeSecrets": "Исключить секреты",
"GHReqPrefix": "Экземпляр 'sky22333/hubproxy' для запросов на GitHub",
"removeAppQuestion": {
"one": "Удалить приложение?",
"other": "Удалить приложения?"

View File

@@ -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?"

View File

@@ -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?"

View File

@@ -336,6 +336,7 @@
"foregroundServiceExplanation": "Використовуйте службу переднього плану для перевірки оновлень (надійніша, споживає більше енергії)",
"fgServiceNotice": "Це сповіщення необхідне для фонової перевірки оновлень (його можна приховати в налаштуваннях ОС)",
"excludeSecrets": "Виключити секрети",
"GHReqPrefix": "екземпляр 'sky22333/hubproxy' для запитів на GitHub",
"removeAppQuestion": {
"one": "Видалити застосунок?",
"other": "Видалити застосунки?"

View File

@@ -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?"

View File

@@ -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": "移除應用程式?"

View File

@@ -336,6 +336,7 @@
"foregroundServiceExplanation": "使用前台服务检查更新(更稳定,但也更耗电)",
"fgServiceNotice": "后台检查更新时需要此通知(可在操作系统设置中隐藏)",
"excludeSecrets": "排除机密",
"GHReqPrefix": "用于 GitHub 请求的 \"sky22333/hubproxy \"实例",
"removeAppQuestion": {
"one": "是否删除应用?",
"other": "是否删除应用?"

View File

@@ -82,13 +82,13 @@ class APKCombo extends AppSource {
}
@override
Future<String> apkUrlPrefetchModifier(
String apkUrl,
Future<String> assetUrlPrefetchModifier(
String assetUrl,
String standardUrl,
Map<String, dynamic> 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;

View File

@@ -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();

View File

@@ -45,6 +45,45 @@ class GitHub extends AppSource {
const SizedBox(height: 4),
],
),
GeneratedFormTextField(
'GHReqPrefix',
label: tr('GHReqPrefix'),
hint: 'gh-proxy.com',
required: false,
additionalValidators: [
(value) {
try {
if (Uri.parse(
'https://${value}/api.github.com',
).scheme.isNotEmpty) {
throw true;
}
} 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 = [
@@ -270,6 +309,18 @@ class GitHub extends AppSource {
return null;
}
@override
Future<String> generalReqPrefetchModifier(
String reqUrl,
Map<String, dynamic> 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<String> getAPIHost(Map<String, dynamic> additionalSettings) async =>
'https://api.${hosts[0]}';
@@ -289,6 +340,12 @@ class GitHub extends AppSource {
Map<String, dynamic> 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 +401,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 +580,10 @@ class GitHub extends AppSource {
allAssetUrls.add(
MapEntry(
(targetRelease['version'] ?? 'source') + '.tar.gz',
targetRelease['tarball_url'],
undoGHProxyMod(
targetRelease['tarball_url'],
sourceConfigSettingValues,
),
),
);
}
@@ -530,7 +591,10 @@ class GitHub extends AppSource {
allAssetUrls.add(
MapEntry(
(targetRelease['version'] ?? 'source') + '.zip',
targetRelease['zipball_url'],
undoGHProxyMod(
targetRelease['zipball_url'],
sourceConfigSettingValues,
),
),
);
}
@@ -652,12 +716,23 @@ class GitHub extends AppSource {
}
}
undoGHProxyMod(
String reqUrl,
Map<String, String> sourceConfigSettingValues,
) => reqUrl.replaceFirst(
'https://${sourceConfigSettingValues['GHReqPrefix']}/',
'',
);
@override
Future<Map<String, List<String>>> search(
String query, {
Map<String, dynamic> 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 +741,15 @@ class GitHub extends AppSource {
},
querySettings: querySettings,
);
if ((sourceConfigSettingValues['GHReqPrefix'] ?? '').isNotEmpty) {
Map<String, List<String>> results2 = {};
results.forEach((k, v) {
results2[undoGHProxyMod(k, sourceConfigSettingValues)] = v;
});
return results2;
} else {
return results;
}
}
void rateLimitErrorCheck(Response res) {

View File

@@ -129,14 +129,14 @@ class GitLab extends AppSource {
}
@override
Future<String> apkUrlPrefetchModifier(
String apkUrl,
Future<String> assetUrlPrefetchModifier(
String assetUrl,
String standardUrl,
Map<String, dynamic> 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

View File

@@ -124,12 +124,12 @@ class Uptodown extends AppSource {
}
@override
Future<String> apkUrlPrefetchModifier(
String apkUrl,
Future<String> assetUrlPrefetchModifier(
String assetUrl,
String standardUrl,
Map<String, dynamic> additionalSettings,
) async {
var res = await sourceRequest(apkUrl, additionalSettings);
var res = await sourceRequest(assetUrl, additionalSettings);
if (res.statusCode != 200) {
throw getObtainiumHttpError(res);
}

View File

@@ -70,12 +70,14 @@ class VivoAppStore extends AppSource {
throw NoReleasesError();
}
Map<String, List<String>> results = {};
var resultsJson = json['data']['appSearchResponse']['value'];
for (var item in (resultsJson as List<dynamic>)) {
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<dynamic>)) {
results['$appDetailUrl${item['id']}'] = [
item['title_zh'].toString(),
item['developer'].toString(),
];
}
}
return results;
}

View File

@@ -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<AppPage> createState() => _AppPageState();
@@ -60,6 +65,11 @@ class _AppPageState extends State<AppPage> {
Widget build(BuildContext context) {
var appsProvider = context.watch<AppsProvider>();
var settingsProvider = context.watch<SettingsProvider>();
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<AppPage> {
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<AppPage> {
);
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: [

View File

@@ -451,40 +451,57 @@ class AppsPageState extends State<AppsPage> {
}
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,
),
),
);
},
);
}

View File

@@ -319,13 +319,24 @@ class _SettingsPageState extends State<SettingsPage> {
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 ?? '');
}
});
}
},

View File

@@ -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,
),
);
}

View File

@@ -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<String, int> get categories =>
Map<String, int>.from(jsonDecode(prefs?.getString('categories') ?? '{}'));

View File

@@ -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<String> apkUrlPrefetchModifier(
String apkUrl,
Future<String> assetUrlPrefetchModifier(
String assetUrl,
String standardUrl,
Map<String, dynamic> additionalSettings,
) async {
return apkUrl;
return assetUrl;
}
Future<String> generalReqPrefetchModifier(
String reqUrl,
Map<String, dynamic> additionalSettings,
) async {
return reqUrl;
}
bool canSearch = false;

View File

@@ -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:
@@ -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:

View File

@@ -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.5+2321
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