Compare commits

...

18 Commits

Author SHA1 Message Date
8f0aac057e Merge pull request #442 from bluefly000/japanese-translation
Update ja.json
2023-04-07 22:11:35 -04:00
e929920a48 Merge pull request #440 from atilluF/main
Update it.json
2023-04-07 22:11:27 -04:00
8ed254c7dd Merge pull request #446 from ImranR98/dev
Bugfix: GitHub/Codeberg fallback + no-prerel fail
2023-04-07 22:11:20 -04:00
46a00836df Bugfix: GitHub/Codeberg fallback + no-prerel fail 2023-04-07 22:10:55 -04:00
f144ffdded Update ja.json 2023-04-08 00:03:31 +09:00
d597d569e2 Update it.json 2023-04-07 13:19:12 +02:00
b62475de87 Merge pull request #439 from ImranR98/dev
Use app deep copy in places to avoid bugs
2023-04-07 02:20:32 -04:00
334ac8d3d6 Use app deep copy in places to avoid bugs 2023-04-07 01:54:14 -04:00
9193788356 Merge pull request #438 from ImranR98/dev
Better downloaded file naming (reduces conflicts)
2023-04-06 23:00:28 -04:00
8f75ddd43f Better download file naming (reduces conflicts) 2023-04-06 22:59:40 -04:00
a2edc86bfa Merge pull request #437 from ImranR98/dev
Added simple APK try auto-select by CPU arch #436
2023-04-06 22:37:55 -04:00
0804e680b2 Added simple APK try auto-select by CPU arch #436
Plus minor form switch UI fixes (overflow, spacing)
2023-04-06 22:37:24 -04:00
49affd1bd4 Merge pull request #434 from ImranR98/dev
Store APK names with URLs (#432)
2023-04-05 18:56:16 -04:00
202ce4f0d5 Store APK names with URLs (#432) 2023-04-05 18:50:19 -04:00
361a3e1bc2 Merge pull request #426 from ImranR98/dev
Increment version
2023-04-04 21:46:15 -04:00
f33a26d4f4 Increment version 2023-04-04 21:45:57 -04:00
7aaf56ec8c Merge pull request #425 from HRTK92/main
Add long-press URL copy and snackbar message
2023-04-04 21:44:35 -04:00
ed120016d9 Add long-press URL copy and snackbar message 2023-04-05 10:32:56 +09:00
30 changed files with 188 additions and 86 deletions

View File

@ -207,6 +207,7 @@
"addCategory": "Kategorie hinzufügen", "addCategory": "Kategorie hinzufügen",
"label": "Bezeichnung", "label": "Bezeichnung",
"language": "Sprache", "language": "Sprache",
"copiedToClipboard": "Copied to Clipboard",
"storagePermissionDenied": "Speicherberechtigung verweigert", "storagePermissionDenied": "Speicherberechtigung verweigert",
"selectedCategorizeWarning": "Dadurch werden alle bestehenden Kategorieeinstellungen für die ausgewählten Apps ersetzt.", "selectedCategorizeWarning": "Dadurch werden alle bestehenden Kategorieeinstellungen für die ausgewählten Apps ersetzt.",
"filterAPKsByRegEx": "APKs nach regulärem Ausdruck filtern", "filterAPKsByRegEx": "APKs nach regulärem Ausdruck filtern",
@ -221,6 +222,7 @@
"versionDetection": "Versionserkennung", "versionDetection": "Versionserkennung",
"standardVersionDetection": "Standardversionserkennung", "standardVersionDetection": "Standardversionserkennung",
"groupByCategory": "Group by Category", "groupByCategory": "Group by Category",
"autoApkFilterByArch": "Attempt to filter APKs by CPU architecture if possible",
"removeAppQuestion": { "removeAppQuestion": {
"one": "App entfernen?", "one": "App entfernen?",
"other": "App entfernen?" "other": "App entfernen?"
@ -269,4 +271,4 @@
"one": "{} und 1 weitere Anwendung wurden aktualisiert.", "one": "{} und 1 weitere Anwendung wurden aktualisiert.",
"other": "{} und {} weitere Anwendungen wurden aktualisiert." "other": "{} und {} weitere Anwendungen wurden aktualisiert."
} }
} }

View File

@ -207,6 +207,7 @@
"addCategory": "Add Category", "addCategory": "Add Category",
"label": "Label", "label": "Label",
"language": "Language", "language": "Language",
"copiedToClipboard": "Copied to Clipboard",
"storagePermissionDenied": "Storage permission denied", "storagePermissionDenied": "Storage permission denied",
"selectedCategorizeWarning": "This will replace any existing category settings for the selected Apps.", "selectedCategorizeWarning": "This will replace any existing category settings for the selected Apps.",
"filterAPKsByRegEx": "Filter APKs by Regular Expression", "filterAPKsByRegEx": "Filter APKs by Regular Expression",
@ -221,6 +222,7 @@
"versionDetection": "Version Detection", "versionDetection": "Version Detection",
"standardVersionDetection": "Standard version detection", "standardVersionDetection": "Standard version detection",
"groupByCategory": "Group by Category", "groupByCategory": "Group by Category",
"autoApkFilterByArch": "Attempt to filter APKs by CPU architecture if possible",
"removeAppQuestion": { "removeAppQuestion": {
"one": "Remove App?", "one": "Remove App?",
"other": "Remove Apps?" "other": "Remove Apps?"
@ -269,4 +271,4 @@
"one": "{} and 1 more app were updated.", "one": "{} and 1 more app were updated.",
"other": "{} and {} more apps were updated." "other": "{} and {} more apps were updated."
} }
} }

View File

