mirror of
https://github.com/ImranR98/Obtainium.git
synced 2025-07-31 13:00:14 +02:00
Compare commits
19 Commits
v0.15.8-be
...
v0.15.9-be
Author | SHA1 | Date | |
---|---|---|---|
|
5e869b7e03 | ||
|
34fee36132 | ||
|
ea09bc36a5 | ||
|
f7e0678cb2 | ||
|
223ae378a9 | ||
|
6f52a48991 | ||
|
0280935955 | ||
|
eebc7d9ab3 | ||
|
aa7b5652ff | ||
|
ec6683a198 | ||
|
05372924a0 | ||
|
e63c1399dd | ||
|
4fcad92e1d | ||
|
e18e7298bc | ||
|
76a6a509cd | ||
|
7a03561ff6 | ||
|
70e54ce14a | ||
|
b5f86f0e79 | ||
|
4ebca49ef7 |
2
.github/workflows/release.yml
vendored
2
.github/workflows/release.yml
vendored
@@ -37,7 +37,7 @@ jobs:
|
|||||||
for apk in ./build/app/outputs/flutter-apk/*-release*.apk; do
|
for apk in ./build/app/outputs/flutter-apk/*-release*.apk; do
|
||||||
unsignedFn=${apk/-release/-unsigned}
|
unsignedFn=${apk/-release/-unsigned}
|
||||||
mv "$apk" "$unsignedFn"
|
mv "$apk" "$unsignedFn"
|
||||||
${ANDROID_HOME}/build-tools/30.0.2/apksigner sign --ks apksign.keystore --ks-pass pass:"${KEYSTORE_PASSWORD}" --out "${apk}" "${unsignedFn}"
|
${ANDROID_HOME}/build-tools/$(ls ${ANDROID_HOME}/build-tools/ | tail -1)/apksigner sign --ks apksign.keystore --ks-pass pass:"${KEYSTORE_PASSWORD}" --out "${apk}" "${unsignedFn}"
|
||||||
sha256sum ${apk} | cut -d " " -f 1 > "$apk".sha256
|
sha256sum ${apk} | cut -d " " -f 1 > "$apk".sha256
|
||||||
gpg --batch --pinentry-mode loopback --passphrase "${PGP_PASSPHRASE}" --sign --detach-sig "$apk".sha256
|
gpg --batch --pinentry-mode loopback --passphrase "${PGP_PASSPHRASE}" --sign --detach-sig "$apk".sha256
|
||||||
done
|
done
|
||||||
|
@@ -287,6 +287,8 @@
|
|||||||
"shizuku": "Shizuku",
|
"shizuku": "Shizuku",
|
||||||
"root": "Root",
|
"root": "Root",
|
||||||
"shizukuBinderNotFound": "Shizuku is not running",
|
"shizukuBinderNotFound": "Shizuku is not running",
|
||||||
|
"useVersionCodeAsOSVersion": "Use app versionCode as OS-detected version",
|
||||||
|
"requestHeader": "Request header",
|
||||||
"removeAppQuestion": {
|
"removeAppQuestion": {
|
||||||
"one": "Želite li ukloniti aplikaciju?",
|
"one": "Želite li ukloniti aplikaciju?",
|
||||||
"other": "Želite li ukloniti aplikacije?"
|
"other": "Želite li ukloniti aplikacije?"
|
||||||
|
@@ -287,6 +287,8 @@
|
|||||||
"shizuku": "Shizuku",
|
"shizuku": "Shizuku",
|
||||||
"root": "Správce",
|
"root": "Správce",
|
||||||
"shizukuBinderNotFound": "Shizuku neběží",
|
"shizukuBinderNotFound": "Shizuku neběží",
|
||||||
|
"useVersionCodeAsOSVersion": "Use app versionCode as OS-detected version",
|
||||||
|
"requestHeader": "Request header",
|
||||||
"removeAppQuestion": {
|
"removeAppQuestion": {
|
||||||
"one": "Odstranit Apku?",
|
"one": "Odstranit Apku?",
|
||||||
"other": "Odstranit Apky?"
|
"other": "Odstranit Apky?"
|
||||||
|
@@ -287,6 +287,8 @@
|
|||||||
"shizuku": "Shizuku",
|
"shizuku": "Shizuku",
|
||||||
"root": "Root",
|
"root": "Root",
|
||||||
"shizukuBinderNotFound": "Shizuku läuft nicht",
|
"shizukuBinderNotFound": "Shizuku läuft nicht",
|
||||||
|
"useVersionCodeAsOSVersion": "Use app versionCode as OS-detected version",
|
||||||
|
"requestHeader": "Request header",
|
||||||
"removeAppQuestion": {
|
"removeAppQuestion": {
|
||||||
"one": "App entfernen?",
|
"one": "App entfernen?",
|
||||||
"other": "Apps entfernen?"
|
"other": "Apps entfernen?"
|
||||||
|
@@ -289,6 +289,8 @@
|
|||||||
"shizukuBinderNotFound": "Сompatible Shizuku service wasn't found",
|
"shizukuBinderNotFound": "Сompatible Shizuku service wasn't found",
|
||||||
"useSystemFont": "Use the system font",
|
"useSystemFont": "Use the system font",
|
||||||
"systemFontError": "Error loading the system font: {}",
|
"systemFontError": "Error loading the system font: {}",
|
||||||
|
"useVersionCodeAsOSVersion": "Use app versionCode as OS-detected version",
|
||||||
|
"requestHeader": "Request header",
|
||||||
"removeAppQuestion": {
|
"removeAppQuestion": {
|
||||||
"one": "Remove App?",
|
"one": "Remove App?",
|
||||||
"other": "Remove Apps?"
|
"other": "Remove Apps?"
|
||||||
|
@@ -287,6 +287,8 @@
|
|||||||
"shizuku": "Shizuku",
|
"shizuku": "Shizuku",
|
||||||
"root": "Root",
|
"root": "Root",
|
||||||
"shizukuBinderNotFound": "Shizuku no está operativo",
|
"shizukuBinderNotFound": "Shizuku no está operativo",
|
||||||
|
"useVersionCodeAsOSVersion": "Use app versionCode as OS-detected version",
|
||||||
|
"requestHeader": "Request header",
|
||||||
"removeAppQuestion": {
|
"removeAppQuestion": {
|
||||||
"one": "¿Eliminar Aplicación?",
|
"one": "¿Eliminar Aplicación?",
|
||||||
"other": "¿Eliminar Aplicaciones?"
|
"other": "¿Eliminar Aplicaciones?"
|
||||||
|
@@ -287,6 +287,8 @@
|
|||||||
"shizuku": "Shizuku",
|
"shizuku": "Shizuku",
|
||||||
"root": "Root",
|
"root": "Root",
|
||||||
"shizukuBinderNotFound": "Shizuku is not running",
|
"shizukuBinderNotFound": "Shizuku is not running",
|
||||||
|
"useVersionCodeAsOSVersion": "Use app versionCode as OS-detected version",
|
||||||
|
"requestHeader": "Request header",
|
||||||
"removeAppQuestion": {
|
"removeAppQuestion": {
|
||||||
"one": "برنامه حذف شود؟",
|
"one": "برنامه حذف شود؟",
|
||||||
"other": "برنامه ها حذف شوند؟"
|
"other": "برنامه ها حذف شوند؟"
|
||||||
|
@@ -287,6 +287,8 @@
|
|||||||
"shizuku": "Shizuku",
|
"shizuku": "Shizuku",
|
||||||
"root": "Root",
|
"root": "Root",
|
||||||
"shizukuBinderNotFound": "Shizuku is not running",
|
"shizukuBinderNotFound": "Shizuku is not running",
|
||||||
|
"useVersionCodeAsOSVersion": "Use app versionCode as OS-detected version",
|
||||||
|
"requestHeader": "Request header",
|
||||||
"removeAppQuestion": {
|
"removeAppQuestion": {
|
||||||
"one": "Supprimer l'application ?",
|
"one": "Supprimer l'application ?",
|
||||||
"other": "Supprimer les applications ?"
|
"other": "Supprimer les applications ?"
|
||||||
|
@@ -287,6 +287,8 @@
|
|||||||
"shizuku": "Shizuku",
|
"shizuku": "Shizuku",
|
||||||
"root": "Root",
|
"root": "Root",
|
||||||
"shizukuBinderNotFound": "Shizuku is not running",
|
"shizukuBinderNotFound": "Shizuku is not running",
|
||||||
|
"useVersionCodeAsOSVersion": "Use app versionCode as OS-detected version",
|
||||||
|
"requestHeader": "Request header",
|
||||||
"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?"
|
||||||
|
@@ -287,6 +287,8 @@
|
|||||||
"shizuku": "Shizuku",
|
"shizuku": "Shizuku",
|
||||||
"root": "Root",
|
"root": "Root",
|
||||||
"shizukuBinderNotFound": "Shizuku non è in esecuzione",
|
"shizukuBinderNotFound": "Shizuku non è in esecuzione",
|
||||||
|
"useVersionCodeAsOSVersion": "Use app versionCode as OS-detected version",
|
||||||
|
"requestHeader": "Request header",
|
||||||
"removeAppQuestion": {
|
"removeAppQuestion": {
|
||||||
"one": "Rimuovere l'app?",
|
"one": "Rimuovere l'app?",
|
||||||
"other": "Rimuovere le app?"
|
"other": "Rimuovere le app?"
|
||||||
|
@@ -287,6 +287,10 @@
|
|||||||
"shizuku": "Shizuku",
|
"shizuku": "Shizuku",
|
||||||
"root": "Root",
|
"root": "Root",
|
||||||
"shizukuBinderNotFound": "Shizukuが起動していません",
|
"shizukuBinderNotFound": "Shizukuが起動していません",
|
||||||
|
"useSystemFont": "システムフォントを使用する",
|
||||||
|
"systemFontError": "システムフォントの読み込みエラー: {}",
|
||||||
|
"useVersionCodeAsOSVersion": "Use app versionCode as OS-detected version",
|
||||||
|
"requestHeader": "Request header",
|
||||||
"removeAppQuestion": {
|
"removeAppQuestion": {
|
||||||
"one": "アプリを削除しますか?",
|
"one": "アプリを削除しますか?",
|
||||||
"other": "アプリを削除しますか?"
|
"other": "アプリを削除しますか?"
|
||||||
|
@@ -287,6 +287,8 @@
|
|||||||
"shizuku": "Shizuku",
|
"shizuku": "Shizuku",
|
||||||
"root": "Root",
|
"root": "Root",
|
||||||
"shizukuBinderNotFound": "Shizuku is not running",
|
"shizukuBinderNotFound": "Shizuku is not running",
|
||||||
|
"useVersionCodeAsOSVersion": "Use app versionCode as OS-detected version",
|
||||||
|
"requestHeader": "Request header",
|
||||||
"removeAppQuestion": {
|
"removeAppQuestion": {
|
||||||
"one": "App verwijderen?",
|
"one": "App verwijderen?",
|
||||||
"other": "Apps verwijderen?"
|
"other": "Apps verwijderen?"
|
||||||
|
@@ -287,6 +287,8 @@
|
|||||||
"shizuku": "Shizuku",
|
"shizuku": "Shizuku",
|
||||||
"root": "Root",
|
"root": "Root",
|
||||||
"shizukuBinderNotFound": "Shizuku is not running",
|
"shizukuBinderNotFound": "Shizuku is not running",
|
||||||
|
"useVersionCodeAsOSVersion": "Use app versionCode as OS-detected version",
|
||||||
|
"requestHeader": "Request header",
|
||||||
"removeAppQuestion": {
|
"removeAppQuestion": {
|
||||||
"one": "Usunąć aplikację?",
|
"one": "Usunąć aplikację?",
|
||||||
"few": "Usunąć aplikacje?",
|
"few": "Usunąć aplikacje?",
|
||||||
|
@@ -287,6 +287,8 @@
|
|||||||
"shizuku": "Shizuku",
|
"shizuku": "Shizuku",
|
||||||
"root": "Root",
|
"root": "Root",
|
||||||
"shizukuBinderNotFound": "Shizuku não está rodando",
|
"shizukuBinderNotFound": "Shizuku não está rodando",
|
||||||
|
"useVersionCodeAsOSVersion": "Use app versionCode as OS-detected version",
|
||||||
|
"requestHeader": "Request header",
|
||||||
"removeAppQuestion": {
|
"removeAppQuestion": {
|
||||||
"one": "Remover aplicativo?",
|
"one": "Remover aplicativo?",
|
||||||
"other": "Remover aplicativos?"
|
"other": "Remover aplicativos?"
|
||||||
|
@@ -289,6 +289,8 @@
|
|||||||
"shizukuBinderNotFound": "Совместимый сервис Shizuku не найден",
|
"shizukuBinderNotFound": "Совместимый сервис Shizuku не найден",
|
||||||
"useSystemFont": "Использовать системный шрифт",
|
"useSystemFont": "Использовать системный шрифт",
|
||||||
"systemFontError": "Ошибка загрузки системного шрифта: {}",
|
"systemFontError": "Ошибка загрузки системного шрифта: {}",
|
||||||
|
"useVersionCodeAsOSVersion": "Use app versionCode as OS-detected version",
|
||||||
|
"requestHeader": "Request header",
|
||||||
"removeAppQuestion": {
|
"removeAppQuestion": {
|
||||||
"one": "Удалить приложение?",
|
"one": "Удалить приложение?",
|
||||||
"other": "Удалить приложения?"
|
"other": "Удалить приложения?"
|
||||||
|
@@ -273,6 +273,8 @@
|
|||||||
"shizuku": "Shizuku",
|
"shizuku": "Shizuku",
|
||||||
"root": "Root",
|
"root": "Root",
|
||||||
"shizukuBinderNotFound": "Shizuku is not running",
|
"shizukuBinderNotFound": "Shizuku is not running",
|
||||||
|
"useVersionCodeAsOSVersion": "Use app versionCode as OS-detected version",
|
||||||
|
"requestHeader": "Request header",
|
||||||
"removeAppQuestion": {
|
"removeAppQuestion": {
|
||||||
"one": "Ta Bort App?",
|
"one": "Ta Bort App?",
|
||||||
"other": "Ta Bort Appar?"
|
"other": "Ta Bort Appar?"
|
||||||
|
@@ -287,6 +287,8 @@
|
|||||||
"shizuku": "Shizuku",
|
"shizuku": "Shizuku",
|
||||||
"root": "Root",
|
"root": "Root",
|
||||||
"shizukuBinderNotFound": "Shizuku is not running",
|
"shizukuBinderNotFound": "Shizuku is not running",
|
||||||
|
"useVersionCodeAsOSVersion": "Use app versionCode as OS-detected version",
|
||||||
|
"requestHeader": "Request header",
|
||||||
"removeAppQuestion": {
|
"removeAppQuestion": {
|
||||||
"one": "Uygulamayı Kaldır?",
|
"one": "Uygulamayı Kaldır?",
|
||||||
"other": "Uygulamaları Kaldır?"
|
"other": "Uygulamaları Kaldır?"
|
||||||
|
@@ -11,7 +11,7 @@
|
|||||||
"unexpectedError": "Lỗi không mong đợi",
|
"unexpectedError": "Lỗi không mong đợi",
|
||||||
"ok": "OK",
|
"ok": "OK",
|
||||||
"and": "và",
|
"and": "và",
|
||||||
"githubPATLabel": "Mã thông báo truy cập cá nhân GitHub (Tăng tốc độ giới hạn)",
|
"githubPATLabel": "GitHub Token (Tăng tốc độ, giới hạn)",
|
||||||
"includePrereleases": "Bao gồm các bản phát hành trước",
|
"includePrereleases": "Bao gồm các bản phát hành trước",
|
||||||
"fallbackToOlderReleases": "Dự phòng về bản phát hành cũ hơn",
|
"fallbackToOlderReleases": "Dự phòng về bản phát hành cũ hơn",
|
||||||
"filterReleaseTitlesByRegEx": "Lọc tiêu đề bản phát hành theo biểu thức chính quy",
|
"filterReleaseTitlesByRegEx": "Lọc tiêu đề bản phát hành theo biểu thức chính quy",
|
||||||
@@ -34,7 +34,7 @@
|
|||||||
"cancelled": "Đã hủy",
|
"cancelled": "Đã hủy",
|
||||||
"appAlreadyAdded": "Ứng dụng được thêm rồi",
|
"appAlreadyAdded": "Ứng dụng được thêm rồi",
|
||||||
"alreadyUpToDateQuestion": "Ứng dụng đã được cập nhật?",
|
"alreadyUpToDateQuestion": "Ứng dụng đã được cập nhật?",
|
||||||
"addApp": "Thêm ứng dụng",
|
"addApp": "Thêm",
|
||||||
"appSourceURL": "URL nguồn ứng dụng",
|
"appSourceURL": "URL nguồn ứng dụng",
|
||||||
"error": "Lỗi",
|
"error": "Lỗi",
|
||||||
"add": "Thêm",
|
"add": "Thêm",
|
||||||
@@ -48,7 +48,7 @@
|
|||||||
"noApps": "Không có ứng dụng",
|
"noApps": "Không có ứng dụng",
|
||||||
"noAppsForFilter": "Không có ứng dụng cho bộ lọc",
|
"noAppsForFilter": "Không có ứng dụng cho bộ lọc",
|
||||||
"byX": "Bởi {}",
|
"byX": "Bởi {}",
|
||||||
"percentProgress": "Tiến triển: {}%",
|
"percentProgress": "Đang tải {}%",
|
||||||
"pleaseWait": "Vui lòng chờ",
|
"pleaseWait": "Vui lòng chờ",
|
||||||
"updateAvailable": "Có sẵn bản cập nhật",
|
"updateAvailable": "Có sẵn bản cập nhật",
|
||||||
"estimateInBracketsShort": "(Ước lượng.)",
|
"estimateInBracketsShort": "(Ước lượng.)",
|
||||||
@@ -88,10 +88,10 @@
|
|||||||
"importExport": "Nhập/Xuất",
|
"importExport": "Nhập/Xuất",
|
||||||
"settings": "Cài đặt",
|
"settings": "Cài đặt",
|
||||||
"exportedTo": "Đã xuất sang {}",
|
"exportedTo": "Đã xuất sang {}",
|
||||||
"obtainiumExport": "Xuất Obtainium",
|
"obtainiumExport": "Xuất",
|
||||||
"invalidInput": "Đầu vào không hợp lệ",
|
"invalidInput": "Đầu vào không hợp lệ",
|
||||||
"importedX": "Đã nhập {}",
|
"importedX": "Đã nhập {}",
|
||||||
"obtainiumImport": "Nhập Obtainium",
|
"obtainiumImport": "Nhập",
|
||||||
"importFromURLList": "Nhập từ danh sách URL",
|
"importFromURLList": "Nhập từ danh sách URL",
|
||||||
"searchQuery": "Truy vấn tìm kiếm",
|
"searchQuery": "Truy vấn tìm kiếm",
|
||||||
"appURLList": "Danh sách URL ứng dụng",
|
"appURLList": "Danh sách URL ứng dụng",
|
||||||
@@ -120,13 +120,13 @@
|
|||||||
"appSortOrder": "Thứ tự sắp xếp",
|
"appSortOrder": "Thứ tự sắp xếp",
|
||||||
"ascending": "Tăng dần",
|
"ascending": "Tăng dần",
|
||||||
"descending": "Giảm dần",
|
"descending": "Giảm dần",
|
||||||
"bgUpdateCheckInterval": "Khoảng thời gian kiểm tra cập nhật nền",
|
"bgUpdateCheckInterval": "Thời gian tự động kiểm tra cập nhật",
|
||||||
"neverManualOnly": "Không bao giờ - Chỉ thủ công",
|
"neverManualOnly": "Không bao giờ",
|
||||||
"appearance": "Hiển thị",
|
"appearance": "Hiển thị",
|
||||||
"showWebInAppView": "Hiển thị trang web Nguồn trong chế độ xem Ứng dụng",
|
"showWebInAppView": "Hiển thị trang web Nguồn trong chế độ xem chi tiết Ứng dụng",
|
||||||
"pinUpdates": "Ghim nội dung cập nhật lên đầu chế độ xem Ứng dụng",
|
"pinUpdates": "Chuyển ứng dụng có phiên bản mới lên đầu danh sách",
|
||||||
"updates": "Cập nhật",
|
"updates": "Cập nhật",
|
||||||
"sourceSpecific": "Nguồn cụ thể",
|
"sourceSpecific": "Cài đặt Nguồn",
|
||||||
"appSource": "Nguồn ứng dụng",
|
"appSource": "Nguồn ứng dụng",
|
||||||
"noLogs": "Không có nhật ký",
|
"noLogs": "Không có nhật ký",
|
||||||
"appLogs": "Nhật ký ứng dụng",
|
"appLogs": "Nhật ký ứng dụng",
|
||||||
@@ -219,8 +219,8 @@
|
|||||||
"dontShowAgain": "Đừng hiển thị thông tin này nữa",
|
"dontShowAgain": "Đừng hiển thị thông tin này nữa",
|
||||||
"dontShowTrackOnlyWarnings": "Không hiển thị cảnh báo 'Chỉ-Theo dõi'",
|
"dontShowTrackOnlyWarnings": "Không hiển thị cảnh báo 'Chỉ-Theo dõi'",
|
||||||
"dontShowAPKOriginWarnings": "Không hiển thị cảnh báo nguồn gốc APK",
|
"dontShowAPKOriginWarnings": "Không hiển thị cảnh báo nguồn gốc APK",
|
||||||
"moveNonInstalledAppsToBottom": "Di chuyển Ứng dụng chưa được cài đặt xuống cuối chế độ xem Ứng dụng",
|
"moveNonInstalledAppsToBottom": "Chuyển Ứng dụng chưa được cài đặt xuống cuối danh sách",
|
||||||
"gitlabPATLabel": "Mã thông báo truy cập cá nhân GitLab\n(Cho phép tìm kiếm và khám phá APK tốt hơn)",
|
"gitlabPATLabel": "GitLab Token\n(Cho phép tìm kiếm và lọc APK tốt hơn)",
|
||||||
"about": "Giới thiệu",
|
"about": "Giới thiệu",
|
||||||
"requiresCredentialsInSettings": "{}: Điều này cần thông tin xác thực bổ sung (trong Cài đặt)",
|
"requiresCredentialsInSettings": "{}: Điều này cần thông tin xác thực bổ sung (trong Cài đặt)",
|
||||||
"checkOnStart": "Kiểm tra các bản cập nhật khi khởi động",
|
"checkOnStart": "Kiểm tra các bản cập nhật khi khởi động",
|
||||||
@@ -241,9 +241,9 @@
|
|||||||
"appsPossiblyUpdated": "Đã cố gắng cập nhật ứng dụng",
|
"appsPossiblyUpdated": "Đã cố gắng cập nhật ứng dụng",
|
||||||
"appsPossiblyUpdatedNotifDescription": "Thông báo cho người dùng rằng các bản cập nhật cho một hoặc nhiều Ứng dụng có khả năng được áp dụng trong nền",
|
"appsPossiblyUpdatedNotifDescription": "Thông báo cho người dùng rằng các bản cập nhật cho một hoặc nhiều Ứng dụng có khả năng được áp dụng trong nền",
|
||||||
"xWasPossiblyUpdatedToY": "{} có thể đã được cập nhật thành {}.",
|
"xWasPossiblyUpdatedToY": "{} có thể đã được cập nhật thành {}.",
|
||||||
"enableBackgroundUpdates": "Bật cập nhật nền",
|
"enableBackgroundUpdates": "Tự động cập nhật trong nền",
|
||||||
"backgroundUpdateReqsExplanation": "Có thể không thực hiện được cập nhật nền cho tất cả ứng dụng.",
|
"backgroundUpdateReqsExplanation": "Có thể không thực hiện được cập nhật trong nền cho tất cả ứng dụng.",
|
||||||
"backgroundUpdateLimitsExplanation": "Sự thành công của cài đặt nền chỉ có thể được xác định khi mở Obtainium.",
|
"backgroundUpdateLimitsExplanation": "Sự thành công của cài đặt trong nền chỉ có thể được xác định khi mở Obtainium.",
|
||||||
"verifyLatestTag": "Xác minh thẻ 'mới nhất'",
|
"verifyLatestTag": "Xác minh thẻ 'mới nhất'",
|
||||||
"intermediateLinkRegex": "Filter for an 'Intermediate' Link to Visit",
|
"intermediateLinkRegex": "Filter for an 'Intermediate' Link to Visit",
|
||||||
"filterByLinkText": "Filter links by link text",
|
"filterByLinkText": "Filter links by link text",
|
||||||
@@ -256,8 +256,8 @@
|
|||||||
"matchGroupToUse": "Nhóm đối sánh để sử dụng cho Regex trích xuất phiên bản",
|
"matchGroupToUse": "Nhóm đối sánh để sử dụng cho Regex trích xuất phiên bản",
|
||||||
"highlightTouchTargets": "Đánh dấu các mục tiêu cảm ứng ít rõ ràng hơn",
|
"highlightTouchTargets": "Đánh dấu các mục tiêu cảm ứng ít rõ ràng hơn",
|
||||||
"pickExportDir": "Chọn thư mục xuất",
|
"pickExportDir": "Chọn thư mục xuất",
|
||||||
"autoExportOnChanges": "Tự động xuất khi thay đổi",
|
"autoExportOnChanges": "Tự động xuất",
|
||||||
"includeSettings": "Include settings",
|
"includeSettings": "Bao gồm cài đặt ứng dụng",
|
||||||
"filterVersionsByRegEx": "Lọc phiên bản theo biểu thức chính quy",
|
"filterVersionsByRegEx": "Lọc phiên bản theo biểu thức chính quy",
|
||||||
"trySelectingSuggestedVersionCode": "Thử chọn APK Mã phiên bản được đề xuất",
|
"trySelectingSuggestedVersionCode": "Thử chọn APK Mã phiên bản được đề xuất",
|
||||||
"dontSortReleasesList": "Giữ lại thứ tự phát hành từ API",
|
"dontSortReleasesList": "Giữ lại thứ tự phát hành từ API",
|
||||||
@@ -278,12 +278,12 @@
|
|||||||
"downloadingXNotifChannel": "Đang tải xuống {}",
|
"downloadingXNotifChannel": "Đang tải xuống {}",
|
||||||
"completeAppInstallationNotifChannel": "Hoàn tất cài đặt ứng dụng",
|
"completeAppInstallationNotifChannel": "Hoàn tất cài đặt ứng dụng",
|
||||||
"checkingForUpdatesNotifChannel": "Đang kiểm tra cập nhật",
|
"checkingForUpdatesNotifChannel": "Đang kiểm tra cập nhật",
|
||||||
"onlyCheckInstalledOrTrackOnlyApps": "Chỉ kiểm tra các ứng dụng đã cài đặt và Chỉ-Theo dõi để biết các bản cập nhật",
|
"onlyCheckInstalledOrTrackOnlyApps": "Chỉ kiểm tra cập nhật các ứng dụng đã cài đặt và Chỉ-Theo dõi",
|
||||||
"supportFixedAPKURL": "Support fixed APK URLs",
|
"supportFixedAPKURL": "Support fixed APK URLs",
|
||||||
"selectX": "Select {}",
|
"selectX": "Select {}",
|
||||||
"parallelDownloads": "Allow parallel downloads",
|
"parallelDownloads": "Cho phép tải đa luồng",
|
||||||
"installMethod": "Installation method",
|
"installMethod": "Phương thức cài đặt",
|
||||||
"normal": "Normal",
|
"normal": "Mặc định",
|
||||||
"shizuku": "Shizuku",
|
"shizuku": "Shizuku",
|
||||||
"root": "Root",
|
"root": "Root",
|
||||||
"shizukuBinderNotFound": "Shizuku chưa khởi động",
|
"shizukuBinderNotFound": "Shizuku chưa khởi động",
|
||||||
|
@@ -289,6 +289,8 @@
|
|||||||
"shizukuBinderNotFound": "未发现兼容的 Shizuku 服务",
|
"shizukuBinderNotFound": "未发现兼容的 Shizuku 服务",
|
||||||
"useSystemFont": "使用系统字体",
|
"useSystemFont": "使用系统字体",
|
||||||
"systemFontError": "加载系统字体出错:{}",
|
"systemFontError": "加载系统字体出错:{}",
|
||||||
|
"useVersionCodeAsOSVersion": "Use app versionCode as OS-detected version",
|
||||||
|
"requestHeader": "Request header",
|
||||||
"removeAppQuestion": {
|
"removeAppQuestion": {
|
||||||
"one": "是否删除应用?",
|
"one": "是否删除应用?",
|
||||||
"other": "是否删除应用?"
|
"other": "是否删除应用?"
|
||||||
|
@@ -10,9 +10,10 @@ class APKCombo extends AppSource {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
String sourceSpecificStandardizeURL(String url) {
|
String sourceSpecificStandardizeURL(String url) {
|
||||||
RegExp standardUrlRegEx =
|
RegExp standardUrlRegEx = RegExp(
|
||||||
RegExp('^https?://(www\\.)?${getSourceRegex(hosts)}/+[^/]+/+[^/]+');
|
'^https?://(www\\.)?${getSourceRegex(hosts)}/+[^/]+/+[^/]+',
|
||||||
var match = standardUrlRegEx.firstMatch(url.toLowerCase());
|
caseSensitive: false);
|
||||||
|
var match = standardUrlRegEx.firstMatch(url);
|
||||||
if (match == null) {
|
if (match == null) {
|
||||||
throw InvalidURLError(name);
|
throw InvalidURLError(name);
|
||||||
}
|
}
|
||||||
@@ -27,8 +28,8 @@ class APKCombo extends AppSource {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Future<Map<String, String>?> getRequestHeaders(
|
Future<Map<String, String>?> getRequestHeaders(
|
||||||
{Map<String, dynamic> additionalSettings = const <String, dynamic>{},
|
Map<String, dynamic> additionalSettings,
|
||||||
bool forAPKDownload = false}) async {
|
{bool forAPKDownload = false}) async {
|
||||||
return {
|
return {
|
||||||
"User-Agent": "curl/8.0.1",
|
"User-Agent": "curl/8.0.1",
|
||||||
"Accept": "*/*",
|
"Accept": "*/*",
|
||||||
@@ -37,8 +38,9 @@ class APKCombo extends AppSource {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<List<MapEntry<String, String>>> getApkUrls(String standardUrl) async {
|
Future<List<MapEntry<String, String>>> getApkUrls(
|
||||||
var res = await sourceRequest('$standardUrl/download/apk');
|
String standardUrl, Map<String, dynamic> additionalSettings) async {
|
||||||
|
var res = await sourceRequest('$standardUrl/download/apk', {});
|
||||||
if (res.statusCode != 200) {
|
if (res.statusCode != 200) {
|
||||||
throw getObtainiumHttpError(res);
|
throw getObtainiumHttpError(res);
|
||||||
}
|
}
|
||||||
@@ -71,9 +73,9 @@ class APKCombo extends AppSource {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<String> apkUrlPrefetchModifier(
|
Future<String> apkUrlPrefetchModifier(String apkUrl, String standardUrl,
|
||||||
String apkUrl, String standardUrl) async {
|
Map<String, dynamic> additionalSettings) async {
|
||||||
var freshURLs = await getApkUrls(standardUrl);
|
var freshURLs = await getApkUrls(standardUrl, additionalSettings);
|
||||||
var path2Match = Uri.parse(apkUrl).path;
|
var path2Match = Uri.parse(apkUrl).path;
|
||||||
for (var url in freshURLs) {
|
for (var url in freshURLs) {
|
||||||
if (Uri.parse(url.value).path == path2Match) {
|
if (Uri.parse(url.value).path == path2Match) {
|
||||||
@@ -89,7 +91,7 @@ class APKCombo extends AppSource {
|
|||||||
Map<String, dynamic> additionalSettings,
|
Map<String, dynamic> additionalSettings,
|
||||||
) async {
|
) async {
|
||||||
String appId = (await tryInferringAppId(standardUrl))!;
|
String appId = (await tryInferringAppId(standardUrl))!;
|
||||||
var preres = await sourceRequest(standardUrl);
|
var preres = await sourceRequest(standardUrl, additionalSettings);
|
||||||
if (preres.statusCode != 200) {
|
if (preres.statusCode != 200) {
|
||||||
throw getObtainiumHttpError(preres);
|
throw getObtainiumHttpError(preres);
|
||||||
}
|
}
|
||||||
@@ -113,7 +115,9 @@ class APKCombo extends AppSource {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
return APKDetails(
|
return APKDetails(
|
||||||
version, await getApkUrls(standardUrl), AppNames(author, appName),
|
version,
|
||||||
|
await getApkUrls(standardUrl, additionalSettings),
|
||||||
|
AppNames(author, appName),
|
||||||
releaseDate: releaseDate);
|
releaseDate: releaseDate);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -32,9 +32,10 @@ class APKMirror extends AppSource {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
String sourceSpecificStandardizeURL(String url) {
|
String sourceSpecificStandardizeURL(String url) {
|
||||||
RegExp standardUrlRegEx =
|
RegExp standardUrlRegEx = RegExp(
|
||||||
RegExp('^https?://(www\\.)?${getSourceRegex(hosts)}/apk/[^/]+/[^/]+');
|
'^https?://(www\\.)?${getSourceRegex(hosts)}/apk/[^/]+/[^/]+',
|
||||||
RegExpMatch? match = standardUrlRegEx.firstMatch(url.toLowerCase());
|
caseSensitive: false);
|
||||||
|
RegExpMatch? match = standardUrlRegEx.firstMatch(url);
|
||||||
if (match == null) {
|
if (match == null) {
|
||||||
throw InvalidURLError(name);
|
throw InvalidURLError(name);
|
||||||
}
|
}
|
||||||
@@ -58,7 +59,7 @@ class APKMirror extends AppSource {
|
|||||||
true
|
true
|
||||||
? additionalSettings['filterReleaseTitlesByRegEx']
|
? additionalSettings['filterReleaseTitlesByRegEx']
|
||||||
: null;
|
: null;
|
||||||
Response res = await sourceRequest('$standardUrl/feed');
|
Response res = await sourceRequest('$standardUrl/feed', additionalSettings);
|
||||||
if (res.statusCode == 200) {
|
if (res.statusCode == 200) {
|
||||||
var items = parse(res.body).querySelectorAll('item');
|
var items = parse(res.body).querySelectorAll('item');
|
||||||
dynamic targetRelease;
|
dynamic targetRelease;
|
||||||
|
@@ -27,15 +27,17 @@ class APKPure extends AppSource {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
String sourceSpecificStandardizeURL(String url) {
|
String sourceSpecificStandardizeURL(String url) {
|
||||||
RegExp standardUrlRegExB =
|
RegExp standardUrlRegExB = RegExp(
|
||||||
RegExp('^https?://m.${getSourceRegex(hosts)}/+[^/]+/+[^/]+(/+[^/]+)?');
|
'^https?://m.${getSourceRegex(hosts)}/+[^/]+/+[^/]+(/+[^/]+)?',
|
||||||
RegExpMatch? match = standardUrlRegExB.firstMatch(url.toLowerCase());
|
caseSensitive: false);
|
||||||
|
RegExpMatch? match = standardUrlRegExB.firstMatch(url);
|
||||||
if (match != null) {
|
if (match != null) {
|
||||||
url = 'https://${getSourceRegex(hosts)}${Uri.parse(url).path}';
|
url = 'https://${getSourceRegex(hosts)}${Uri.parse(url).path}';
|
||||||
}
|
}
|
||||||
RegExp standardUrlRegExA = RegExp(
|
RegExp standardUrlRegExA = RegExp(
|
||||||
'^https?://(www\\.)?${getSourceRegex(hosts)}/+[^/]+/+[^/]+(/+[^/]+)?');
|
'^https?://(www\\.)?${getSourceRegex(hosts)}/+[^/]+/+[^/]+(/+[^/]+)?',
|
||||||
match = standardUrlRegExA.firstMatch(url.toLowerCase());
|
caseSensitive: false);
|
||||||
|
match = standardUrlRegExA.firstMatch(url);
|
||||||
if (match == null) {
|
if (match == null) {
|
||||||
throw InvalidURLError(name);
|
throw InvalidURLError(name);
|
||||||
}
|
}
|
||||||
@@ -55,8 +57,8 @@ class APKPure extends AppSource {
|
|||||||
) async {
|
) async {
|
||||||
String appId = (await tryInferringAppId(standardUrl))!;
|
String appId = (await tryInferringAppId(standardUrl))!;
|
||||||
String host = Uri.parse(standardUrl).host;
|
String host = Uri.parse(standardUrl).host;
|
||||||
var res = await sourceRequest('$standardUrl/download');
|
var res = await sourceRequest('$standardUrl/download', additionalSettings);
|
||||||
var resChangelog = await sourceRequest(standardUrl);
|
var resChangelog = await sourceRequest(standardUrl, additionalSettings);
|
||||||
if (res.statusCode == 200 && resChangelog.statusCode == 200) {
|
if (res.statusCode == 200 && resChangelog.statusCode == 200) {
|
||||||
var html = parse(res.body);
|
var html = parse(res.body);
|
||||||
var htmlChangelog = parse(resChangelog.body);
|
var htmlChangelog = parse(resChangelog.body);
|
||||||
@@ -69,7 +71,8 @@ class APKPure extends AppSource {
|
|||||||
DateTime? releaseDate = parseDateTimeMMMddCommayyyy(dateString);
|
DateTime? releaseDate = parseDateTimeMMMddCommayyyy(dateString);
|
||||||
String type = html.querySelector('a.info-tag')?.text.trim() ?? 'APK';
|
String type = html.querySelector('a.info-tag')?.text.trim() ?? 'APK';
|
||||||
List<MapEntry<String, String>> apkUrls = [
|
List<MapEntry<String, String>> apkUrls = [
|
||||||
MapEntry('$appId.apk', 'https://d.$host/b/$type/$appId?version=latest')
|
MapEntry('$appId.apk',
|
||||||
|
'https://d.${hosts.contains(host) ? 'cdnpure.com' : host}/b/$type/$appId?version=latest')
|
||||||
];
|
];
|
||||||
String author = html
|
String author = html
|
||||||
.querySelector('span.info-sdk')
|
.querySelector('span.info-sdk')
|
||||||
|
@@ -14,9 +14,10 @@ class Aptoide extends AppSource {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
String sourceSpecificStandardizeURL(String url) {
|
String sourceSpecificStandardizeURL(String url) {
|
||||||
RegExp standardUrlRegEx =
|
RegExp standardUrlRegEx = RegExp(
|
||||||
RegExp('^https?://([^\\.]+\\.){2,}${getSourceRegex(hosts)}');
|
'^https?://([^\\.]+\\.){2,}${getSourceRegex(hosts)}',
|
||||||
RegExpMatch? match = standardUrlRegEx.firstMatch(url.toLowerCase());
|
caseSensitive: false);
|
||||||
|
RegExpMatch? match = standardUrlRegEx.firstMatch(url);
|
||||||
if (match == null) {
|
if (match == null) {
|
||||||
throw InvalidURLError(name);
|
throw InvalidURLError(name);
|
||||||
}
|
}
|
||||||
@@ -26,11 +27,13 @@ class Aptoide extends AppSource {
|
|||||||
@override
|
@override
|
||||||
Future<String?> tryInferringAppId(String standardUrl,
|
Future<String?> tryInferringAppId(String standardUrl,
|
||||||
{Map<String, dynamic> additionalSettings = const {}}) async {
|
{Map<String, dynamic> additionalSettings = const {}}) async {
|
||||||
return (await getAppDetailsJSON(standardUrl))['package'];
|
return (await getAppDetailsJSON(
|
||||||
|
standardUrl, additionalSettings))['package'];
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<Map<String, dynamic>> getAppDetailsJSON(String standardUrl) async {
|
Future<Map<String, dynamic>> getAppDetailsJSON(
|
||||||
var res = await sourceRequest(standardUrl);
|
String standardUrl, Map<String, dynamic> additionalSettings) async {
|
||||||
|
var res = await sourceRequest(standardUrl, additionalSettings);
|
||||||
if (res.statusCode != 200) {
|
if (res.statusCode != 200) {
|
||||||
throw getObtainiumHttpError(res);
|
throw getObtainiumHttpError(res);
|
||||||
}
|
}
|
||||||
@@ -41,8 +44,8 @@ class Aptoide extends AppSource {
|
|||||||
} else {
|
} else {
|
||||||
throw NoReleasesError();
|
throw NoReleasesError();
|
||||||
}
|
}
|
||||||
var res2 =
|
var res2 = await sourceRequest(
|
||||||
await sourceRequest('https://ws2.aptoide.com/api/7/getApp/app_id/$id');
|
'https://ws2.aptoide.com/api/7/getApp/app_id/$id', additionalSettings);
|
||||||
if (res2.statusCode != 200) {
|
if (res2.statusCode != 200) {
|
||||||
throw getObtainiumHttpError(res);
|
throw getObtainiumHttpError(res);
|
||||||
}
|
}
|
||||||
@@ -54,7 +57,7 @@ class Aptoide extends AppSource {
|
|||||||
String standardUrl,
|
String standardUrl,
|
||||||
Map<String, dynamic> additionalSettings,
|
Map<String, dynamic> additionalSettings,
|
||||||
) async {
|
) async {
|
||||||
var appDetails = await getAppDetailsJSON(standardUrl);
|
var appDetails = await getAppDetailsJSON(standardUrl, additionalSettings);
|
||||||
String appName = appDetails['name'] ?? tr('app');
|
String appName = appDetails['name'] ?? tr('app');
|
||||||
String author = appDetails['developer']?['name'] ?? name;
|
String author = appDetails['developer']?['name'] ?? name;
|
||||||
String? dateStr = appDetails['updated'];
|
String? dateStr = appDetails['updated'];
|
||||||
|
@@ -16,9 +16,10 @@ class Codeberg extends AppSource {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
String sourceSpecificStandardizeURL(String url) {
|
String sourceSpecificStandardizeURL(String url) {
|
||||||
RegExp standardUrlRegEx =
|
RegExp standardUrlRegEx = RegExp(
|
||||||
RegExp('^https?://(www\\.)?${getSourceRegex(hosts)}/[^/]+/[^/]+');
|
'^https?://(www\\.)?${getSourceRegex(hosts)}/[^/]+/[^/]+',
|
||||||
RegExpMatch? match = standardUrlRegEx.firstMatch(url.toLowerCase());
|
caseSensitive: false);
|
||||||
|
RegExpMatch? match = standardUrlRegEx.firstMatch(url);
|
||||||
if (match == null) {
|
if (match == null) {
|
||||||
throw InvalidURLError(name);
|
throw InvalidURLError(name);
|
||||||
}
|
}
|
||||||
|
@@ -38,15 +38,17 @@ class FDroid extends AppSource {
|
|||||||
@override
|
@override
|
||||||
String sourceSpecificStandardizeURL(String url) {
|
String sourceSpecificStandardizeURL(String url) {
|
||||||
RegExp standardUrlRegExB = RegExp(
|
RegExp standardUrlRegExB = RegExp(
|
||||||
'^https?://(www\\.)?${getSourceRegex(hosts)}/+[^/]+/+packages/+[^/]+');
|
'^https?://(www\\.)?${getSourceRegex(hosts)}/+[^/]+/+packages/+[^/]+',
|
||||||
RegExpMatch? match = standardUrlRegExB.firstMatch(url.toLowerCase());
|
caseSensitive: false);
|
||||||
|
RegExpMatch? match = standardUrlRegExB.firstMatch(url);
|
||||||
if (match != null) {
|
if (match != null) {
|
||||||
url =
|
url =
|
||||||
'https://${Uri.parse(match.group(0)!).host}/packages/${Uri.parse(url).pathSegments.last}';
|
'https://${Uri.parse(match.group(0)!).host}/packages/${Uri.parse(url).pathSegments.last}';
|
||||||
}
|
}
|
||||||
RegExp standardUrlRegExA =
|
RegExp standardUrlRegExA = RegExp(
|
||||||
RegExp('^https?://(www\\.)?${getSourceRegex(hosts)}/+packages/+[^/]+');
|
'^https?://(www\\.)?${getSourceRegex(hosts)}/+packages/+[^/]+',
|
||||||
match = standardUrlRegExA.firstMatch(url.toLowerCase());
|
caseSensitive: false);
|
||||||
|
match = standardUrlRegExA.firstMatch(url);
|
||||||
if (match == null) {
|
if (match == null) {
|
||||||
throw InvalidURLError(name);
|
throw InvalidURLError(name);
|
||||||
}
|
}
|
||||||
@@ -67,7 +69,8 @@ class FDroid extends AppSource {
|
|||||||
String? appId = await tryInferringAppId(standardUrl);
|
String? appId = await tryInferringAppId(standardUrl);
|
||||||
String host = Uri.parse(standardUrl).host;
|
String host = Uri.parse(standardUrl).host;
|
||||||
var details = getAPKUrlsFromFDroidPackagesAPIResponse(
|
var details = getAPKUrlsFromFDroidPackagesAPIResponse(
|
||||||
await sourceRequest('https://$host/api/v1/packages/$appId'),
|
await sourceRequest(
|
||||||
|
'https://$host/api/v1/packages/$appId', additionalSettings),
|
||||||
'https://$host/repo/$appId',
|
'https://$host/repo/$appId',
|
||||||
standardUrl,
|
standardUrl,
|
||||||
name,
|
name,
|
||||||
@@ -84,29 +87,30 @@ class FDroid extends AppSource {
|
|||||||
if (!hostChanged) {
|
if (!hostChanged) {
|
||||||
try {
|
try {
|
||||||
var res = await sourceRequest(
|
var res = await sourceRequest(
|
||||||
'https://gitlab.com/fdroid/fdroiddata/-/raw/master/metadata/$appId.yml');
|
'https://gitlab.com/fdroid/fdroiddata/-/raw/master/metadata/$appId.yml',
|
||||||
|
additionalSettings);
|
||||||
var lines = res.body.split('\n');
|
var lines = res.body.split('\n');
|
||||||
String author = lines
|
var authorLines = lines.where((l) => l.startsWith('AuthorName: '));
|
||||||
.where((l) => l.startsWith('AuthorName: '))
|
if (authorLines.isNotEmpty) {
|
||||||
.first
|
details.names.author =
|
||||||
.split(': ')
|
authorLines.first.split(': ').sublist(1).join(': ');
|
||||||
.sublist(1)
|
}
|
||||||
.join(': ');
|
|
||||||
details.names.author = author;
|
|
||||||
var changelogUrls = lines.where((l) => l.startsWith('Changelog: '));
|
var changelogUrls = lines.where((l) => l.startsWith('Changelog: '));
|
||||||
if (changelogUrls.isNotEmpty) {
|
if (changelogUrls.isNotEmpty) {
|
||||||
details.changeLog = changelogUrls.first;
|
details.changeLog = changelogUrls.first;
|
||||||
details.changeLog = (await sourceRequest(details.changeLog!
|
details.changeLog = (await sourceRequest(
|
||||||
|
details.changeLog!
|
||||||
.split(': ')
|
.split(': ')
|
||||||
.sublist(1)
|
.sublist(1)
|
||||||
.join(': ')
|
.join(': ')
|
||||||
.replaceFirst('/blob/', '/raw/')))
|
.replaceFirst('/blob/', '/raw/'),
|
||||||
|
additionalSettings))
|
||||||
.body;
|
.body;
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// Fail silently
|
// Fail silently
|
||||||
}
|
}
|
||||||
if ((details.changeLog?.length ?? 0) > 1000) {
|
if ((details.changeLog?.length ?? 0) > 2048) {
|
||||||
details.changeLog = '${details.changeLog!.substring(0, 2048)}...';
|
details.changeLog = '${details.changeLog!.substring(0, 2048)}...';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -117,7 +121,7 @@ class FDroid extends AppSource {
|
|||||||
Future<Map<String, List<String>>> search(String query,
|
Future<Map<String, List<String>>> search(String query,
|
||||||
{Map<String, dynamic> querySettings = const {}}) async {
|
{Map<String, dynamic> querySettings = const {}}) async {
|
||||||
Response res = await sourceRequest(
|
Response res = await sourceRequest(
|
||||||
'https://search.${hosts[0]}/?q=${Uri.encodeQueryComponent(query)}');
|
'https://search.${hosts[0]}/?q=${Uri.encodeQueryComponent(query)}', {});
|
||||||
if (res.statusCode == 200) {
|
if (res.statusCode == 200) {
|
||||||
Map<String, List<String>> urlsWithDescriptions = {};
|
Map<String, List<String>> urlsWithDescriptions = {};
|
||||||
parse(res.body).querySelectorAll('.package-header').forEach((e) {
|
parse(res.body).querySelectorAll('.package-header').forEach((e) {
|
||||||
|
@@ -59,7 +59,7 @@ class FDroidRepo extends AppSource {
|
|||||||
throw NoReleasesError();
|
throw NoReleasesError();
|
||||||
}
|
}
|
||||||
url = removeQueryParamsFromUrl(standardizeUrl(url));
|
url = removeQueryParamsFromUrl(standardizeUrl(url));
|
||||||
var res = await sourceRequest('$url/index.xml');
|
var res = await sourceRequest('$url/index.xml', {});
|
||||||
if (res.statusCode == 200) {
|
if (res.statusCode == 200) {
|
||||||
var body = parse(res.body);
|
var body = parse(res.body);
|
||||||
Map<String, List<String>> results = {};
|
Map<String, List<String>> results = {};
|
||||||
@@ -117,7 +117,8 @@ class FDroidRepo extends AppSource {
|
|||||||
throw NoReleasesError();
|
throw NoReleasesError();
|
||||||
}
|
}
|
||||||
var res = await sourceRequest(
|
var res = await sourceRequest(
|
||||||
'$standardUrl${standardUrl.endsWith('/index.xml') ? '' : '/index.xml'}');
|
'$standardUrl${standardUrl.endsWith('/index.xml') ? '' : '/index.xml'}',
|
||||||
|
additionalSettings);
|
||||||
if (res.statusCode == 200) {
|
if (res.statusCode == 200) {
|
||||||
var body = parse(res.body);
|
var body = parse(res.body);
|
||||||
var foundApps = body.querySelectorAll('application').where((element) {
|
var foundApps = body.querySelectorAll('application').where((element) {
|
||||||
|
@@ -108,7 +108,8 @@ class GitHub extends AppSource {
|
|||||||
for (var path in possibleBuildGradleLocations) {
|
for (var path in possibleBuildGradleLocations) {
|
||||||
try {
|
try {
|
||||||
var res = await sourceRequest(
|
var res = await sourceRequest(
|
||||||
'${await convertStandardUrlToAPIUrl(standardUrl, additionalSettings)}/contents/$path');
|
'${await convertStandardUrlToAPIUrl(standardUrl, additionalSettings)}/contents/$path',
|
||||||
|
additionalSettings);
|
||||||
if (res.statusCode == 200) {
|
if (res.statusCode == 200) {
|
||||||
try {
|
try {
|
||||||
var body = jsonDecode(res.body);
|
var body = jsonDecode(res.body);
|
||||||
@@ -149,9 +150,10 @@ class GitHub extends AppSource {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
String sourceSpecificStandardizeURL(String url) {
|
String sourceSpecificStandardizeURL(String url) {
|
||||||
RegExp standardUrlRegEx =
|
RegExp standardUrlRegEx = RegExp(
|
||||||
RegExp('^https?://(www\\.)?${getSourceRegex(hosts)}/[^/]+/[^/]+');
|
'^https?://(www\\.)?${getSourceRegex(hosts)}/[^/]+/[^/]+',
|
||||||
RegExpMatch? match = standardUrlRegEx.firstMatch(url.toLowerCase());
|
caseSensitive: false);
|
||||||
|
RegExpMatch? match = standardUrlRegEx.firstMatch(url);
|
||||||
if (match == null) {
|
if (match == null) {
|
||||||
throw InvalidURLError(name);
|
throw InvalidURLError(name);
|
||||||
}
|
}
|
||||||
@@ -160,8 +162,8 @@ class GitHub extends AppSource {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Future<Map<String, String>?> getRequestHeaders(
|
Future<Map<String, String>?> getRequestHeaders(
|
||||||
{Map<String, dynamic> additionalSettings = const <String, dynamic>{},
|
Map<String, dynamic> additionalSettings,
|
||||||
bool forAPKDownload = false}) async {
|
{bool forAPKDownload = false}) async {
|
||||||
var token = await getTokenIfAny(additionalSettings);
|
var token = await getTokenIfAny(additionalSettings);
|
||||||
var headers = <String, String>{};
|
var headers = <String, String>{};
|
||||||
if (token != null) {
|
if (token != null) {
|
||||||
@@ -239,7 +241,8 @@ class GitHub extends AppSource {
|
|||||||
if (verifyLatestTag) {
|
if (verifyLatestTag) {
|
||||||
var temp = requestUrl.split('?');
|
var temp = requestUrl.split('?');
|
||||||
Response res = await sourceRequest(
|
Response res = await sourceRequest(
|
||||||
'${temp[0]}/latest${temp.length > 1 ? '?${temp.sublist(1).join('?')}' : ''}');
|
'${temp[0]}/latest${temp.length > 1 ? '?${temp.sublist(1).join('?')}' : ''}',
|
||||||
|
additionalSettings);
|
||||||
if (res.statusCode != 200) {
|
if (res.statusCode != 200) {
|
||||||
if (onHttpErrorCode != null) {
|
if (onHttpErrorCode != null) {
|
||||||
onHttpErrorCode(res);
|
onHttpErrorCode(res);
|
||||||
@@ -248,7 +251,7 @@ class GitHub extends AppSource {
|
|||||||
}
|
}
|
||||||
latestRelease = jsonDecode(res.body);
|
latestRelease = jsonDecode(res.body);
|
||||||
}
|
}
|
||||||
Response res = await sourceRequest(requestUrl);
|
Response res = await sourceRequest(requestUrl, additionalSettings);
|
||||||
if (res.statusCode == 200) {
|
if (res.statusCode == 200) {
|
||||||
var releases = jsonDecode(res.body) as List<dynamic>;
|
var releases = jsonDecode(res.body) as List<dynamic>;
|
||||||
if (latestRelease != null) {
|
if (latestRelease != null) {
|
||||||
@@ -425,7 +428,7 @@ class GitHub extends AppSource {
|
|||||||
String query, String requestUrl, String rootProp,
|
String query, String requestUrl, String rootProp,
|
||||||
{Function(Response)? onHttpErrorCode,
|
{Function(Response)? onHttpErrorCode,
|
||||||
Map<String, dynamic> querySettings = const {}}) async {
|
Map<String, dynamic> querySettings = const {}}) async {
|
||||||
Response res = await sourceRequest(requestUrl);
|
Response res = await sourceRequest(requestUrl, {});
|
||||||
if (res.statusCode == 200) {
|
if (res.statusCode == 200) {
|
||||||
int minStarCount = querySettings['minStarCount'] != null
|
int minStarCount = querySettings['minStarCount'] != null
|
||||||
? int.parse(querySettings['minStarCount'])
|
? int.parse(querySettings['minStarCount'])
|
||||||
|
@@ -52,9 +52,10 @@ class GitLab extends AppSource {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
String sourceSpecificStandardizeURL(String url) {
|
String sourceSpecificStandardizeURL(String url) {
|
||||||
RegExp standardUrlRegEx =
|
RegExp standardUrlRegEx = RegExp(
|
||||||
RegExp('^https?://(www\\.)?${getSourceRegex(hosts)}/[^/]+/[^/]+');
|
'^https?://(www\\.)?${getSourceRegex(hosts)}/[^/]+/[^/]+',
|
||||||
RegExpMatch? match = standardUrlRegEx.firstMatch(url.toLowerCase());
|
caseSensitive: false);
|
||||||
|
RegExpMatch? match = standardUrlRegEx.firstMatch(url);
|
||||||
if (match == null) {
|
if (match == null) {
|
||||||
throw InvalidURLError(name);
|
throw InvalidURLError(name);
|
||||||
}
|
}
|
||||||
@@ -83,7 +84,7 @@ class GitLab extends AppSource {
|
|||||||
{Map<String, dynamic> querySettings = const {}}) async {
|
{Map<String, dynamic> querySettings = const {}}) async {
|
||||||
var url =
|
var url =
|
||||||
'https://${hosts[0]}/api/v4/projects?search=${Uri.encodeQueryComponent(query)}';
|
'https://${hosts[0]}/api/v4/projects?search=${Uri.encodeQueryComponent(query)}';
|
||||||
var res = await sourceRequest(url);
|
var res = await sourceRequest(url, {});
|
||||||
if (res.statusCode != 200) {
|
if (res.statusCode != 200) {
|
||||||
throw getObtainiumHttpError(res);
|
throw getObtainiumHttpError(res);
|
||||||
}
|
}
|
||||||
@@ -114,7 +115,8 @@ class GitLab extends AppSource {
|
|||||||
if (PAT != null) {
|
if (PAT != null) {
|
||||||
var names = GitHub().getAppNames(standardUrl);
|
var names = GitHub().getAppNames(standardUrl);
|
||||||
Response res = await sourceRequest(
|
Response res = await sourceRequest(
|
||||||
'https://${hosts[0]}/api/v4/projects/${names.author}%2F${names.name}/releases?private_token=$PAT');
|
'https://${hosts[0]}/api/v4/projects/${names.author}%2F${names.name}/releases?private_token=$PAT',
|
||||||
|
additionalSettings);
|
||||||
if (res.statusCode != 200) {
|
if (res.statusCode != 200) {
|
||||||
throw getObtainiumHttpError(res);
|
throw getObtainiumHttpError(res);
|
||||||
}
|
}
|
||||||
@@ -149,7 +151,8 @@ class GitLab extends AppSource {
|
|||||||
releaseDate: releaseDate);
|
releaseDate: releaseDate);
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
Response res = await sourceRequest('$standardUrl/-/tags?format=atom');
|
Response res = await sourceRequest(
|
||||||
|
'$standardUrl/-/tags?format=atom', additionalSettings);
|
||||||
if (res.statusCode != 200) {
|
if (res.statusCode != 200) {
|
||||||
throw getObtainiumHttpError(res);
|
throw getObtainiumHttpError(res);
|
||||||
}
|
}
|
||||||
|
@@ -141,7 +141,37 @@ class HTML extends AppSource {
|
|||||||
],
|
],
|
||||||
finalStepFormitems[0],
|
finalStepFormitems[0],
|
||||||
...commonFormItems,
|
...commonFormItems,
|
||||||
...finalStepFormitems.sublist(1)
|
...finalStepFormitems.sublist(1),
|
||||||
|
[
|
||||||
|
GeneratedFormSubForm(
|
||||||
|
'requestHeader',
|
||||||
|
[
|
||||||
|
[
|
||||||
|
GeneratedFormTextField('requestHeader',
|
||||||
|
label: tr('requestHeader'),
|
||||||
|
additionalValidators: [
|
||||||
|
(value) {
|
||||||
|
if ((value ?? 'empty:valid')
|
||||||
|
.split(':')
|
||||||
|
.map((e) => e.trim())
|
||||||
|
.where((e) => e.isNotEmpty)
|
||||||
|
.length <
|
||||||
|
2) {
|
||||||
|
return tr('invalidInput');
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
])
|
||||||
|
]
|
||||||
|
],
|
||||||
|
label: tr('requestHeader'),
|
||||||
|
defaultValue: [
|
||||||
|
{
|
||||||
|
'requestHeader':
|
||||||
|
'User-Agent: Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Mobile Safari/537.36'
|
||||||
|
}
|
||||||
|
])
|
||||||
|
]
|
||||||
];
|
];
|
||||||
overrideVersionDetectionFormDefault('noVersionDetection',
|
overrideVersionDetectionFormDefault('noVersionDetection',
|
||||||
disableStandard: false, disableRelDate: true);
|
disableStandard: false, disableRelDate: true);
|
||||||
@@ -149,12 +179,25 @@ class HTML extends AppSource {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Future<Map<String, String>?> getRequestHeaders(
|
Future<Map<String, String>?> getRequestHeaders(
|
||||||
{Map<String, dynamic> additionalSettings = const <String, dynamic>{},
|
Map<String, dynamic> additionalSettings,
|
||||||
bool forAPKDownload = false}) async {
|
{bool forAPKDownload = false}) async {
|
||||||
return {
|
if (additionalSettings.isNotEmpty) {
|
||||||
"User-Agent":
|
if (additionalSettings['requestHeader']?.isNotEmpty != true) {
|
||||||
"Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Mobile Safari/537.36"
|
additionalSettings['requestHeader'] = [];
|
||||||
};
|
}
|
||||||
|
additionalSettings['requestHeader'] = additionalSettings['requestHeader']
|
||||||
|
.where((l) => l['requestHeader'].isNotEmpty == true)
|
||||||
|
.toList();
|
||||||
|
Map<String, String> requestHeaders = {};
|
||||||
|
for (int i = 0; i < (additionalSettings['requestHeader'].length); i++) {
|
||||||
|
var temp =
|
||||||
|
(additionalSettings['requestHeader'][i]['requestHeader'] as String)
|
||||||
|
.split(':');
|
||||||
|
requestHeaders[temp[0].trim()] = temp.sublist(1).join(':').trim();
|
||||||
|
}
|
||||||
|
return requestHeaders;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -235,7 +278,8 @@ class HTML extends AppSource {
|
|||||||
.where((l) => l['customLinkFilterRegex'].isNotEmpty == true)
|
.where((l) => l['customLinkFilterRegex'].isNotEmpty == true)
|
||||||
.toList();
|
.toList();
|
||||||
for (int i = 0; i < (additionalSettings['intermediateLink'].length); i++) {
|
for (int i = 0; i < (additionalSettings['intermediateLink'].length); i++) {
|
||||||
var intLinks = await grabLinksCommon(await sourceRequest(currentUrl),
|
var intLinks = await grabLinksCommon(
|
||||||
|
await sourceRequest(currentUrl, additionalSettings),
|
||||||
additionalSettings['intermediateLink'][i]);
|
additionalSettings['intermediateLink'][i]);
|
||||||
if (intLinks.isEmpty) {
|
if (intLinks.isEmpty) {
|
||||||
throw NoReleasesError();
|
throw NoReleasesError();
|
||||||
@@ -245,7 +289,7 @@ class HTML extends AppSource {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var uri = Uri.parse(currentUrl);
|
var uri = Uri.parse(currentUrl);
|
||||||
Response res = await sourceRequest(currentUrl);
|
Response res = await sourceRequest(currentUrl, additionalSettings);
|
||||||
var links = await grabLinksCommon(res, additionalSettings);
|
var links = await grabLinksCommon(res, additionalSettings);
|
||||||
|
|
||||||
if ((additionalSettings['apkFilterRegEx'] as String?)?.isNotEmpty == true) {
|
if ((additionalSettings['apkFilterRegEx'] as String?)?.isNotEmpty == true) {
|
||||||
|
@@ -13,9 +13,10 @@ class HuaweiAppGallery extends AppSource {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
String sourceSpecificStandardizeURL(String url) {
|
String sourceSpecificStandardizeURL(String url) {
|
||||||
RegExp standardUrlRegEx =
|
RegExp standardUrlRegEx = RegExp(
|
||||||
RegExp('^https?://(www\\.)?${getSourceRegex(hosts)}/app/[^/]+');
|
'^https?://(www\\.)?${getSourceRegex(hosts)}/app/[^/]+',
|
||||||
RegExpMatch? match = standardUrlRegEx.firstMatch(url.toLowerCase());
|
caseSensitive: false);
|
||||||
|
RegExpMatch? match = standardUrlRegEx.firstMatch(url);
|
||||||
if (match == null) {
|
if (match == null) {
|
||||||
throw InvalidURLError(name);
|
throw InvalidURLError(name);
|
||||||
}
|
}
|
||||||
@@ -25,8 +26,10 @@ class HuaweiAppGallery extends AppSource {
|
|||||||
getDlUrl(String standardUrl) =>
|
getDlUrl(String standardUrl) =>
|
||||||
'https://${hosts[0].replaceAll('appgallery.', 'appgallery.cloud.')}/appdl/${standardUrl.split('/').last}';
|
'https://${hosts[0].replaceAll('appgallery.', 'appgallery.cloud.')}/appdl/${standardUrl.split('/').last}';
|
||||||
|
|
||||||
requestAppdlRedirect(String dlUrl) async {
|
requestAppdlRedirect(
|
||||||
Response res = await sourceRequest(dlUrl, followRedirects: false);
|
String dlUrl, Map<String, dynamic> additionalSettings) async {
|
||||||
|
Response res =
|
||||||
|
await sourceRequest(dlUrl, additionalSettings, followRedirects: false);
|
||||||
if (res.statusCode == 200 ||
|
if (res.statusCode == 200 ||
|
||||||
res.statusCode == 302 ||
|
res.statusCode == 302 ||
|
||||||
res.statusCode == 304) {
|
res.statusCode == 304) {
|
||||||
@@ -53,7 +56,7 @@ class HuaweiAppGallery extends AppSource {
|
|||||||
Future<String?> tryInferringAppId(String standardUrl,
|
Future<String?> tryInferringAppId(String standardUrl,
|
||||||
{Map<String, dynamic> additionalSettings = const {}}) async {
|
{Map<String, dynamic> additionalSettings = const {}}) async {
|
||||||
String dlUrl = getDlUrl(standardUrl);
|
String dlUrl = getDlUrl(standardUrl);
|
||||||
Response res = await requestAppdlRedirect(dlUrl);
|
Response res = await requestAppdlRedirect(dlUrl, additionalSettings);
|
||||||
return res.headers['location'] != null
|
return res.headers['location'] != null
|
||||||
? appIdFromRedirectDlUrl(res.headers['location']!)
|
? appIdFromRedirectDlUrl(res.headers['location']!)
|
||||||
: null;
|
: null;
|
||||||
@@ -65,7 +68,7 @@ class HuaweiAppGallery extends AppSource {
|
|||||||
Map<String, dynamic> additionalSettings,
|
Map<String, dynamic> additionalSettings,
|
||||||
) async {
|
) async {
|
||||||
String dlUrl = getDlUrl(standardUrl);
|
String dlUrl = getDlUrl(standardUrl);
|
||||||
Response res = await requestAppdlRedirect(dlUrl);
|
Response res = await requestAppdlRedirect(dlUrl, additionalSettings);
|
||||||
if (res.headers['location'] == null) {
|
if (res.headers['location'] == null) {
|
||||||
throw NoReleasesError();
|
throw NoReleasesError();
|
||||||
}
|
}
|
||||||
|
@@ -15,13 +15,15 @@ class IzzyOnDroid extends AppSource {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
String sourceSpecificStandardizeURL(String url) {
|
String sourceSpecificStandardizeURL(String url) {
|
||||||
RegExp standardUrlRegExA =
|
RegExp standardUrlRegExA = RegExp(
|
||||||
RegExp('^https?://android.${getSourceRegex(hosts)}/repo/apk/[^/]+');
|
'^https?://android.${getSourceRegex(hosts)}/repo/apk/[^/]+',
|
||||||
RegExpMatch? match = standardUrlRegExA.firstMatch(url.toLowerCase());
|
caseSensitive: false);
|
||||||
|
RegExpMatch? match = standardUrlRegExA.firstMatch(url);
|
||||||
if (match == null) {
|
if (match == null) {
|
||||||
RegExp standardUrlRegExB = RegExp(
|
RegExp standardUrlRegExB = RegExp(
|
||||||
'^https?://apt.${getSourceRegex(hosts)}/fdroid/index/apk/[^/]+');
|
'^https?://apt.${getSourceRegex(hosts)}/fdroid/index/apk/[^/]+',
|
||||||
match = standardUrlRegExB.firstMatch(url.toLowerCase());
|
caseSensitive: false);
|
||||||
|
match = standardUrlRegExB.firstMatch(url);
|
||||||
}
|
}
|
||||||
if (match == null) {
|
if (match == null) {
|
||||||
throw InvalidURLError(name);
|
throw InvalidURLError(name);
|
||||||
@@ -43,7 +45,8 @@ class IzzyOnDroid extends AppSource {
|
|||||||
String? appId = await tryInferringAppId(standardUrl);
|
String? appId = await tryInferringAppId(standardUrl);
|
||||||
return fd.getAPKUrlsFromFDroidPackagesAPIResponse(
|
return fd.getAPKUrlsFromFDroidPackagesAPIResponse(
|
||||||
await sourceRequest(
|
await sourceRequest(
|
||||||
'https://apt.izzysoft.de/fdroid/api/v1/packages/$appId'),
|
'https://apt.izzysoft.de/fdroid/api/v1/packages/$appId',
|
||||||
|
additionalSettings),
|
||||||
'https://android.izzysoft.de/frepo/$appId',
|
'https://android.izzysoft.de/frepo/$appId',
|
||||||
standardUrl,
|
standardUrl,
|
||||||
name,
|
name,
|
||||||
|
@@ -8,6 +8,7 @@ class Jenkins extends AppSource {
|
|||||||
Jenkins() {
|
Jenkins() {
|
||||||
overrideVersionDetectionFormDefault('releaseDateAsVersion',
|
overrideVersionDetectionFormDefault('releaseDateAsVersion',
|
||||||
disableStandard: true);
|
disableStandard: true);
|
||||||
|
neverAutoSelect = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
String trimJobUrl(String url) {
|
String trimJobUrl(String url) {
|
||||||
@@ -29,8 +30,8 @@ class Jenkins extends AppSource {
|
|||||||
Map<String, dynamic> additionalSettings,
|
Map<String, dynamic> additionalSettings,
|
||||||
) async {
|
) async {
|
||||||
standardUrl = trimJobUrl(standardUrl);
|
standardUrl = trimJobUrl(standardUrl);
|
||||||
Response res =
|
Response res = await sourceRequest(
|
||||||
await sourceRequest('$standardUrl/lastSuccessfulBuild/api/json');
|
'$standardUrl/lastSuccessfulBuild/api/json', additionalSettings);
|
||||||
if (res.statusCode == 200) {
|
if (res.statusCode == 200) {
|
||||||
var json = jsonDecode(res.body);
|
var json = jsonDecode(res.body);
|
||||||
var releaseDate = json['timestamp'] == null
|
var releaseDate = json['timestamp'] == null
|
||||||
|
@@ -11,9 +11,10 @@ class Mullvad extends AppSource {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
String sourceSpecificStandardizeURL(String url) {
|
String sourceSpecificStandardizeURL(String url) {
|
||||||
RegExp standardUrlRegEx =
|
RegExp standardUrlRegEx = RegExp(
|
||||||
RegExp('^https?://(www\\.)?${getSourceRegex(hosts)}');
|
'^https?://(www\\.)?${getSourceRegex(hosts)}',
|
||||||
RegExpMatch? match = standardUrlRegEx.firstMatch(url.toLowerCase());
|
caseSensitive: false);
|
||||||
|
RegExpMatch? match = standardUrlRegEx.firstMatch(url);
|
||||||
if (match == null) {
|
if (match == null) {
|
||||||
throw InvalidURLError(name);
|
throw InvalidURLError(name);
|
||||||
}
|
}
|
||||||
@@ -29,7 +30,8 @@ class Mullvad extends AppSource {
|
|||||||
String standardUrl,
|
String standardUrl,
|
||||||
Map<String, dynamic> additionalSettings,
|
Map<String, dynamic> additionalSettings,
|
||||||
) async {
|
) async {
|
||||||
Response res = await sourceRequest('$standardUrl/en/download/android');
|
Response res = await sourceRequest(
|
||||||
|
'$standardUrl/en/download/android', additionalSettings);
|
||||||
if (res.statusCode == 200) {
|
if (res.statusCode == 200) {
|
||||||
var versions = parse(res.body)
|
var versions = parse(res.body)
|
||||||
.querySelectorAll('p')
|
.querySelectorAll('p')
|
||||||
|
@@ -11,8 +11,9 @@ class NeutronCode extends AppSource {
|
|||||||
@override
|
@override
|
||||||
String sourceSpecificStandardizeURL(String url) {
|
String sourceSpecificStandardizeURL(String url) {
|
||||||
RegExp standardUrlRegEx = RegExp(
|
RegExp standardUrlRegEx = RegExp(
|
||||||
'^https?://(www\\.)?${getSourceRegex(hosts)}/downloads/file/[^/]+');
|
'^https?://(www\\.)?${getSourceRegex(hosts)}/downloads/file/[^/]+',
|
||||||
RegExpMatch? match = standardUrlRegEx.firstMatch(url.toLowerCase());
|
caseSensitive: false);
|
||||||
|
RegExpMatch? match = standardUrlRegEx.firstMatch(url);
|
||||||
if (match == null) {
|
if (match == null) {
|
||||||
throw InvalidURLError(name);
|
throw InvalidURLError(name);
|
||||||
}
|
}
|
||||||
@@ -79,7 +80,7 @@ class NeutronCode extends AppSource {
|
|||||||
String standardUrl,
|
String standardUrl,
|
||||||
Map<String, dynamic> additionalSettings,
|
Map<String, dynamic> additionalSettings,
|
||||||
) async {
|
) async {
|
||||||
Response res = await sourceRequest(standardUrl);
|
Response res = await sourceRequest(standardUrl, additionalSettings);
|
||||||
if (res.statusCode == 200) {
|
if (res.statusCode == 200) {
|
||||||
var http = parse(res.body);
|
var http = parse(res.body);
|
||||||
var name = http.querySelector('.pd-title')?.innerHtml;
|
var name = http.querySelector('.pd-title')?.innerHtml;
|
||||||
|
@@ -18,8 +18,8 @@ class Signal extends AppSource {
|
|||||||
String standardUrl,
|
String standardUrl,
|
||||||
Map<String, dynamic> additionalSettings,
|
Map<String, dynamic> additionalSettings,
|
||||||
) async {
|
) async {
|
||||||
Response res =
|
Response res = await sourceRequest(
|
||||||
await sourceRequest('https://updates.${hosts[0]}/android/latest.json');
|
'https://updates.${hosts[0]}/android/latest.json', additionalSettings);
|
||||||
if (res.statusCode == 200) {
|
if (res.statusCode == 200) {
|
||||||
var json = jsonDecode(res.body);
|
var json = jsonDecode(res.body);
|
||||||
String? apkUrl = json['url'];
|
String? apkUrl = json['url'];
|
||||||
|
@@ -10,16 +10,18 @@ class SourceForge extends AppSource {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
String sourceSpecificStandardizeURL(String url) {
|
String sourceSpecificStandardizeURL(String url) {
|
||||||
RegExp standardUrlRegExB =
|
RegExp standardUrlRegExB = RegExp(
|
||||||
RegExp('^https?://(www\\.)?${getSourceRegex(hosts)}/p/[^/]+');
|
'^https?://(www\\.)?${getSourceRegex(hosts)}/p/[^/]+',
|
||||||
RegExpMatch? match = standardUrlRegExB.firstMatch(url.toLowerCase());
|
caseSensitive: false);
|
||||||
|
RegExpMatch? match = standardUrlRegExB.firstMatch(url);
|
||||||
if (match != null) {
|
if (match != null) {
|
||||||
url =
|
url =
|
||||||
'https://${Uri.parse(match.group(0)!).host}/projects/${url.substring(Uri.parse(match.group(0)!).host.length + '/projects/'.length + 1)}';
|
'https://${Uri.parse(match.group(0)!).host}/projects/${url.substring(Uri.parse(match.group(0)!).host.length + '/projects/'.length + 1)}';
|
||||||
}
|
}
|
||||||
RegExp standardUrlRegExA =
|
RegExp standardUrlRegExA = RegExp(
|
||||||
RegExp('^https?://(www\\.)?${getSourceRegex(hosts)}/projects/[^/]+');
|
'^https?://(www\\.)?${getSourceRegex(hosts)}/projects/[^/]+',
|
||||||
match = standardUrlRegExA.firstMatch(url.toLowerCase());
|
caseSensitive: false);
|
||||||
|
match = standardUrlRegExA.firstMatch(url);
|
||||||
if (match == null) {
|
if (match == null) {
|
||||||
throw InvalidURLError(name);
|
throw InvalidURLError(name);
|
||||||
}
|
}
|
||||||
@@ -31,7 +33,8 @@ class SourceForge extends AppSource {
|
|||||||
String standardUrl,
|
String standardUrl,
|
||||||
Map<String, dynamic> additionalSettings,
|
Map<String, dynamic> additionalSettings,
|
||||||
) async {
|
) async {
|
||||||
Response res = await sourceRequest('$standardUrl/rss?path=/');
|
Response res =
|
||||||
|
await sourceRequest('$standardUrl/rss?path=/', additionalSettings);
|
||||||
if (res.statusCode == 200) {
|
if (res.statusCode == 200) {
|
||||||
var parsedHtml = parse(res.body);
|
var parsedHtml = parse(res.body);
|
||||||
var allDownloadLinks =
|
var allDownloadLinks =
|
||||||
|
@@ -20,9 +20,10 @@ class SourceHut extends AppSource {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
String sourceSpecificStandardizeURL(String url) {
|
String sourceSpecificStandardizeURL(String url) {
|
||||||
RegExp standardUrlRegEx =
|
RegExp standardUrlRegEx = RegExp(
|
||||||
RegExp('^https?://(www\\.)?${getSourceRegex(hosts)}/[^/]+/[^/]+');
|
'^https?://(www\\.)?${getSourceRegex(hosts)}/[^/]+/[^/]+',
|
||||||
RegExpMatch? match = standardUrlRegEx.firstMatch(url.toLowerCase());
|
caseSensitive: false);
|
||||||
|
RegExpMatch? match = standardUrlRegEx.firstMatch(url);
|
||||||
if (match == null) {
|
if (match == null) {
|
||||||
throw InvalidURLError(name);
|
throw InvalidURLError(name);
|
||||||
}
|
}
|
||||||
@@ -41,7 +42,8 @@ class SourceHut extends AppSource {
|
|||||||
String appName = standardUri.pathSegments.last;
|
String appName = standardUri.pathSegments.last;
|
||||||
bool fallbackToOlderReleases =
|
bool fallbackToOlderReleases =
|
||||||
additionalSettings['fallbackToOlderReleases'] == true;
|
additionalSettings['fallbackToOlderReleases'] == true;
|
||||||
Response res = await sourceRequest('$standardUrl/refs/rss.xml');
|
Response res =
|
||||||
|
await sourceRequest('$standardUrl/refs/rss.xml', additionalSettings);
|
||||||
if (res.statusCode == 200) {
|
if (res.statusCode == 200) {
|
||||||
var parsedHtml = parse(res.body);
|
var parsedHtml = parse(res.body);
|
||||||
List<APKDetails> apkDetailsList = [];
|
List<APKDetails> apkDetailsList = [];
|
||||||
@@ -70,7 +72,7 @@ class SourceHut extends AppSource {
|
|||||||
} catch (e) {
|
} catch (e) {
|
||||||
// ignore
|
// ignore
|
||||||
}
|
}
|
||||||
var res2 = await sourceRequest(releasePage);
|
var res2 = await sourceRequest(releasePage, additionalSettings);
|
||||||
List<MapEntry<String, String>> apkUrls = [];
|
List<MapEntry<String, String>> apkUrls = [];
|
||||||
if (res2.statusCode == 200) {
|
if (res2.statusCode == 200) {
|
||||||
apkUrls = getApkUrlsFromUrls(parse(res2.body)
|
apkUrls = getApkUrlsFromUrls(parse(res2.body)
|
||||||
|
@@ -29,7 +29,8 @@ class SteamMobile extends AppSource {
|
|||||||
String standardUrl,
|
String standardUrl,
|
||||||
Map<String, dynamic> additionalSettings,
|
Map<String, dynamic> additionalSettings,
|
||||||
) async {
|
) async {
|
||||||
Response res = await sourceRequest('https://${hosts[0]}/mobile');
|
Response res =
|
||||||
|
await sourceRequest('https://${hosts[0]}/mobile', additionalSettings);
|
||||||
if (res.statusCode == 200) {
|
if (res.statusCode == 200) {
|
||||||
var apkNamePrefix = additionalSettings['app'] as String?;
|
var apkNamePrefix = additionalSettings['app'] as String?;
|
||||||
if (apkNamePrefix == null) {
|
if (apkNamePrefix == null) {
|
||||||
|
@@ -20,7 +20,8 @@ class TelegramApp extends AppSource {
|
|||||||
String standardUrl,
|
String standardUrl,
|
||||||
Map<String, dynamic> additionalSettings,
|
Map<String, dynamic> additionalSettings,
|
||||||
) async {
|
) async {
|
||||||
Response res = await sourceRequest('https://t.me/s/TAndroidAPK');
|
Response res =
|
||||||
|
await sourceRequest('https://t.me/s/TAndroidAPK', additionalSettings);
|
||||||
if (res.statusCode == 200) {
|
if (res.statusCode == 200) {
|
||||||
var http = parse(res.body);
|
var http = parse(res.body);
|
||||||
var messages =
|
var messages =
|
||||||
|
@@ -13,9 +13,10 @@ class Uptodown extends AppSource {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
String sourceSpecificStandardizeURL(String url) {
|
String sourceSpecificStandardizeURL(String url) {
|
||||||
RegExp standardUrlRegEx =
|
RegExp standardUrlRegEx = RegExp(
|
||||||
RegExp('^https?://([^\\.]+\\.){2,}${getSourceRegex(hosts)}');
|
'^https?://([^\\.]+\\.){2,}${getSourceRegex(hosts)}',
|
||||||
RegExpMatch? match = standardUrlRegEx.firstMatch(url.toLowerCase());
|
caseSensitive: false);
|
||||||
|
RegExpMatch? match = standardUrlRegEx.firstMatch(url);
|
||||||
if (match == null) {
|
if (match == null) {
|
||||||
throw InvalidURLError(name);
|
throw InvalidURLError(name);
|
||||||
}
|
}
|
||||||
@@ -25,11 +26,13 @@ class Uptodown extends AppSource {
|
|||||||
@override
|
@override
|
||||||
Future<String?> tryInferringAppId(String standardUrl,
|
Future<String?> tryInferringAppId(String standardUrl,
|
||||||
{Map<String, dynamic> additionalSettings = const {}}) async {
|
{Map<String, dynamic> additionalSettings = const {}}) async {
|
||||||
return (await getAppDetailsFromPage(standardUrl))['appId'];
|
return (await getAppDetailsFromPage(
|
||||||
|
standardUrl, additionalSettings))['appId'];
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<Map<String, String?>> getAppDetailsFromPage(String standardUrl) async {
|
Future<Map<String, String?>> getAppDetailsFromPage(
|
||||||
var res = await sourceRequest(standardUrl);
|
String standardUrl, Map<String, dynamic> additionalSettings) async {
|
||||||
|
var res = await sourceRequest(standardUrl, additionalSettings);
|
||||||
if (res.statusCode != 200) {
|
if (res.statusCode != 200) {
|
||||||
throw getObtainiumHttpError(res);
|
throw getObtainiumHttpError(res);
|
||||||
}
|
}
|
||||||
@@ -57,7 +60,8 @@ class Uptodown extends AppSource {
|
|||||||
String standardUrl,
|
String standardUrl,
|
||||||
Map<String, dynamic> additionalSettings,
|
Map<String, dynamic> additionalSettings,
|
||||||
) async {
|
) async {
|
||||||
var appDetails = await getAppDetailsFromPage(standardUrl);
|
var appDetails =
|
||||||
|
await getAppDetailsFromPage(standardUrl, additionalSettings);
|
||||||
var version = appDetails['version'];
|
var version = appDetails['version'];
|
||||||
var apkUrl = appDetails['apkUrl'];
|
var apkUrl = appDetails['apkUrl'];
|
||||||
var appId = appDetails['appId'];
|
var appId = appDetails['appId'];
|
||||||
@@ -83,9 +87,9 @@ class Uptodown extends AppSource {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<String> apkUrlPrefetchModifier(
|
Future<String> apkUrlPrefetchModifier(String apkUrl, String standardUrl,
|
||||||
String apkUrl, String standardUrl) async {
|
Map<String, dynamic> additionalSettings) async {
|
||||||
var res = await sourceRequest(apkUrl);
|
var res = await sourceRequest(apkUrl, additionalSettings);
|
||||||
if (res.statusCode != 200) {
|
if (res.statusCode != 200) {
|
||||||
throw getObtainiumHttpError(res);
|
throw getObtainiumHttpError(res);
|
||||||
}
|
}
|
||||||
|
@@ -1,7 +1,6 @@
|
|||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:html/parser.dart';
|
import 'package:html/parser.dart';
|
||||||
import 'package:http/http.dart';
|
import 'package:http/http.dart';
|
||||||
import 'package:obtainium/app_sources/html.dart';
|
|
||||||
import 'package:obtainium/custom_errors.dart';
|
import 'package:obtainium/custom_errors.dart';
|
||||||
import 'package:obtainium/providers/source_provider.dart';
|
import 'package:obtainium/providers/source_provider.dart';
|
||||||
|
|
||||||
@@ -13,19 +12,22 @@ class VLC extends AppSource {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Future<Map<String, String>?> getRequestHeaders(
|
Future<Map<String, String>?> getRequestHeaders(
|
||||||
{Map<String, dynamic> additionalSettings = const <String, dynamic>{},
|
Map<String, dynamic> additionalSettings,
|
||||||
bool forAPKDownload = false}) =>
|
{bool forAPKDownload = false}) async {
|
||||||
HTML().getRequestHeaders(
|
return {
|
||||||
additionalSettings: additionalSettings,
|
"User-Agent":
|
||||||
forAPKDownload: forAPKDownload);
|
"Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Mobile Safari/537.36"
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String sourceSpecificStandardizeURL(String url) {
|
String sourceSpecificStandardizeURL(String url) {
|
||||||
return 'https://${hosts[0]}';
|
return 'https://${hosts[0]}';
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<String?> getLatestVersion(String standardUrl) async {
|
Future<String?> getLatestVersion(
|
||||||
Response res = await sourceRequest(dwUrlBase);
|
String standardUrl, Map<String, dynamic> additionalSettings) async {
|
||||||
|
Response res = await sourceRequest(dwUrlBase, additionalSettings);
|
||||||
if (res.statusCode == 200) {
|
if (res.statusCode == 200) {
|
||||||
var dwLinks = parse(res.body)
|
var dwLinks = parse(res.body)
|
||||||
.querySelectorAll('a')
|
.querySelectorAll('a')
|
||||||
@@ -77,9 +79,9 @@ class VLC extends AppSource {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<String> apkUrlPrefetchModifier(
|
Future<String> apkUrlPrefetchModifier(String apkUrl, String standardUrl,
|
||||||
String apkUrl, String standardUrl) async {
|
Map<String, dynamic> additionalSettings) async {
|
||||||
Response res = await sourceRequest(apkUrl);
|
Response res = await sourceRequest(apkUrl, additionalSettings);
|
||||||
if (res.statusCode == 200) {
|
if (res.statusCode == 200) {
|
||||||
String? apkUrl =
|
String? apkUrl =
|
||||||
parse(res.body).querySelector('#alt_link')?.attributes['href'];
|
parse(res.body).querySelector('#alt_link')?.attributes['href'];
|
||||||
|
@@ -16,9 +16,10 @@ class WhatsApp extends AppSource {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<String> apkUrlPrefetchModifier(
|
Future<String> apkUrlPrefetchModifier(String apkUrl, String standardUrl,
|
||||||
String apkUrl, String standardUrl) async {
|
Map<String, dynamic> additionalSettings) async {
|
||||||
Response res = await sourceRequest('$standardUrl/android');
|
Response res =
|
||||||
|
await sourceRequest('$standardUrl/android', additionalSettings);
|
||||||
if (res.statusCode == 200) {
|
if (res.statusCode == 200) {
|
||||||
var targetLinks = parse(res.body)
|
var targetLinks = parse(res.body)
|
||||||
.querySelectorAll('a')
|
.querySelectorAll('a')
|
||||||
@@ -42,8 +43,8 @@ class WhatsApp extends AppSource {
|
|||||||
) async {
|
) async {
|
||||||
// This is a CDN link that is consistent per version
|
// This is a CDN link that is consistent per version
|
||||||
// But it has query params that change constantly
|
// But it has query params that change constantly
|
||||||
Uri apkUri =
|
Uri apkUri = Uri.parse(await apkUrlPrefetchModifier(
|
||||||
Uri.parse(await apkUrlPrefetchModifier(standardUrl, standardUrl));
|
standardUrl, standardUrl, additionalSettings));
|
||||||
var unusableApkUrl = '${apkUri.origin}/${apkUri.path}';
|
var unusableApkUrl = '${apkUri.origin}/${apkUri.path}';
|
||||||
// So we use the param-less URL is a pseudo-version to add the app and check for updates
|
// So we use the param-less URL is a pseudo-version to add the app and check for updates
|
||||||
// See #357 for why we can't scrape the version number directly
|
// See #357 for why we can't scrape the version number directly
|
||||||
|
@@ -510,9 +510,10 @@ class _GeneratedFormState extends State<GeneratedForm> {
|
|||||||
]);
|
]);
|
||||||
} else if (widget.items[r][e] is GeneratedFormSubForm) {
|
} else if (widget.items[r][e] is GeneratedFormSubForm) {
|
||||||
List<Widget> subformColumn = [];
|
List<Widget> subformColumn = [];
|
||||||
|
var formItems = (widget.items[r][e] as GeneratedFormSubForm).items;
|
||||||
|
var compact = formItems.length == 1 && formItems[0].length == 1;
|
||||||
for (int i = 0; i < values[fieldKey].length; i++) {
|
for (int i = 0; i < values[fieldKey].length; i++) {
|
||||||
var items = (widget.items[r][e] as GeneratedFormSubForm)
|
var items = formItems
|
||||||
.items
|
|
||||||
.map((x) => x.map((y) {
|
.map((x) => x.map((y) {
|
||||||
y.defaultValue = values[fieldKey]?[i]?[y.key];
|
y.defaultValue = values[fieldKey]?[i]?[y.key];
|
||||||
return y;
|
return y;
|
||||||
@@ -525,10 +526,11 @@ class _GeneratedFormState extends State<GeneratedForm> {
|
|||||||
subformColumn.add(Column(
|
subformColumn.add(Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
const Divider(),
|
if (!compact)
|
||||||
const SizedBox(
|
const SizedBox(
|
||||||
height: 16,
|
height: 16,
|
||||||
),
|
),
|
||||||
|
if (!compact)
|
||||||
Text(
|
Text(
|
||||||
'${(widget.items[r][e] as GeneratedFormSubForm).label} (${i + 1})',
|
'${(widget.items[r][e] as GeneratedFormSubForm).label} (${i + 1})',
|
||||||
style: const TextStyle(fontWeight: FontWeight.bold),
|
style: const TextStyle(fontWeight: FontWeight.bold),
|
||||||
@@ -567,13 +569,12 @@ class _GeneratedFormState extends State<GeneratedForm> {
|
|||||||
Icons.delete_outline_rounded,
|
Icons.delete_outline_rounded,
|
||||||
))
|
))
|
||||||
],
|
],
|
||||||
),
|
)
|
||||||
],
|
],
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
subformColumn.add(Padding(
|
subformColumn.add(Padding(
|
||||||
padding: EdgeInsets.only(
|
padding: const EdgeInsets.only(bottom: 0, top: 8),
|
||||||
bottom: values[fieldKey].length > 0 ? 24 : 0, top: 8),
|
|
||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
Expanded(
|
Expanded(
|
||||||
@@ -591,9 +592,6 @@ class _GeneratedFormState extends State<GeneratedForm> {
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
));
|
));
|
||||||
if (values[fieldKey].length > 0) {
|
|
||||||
subformColumn.add(const Divider());
|
|
||||||
}
|
|
||||||
formInputs[r][e] = Column(children: subformColumn);
|
formInputs[r][e] = Column(children: subformColumn);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -19,12 +19,10 @@ 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.15.8';
|
const String currentVersion = '0.15.9';
|
||||||
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
|
||||||
|
|
||||||
const int bgUpdateCheckAlarmId = 666;
|
|
||||||
|
|
||||||
List<MapEntry<Locale, String>> supportedLocales = const [
|
List<MapEntry<Locale, String>> supportedLocales = const [
|
||||||
MapEntry(Locale('en'), 'English'),
|
MapEntry(Locale('en'), 'English'),
|
||||||
MapEntry(Locale('zh'), '简体中文'),
|
MapEntry(Locale('zh'), '简体中文'),
|
||||||
|
@@ -18,7 +18,7 @@ class GitHubStars implements MassAppUrlSource {
|
|||||||
Response res = await get(
|
Response res = await get(
|
||||||
Uri.parse(
|
Uri.parse(
|
||||||
'https://api.github.com/users/$username/starred?per_page=100&page=$page'),
|
'https://api.github.com/users/$username/starred?per_page=100&page=$page'),
|
||||||
headers: await GitHub().getRequestHeaders());
|
headers: await GitHub().getRequestHeaders({}));
|
||||||
if (res.statusCode == 200) {
|
if (res.statusCode == 200) {
|
||||||
Map<String, List<String>> urlsWithDescriptions = {};
|
Map<String, List<String>> urlsWithDescriptions = {};
|
||||||
for (var e in (jsonDecode(res.body) as List<dynamic>)) {
|
for (var e in (jsonDecode(res.body) as List<dynamic>)) {
|
||||||
|
@@ -326,13 +326,15 @@ class AppsProvider with ChangeNotifier {
|
|||||||
AppSource source = SourceProvider()
|
AppSource source = SourceProvider()
|
||||||
.getSource(app.url, overrideSource: app.overrideSource);
|
.getSource(app.url, overrideSource: app.overrideSource);
|
||||||
String downloadUrl = await source.apkUrlPrefetchModifier(
|
String downloadUrl = await source.apkUrlPrefetchModifier(
|
||||||
app.apkUrls[app.preferredApkIndex].value, app.url);
|
app.apkUrls[app.preferredApkIndex].value,
|
||||||
|
app.url,
|
||||||
|
app.additionalSettings);
|
||||||
var notif = DownloadNotification(app.finalName, 100);
|
var notif = DownloadNotification(app.finalName, 100);
|
||||||
notificationsProvider?.cancel(notif.id);
|
notificationsProvider?.cancel(notif.id);
|
||||||
int? prevProg;
|
int? prevProg;
|
||||||
var fileNameNoExt = '${app.id}-${downloadUrl.hashCode}';
|
var fileNameNoExt = '${app.id}-${downloadUrl.hashCode}';
|
||||||
var headers = await source.getRequestHeaders(
|
var headers = await source.getRequestHeaders(app.additionalSettings,
|
||||||
additionalSettings: app.additionalSettings, forAPKDownload: true);
|
forAPKDownload: true);
|
||||||
var downloadedFile = await downloadFileWithRetry(
|
var downloadedFile = await downloadFileWithRetry(
|
||||||
downloadUrl, fileNameNoExt,
|
downloadUrl, fileNameNoExt,
|
||||||
headers: headers, (double? progress) {
|
headers: headers, (double? progress) {
|
||||||
@@ -796,13 +798,17 @@ class AppsProvider with ChangeNotifier {
|
|||||||
SourceProvider()
|
SourceProvider()
|
||||||
.getSource(app.app.url, overrideSource: app.app.overrideSource)
|
.getSource(app.app.url, overrideSource: app.app.overrideSource)
|
||||||
.naiveStandardVersionDetection;
|
.naiveStandardVersionDetection;
|
||||||
|
String? realInstalledVersion =
|
||||||
|
app.app.additionalSettings['useVersionCodeAsOSVersion'] == true
|
||||||
|
? app.installedInfo?.versionCode.toString()
|
||||||
|
: app.installedInfo?.versionName;
|
||||||
return app.app.additionalSettings['trackOnly'] != true &&
|
return app.app.additionalSettings['trackOnly'] != true &&
|
||||||
app.app.additionalSettings['versionDetection'] !=
|
app.app.additionalSettings['versionDetection'] !=
|
||||||
'releaseDateAsVersion' &&
|
'releaseDateAsVersion' &&
|
||||||
app.installedInfo?.versionName != null &&
|
realInstalledVersion != null &&
|
||||||
app.app.installedVersion != null &&
|
app.app.installedVersion != null &&
|
||||||
(reconcileVersionDifferences(app.installedInfo!.versionName!,
|
(reconcileVersionDifferences(
|
||||||
app.app.installedVersion!) !=
|
realInstalledVersion, app.app.installedVersion!) !=
|
||||||
null ||
|
null ||
|
||||||
naiveStandardVersionDetection);
|
naiveStandardVersionDetection);
|
||||||
}
|
}
|
||||||
@@ -821,30 +827,33 @@ class AppsProvider with ChangeNotifier {
|
|||||||
SourceProvider()
|
SourceProvider()
|
||||||
.getSource(app.url, overrideSource: app.overrideSource)
|
.getSource(app.url, overrideSource: app.overrideSource)
|
||||||
.naiveStandardVersionDetection;
|
.naiveStandardVersionDetection;
|
||||||
|
String? realInstalledVersion =
|
||||||
|
app.additionalSettings['useVersionCodeAsOSVersion'] == true
|
||||||
|
? installedInfo?.versionCode.toString()
|
||||||
|
: installedInfo?.versionName;
|
||||||
// FIRST, COMPARE THE APP'S REPORTED AND REAL INSTALLED VERSIONS, WHERE ONE IS NULL
|
// FIRST, COMPARE THE APP'S REPORTED AND REAL INSTALLED VERSIONS, WHERE ONE IS NULL
|
||||||
if (installedInfo == null && app.installedVersion != null && !trackOnly) {
|
if (installedInfo == null && app.installedVersion != null && !trackOnly) {
|
||||||
// App says it's installed but isn't really (and isn't track only) - set to not installed
|
// App says it's installed but isn't really (and isn't track only) - set to not installed
|
||||||
app.installedVersion = null;
|
app.installedVersion = null;
|
||||||
modded = true;
|
modded = true;
|
||||||
} else if (installedInfo?.versionName != null &&
|
} else if (realInstalledVersion != null && app.installedVersion == null) {
|
||||||
app.installedVersion == null) {
|
// App says it's not installed but really is - set to installed and use real package versionName (or versionCode if chosen)
|
||||||
// App says it's not installed but really is - set to installed and use real package versionName
|
app.installedVersion = realInstalledVersion;
|
||||||
app.installedVersion = installedInfo!.versionName;
|
|
||||||
modded = true;
|
modded = true;
|
||||||
}
|
}
|
||||||
// SECOND, RECONCILE DIFFERENCES BETWEEN THE APP'S REPORTED AND REAL INSTALLED VERSIONS, WHERE NEITHER IS NULL
|
// SECOND, RECONCILE DIFFERENCES BETWEEN THE APP'S REPORTED AND REAL INSTALLED VERSIONS, WHERE NEITHER IS NULL
|
||||||
if (installedInfo?.versionName != null &&
|
if (realInstalledVersion != null &&
|
||||||
installedInfo!.versionName != app.installedVersion &&
|
realInstalledVersion != app.installedVersion &&
|
||||||
versionDetectionIsStandard) {
|
versionDetectionIsStandard) {
|
||||||
// App's reported version and real version don't match (and it uses standard version detection)
|
// App's reported version and real version don't match (and it uses standard version detection)
|
||||||
// If they share a standard format (and are still different under it), update the reported version accordingly
|
// If they share a standard format (and are still different under it), update the reported version accordingly
|
||||||
var correctedInstalledVersion = reconcileVersionDifferences(
|
var correctedInstalledVersion = reconcileVersionDifferences(
|
||||||
installedInfo.versionName!, app.installedVersion!);
|
realInstalledVersion, app.installedVersion!);
|
||||||
if (correctedInstalledVersion?.key == false) {
|
if (correctedInstalledVersion?.key == false) {
|
||||||
app.installedVersion = correctedInstalledVersion!.value;
|
app.installedVersion = correctedInstalledVersion!.value;
|
||||||
modded = true;
|
modded = true;
|
||||||
} else if (naiveStandardVersionDetection) {
|
} else if (naiveStandardVersionDetection) {
|
||||||
app.installedVersion = installedInfo.versionName;
|
app.installedVersion = realInstalledVersion;
|
||||||
modded = true;
|
modded = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1289,8 +1298,11 @@ class AppsProvider with ChangeNotifier {
|
|||||||
await Future.delayed(const Duration(microseconds: 1));
|
await Future.delayed(const Duration(microseconds: 1));
|
||||||
}
|
}
|
||||||
for (App a in importedApps) {
|
for (App a in importedApps) {
|
||||||
|
var installedInfo = await getInstalledInfo(a.id, printErr: false);
|
||||||
a.installedVersion =
|
a.installedVersion =
|
||||||
(await getInstalledInfo(a.id, printErr: false))?.versionName;
|
a.additionalSettings['useVersionCodeAsOSVersion'] == true
|
||||||
|
? installedInfo?.versionCode.toString()
|
||||||
|
: installedInfo?.versionName;
|
||||||
}
|
}
|
||||||
await saveApps(importedApps, onlyIfExists: false);
|
await saveApps(importedApps, onlyIfExists: false);
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
|
@@ -19,7 +19,6 @@ import 'package:obtainium/app_sources/huaweiappgallery.dart';
|
|||||||
import 'package:obtainium/app_sources/izzyondroid.dart';
|
import 'package:obtainium/app_sources/izzyondroid.dart';
|
||||||
import 'package:obtainium/app_sources/html.dart';
|
import 'package:obtainium/app_sources/html.dart';
|
||||||
import 'package:obtainium/app_sources/jenkins.dart';
|
import 'package:obtainium/app_sources/jenkins.dart';
|
||||||
import 'package:obtainium/app_sources/mullvad.dart';
|
|
||||||
import 'package:obtainium/app_sources/neutroncode.dart';
|
import 'package:obtainium/app_sources/neutroncode.dart';
|
||||||
import 'package:obtainium/app_sources/signal.dart';
|
import 'package:obtainium/app_sources/signal.dart';
|
||||||
import 'package:obtainium/app_sources/sourceforge.dart';
|
import 'package:obtainium/app_sources/sourceforge.dart';
|
||||||
@@ -417,8 +416,8 @@ abstract class AppSource {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<Map<String, String>?> getRequestHeaders(
|
Future<Map<String, String>?> getRequestHeaders(
|
||||||
{Map<String, dynamic> additionalSettings = const <String, dynamic>{},
|
Map<String, dynamic> additionalSettings,
|
||||||
bool forAPKDownload = false}) async {
|
{bool forAPKDownload = false}) async {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -426,12 +425,10 @@ abstract class AppSource {
|
|||||||
return app;
|
return app;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<Response> sourceRequest(String url,
|
Future<Response> sourceRequest(
|
||||||
{bool followRedirects = true,
|
String url, Map<String, dynamic> additionalSettings,
|
||||||
Map<String, dynamic> additionalSettings =
|
{bool followRedirects = true}) async {
|
||||||
const <String, dynamic>{}}) async {
|
var requestHeaders = await getRequestHeaders(additionalSettings);
|
||||||
var requestHeaders =
|
|
||||||
await getRequestHeaders(additionalSettings: additionalSettings);
|
|
||||||
if (requestHeaders != null || followRedirects == false) {
|
if (requestHeaders != null || followRedirects == false) {
|
||||||
var req = Request('GET', Uri.parse(url));
|
var req = Request('GET', Uri.parse(url));
|
||||||
req.followRedirects = followRedirects;
|
req.followRedirects = followRedirects;
|
||||||
@@ -488,6 +485,10 @@ abstract class AppSource {
|
|||||||
label: tr('versionDetection'),
|
label: tr('versionDetection'),
|
||||||
defaultValue: 'standardVersionDetection')
|
defaultValue: 'standardVersionDetection')
|
||||||
],
|
],
|
||||||
|
[
|
||||||
|
GeneratedFormSwitch('useVersionCodeAsOSVersion',
|
||||||
|
label: tr('useVersionCodeAsOSVersion'), defaultValue: false)
|
||||||
|
],
|
||||||
[
|
[
|
||||||
GeneratedFormTextField('apkFilterRegEx',
|
GeneratedFormTextField('apkFilterRegEx',
|
||||||
label: tr('filterAPKsByRegEx'),
|
label: tr('filterAPKsByRegEx'),
|
||||||
@@ -548,8 +549,8 @@ abstract class AppSource {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<String> apkUrlPrefetchModifier(
|
Future<String> apkUrlPrefetchModifier(String apkUrl, String standardUrl,
|
||||||
String apkUrl, String standardUrl) async {
|
Map<String, dynamic> additionalSettings) async {
|
||||||
return apkUrl;
|
return apkUrl;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -680,7 +681,6 @@ class SourceProvider {
|
|||||||
APKMirror(),
|
APKMirror(),
|
||||||
HuaweiAppGallery(),
|
HuaweiAppGallery(),
|
||||||
Jenkins(),
|
Jenkins(),
|
||||||
Mullvad(),
|
|
||||||
Signal(),
|
Signal(),
|
||||||
VLC(),
|
VLC(),
|
||||||
WhatsApp(),
|
WhatsApp(),
|
||||||
|
56
pubspec.lock
56
pubspec.lock
@@ -22,10 +22,10 @@ packages:
|
|||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: android_package_manager
|
name: android_package_manager
|
||||||
sha256: b873fe5856f7c442aca9751dac05d117285be9e4de08eb15d1ffb811fd1b688d
|
sha256: e52ca607b9f19f95d5dae4211ed8fa93e67093f22ac570db47489c5bca512940
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.6.0"
|
version: "0.7.0"
|
||||||
animations:
|
animations:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@@ -70,10 +70,10 @@ packages:
|
|||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: background_fetch
|
name: background_fetch
|
||||||
sha256: f70b28a0f7a3156195e9742229696f004ea3bf10f74039b7bf4c78a74fbda8a4
|
sha256: "34550cf9b383e5a1844e7d22119aa500508c7df9421fa967c9fb4430d6cb2878"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.2.1"
|
version: "1.2.2"
|
||||||
boolean_selector:
|
boolean_selector:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -258,6 +258,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "6.1.1"
|
version: "6.1.1"
|
||||||
|
fixnum:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: fixnum
|
||||||
|
sha256: "25517a4deb0c03aa0f32fd12db525856438902d9c16536311e76cdc57b31d7d1"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.1.0"
|
||||||
flutter:
|
flutter:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description: flutter
|
description: flutter
|
||||||
@@ -506,10 +514,10 @@ packages:
|
|||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: path_provider
|
name: path_provider
|
||||||
sha256: a1aa8aaa2542a6bc57e381f132af822420216c80d4781f7aa085ca3229208aaa
|
sha256: b27217933eeeba8ff24845c34003b003b2b22151de3c908d0e679e8fe1aa078b
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.1.1"
|
version: "2.1.2"
|
||||||
path_provider_android:
|
path_provider_android:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -538,10 +546,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: path_provider_platform_interface
|
name: path_provider_platform_interface
|
||||||
sha256: "94b1e0dd80970c1ce43d5d4e050a9918fce4f4a775e6142424c30a29a363265c"
|
sha256: "88f5779f72ba699763fa3a3b06aa4bf6de76c8e5de842cf6f29e2e06476c2334"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.1.1"
|
version: "2.1.2"
|
||||||
path_provider_windows:
|
path_provider_windows:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -690,10 +698,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: shared_preferences_platform_interface
|
name: shared_preferences_platform_interface
|
||||||
sha256: d4ec5fc9ebb2f2e056c617112aa75dcf92fc2e4faaf2ae999caa297473f75d8a
|
sha256: "22e2ecac9419b4246d7c22bfbbda589e3acf5c0351137d87dd2939d984d37c3b"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.3.1"
|
version: "2.3.2"
|
||||||
shared_preferences_web:
|
shared_preferences_web:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -823,26 +831,26 @@ packages:
|
|||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: url_launcher
|
name: url_launcher
|
||||||
sha256: e9aa5ea75c84cf46b3db4eea212523591211c3cf2e13099ee4ec147f54201c86
|
sha256: d25bb0ca00432a5e1ee40e69c36c85863addf7cc45e433769d61bed3fe81fd96
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "6.2.2"
|
version: "6.2.3"
|
||||||
url_launcher_android:
|
url_launcher_android:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: url_launcher_android
|
name: url_launcher_android
|
||||||
sha256: c0766a55ab42cefaa728cabc951e82919ab41a3a4fee0aaa96176ca82da8cc51
|
sha256: "507dc655b1d9cb5ebc756032eb785f114e415f91557b73bf60b7e201dfedeb2f"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "6.2.1"
|
version: "6.2.2"
|
||||||
url_launcher_ios:
|
url_launcher_ios:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: url_launcher_ios
|
name: url_launcher_ios
|
||||||
sha256: "46b81e3109cbb2d6b81702ad3077540789a3e74e22795eb9f0b7d494dbaa72ea"
|
sha256: cdb7b6da34483f9b2c9f8b2b29bc468fa7271d92e2021607ca0c4d3bcb04cdd4
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "6.2.2"
|
version: "6.2.3"
|
||||||
url_launcher_linux:
|
url_launcher_linux:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -863,10 +871,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: url_launcher_platform_interface
|
name: url_launcher_platform_interface
|
||||||
sha256: "4aca1e060978e19b2998ee28503f40b5ba6226819c2b5e3e4d1821e8ccd92198"
|
sha256: a932c3a8082e118f80a475ce692fde89dc20fddb24c57360b96bc56f7035de1f
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.3.0"
|
version: "2.3.1"
|
||||||
url_launcher_web:
|
url_launcher_web:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -887,10 +895,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: uuid
|
name: uuid
|
||||||
sha256: "22c94e5ad1e75f9934b766b53c742572ee2677c56bc871d850a57dad0f82127f"
|
sha256: cd210a09f7c18cbe5a02511718e0334de6559871052c90a90c0cca46a4aa81c8
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "4.2.2"
|
version: "4.3.3"
|
||||||
vector_math:
|
vector_math:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -911,10 +919,10 @@ packages:
|
|||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: webview_flutter
|
name: webview_flutter
|
||||||
sha256: "60e23976834e995c404c0b21d3b9db37ecd77d3303ef74f8b8d7a7b19947fc04"
|
sha256: "71e1bfaef41016c8d5954291df5e9f8c6172f1f6ff3af01b5656456ddb11f94c"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "4.4.3"
|
version: "4.4.4"
|
||||||
webview_flutter_android:
|
webview_flutter_android:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -927,10 +935,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: webview_flutter_platform_interface
|
name: webview_flutter_platform_interface
|
||||||
sha256: dbe745ee459a16b6fec296f7565a8ef430d0d681001d8ae521898b9361854943
|
sha256: "80b40ae4fb959957eef9fa8970b6c9accda9f49fc45c2b75154696a8e8996cfe"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.9.0"
|
version: "2.9.1"
|
||||||
webview_flutter_wkwebview:
|
webview_flutter_wkwebview:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@@ -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.15.8+244 # When changing this, update the tag in main() accordingly
|
version: 0.15.9+245 # When changing this, update the tag in main() accordingly
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: '>=3.0.0 <4.0.0'
|
sdk: '>=3.0.0 <4.0.0'
|
||||||
@@ -55,7 +55,7 @@ dependencies:
|
|||||||
git:
|
git:
|
||||||
url: https://github.com/ImranR98/android_package_installer
|
url: https://github.com/ImranR98/android_package_installer
|
||||||
ref: main
|
ref: main
|
||||||
android_package_manager: ^0.6.0
|
android_package_manager: ^0.7.0
|
||||||
share_plus: ^7.0.0
|
share_plus: ^7.0.0
|
||||||
sqflite: ^2.2.0+3
|
sqflite: ^2.2.0+3
|
||||||
easy_localization: ^3.0.1
|
easy_localization: ^3.0.1
|
||||||
|
Reference in New Issue
Block a user