@ -207,6 +207,7 @@
"addCategory": "اضافه کردن دسته", "addCategory": "اضافه کردن دسته",
"label": "برچسب", "label": "برچسب",
"language": "زبان", "language": "زبان",
"copiedToClipboard": "Copied to Clipboard",
"storagePermissionDenied": "مجوز ذخیره سازی رد شد", "storagePermissionDenied": "مجوز ذخیره سازی رد شد",
"selectedCategorizeWarning": "این جایگزین تنظیمات دسته بندی موجود برای برنامه های انتخابی می شود.", "selectedCategorizeWarning": "این جایگزین تنظیمات دسته بندی موجود برای برنامه های انتخابی می شود.",
"filterAPKsByRegEx": "فایل‌های APK را با نظم فیلتر کنید", "filterAPKsByRegEx": "فایل‌های APK را با نظم فیلتر کنید",
@ -221,6 +222,7 @@
"versionDetection": "تشخیص نسخه", "versionDetection": "تشخیص نسخه",
"standardVersionDetection": "تشخیص نسخه استاندارد", "standardVersionDetection": "تشخیص نسخه استاندارد",
"groupByCategory": "Group by Category", "groupByCategory": "Group by Category",
"autoApkFilterByArch": "Attempt to filter APKs by CPU architecture if possible",
"removeAppQuestion": { "removeAppQuestion": {
"one": "برنامه حذف شود؟", "one": "برنامه حذف شود؟",
"other": "برنامه ها حذف شوند؟" "other": "برنامه ها حذف شوند؟"

View File

@ -207,6 +207,7 @@
"addCategory": "Ajouter une catégorie", "addCategory": "Ajouter une catégorie",
"label": "Étiquette", "label": "Étiquette",
"language": "Langue", "language": "Langue",
"copiedToClipboard": "Copied to Clipboard",
"storagePermissionDenied": "Autorisation de stockage refusée", "storagePermissionDenied": "Autorisation de stockage refusée",
"selectedCategorizeWarning": "Cela remplacera tous les paramètres de catégorie existants pour les applications sélectionnées.", "selectedCategorizeWarning": "Cela remplacera tous les paramètres de catégorie existants pour les applications sélectionnées.",
"filterAPKsByRegEx": "Filtrer les APK par expression régulière", "filterAPKsByRegEx": "Filtrer les APK par expression régulière",
@ -221,6 +222,7 @@
"versionDetection": "Détection des versions", "versionDetection": "Détection des versions",
"standardVersionDetection": "Détection de version standard", "standardVersionDetection": "Détection de version standard",
"groupByCategory": "Group by Category", "groupByCategory": "Group by Category",
"autoApkFilterByArch": "Attempt to filter APKs by CPU architecture if possible",
"removeAppQuestion": { "removeAppQuestion": {
"one": "Supprimer l'application ?", "one": "Supprimer l'application ?",
"other": "Supprimer les applications ?" "other": "Supprimer les applications ?"

View File

@ -206,6 +206,7 @@
"addCategory": "Új kategória", "addCategory": "Új kategória",
"label": "Címke", "label": "Címke",
"language": "Nyelv", "language": "Nyelv",
"copiedToClipboard": "Copied to Clipboard",
"storagePermissionDenied": "Tárhely engedély megtagadva", "storagePermissionDenied": "Tárhely engedély megtagadva",
"selectedCategorizeWarning": "Ez felváltja a kiválasztott alkalmazások meglévő kategória-beállításait.", "selectedCategorizeWarning": "Ez felváltja a kiválasztott alkalmazások meglévő kategória-beállításait.",
"filterAPKsByRegEx": "Az APK-k szűrése reguláris kifejezéssel", "filterAPKsByRegEx": "Az APK-k szűrése reguláris kifejezéssel",
@ -220,6 +221,7 @@
"versionDetection": "Verzió érzékelés", "versionDetection": "Verzió érzékelés",
"standardVersionDetection": "Alapért. verzió érzékelés", "standardVersionDetection": "Alapért. verzió érzékelés",
"groupByCategory": "Csoportosítás Kategória alapján", "groupByCategory": "Csoportosítás Kategória alapján",
"autoApkFilterByArch": "Attempt to filter APKs by CPU architecture if possible",
"removeAppQuestion": { "removeAppQuestion": {
"one": "Eltávolítja az alkalmazást?", "one": "Eltávolítja az alkalmazást?",
"other": "Eltávolítja az alkalmazást?" "other": "Eltávolítja az alkalmazást?"

View File

@ -207,6 +207,7 @@
"addCategory": "Aggiungi categoria", "addCategory": "Aggiungi categoria",
"label": "Etichetta", "label": "Etichetta",
"language": "Lingua", "language": "Lingua",
"copiedToClipboard": "Copiato negli appunti",
"storagePermissionDenied": "Accesso ai file non autorizzato", "storagePermissionDenied": "Accesso ai file non autorizzato",
"selectedCategorizeWarning": "Ciò sostituirà le impostazioni di categoria esistenti per le App selezionate.", "selectedCategorizeWarning": "Ciò sostituirà le impostazioni di categoria esistenti per le App selezionate.",
"filterAPKsByRegEx": "Filtra file APK con espressioni regolari", "filterAPKsByRegEx": "Filtra file APK con espressioni regolari",
@ -220,7 +221,8 @@
"importFromURLsInFile": "Importa da URL in file (come OPML)", "importFromURLsInFile": "Importa da URL in file (come OPML)",
"versionDetection": "Rilevamento di versione", "versionDetection": "Rilevamento di versione",
"standardVersionDetection": "Rilevamento di versione standard", "standardVersionDetection": "Rilevamento di versione standard",
"groupByCategory": "Group by Category", "groupByCategory": "Raggruppa per categoria",
"autoApkFilterByArch": "Tenta di filtrare gli APK in base all'architettura della CPU, se possibile",
"removeAppQuestion": { "removeAppQuestion": {
"one": "Rimuovere l'App?", "one": "Rimuovere l'App?",
"other": "Rimuovere le App?" "other": "Rimuovere le App?"

View File

@ -207,6 +207,7 @@
"addCategory": "カテゴリを追加", "addCategory": "カテゴリを追加",
"label": "ラベル", "label": "ラベル",
"language": "言語", "language": "言語",
"copiedToClipboard": "クリップボードにコピーしました",
"storagePermissionDenied": "ストレージ権限が拒否されました", "storagePermissionDenied": "ストレージ権限が拒否されました",
"selectedCategorizeWarning": "これにより、選択したアプリの既存のカテゴリ設定がすべて置き換えられます。", "selectedCategorizeWarning": "これにより、選択したアプリの既存のカテゴリ設定がすべて置き換えられます。",
"filterAPKsByRegEx": "正規表現でAPKを絞り込む", "filterAPKsByRegEx": "正規表現でAPKを絞り込む",
@ -220,7 +221,8 @@
"importFromURLsInFile": "ファイルOPMLなど内のURLからインポート", "importFromURLsInFile": "ファイルOPMLなど内のURLからインポート",
"versionDetection": "バージョン検出", "versionDetection": "バージョン検出",
"standardVersionDetection": "標準のバージョン検出", "standardVersionDetection": "標準のバージョン検出",
"groupByCategory": "Group by Category", "groupByCategory": "カテゴリ別にグループ化する",
"autoApkFilterByArch": "可能であればCPUアーキテクチャによるAPKのフィルタリングを試みる",
"removeAppQuestion": { "removeAppQuestion": {
"one": "アプリを削除しますか?", "one": "アプリを削除しますか?",
"other": "アプリを削除しますか?" "other": "アプリを削除しますか?"
@ -269,4 +271,4 @@
"one": "{} とさらに {} 個のアプリがアップデートされました", "one": "{} とさらに {} 個のアプリがアップデートされました",
"other": "{} とさらに {} 個のアプリがアップデートされました" "other": "{} とさらに {} 個のアプリがアップデートされました"
} }
} }

View File

@ -208,6 +208,7 @@
"addCategory": "添加类别", "addCategory": "添加类别",
"label": "标签", "label": "标签",
"language": "语言", "language": "语言",
"copiedToClipboard": "Copied to Clipboard",
"storagePermissionDenied": "存储权限已被拒绝", "storagePermissionDenied": "存储权限已被拒绝",
"selectedCategorizeWarning": "这将取代所选应用程序的任何现有类别", "selectedCategorizeWarning": "这将取代所选应用程序的任何现有类别",
"filterAPKsByRegEx": "Filter APKs by Regular Expression", "filterAPKsByRegEx": "Filter APKs by Regular Expression",
@ -221,6 +222,7 @@
"versionDetection": "Version Detection", "versionDetection": "Version Detection",
"standardVersionDetection": "Standard version detection", "standardVersionDetection": "Standard version detection",
"groupByCategory": "Group by Category", "groupByCategory": "Group by Category",
"autoApkFilterByArch": "Attempt to filter APKs by CPU architecture if possible",
"removeAppQuestion": { "removeAppQuestion": {
"one": "删除应用?", "one": "删除应用?",
"other": "删除应用?" "other": "删除应用?"
@ -269,4 +271,4 @@
"one": "{} 和 {} 更多应用已被安装", "one": "{} 和 {} 更多应用已被安装",
"other": "{} 和 {} 更多应用已被安装" "other": "{} 和 {} 更多应用已被安装"
} }
} }

View File

@ -68,7 +68,7 @@ class Codeberg extends AppSource {
if (res.statusCode == 200) { if (res.statusCode == 200) {
var releases = jsonDecode(res.body) as List<dynamic>; var releases = jsonDecode(res.body) as List<dynamic>;
List<String> getReleaseAPKUrls(dynamic release) => List<MapEntry<String, String>> getReleaseAPKUrls(dynamic release) =>
(release['assets'] as List<dynamic>?) (release['assets'] as List<dynamic>?)
?.map((e) { ?.map((e) {
return e['name'] != null && e['browser_download_url'] != null return e['name'] != null && e['browser_download_url'] != null
@ -77,15 +77,15 @@ class Codeberg extends AppSource {
: const MapEntry('', ''); : const MapEntry('', '');
}) })
.where((element) => element.key.toLowerCase().endsWith('.apk')) .where((element) => element.key.toLowerCase().endsWith('.apk'))
.map((e) => e.value)
.toList() ?? .toList() ??
[]; [];
dynamic targetRelease; dynamic targetRelease;
var prerrelsSkipped = 0;
for (int i = 0; i < releases.length; i++) { for (int i = 0; i < releases.length; i++) {
if (!fallbackToOlderReleases && i > 0) break; if (!fallbackToOlderReleases && i > prerrelsSkipped) break;
if (!includePrereleases && releases[i]['prerelease'] == true) { if (!includePrereleases && releases[i]['prerelease'] == true) {
prerrelsSkipped++;
continue; continue;
} }
if (releases[i]['draft'] == true) { if (releases[i]['draft'] == true) {
@ -119,7 +119,9 @@ class Codeberg extends AppSource {
throw NoVersionError(); throw NoVersionError();
} }
var changeLog = targetRelease['body'].toString(); var changeLog = targetRelease['body'].toString();
return APKDetails(version, targetRelease['apkUrls'] as List<String>, return APKDetails(
version,
targetRelease['apkUrls'] as List<MapEntry<String, String>>,
getAppNames(standardUrl), getAppNames(standardUrl),
releaseDate: releaseDate, releaseDate: releaseDate,
changeLog: changeLog.isEmpty ? null : changeLog); changeLog: changeLog.isEmpty ? null : changeLog);

View File

@ -50,7 +50,7 @@ class FDroid extends AppSource {
.where((element) => element['versionName'] == latestVersion) .where((element) => element['versionName'] == latestVersion)
.map((e) => '${apkUrlPrefix}_${e['versionCode']}.apk') .map((e) => '${apkUrlPrefix}_${e['versionCode']}.apk')
.toList(); .toList();
return APKDetails(latestVersion, apkUrls, return APKDetails(latestVersion, getApkUrlsFromUrls(apkUrls),
AppNames(name, Uri.parse(standardUrl).pathSegments.last)); AppNames(name, Uri.parse(standardUrl).pathSegments.last));
} else { } else {
throw getObtainiumHttpError(res); throw getObtainiumHttpError(res);

View File

@ -80,7 +80,8 @@ class FDroidRepo extends AppSource {
element.querySelector('apkname') != null) element.querySelector('apkname') != null)
.map((e) => '$standardUrl/${e.querySelector('apkname')!.innerHtml}') .map((e) => '$standardUrl/${e.querySelector('apkname')!.innerHtml}')
.toList(); .toList();
return APKDetails(latestVersion, apkUrls, AppNames(authorName, appName), return APKDetails(latestVersion, getApkUrlsFromUrls(apkUrls),
AppNames(authorName, appName),
releaseDate: releaseDate); releaseDate: releaseDate);
} else { } else {
throw getObtainiumHttpError(res); throw getObtainiumHttpError(res);

View File

@ -127,10 +127,11 @@ class GitHub extends AppSource {
[]; [];
dynamic targetRelease; dynamic targetRelease;
var prerrelsSkipped = 0;
for (int i = 0; i < releases.length; i++) { for (int i = 0; i < releases.length; i++) {
if (!fallbackToOlderReleases && i > 0) break; if (!fallbackToOlderReleases && i > prerrelsSkipped) break;
if (!includePrereleases && releases[i]['prerelease'] == true) { if (!includePrereleases && releases[i]['prerelease'] == true) {
prerrelsSkipped++;
continue; continue;
} }
var nameToFilter = releases[i]['name'] as String?; var nameToFilter = releases[i]['name'] as String?;
@ -161,7 +162,9 @@ class GitHub extends AppSource {
throw NoVersionError(); throw NoVersionError();
} }
var changeLog = targetRelease['body'].toString(); var changeLog = targetRelease['body'].toString();
return APKDetails(version, targetRelease['apkUrls'] as List<String>, return APKDetails(
version,
getApkUrlsFromUrls(targetRelease['apkUrls'] as List<String>),
getAppNames(standardUrl), getAppNames(standardUrl),
releaseDate: releaseDate, releaseDate: releaseDate,
changeLog: changeLog.isEmpty ? null : changeLog); changeLog: changeLog.isEmpty ? null : changeLog);

View File

@ -60,7 +60,8 @@ class GitLab extends AppSource {
if (version == null) { if (version == null) {
throw NoVersionError(); throw NoVersionError();
} }
return APKDetails(version, apkUrls, GitHub().getAppNames(standardUrl), return APKDetails(version, getApkUrlsFromUrls(apkUrls),
GitHub().getAppNames(standardUrl),
releaseDate: releaseDate); releaseDate: releaseDate);
} else { } else {
throw getObtainiumHttpError(res); throw getObtainiumHttpError(res);

View File

@ -42,7 +42,8 @@ class HTML extends AppSource {
? '${uri.origin}/$e' ? '${uri.origin}/$e'
: '${uri.origin}/${uri.path}/$e') : '${uri.origin}/${uri.path}/$e')
.toList(); .toList();
return APKDetails(version, apkUrls, AppNames(uri.host, tr('app'))); return APKDetails(
version, getApkUrlsFromUrls(apkUrls), AppNames(uri.host, tr('app')));
} else { } else {
throw getObtainiumHttpError(res); throw getObtainiumHttpError(res);
} }

View File

@ -58,7 +58,7 @@ class Mullvad extends AppSource {
} }
return APKDetails( return APKDetails(
versions[0], versions[0],
['https://mullvad.net/download/app/apk/latest'], getApkUrlsFromUrls(['https://mullvad.net/download/app/apk/latest']),
AppNames(name, 'Mullvad-VPN'), AppNames(name, 'Mullvad-VPN'),
changeLog: changeLog); changeLog: changeLog);
} else { } else {

View File

@ -98,7 +98,7 @@ class NeutronCode extends AppSource {
? (customDateParse(dateStringOriginal)) ? (customDateParse(dateStringOriginal))
: null; : null;
var changeLogElements = http.querySelectorAll('.pd-fdesc p'); var changeLogElements = http.querySelectorAll('.pd-fdesc p');
return APKDetails(version, [apkUrl], return APKDetails(version, getApkUrlsFromUrls([apkUrl]),
AppNames(runtimeType.toString(), name ?? standardUrl.split('/').last), AppNames(runtimeType.toString(), name ?? standardUrl.split('/').last),
releaseDate: dateString != null ? DateTime.parse(dateString) : null, releaseDate: dateString != null ? DateTime.parse(dateString) : null,
changeLog: changeLogElements.isNotEmpty changeLog: changeLogElements.isNotEmpty

View File

@ -28,7 +28,8 @@ class Signal extends AppSource {
if (version == null) { if (version == null) {
throw NoVersionError(); throw NoVersionError();
} }
return APKDetails(version, apkUrls, AppNames(name, 'Signal')); return APKDetails(
version, getApkUrlsFromUrls(apkUrls), AppNames(name, 'Signal'));
} else { } else {
throw getObtainiumHttpError(res); throw getObtainiumHttpError(res);
} }

View File

@ -50,7 +50,7 @@ class SourceForge extends AppSource {
.toList(); .toList();
return APKDetails( return APKDetails(
version, version,
apkUrlList, getApkUrlsFromUrls(apkUrlList),
AppNames( AppNames(
name, standardUrl.substring(standardUrl.lastIndexOf('/') + 1))); name, standardUrl.substring(standardUrl.lastIndexOf('/') + 1)));
} else { } else {

View File

@ -53,7 +53,8 @@ class SteamMobile extends AppSource {
var version = links[0].substring( var version = links[0].substring(
versionMatch.start + apkNamePrefix.length + 2, versionMatch.end - 4); versionMatch.start + apkNamePrefix.length + 2, versionMatch.end - 4);
var apkUrls = [links[0]]; var apkUrls = [links[0]];
return APKDetails(version, apkUrls, AppNames(name, apks[apkNamePrefix]!)); return APKDetails(version, getApkUrlsFromUrls(apkUrls),
AppNames(name, apks[apkNamePrefix]!));
} else { } else {
throw getObtainiumHttpError(res); throw getObtainiumHttpError(res);
} }

View File

@ -32,7 +32,8 @@ class TelegramApp extends AppSource {
throw NoVersionError(); throw NoVersionError();
} }
String? apkUrl = 'https://telegram.org/dl/android/apk'; String? apkUrl = 'https://telegram.org/dl/android/apk';
return APKDetails(version, [apkUrl], AppNames('Telegram', 'Telegram')); return APKDetails(version, getApkUrlsFromUrls([apkUrl]),
AppNames('Telegram', 'Telegram'));
} else { } else {
throw getObtainiumHttpError(res); throw getObtainiumHttpError(res);
} }

View File

@ -54,7 +54,8 @@ class VLC extends AppSource {
throw getObtainiumHttpError(res2); throw getObtainiumHttpError(res2);
} }
return APKDetails(version, apkUrls, AppNames('VideoLAN', 'VLC')); return APKDetails(
version, getApkUrlsFromUrls(apkUrls), AppNames('VideoLAN', 'VLC'));
} else { } else {
throw getObtainiumHttpError(res); throw getObtainiumHttpError(res);
} }

View File

@ -64,9 +64,9 @@ class WhatsApp extends AppSource {
vLines[0].substring(versionMatch.start, versionMatch.end); vLines[0].substring(versionMatch.start, versionMatch.end);
return APKDetails( return APKDetails(
version, version,
[ getApkUrlsFromUrls([
'https://www.whatsapp.com/android?v=$version&=thisIsaPlaceholder&a=realURLPrefetchedAtDownloadTime' 'https://www.whatsapp.com/android?v=$version&=thisIsaPlaceholder&a=realURLPrefetchedAtDownloadTime'
], ]),
AppNames('Meta', 'WhatsApp')); AppNames('Meta', 'WhatsApp'));
} else { } else {
throw getObtainiumHttpError(res); throw getObtainiumHttpError(res);

View File

@ -267,7 +267,10 @@ class _GeneratedFormState extends State<GeneratedForm> {
formInputs[r][e] = Row( formInputs[r][e] = Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
Text(widget.items[r][e].label), Flexible(child: Text(widget.items[r][e].label)),
const SizedBox(
width: 8,
),
Switch( Switch(
value: values[widget.items[r][e].key], value: values[widget.items[r][e].key],
onChanged: (value) { onChanged: (value) {

View File

@ -21,7 +21,7 @@ import 'package:easy_localization/src/easy_localization_controller.dart';
// ignore: implementation_imports // ignore: implementation_imports
import 'package:easy_localization/src/localization.dart'; import 'package:easy_localization/src/localization.dart';
const String currentVersion = '0.11.23'; const String currentVersion = '0.11.29';
const String currentReleaseTag = const String currentReleaseTag =
'v$currentVersion-beta'; // KEEP THIS IN SYNC WITH GITHUB RELEASES 'v$currentVersion-beta'; // KEEP THIS IN SYNC WITH GITHUB RELEASES

View File

@ -38,7 +38,7 @@ class _AppPageState extends State<AppPage> {
bool areDownloadsRunning = appsProvider.areDownloadsRunning(); bool areDownloadsRunning = appsProvider.areDownloadsRunning();
var sourceProvider = SourceProvider(); var sourceProvider = SourceProvider();
AppInMemory? app = appsProvider.apps[widget.appId]; AppInMemory? app = appsProvider.apps[widget.appId]?.deepCopy();
var source = app != null ? sourceProvider.getSource(app.app.url) : null; var source = app != null ? sourceProvider.getSource(app.app.url) : null;
if (!areDownloadsRunning && prevApp == null && app != null) { if (!areDownloadsRunning && prevApp == null && app != null) {
prevApp = app; prevApp = app;
@ -61,6 +61,12 @@ class _AppPageState extends State<AppPage> {
mode: LaunchMode.externalApplication); mode: LaunchMode.externalApplication);
} }
}, },
onLongPress: () {
Clipboard.setData(ClipboardData(text: app?.app.url ?? ''));
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
content: Text(tr('copiedToClipboard')),
));
},
child: Text( child: Text(
app?.app.url ?? '', app?.app.url ?? '',
textAlign: TextAlign.center, textAlign: TextAlign.center,

View File

@ -29,13 +29,13 @@ class AppsPageState extends State<AppsPage> {
final AppsFilter neutralFilter = AppsFilter(); final AppsFilter neutralFilter = AppsFilter();
var updatesOnlyFilter = var updatesOnlyFilter =
AppsFilter(includeUptodate: false, includeNonInstalled: false); AppsFilter(includeUptodate: false, includeNonInstalled: false);
Set<App> selectedApps = {}; Set<String> selectedAppIds = {};
DateTime? refreshingSince; DateTime? refreshingSince;
clearSelected() { clearSelected() {
if (selectedApps.isNotEmpty) { if (selectedAppIds.isNotEmpty) {
setState(() { setState(() {
selectedApps.clear(); selectedAppIds.clear();
}); });
return true; return true;
} }
@ -43,10 +43,10 @@ class AppsPageState extends State<AppsPage> {
} }
selectThese(List<App> apps) { selectThese(List<App> apps) {
if (selectedApps.isEmpty) { if (selectedAppIds.isEmpty) {
setState(() { setState(() {
for (var a in apps) { for (var a in apps) {
selectedApps.add(a); selectedAppIds.add(a.id);
} }
}); });
} }
@ -57,20 +57,20 @@ class AppsPageState extends State<AppsPage> {
var appsProvider = context.watch<AppsProvider>(); var appsProvider = context.watch<AppsProvider>();
var settingsProvider = context.watch<SettingsProvider>(); var settingsProvider = context.watch<SettingsProvider>();
var sourceProvider = SourceProvider(); var sourceProvider = SourceProvider();
var listedApps = appsProvider.apps.values.toList(); var listedApps = appsProvider.getAppValues().toList();
var currentFilterIsUpdatesOnly = var currentFilterIsUpdatesOnly =
filter.isIdenticalTo(updatesOnlyFilter, settingsProvider); filter.isIdenticalTo(updatesOnlyFilter, settingsProvider);
selectedApps = selectedApps selectedAppIds = selectedAppIds
.where((element) => listedApps.map((e) => e.app).contains(element)) .where((element) => listedApps.map((e) => e.app.id).contains(element))
.toSet(); .toSet();
toggleAppSelected(App app) { toggleAppSelected(App app) {
setState(() { setState(() {
if (selectedApps.contains(app)) { if (selectedAppIds.map((e) => e).contains(app.id)) {
selectedApps.remove(app); selectedAppIds.removeWhere((a) => a == app.id);
} else { } else {
selectedApps.add(app); selectedAppIds.add(app.id);
} }
}); });
} }
@ -143,15 +143,15 @@ class AppsPageState extends State<AppsPage> {
var existingUpdates = appsProvider.findExistingUpdates(installedOnly: true); var existingUpdates = appsProvider.findExistingUpdates(installedOnly: true);
var existingUpdateIdsAllOrSelected = existingUpdates var existingUpdateIdsAllOrSelected = existingUpdates
.where((element) => selectedApps.isEmpty .where((element) => selectedAppIds.isEmpty
? listedApps.where((a) => a.app.id == element).isNotEmpty ? listedApps.where((a) => a.app.id == element).isNotEmpty
: selectedApps.map((e) => e.id).contains(element)) : selectedAppIds.map((e) => e).contains(element))
.toList(); .toList();
var newInstallIdsAllOrSelected = appsProvider var newInstallIdsAllOrSelected = appsProvider
.findExistingUpdates(nonInstalledOnly: true) .findExistingUpdates(nonInstalledOnly: true)
.where((element) => selectedApps.isEmpty .where((element) => selectedAppIds.isEmpty
? listedApps.where((a) => a.app.id == element).isNotEmpty ? listedApps.where((a) => a.app.id == element).isNotEmpty
: selectedApps.map((e) => e.id).contains(element)) : selectedAppIds.map((e) => e).contains(element))
.toList(); .toList();
List<String> trackOnlyUpdateIdsAllOrSelected = []; List<String> trackOnlyUpdateIdsAllOrSelected = [];
@ -212,6 +212,11 @@ class AppsPageState extends State<AppsPage> {
: -1; : -1;
}); });
Set<App> selectedApps = listedApps
.map((e) => e.app)
.where((a) => selectedAppIds.contains(a.id))
.toSet();
showChangeLogDialog( showChangeLogDialog(
String? changesUrl, AppSource appSource, String changeLog, int index) { String? changesUrl, AppSource appSource, String changeLog, int index) {
showDialog( showDialog(
@ -288,7 +293,8 @@ class AppsPageState extends State<AppsPage> {
if (refreshingSince != null) if (refreshingSince != null)
SliverToBoxAdapter( SliverToBoxAdapter(
child: LinearProgressIndicator( child: LinearProgressIndicator(
value: appsProvider.apps.values value: appsProvider
.getAppValues()
.where((element) => !(element.app.lastUpdateCheck .where((element) => !(element.app.lastUpdateCheck
?.isBefore(refreshingSince!) ?? ?.isBefore(refreshingSince!) ??
true)) true))
@ -467,7 +473,8 @@ class AppsPageState extends State<AppsPage> {
.colorScheme .colorScheme
.primary .primary
.withOpacity(listedApps[index].app.pinned ? 0.2 : 0.1), .withOpacity(listedApps[index].app.pinned ? 0.2 : 0.1),
selected: selectedApps.contains(listedApps[index].app), selected:
selectedAppIds.map((e) => e).contains(listedApps[index].app.id),
onLongPress: () { onLongPress: () {
toggleAppSelected(listedApps[index].app); toggleAppSelected(listedApps[index].app);
}, },
@ -497,7 +504,7 @@ class AppsPageState extends State<AppsPage> {
])) ]))
: trailingRow, : trailingRow,
onTap: () { onTap: () {
if (selectedApps.isNotEmpty) { if (selectedAppIds.isNotEmpty) {
toggleAppSelected(listedApps[index].app); toggleAppSelected(listedApps[index].app);
} else { } else {
Navigator.push( Navigator.push(
@ -534,7 +541,7 @@ class AppsPageState extends State<AppsPage> {
} }
getSelectAllButton() { getSelectAllButton() {
return selectedApps.isEmpty return selectedAppIds.isEmpty
? TextButton.icon( ? TextButton.icon(
style: const ButtonStyle(visualDensity: VisualDensity.compact), style: const ButtonStyle(visualDensity: VisualDensity.compact),
onPressed: () { onPressed: () {
@ -548,17 +555,17 @@ class AppsPageState extends State<AppsPage> {
: TextButton.icon( : TextButton.icon(
style: const ButtonStyle(visualDensity: VisualDensity.compact), style: const ButtonStyle(visualDensity: VisualDensity.compact),
onPressed: () { onPressed: () {
selectedApps.isEmpty selectedAppIds.isEmpty
? selectThese(listedApps.map((e) => e.app).toList()) ? selectThese(listedApps.map((e) => e.app).toList())
: clearSelected(); : clearSelected();
}, },
icon: Icon( icon: Icon(
selectedApps.isEmpty selectedAppIds.isEmpty
? Icons.select_all_outlined ? Icons.select_all_outlined
: Icons.deselect_outlined, : Icons.deselect_outlined,
color: Theme.of(context).colorScheme.primary, color: Theme.of(context).colorScheme.primary,
), ),
label: Text(selectedApps.length.toString())); label: Text(selectedAppIds.length.toString()));
} }
getMassObtainFunction() { getMassObtainFunction() {
@ -706,7 +713,7 @@ class AppsPageState extends State<AppsPage> {
builder: (BuildContext ctx) { builder: (BuildContext ctx) {
return AlertDialog( return AlertDialog(
title: Text(tr('markXSelectedAppsAsUpdated', title: Text(tr('markXSelectedAppsAsUpdated',
args: [selectedApps.length.toString()])), args: [selectedAppIds.length.toString()])),
content: Text( content: Text(
tr('onlyWorksWithNonVersionDetectApps'), tr('onlyWorksWithNonVersionDetectApps'),
style: const TextStyle( style: const TextStyle(
@ -760,7 +767,7 @@ class AppsPageState extends State<AppsPage> {
items: const [], items: const [],
initValid: true, initValid: true,
message: tr('installStatusOfXWillBeResetExplanation', message: tr('installStatusOfXWillBeResetExplanation',
args: [plural('app', selectedApps.length)]), args: [plural('app', selectedAppIds.length)]),
); );
}); });
if (values != null) { if (values != null) {
@ -836,7 +843,7 @@ class AppsPageState extends State<AppsPage> {
children: [ children: [
IconButton( IconButton(
visualDensity: VisualDensity.compact, visualDensity: VisualDensity.compact,
onPressed: selectedApps.isEmpty onPressed: selectedAppIds.isEmpty
? null ? null
: () { : () {
appsProvider.removeAppsWithModal( appsProvider.removeAppsWithModal(
@ -848,7 +855,7 @@ class AppsPageState extends State<AppsPage> {
IconButton( IconButton(
visualDensity: VisualDensity.compact, visualDensity: VisualDensity.compact,
onPressed: getMassObtainFunction(), onPressed: getMassObtainFunction(),
tooltip: selectedApps.isEmpty tooltip: selectedAppIds.isEmpty
? tr('installUpdateApps') ? tr('installUpdateApps')
: tr('installUpdateSelectedApps'), : tr('installUpdateSelectedApps'),
icon: const Icon( icon: const Icon(
@ -856,13 +863,13 @@ class AppsPageState extends State<AppsPage> {
)), )),
IconButton( IconButton(
visualDensity: VisualDensity.compact, visualDensity: VisualDensity.compact,
onPressed: selectedApps.isEmpty ? null : launchCategorizeDialog(), onPressed: selectedAppIds.isEmpty ? null : launchCategorizeDialog(),
tooltip: tr('categorize'), tooltip: tr('categorize'),
icon: const Icon(Icons.category_outlined), icon: const Icon(Icons.category_outlined),
), ),
IconButton( IconButton(
visualDensity: VisualDensity.compact, visualDensity: VisualDensity.compact,
onPressed: selectedApps.isEmpty ? null : showMoreOptionsDialog, onPressed: selectedAppIds.isEmpty ? null : showMoreOptionsDialog,
tooltip: tr('more'), tooltip: tr('more'),
icon: const Icon(Icons.more_horiz), icon: const Icon(Icons.more_horiz),
), ),

View File

@ -34,6 +34,8 @@ class AppInMemory {
AppInfo? installedInfo; AppInfo? installedInfo;
AppInMemory(this.app, this.downloadProgress, this.installedInfo); AppInMemory(this.app, this.downloadProgress, this.installedInfo);
AppInMemory deepCopy() =>
AppInMemory(app.deepCopy(), downloadProgress, installedInfo);
} }
class DownloadedApk { class DownloadedApk {
@ -97,6 +99,8 @@ class AppsProvider with ChangeNotifier {
late Stream<FGBGType>? foregroundStream; late Stream<FGBGType>? foregroundStream;
late StreamSubscription<FGBGType>? foregroundSubscription; late StreamSubscription<FGBGType>? foregroundSubscription;
Iterable<AppInMemory> getAppValues() => apps.values.map((a) => a.deepCopy());
AppsProvider() { AppsProvider() {
// Subscribe to changes in the app foreground status // Subscribe to changes in the app foreground status
foregroundStream = FGBGEvents.stream.asBroadcastStream(); foregroundStream = FGBGEvents.stream.asBroadcastStream();
@ -165,11 +169,10 @@ class AppsProvider with ChangeNotifier {
notifyListeners(); notifyListeners();
} }
try { try {
var fileName =
'${app.id}-${app.latestVersion}-${app.preferredApkIndex}.apk';
String downloadUrl = await SourceProvider() String downloadUrl = await SourceProvider()
.getSource(app.url) .getSource(app.url)
.apkUrlPrefetchModifier(app.apkUrls[app.preferredApkIndex]); .apkUrlPrefetchModifier(app.apkUrls[app.preferredApkIndex].value);
var fileName = '${app.id}-${downloadUrl.hashCode}.apk';
var notif = DownloadNotification(app.name, 100); var notif = DownloadNotification(app.name, 100);
notificationsProvider?.cancel(notif.id); notificationsProvider?.cancel(notif.id);
int? prevProg; int? prevProg;
@ -205,7 +208,7 @@ class AppsProvider with ChangeNotifier {
var originalAppId = app.id; var originalAppId = app.id;
app.id = newInfo.packageName; app.id = newInfo.packageName;
downloadedFile = downloadedFile.renameSync( downloadedFile = downloadedFile.renameSync(
'${downloadedFile.parent.path}/${app.id}-${app.latestVersion}-${app.preferredApkIndex}.apk'); '${downloadedFile.parent.path}/${app.id}-${downloadUrl.hashCode}.apk');
if (apps[originalAppId] != null) { if (apps[originalAppId] != null) {
await removeApps([originalAppId]); await removeApps([originalAppId]);
await saveApps([app]); await saveApps([app]);
@ -296,9 +299,10 @@ class AppsProvider with ChangeNotifier {
await intent.launch(); await intent.launch();
} }
Future<String?> confirmApkUrl(App app, BuildContext? context) async { Future<MapEntry<String, String>?> confirmApkUrl(
App app, BuildContext? context) async {
// If the App has more than one APK, the user should pick one (if context provided) // If the App has more than one APK, the user should pick one (if context provided)
String? apkUrl = app.apkUrls[app.preferredApkIndex]; MapEntry<String, String>? apkUrl = app.apkUrls[app.preferredApkIndex];
// get device supported architecture // get device supported architecture
List<String> archs = (await DeviceInfoPlugin().androidInfo).supportedAbis; List<String> archs = (await DeviceInfoPlugin().androidInfo).supportedAbis;
@ -321,14 +325,14 @@ class AppsProvider with ChangeNotifier {
// If the picked APK comes from an origin different from the source, get user confirmation (if context provided) // If the picked APK comes from an origin different from the source, get user confirmation (if context provided)
if (apkUrl != null && if (apkUrl != null &&
getHost(apkUrl) != getHost(app.url) && getHost(apkUrl.value) != getHost(app.url) &&
context != null) { context != null) {
// ignore: use_build_context_synchronously // ignore: use_build_context_synchronously
if (await showDialog( if (await showDialog(
context: context, context: context,
builder: (BuildContext ctx) { builder: (BuildContext ctx) {
return APKOriginWarningDialog( return APKOriginWarningDialog(
sourceUrl: app.url, apkUrl: apkUrl!); sourceUrl: app.url, apkUrl: apkUrl!.value);
}) != }) !=
true) { true) {
apkUrl = null; apkUrl = null;
@ -353,7 +357,7 @@ class AppsProvider with ChangeNotifier {
if (apps[id] == null) { if (apps[id] == null) {
throw ObtainiumError(tr('appNotFound')); throw ObtainiumError(tr('appNotFound'));
} }
String? apkUrl; MapEntry<String, String>? apkUrl;
var trackOnly = apps[id]!.app.additionalSettings['trackOnly'] == true; var trackOnly = apps[id]!.app.additionalSettings['trackOnly'] == true;
if (!trackOnly) { if (!trackOnly) {
apkUrl = await confirmApkUrl(apps[id]!.app, context); apkUrl = await confirmApkUrl(apps[id]!.app, context);
@ -667,7 +671,8 @@ class AppsProvider with ChangeNotifier {
bool onlyIfExists = true}) async { bool onlyIfExists = true}) async {
attemptToCorrectInstallStatus = attemptToCorrectInstallStatus =
attemptToCorrectInstallStatus && (await doesInstalledAppsPluginWork()); attemptToCorrectInstallStatus && (await doesInstalledAppsPluginWork());
for (var app in apps) { for (var a in apps) {
var app = a.deepCopy();
AppInfo? info = await getInstalledInfo(app.id); AppInfo? info = await getInstalledInfo(app.id);
app.name = info?.name ?? app.name; app.name = info?.name ?? app.name;
if (app.additionalSettings['appName']?.toString().isNotEmpty == true) { if (app.additionalSettings['appName']?.toString().isNotEmpty == true) {
@ -923,7 +928,7 @@ class APKPicker extends StatefulWidget {
const APKPicker({super.key, required this.app, this.initVal, this.archs}); const APKPicker({super.key, required this.app, this.initVal, this.archs});
final App app; final App app;
final String? initVal; final MapEntry<String, String>? initVal;
final List<String>? archs; final List<String>? archs;
@override @override
@ -931,7 +936,7 @@ class APKPicker extends StatefulWidget {
} }
class _APKPickerState extends State<APKPicker> { class _APKPickerState extends State<APKPicker> {
String? apkUrl; MapEntry<String, String>? apkUrl;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@ -944,15 +949,13 @@ class _APKPickerState extends State<APKPicker> {
const SizedBox(height: 16), const SizedBox(height: 16),
...widget.app.apkUrls.map( ...widget.app.apkUrls.map(
(u) => RadioListTile<String>( (u) => RadioListTile<String>(
title: Text(Uri.parse(u) title: Text(u.key),
.pathSegments value: u.value,
.where((element) => element.isNotEmpty) groupValue: apkUrl!.value,
.last),
value: u,
groupValue: apkUrl,
onChanged: (String? val) { onChanged: (String? val) {
setState(() { setState(() {
apkUrl = val; apkUrl =
widget.app.apkUrls.where((e) => e.value == val).first;
}); });
}), }),
), ),

View File

@ -164,7 +164,8 @@ class SettingsProvider with ChangeNotifier {
void setCategories(Map<String, int> cats, {AppsProvider? appsProvider}) { void setCategories(Map<String, int> cats, {AppsProvider? appsProvider}) {
if (appsProvider != null) { if (appsProvider != null) {
List<App> changedApps = appsProvider.apps.values List<App> changedApps = appsProvider
.getAppValues()
.map((a) { .map((a) {
var n1 = a.app.categories.length; var n1 = a.app.categories.length;
a.app.categories.removeWhere((c) => !cats.keys.contains(c)); a.app.categories.removeWhere((c) => !cats.keys.contains(c));

View File

@ -3,6 +3,7 @@
import 'dart:convert'; import 'dart:convert';
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/dom.dart'; import 'package:html/dom.dart';
import 'package:http/http.dart'; import 'package:http/http.dart';
@ -34,7 +35,7 @@ class AppNames {
class APKDetails { class APKDetails {
late String version; late String version;
late List<String> apkUrls; late List<MapEntry<String, String>> apkUrls;
late AppNames names; late AppNames names;
late DateTime? releaseDate; late DateTime? releaseDate;
late String? changeLog; late String? changeLog;
@ -50,7 +51,7 @@ class App {
late String name; late String name;
String? installedVersion; String? installedVersion;
late String latestVersion; late String latestVersion;
List<String> apkUrls = []; List<MapEntry<String, String>> apkUrls = [];
late int preferredApkIndex; late int preferredApkIndex;
late Map<String, dynamic> additionalSettings; late Map<String, dynamic> additionalSettings;
late DateTime? lastUpdateCheck; late DateTime? lastUpdateCheck;
@ -79,6 +80,22 @@ class App {
return 'ID: $id URL: $url INSTALLED: $installedVersion LATEST: $latestVersion APK: $apkUrls PREFERREDAPK: $preferredApkIndex ADDITIONALSETTINGS: ${additionalSettings.toString()} LASTCHECK: ${lastUpdateCheck.toString()} PINNED $pinned'; return 'ID: $id URL: $url INSTALLED: $installedVersion LATEST: $latestVersion APK: $apkUrls PREFERREDAPK: $preferredApkIndex ADDITIONALSETTINGS: ${additionalSettings.toString()} LASTCHECK: ${lastUpdateCheck.toString()} PINNED $pinned';
} }
App deepCopy() => App(
id,
url,
author,
name,
installedVersion,
latestVersion,
apkUrls,
preferredApkIndex,
Map.from(additionalSettings),
lastUpdateCheck,
pinned,
categories: categories,
changeLog: changeLog,
releaseDate: releaseDate);
factory App.fromJson(Map<String, dynamic> json) { factory App.fromJson(Map<String, dynamic> json) {
var source = SourceProvider().getSource(json['url']); var source = SourceProvider().getSource(json['url']);
var formItems = source.combinedAppSpecificSettingFormItems var formItems = source.combinedAppSpecificSettingFormItems
@ -134,6 +151,23 @@ class App {
if (preferredApkIndex < 0) { if (preferredApkIndex < 0) {
preferredApkIndex = 0; preferredApkIndex = 0;
} }
// apkUrls can either be old list or new named list apkUrls
List<MapEntry<String, String>> apkUrls = [];
if (json['apkUrls'] != null) {
var apkUrlJson = jsonDecode(json['apkUrls']);
try {
apkUrls = getApkUrlsFromUrls(List<String>.from(apkUrlJson));
} catch (e) {
apkUrls = List<dynamic>.from(apkUrlJson)
.map((e) => MapEntry(e[0] as String, e[1] as String))
.toList();
}
}
// Arch based APK filter option should be disabled if it previously did not exist
if (json['additionalSettings'] != null &&
jsonDecode(json['additionalSettings'])['autoApkFilterByArch'] == null) {
additionalSettings['autoApkFilterByArch'] = false;
}
return App( return App(
json['id'] as String, json['id'] as String,
json['url'] as String, json['url'] as String,
@ -143,9 +177,7 @@ class App {
? null ? null
: json['installedVersion'] as String, : json['installedVersion'] as String,
json['latestVersion'] as String, json['latestVersion'] as String,
json['apkUrls'] == null apkUrls,
? []
: List<String>.from(jsonDecode(json['apkUrls'])),
preferredApkIndex, preferredApkIndex,
additionalSettings, additionalSettings,
json['lastUpdateCheck'] == null json['lastUpdateCheck'] == null
@ -173,7 +205,7 @@ class App {
'name': name, 'name': name,
'installedVersion': installedVersion, 'installedVersion': installedVersion,
'latestVersion': latestVersion, 'latestVersion': latestVersion,
'apkUrls': jsonEncode(apkUrls), 'apkUrls': jsonEncode(apkUrls.map((e) => [e.key, e.value]).toList()),
'preferredApkIndex': preferredApkIndex, 'preferredApkIndex': preferredApkIndex,
'additionalSettings': jsonEncode(additionalSettings), 'additionalSettings': jsonEncode(additionalSettings),
'lastUpdateCheck': lastUpdateCheck?.microsecondsSinceEpoch, 'lastUpdateCheck': lastUpdateCheck?.microsecondsSinceEpoch,
@ -225,6 +257,11 @@ Map<String, dynamic> getDefaultValuesFromFormItems(
.reduce((value, element) => [...value, ...element])); .reduce((value, element) => [...value, ...element]));
} }
getApkUrlsFromUrls(List<String> urls) => urls
.map((e) =>
MapEntry(e.split('/').where((el) => el.trim().isNotEmpty).last, e))
.toList();
class AppSource { class AppSource {
String? host; String? host;
late String name; late String name;
@ -279,6 +316,10 @@ class AppSource {
} }
]) ])
], ],
[
GeneratedFormSwitch('autoApkFilterByArch',
label: tr('autoApkFilterByArch'), defaultValue: true)
],
[GeneratedFormTextField('appName', label: tr('appName'), required: false)] [GeneratedFormTextField('appName', label: tr('appName'), required: false)]
]; ];
@ -422,11 +463,24 @@ class SourceProvider {
if (additionalSettings['apkFilterRegEx'] != null) { if (additionalSettings['apkFilterRegEx'] != null) {
var reg = RegExp(additionalSettings['apkFilterRegEx']); var reg = RegExp(additionalSettings['apkFilterRegEx']);
apk.apkUrls = apk.apkUrls =
apk.apkUrls.where((element) => reg.hasMatch(element)).toList(); apk.apkUrls.where((element) => reg.hasMatch(element.key)).toList();
} }
if (apk.apkUrls.isEmpty && !trackOnly) { if (apk.apkUrls.isEmpty && !trackOnly) {
throw NoAPKError(); throw NoAPKError();
} }
if (apk.apkUrls.length > 1 &&
additionalSettings['autoApkFilterByArch'] == true) {
var abis = (await DeviceInfoPlugin().androidInfo).supportedAbis;
for (var abi in abis) {
var urls2 = apk.apkUrls
.where((element) => RegExp('.*$abi.*').hasMatch(element.key))
.toList();
if (urls2.isNotEmpty && urls2.length < apk.apkUrls.length) {
apk.apkUrls = urls2;
break;
}
}
}
String apkVersion = apk.version.replaceAll('/', '-'); String apkVersion = apk.version.replaceAll('/', '-');
var name = currentApp != null ? currentApp.name.trim() : ''; var name = currentApp != null ? currentApp.name.trim() : '';
name = name.isNotEmpty name = name.isNotEmpty

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: 0.11.23+145 # When changing this, update the tag in main() accordingly version: 0.11.29+151 # When changing this, update the tag in main() accordingly
environment: environment:
sdk: '>=2.18.2 <3.0.0' sdk: '>=2.18.2 <3.0.0'