Compare commits

...

69 Commits

Author SHA1 Message Date
43d4f89d61 Merge pull request #328 from ImranR98/dev
Added "URLs in File" import (#326) + UI Improvements (#312, #325)
2023-02-24 23:12:02 -05:00
2190da162d Merge remote-tracking branch 'origin/main' into dev 2023-02-24 23:09:18 -05:00
f10bb5ac91 Increment version, upgrade packages 2023-02-24 23:02:32 -05:00
8e52f9666d UI Bugfix 2023-02-24 22:58:56 -05:00
a8a47bb153 Unified version detection setting 2023-02-24 22:48:30 -05:00
728dafcc28 Added "URLs in file (like OPML)" import 2023-02-24 21:54:27 -05:00
d53b21906c Merge pull request #324 from markus-gitdev/main
Update de.json
2023-02-24 18:26:39 -05:00
d6dcac0f97 Update de.json
Improve readability.
2023-02-24 10:00:15 +01:00
dae5a67652 Merge pull request #323 from ImranR98/dev
Bugfix
2023-02-23 18:31:59 -05:00
508fcccec9 Increment version, update packages 2023-02-23 18:31:08 -05:00
cc8a4c3760 Merge remote-tracking branch 'origin/main' into dev 2023-02-23 18:27:59 -05:00
814e2b7306 Merge pull request #311 from markus-gitdev/main
Update de.json
2023-02-23 18:27:20 -05:00
2e159c9886 Bugfix 2023-02-23 16:20:03 -05:00
b82d28f2a7 Update de.json
Update German translation.
2023-02-21 16:02:32 +01:00
3c61735706 Merge pull request #309 from ImranR98/dev
Release Filter Support for APKMirror (#307) + UI Bugfix (#303)
2023-02-19 18:50:12 -05:00
a2879f5bfa Increment version, update package 2023-02-19 18:48:21 -05:00
b57f023739 Added prev. rel. and regex title filter support to APKMirror 2023-02-19 18:46:30 -05:00
c376a7abec Longer version names bugfix for apps list UI 2023-02-19 18:20:28 -05:00
31c6cc3f6f Merge pull request #305 from atilluF/ita
Update Italian translation
2023-02-19 17:54:53 -05:00
8de8438aeb Merge pull request #302 from bluefly000/japanese-translation
Update Japanese translation
2023-02-19 17:54:47 -05:00
2b0225dd5b Merge pull request #306 from gidano/main
Update hu.json
2023-02-19 17:54:41 -05:00
f6af3a7998 Update hu.json 2023-02-19 15:12:06 +01:00
bd29d7bc10 Update it.json 2023-02-19 12:44:31 +01:00
ffb3516a4b Update Japanese translation 2023-02-19 15:41:14 +09:00
6a5e7942ee Merge pull request #301 from ImranR98/dev
App edit bugfixes
2023-02-18 21:39:55 -05:00
859158e84a App edit bugfixes 2023-02-18 21:39:26 -05:00
435116e10b Merge pull request #300 from ImranR98/dev
Release Date Support for Some Sources (#210 + #298) + UI Changes (#274) + Bugfix (#299)
2023-02-18 21:24:36 -05:00
a788d9d7cd Increment version 2023-02-18 21:22:36 -05:00
4be3478b97 Added release date support to APKMirror 2023-02-18 21:16:28 -05:00
fe0126095a Added release date support to third part f-droid repos 2023-02-18 21:03:22 -05:00
d5fdf28a98 Added release date support to Codeberg 2023-02-18 20:58:08 -05:00
f06d245e20 Added release date support to GitLab 2023-02-18 20:55:23 -05:00
2b4f94b407 Date sort bugfix 2023-02-18 20:49:45 -05:00
5f7e342e6b Added rel. date sort 2023-02-18 20:47:29 -05:00
191776d0d5 Initial release date support 2023-02-18 20:37:30 -05:00
ea81b0e66e Bugfix for different ID same URL Apps (#299) 2023-02-18 18:31:42 -05:00
86131ae3ce Merge pull request #297 from ImranR98/dev
Bugfixes (#292 and #293)
2023-02-16 22:40:38 -05:00
64ded1d720 Updated packages 2023-02-16 22:39:35 -05:00
a11c2f1d37 Increment version 2023-02-16 22:37:17 -05:00
890787f87f Fixed type errors and HTML APK filter 2023-02-16 22:36:53 -05:00
c5ff1de950 Merge pull request #291 from ImranR98/dev
Bugfixes (#286, #289)
2023-02-15 21:27:30 -05:00
56658abd60 Increment version 2023-02-15 21:26:55 -05:00
b60622e2cb Steam bugfix 2023-02-15 21:26:05 -05:00
e149f0b225 HTML Source bugfix 2023-02-15 21:05:14 -05:00
d9729f08c0 Merge pull request #278 from ImranR98/dev
Fixed breaking typo in fa.json
2023-02-12 19:32:25 -05:00
eda5c1bac6 Fixed breaking typo in fa.json 2023-02-12 19:32:05 -05:00
5574ea870b Merge pull request #277 from ImranR98/dev
Added FA to language menu
2023-02-12 19:26:09 -05:00
9f03234ac1 Added FA to language menu
(and renamed file for consistency)
2023-02-12 19:25:44 -05:00
b2503dd43d Merge pull request #276 from ImranR98/dev
Increment version
2023-02-12 19:20:18 -05:00
e01ca704bc Increment version 2023-02-12 19:19:59 -05:00
6aa4ace8e2 Merge pull request #275 from mehdijahann/main
Add FA(Persian) language
2023-02-12 19:19:14 -05:00
d762467a31 Update FA.json 2023-02-13 08:16:06 +09:00
b07cce8ecd Create FA.json 2023-02-13 07:07:30 +09:00
8002a946b2 Merge pull request #273 from ImranR98/dev
Reverse changes related to App ID changes (#270)
2023-02-12 14:37:21 -05:00
fd9aebc5b2 Reverse changes related to App ID changes (#270) 2023-02-12 14:36:54 -05:00
1be38d361f Merge pull request #272 from ImranR98/dev
No longer blocking App ID changes in updates
2023-02-12 14:20:04 -05:00
32c40ae7b3 No longer blocking App ID changes in updates 2023-02-12 14:16:19 -05:00
07223d81c7 Merge pull request #268 from gidano/main
Updated hu.json
2023-02-11 12:02:41 -05:00
78baee7265 Updated hu.json 2023-02-11 11:41:52 +01:00
348c33dfe9 Merge pull request #266 from rollingmoai/patch-1
Add installation badges
2023-02-10 20:36:38 -05:00
c408d70ae6 Merge pull request #260 from atilluF/ita
Update Italian translation
2023-02-10 20:23:13 -05:00
3ae4e7cc8a Merge pull request #259 from bluefly000/japanese-translation
Update Japanese translation
2023-02-10 20:23:06 -05:00
dab0f2bb72 Add installation badges 2023-02-09 22:13:16 +08:00
4baf6bcd3b Update it.json 2023-02-05 12:10:59 +01:00
db4517aa13 Update Japanese translation 2023-02-05 12:23:30 +09:00
55d4d1f978 Merge pull request #258 from ImranR98/dev
Removed unused commented code
2023-02-04 20:06:57 -05:00
f89ac5965f Removed unused commented code 2023-02-04 20:06:22 -05:00
d5ebaa161f Merge pull request #257 from ImranR98/dev
Remove unused class
2023-02-04 20:05:39 -05:00
a4c014a8bf Remove unused class 2023-02-04 20:05:20 -05:00
26 changed files with 906 additions and 378 deletions

View File

@ -22,6 +22,15 @@ Currently supported App sources:
- "HTML" (Fallback)
- Any other URL that returns an HTML page with links to APK files (if multiple, the last file alphabetically is picked)
## Installation
[<img src="https://github.com/machiav3lli/oandbackupx/blob/034b226cea5c1b30eb4f6a6f313e4dadcbb0ece4/badge_github.png"
alt="Get it on GitHub"
height="80">](https://github.com/ImranR98/Obtainium/releases)
[<img src="https://gitlab.com/IzzyOnDroid/repo/-/raw/master/assets/IzzyOnDroid.png"
alt="Get it on IzzyOnDroid"
height="80">](https://apt.izzysoft.de/fdroid/index/apk/dev.imranr.obtainium)
## Limitations
- App installs happen asynchronously and the success/failure of an install cannot be determined directly. This results in install statuses and versions sometimes being out of sync with the OS until the next launch or until the problem is manually corrected.
- Auto (unattended) updates are unsupported due to a lack of any capable Flutter plugin.

View File

@ -207,12 +207,19 @@
"addCategory": "Kategorie hinzufügen",
"label": "Bezeichnung",
"language": "Sprache",
"storagePermissionDenied": "Storage permission denied",
"selectedCategorizeWarning": "This will replace any existing category settings for the selected Apps.",
"filterAPKsByRegEx": "Filter APKs by Regular Expression",
"removeFromObtainium": "Remove from Obtainium",
"uninstallFromDevice": "Uninstall from Device",
"onlyWorksWithNonVersionDetectApps": "Only works for Apps with version detection disabled.",
"storagePermissionDenied": "Speicherberechtigung verweigert",
"selectedCategorizeWarning": "Dadurch werden alle bestehenden Kategorieeinstellungen für die ausgewählten Apps ersetzt.",
"filterAPKsByRegEx": "APKs nach regulärem Ausdruck filtern",
"removeFromObtainium": "Aus Obtainium entfernen",
"uninstallFromDevice": "Vom Gerät deinstallieren",
"onlyWorksWithNonVersionDetectApps": "Funktioniert nur bei Apps mit deaktivierter Versionserkennung.",
"releaseDateAsVersion": "Veröffentlichungsdatum als Version verwenden",
"releaseDateAsVersionExplanation": "Diese Option sollte nur für Apps verwendet werden, bei denen die Versionserkennung nicht korrekt funktioniert, aber ein Veröffentlichungsdatum verfügbar ist.",
"changes": "Änderungen",
"releaseDate": "Veröffentlichungsdatum",
"importFromURLsInFile": "Import from URLs in File (like OPML)",
"versionDetection": "Version Detection",
"standardVersionDetection": "Standard version detection",
"removeAppQuestion": {
"one": "App entfernen?",
"other": "App entfernen?"

View File

@ -213,6 +213,13 @@
"removeFromObtainium": "Remove from Obtainium",
"uninstallFromDevice": "Uninstall from Device",
"onlyWorksWithNonVersionDetectApps": "Only works for Apps with version detection disabled.",
"releaseDateAsVersion": "Use Release Date as Version",
"releaseDateAsVersionExplanation": "This option should only be used for Apps where version detection does not work correctly, but a release date is available.",
"changes": "Changes",
"releaseDate": "Release Date",
"importFromURLsInFile": "Import from URLs in File (like OPML)",
"versionDetection": "Version Detection",
"standardVersionDetection": "Standard version detection",
"removeAppQuestion": {
"one": "Remove App?",
"other": "Remove Apps?"

271
assets/translations/fa.json Normal file
View File

@ -0,0 +1,271 @@
{
"invalidURLForSource": "آدرس اینترنتی برنامه {} معتبر نیست",
"noReleaseFound": "نسخه مناسبی پیدا نشد",
"noVersionFound": "نمی توان نسخه منتشر شده را تعیین کرد",
"urlMatchesNoSource": "آدرس اینترنتی با منبع شناخته شده مطابقت ندارد",
"cantInstallOlderVersion": "نمی توان نسخه قدیمی یک برنامه را نصب کرد",
"appIdMismatch": "شناسه بسته دانلود شده با شناسه برنامه موجود مطابقت ندارد",
"functionNotImplemented": "این کلاس این تابع را پیاده سازی نکرده است",
"placeholder": "نگهدارنده مکان",
"someErrors": "برخی از خطاها رخ داده است",
"unexpectedError": "خطای غیرمنتظره",
"ok": "باشه",
"and": "و",
"startedBgUpdateTask": "شروع بررسی بروزرسانی BG",
"bgUpdateIgnoreAfterIs": "نادیده گرفتن بروزرسانی BG بعد از {} است",
"startedActualBGUpdateCheck": "بررسی به‌روزرسانی واقعی BG آغاز شد",
"bgUpdateTaskFinished": "کار بررسی به‌روزرسانی BG تمام شد",
"firstRun": "این اولین اجرای Obtainium است",
"settingUpdateCheckIntervalTo": "تنظیم فاصله به‌روزرسانی روی {}",
"githubPATLabel": "توکن دسترسی شخصی گیت هاب(محدودیت نرخ را افزایش میدهد)",
"githubPATHint": "PAT باید در این قالب باشد: username:token",
"githubPATFormat": "username:token",
"githubPATLinkText": "درباره گیتهاب PATs",
"includePrereleases": "شامل نسخه های اولیه",
"fallbackToOlderReleases": "بازگشت به نسخه های قدیمی تر",
"filterReleaseTitlesByRegEx": "عناوین انتشار را با بیان منظم فیلتر کنید",
"invalidRegEx": "عبارت منظم نامعتبر است",
"noDescription": "بدون توضیحات",
"cancel": "لغو",
"continue": "ادامه دهید",
"requiredInBrackets": "(ضروری)",
"dropdownNoOptsError": "خطا: کشویی باید حداقل یک گزینه داشته باشد",
"colour": "رنگ",
"githubStarredRepos": "مخازن ستاره دار گیتهاب",
"uname": "نام کاربری",
"wrongArgNum": "تعداد آرگومان های ارائه شده اشتباه است",
"xIsTrackOnly": "{} فقط ردیابی",
"source": "منبع",
"app": "برنامه",
"appsFromSourceAreTrackOnly": "برنامه‌های این منبع «فقط ردیابی» هستند",
"youPickedTrackOnly": "شما گزینه ی «فقط ردیابی» را انتخاب کرده اید",
"trackOnlyAppDescription": "برنامه برای به روز رسانی ها ردیابی می شود، اما Obtainium قادر به دانلود یا نصب آن نخواهد بود.",
"cancelled": "لغو شد",
"appAlreadyAdded": "برنامه قبلاً اضافه شده است",
"alreadyUpToDateQuestion": "برنامه از قبل به روز شده است؟",
"addApp": "افزودن برنامه",
"appSourceURL": "آدرس اینترنتی منبع برنامه",
"error": "خطا",
"add": "اضافه کردن",
"searchSomeSourcesLabel": "جستجو (فقط برخی منابع)",
"search": "جستجو کردن",
"additionalOptsFor": "گزینه های اضافی برای {}",
"supportedSourcesBelow": "منابع پشتیبانی شده:",
"trackOnlyInBrackets": "«فقط ردیابی»",
"searchableInBrackets": "(قابل جستجو)",
"appsString": "برنامه ها",
"noApps": "برنامه ای وجود ندارد",
"noAppsForFilter": "برنامه ای برای فیلتر کردن وجود ندارد",
"byX": "توسط {}",
"percentProgress": "پیش رفتن: {}%",
"pleaseWait": "لطفا صبر کنید",
"updateAvailable": "بروزرسانی در دسترس",
"estimateInBracketsShort": "(تخمین)",
"notInstalled": "نصب نشده",
"estimateInBrackets": "(تخمین زدن)",
"selectAll": "انتخاب همه",
"deselectN": "لغو انتخاب {}",
"xWillBeRemovedButRemainInstalled": "{} از Obtainium حذف می‌شود اما روی دستگاه نصب می‌ماند.",
"removeSelectedAppsQuestion": "برنامه های انتخابی حذف شود؟",
"removeSelectedApps": "حذف برنامه های انتخاب شده",
"updateX": "به روز رسانی {}",
"installX": "نصب {}",
"markXTrackOnlyAsUpdated": "علامت {}\n(فقط ردیابی)\nبروز شده",
"changeX": "تغییر دادن {}",
"installUpdateApps": "نصب/به‌روزرسانی برنامه‌ها",
"installUpdateSelectedApps": "برنامه‌های انتخابی را نصب/به‌روزرسانی کنید",
"markXSelectedAppsAsUpdated": "{} برنامه های انتخابی را به عنوان به روز علامت گذاری کنید؟",
"no": "خیر",
"yes": "بله",
"markSelectedAppsUpdated": "برنامه های انتخاب شده را به عنوان به روز علامت گذاری کنید",
"pinToTop": "پین به بالا",
"unpinFromTop": "برداشتن پین از بالا",
"resetInstallStatusForSelectedAppsQuestion": "وضعیت نصب برنامه‌های انتخابی بازنشانی شود؟",
"installStatusOfXWillBeResetExplanation": "وضعیت نصب برنامه‌های انتخاب‌شده بازنشانی می‌شود.\n\nاگر نسخه برنامه نشان‌داده‌شده در Obtainium به دلیل به‌روزرسانی‌های ناموفق یا مشکلات دیگر نادرست باشد، می‌تواند کمک کند.",
"shareSelectedAppURLs": "اشتراک گذاری آدرس اینترنتی برنامه های انتخاب شده",
"resetInstallStatus": "بازنشانی وضعیت نصب",
"more": "بیشتر",
"removeOutdatedFilter": "فیلتر برنامه قدیمی را حذف کنید",
"showOutdatedOnly": "فقط برنامه های قدیمی را نشان دهید",
"filter": "فیلتر",
"filterActive": "فیلتر *",
"filterApps": "فیلتر کردن برنامه ها",
"appName": "نام برنامه",
"author": "سازنده",
"upToDateApps": "برنامه های به روز",
"nonInstalledApps": "برنامه های نصب نشده",
"importExport": "وادر کردن/صادر کردن",
"settings": "تنظیمات",
"exportedTo": "صادر کردن به{}",
"obtainiumExport": "صادرکردن Obtainium",
"invalidInput": "ورودی نامعتبر",
"importedX": "وارد شده {}",
"obtainiumImport": "واردکردن Obtainium",
"importFromURLList": "وارد کردن از فهرست آدرس اینترنتی",
"searchQuery": "جستجوی سوال",
"appURLList": "فهرست آدرس اینترنتی برنامه",
"line": "خط",
"searchX": "جستجو {}",
"noResults": "نتیجه ای پیدا نشد",
"importX": "وارد کردن {}",
"importedAppsIdDisclaimer": "ممکن است برنامه‌های وارد شده به اشتباه به‌عنوان \"نصب نشده\" نشان داده شوند.\nبرای رفع این مشکل، آنها را دوباره از طریق Obtainium نصب کنید.\nاین نباید روی داده‌های برنامه تأثیر بگذارد.\n\nفقط بر روی آدرس اینترنتی و روش‌های وارد کردن شخص ثالث تأثیر می‌گذارد.",
"importErrors": "خطاهای وارد کردن",
"importedXOfYApps": "{} از {} برنامه وارد شد.",
"followingURLsHadErrors": "آدرس های اینترنتی زیر دارای خطا بودند:",
"okay": "باشه",
"selectURL": "آدرس اینترنتی انتخاب شده",
"selectURLs": "آدرس های اینترنتی انتخاب شده",
"pick": "انتخاب",
"theme": "تم",
"dark": "تاریک",
"light": "روشن",
"followSystem": "هماهنگ با سیستم",
"obtainium": "Obtainium",
"materialYou": "Material You",
"appSortBy": "مرتب سازی برنامه بر اساس",
"authorName": "سازنده/اسم",
"nameAuthor": "اسم/سازنده",
"asAdded": "همانطور که اضافه شد",
"appSortOrder": "ترتیب مرتب سازی برنامه",
"ascending": "صعودی",
"descending": "نزولی",
"bgUpdateCheckInterval": "فاصله بررسی به‌روزرسانی در پس‌زمینه",
"neverManualOnly": "هرگز - فقط دستی",
"appearance": "ظاهر",
"showWebInAppView": "نمایش صفحه وب منبع در نمای برنامه",
"pinUpdates": "به‌روزرسانی‌ها را به نمای بالای برنامه‌ها پین کنید",
"updates": "به روز رسانی ها",
"sourceSpecific": "منبع خاص",
"appSource": "منبع برنامه",
"noLogs": "بدون گزارش",
"appLogs": "گزارش های برنامه",
"close": "بستن",
"share": "اشتراک گذاری",
"appNotFound": "برنامه پیدا نشد",
"obtainiumExportHyphenatedLowercase": "صادر کردن-obtainium",
"pickAnAPK": "یک APK انتخاب کنید",
"appHasMoreThanOnePackage": "{} بیش از یک بسته دارد:",
"deviceSupportsXArch": "دستگاه شما از معماری پردازنده {} پشتیبانی میکند",
"deviceSupportsFollowingArchs": "دستگاه شما از معماری های پردازنده زیر پشتیبانی می کند:",
"warning": "اخطار",
"sourceIsXButPackageFromYPrompt": "منبع برنامه \"{}\" است اما بسته انتشار از \"{}\" آمده است. ادامه هید؟",
"updatesAvailable": "بروزرسانی در دسترس ",
"updatesAvailableNotifDescription": "به کاربر اطلاع می دهد که به روز رسانی برای یک یا چند برنامه ردیابی شده توسط Obtainium در دسترس است",
"noNewUpdates": "به روز رسانی جدیدی وجود ندارد.",
"xHasAnUpdate": "{} یک به روز رسانی دارد.",
"appsUpdated": "برنامه ها به روز شدند",
"appsUpdatedNotifDescription": "به کاربر اطلاع می دهد که به روز رسانی یک یا چند برنامه در پس زمینه اعمال شده است",
"xWasUpdatedToY": "{} به {} به روز شد.",
"errorCheckingUpdates": "خطا در بررسی به‌روزرسانی‌ها",
"errorCheckingUpdatesNotifDescription": "اعلانی که وقتی بررسی به‌روزرسانی پس‌زمینه ناموفق است نشان می‌دهد",
"appsRemoved": "برنامه ها حذف شدند",
"appsRemovedNotifDescription": "به کاربر اطلاع می دهد که یک یا چند برنامه به دلیل خطا در هنگام بارگیری حذف شده است",
"xWasRemovedDueToErrorY": "{} به دلیل این خطا حذف شد: {}",
"completeAppInstallation": "نصب کامل برنامه",
"obtainiumMustBeOpenToInstallApps": "Obtainium باید برای نصب برنامه ها باز باشد",
"completeAppInstallationNotifDescription": "از کاربر می‌خواهد برای پایان نصب برنامه به Obtainium برگردد",
"checkingForUpdates": "بررسی به‌روزرسانی‌ها",
"checkingForUpdatesNotifDescription": "اعلان گذرا که هنگام بررسی به روز رسانی ظاهر می شود",
"pleaseAllowInstallPerm": "لطفاً به Obtainium اجازه دهید برنامه‌ها را نصب کند",
"trackOnly": "فقط ردیابی",
"errorWithHttpStatusCode": "خطا {}",
"versionCorrectionDisabled": "تصحیح نسخه غیرفعال شد (به نظر می رسد افزونه کار نمی کند)",
"unknown": "ناشناخته",
"none": "هیچ",
"never": "هرگز",
"latestVersionX": "آخرین نسخه: {}",
"installedVersionX": "نسخه نصب شده: {}",
"lastUpdateCheckX": "بررسی آخرین به‌روزرسانی: {}",
"remove": "حذف",
"yesMarkUpdated": "بله، علامت گذاری به عنوان به روز شده",
"fdroid": "F-Droid",
"appIdOrName": "شناسه یا نام برنامه",
"appWithIdOrNameNotFound": "هیچ برنامه ای با آن شناسه یا نام یافت نشد",
"reposHaveMultipleApps": "مخازن ممکن است شامل چندین برنامه باشد",
"fdroidThirdPartyRepo": "مخازن شخص ثالث F-Droid",
"steam": "Steam",
"steamMobile": "Steam Mobile",
"steamChat": "Steam Chat",
"install": "نصب",
"markInstalled": "علامت گذاری به عنوان نصب شده",
"update": "به روز رسانی",
"markUpdated": "علامت گذاری به روز شد",
"additionalOptions": "گزینه های اضافی",
"disableVersionDetection": "غیرفعال کردن تشخیص نسخه",
"noVersionDetectionExplanation": "این گزینه فقط باید برای برنامه هایی استفاده شود که تشخیص نسخه به درستی کار نمی کند.",
"downloadingX": "در حال دانلود {}",
"downloadNotifDescription": "کاربر را از پیشرفت دانلود یک برنامه مطلع می کند",
"noAPKFound": "APK پیدا نشد فایل",
"noVersionDetection": "بدون تشخیص نسخه",
"categorize": "دسته بندی کردن",
"categories": "دسته بندی ها",
"category": "دسته بندی",
"noCategory": "بدون دسته بندی",
"noCategories": "بدون دسته بندی ها",
"deleteCategoriesQuestion": "دسته بندی ها حذف شوند؟",
"categoryDeleteWarning": "همه برنامه‌ها در دسته‌های حذف شده روی دسته‌بندی نشده تنظیم می‌شوند.",
"addCategory": "اضافه کردن دسته",
"label": "برچسب",
"language": "زبان",
"storagePermissionDenied": "مجوز ذخیره سازی رد شد",
"selectedCategorizeWarning": "این جایگزین تنظیمات دسته بندی موجود برای برنامه های انتخابی می شود.",
"filterAPKsByRegEx": "فایل‌های APK را با نظم فیلتر کنید",
"removeFromObtainium": "از Obtainium حذف کنید",
"uninstallFromDevice": "حذف نصب از دستگاه",
"onlyWorksWithNonVersionDetectApps": "فقط برای برنامه‌هایی کار می‌کند که تشخیص نسخه غیرفعال است.",
"releaseDateAsVersion": "Use Release Date as Version",
"releaseDateAsVersionExplanation": "This option should only be used for Apps where version detection does not work correctly, but a release date is available.",
"changes": "Changes",
"releaseDate": "Release Date",
"importFromURLsInFile": "Import from URLs in File (like OPML)",
"versionDetection": "Version Detection",
"standardVersionDetection": "Standard version detection",
"removeAppQuestion": {
"one": "برنامه حذف شود؟",
"other": "برنامه ها حذف شوند؟"
},
"tooManyRequestsTryAgainInMinutes": {
"one": "درخواست‌های بسیار زیاد (نرخ محدود) - {} دقیقه دیگر دوباره امتحان کنید",
"other": "درخواست های بسیار زیاد (نرخ محدود) - بعد از {} دقیقه دوباره امتحان کنید"
},
"bgUpdateGotErrorRetryInMinutes": {
"one": "بررسی به‌روزرسانی BG با یک {} مواجه شد، یک بررسی مجدد را در {} دقیقه برنامه‌ریزی می‌کند",
"other": "بررسی به‌روزرسانی BG با {} مواجه شد، یک بررسی مجدد را در {} دقیقه برنامه‌ریزی می‌کند"
},
"bgCheckFoundUpdatesWillNotifyIfNeeded": {
"one": "بررسی به‌روزرسانی BG پیدا شد {} به‌روزرسانی - در صورت نیاز به کاربر اطلاع می‌دهد",
"other": "بررسی به‌روزرسانی BG {} به‌روزرسانی‌های یافت شده - در صورت نیاز به کاربر اطلاع می‌دهد"
},
"apps": {
"one": "برنامه {}",
"other": "{} برنامه ها"
},
"url": {
"one": "{} آدرس اینترنتی",
"other": "{} آدرس های اینترنتی"
},
"minute": {
"one": "{} دقیقه",
"other": "{} دقیقه"
},
"hour": {
"one": "{} ساعت",
"other": "{} ساعت"
},
"day": {
"one": "{} روز",
"other": "{} روز"
},
"clearedNLogsBeforeXAfterY": {
"one": "گزارش {n} پاک شد (قبل از = {پیش از}، بعد = {بعد})",
"other": "{n} گزارش پاک شد (قبل از = {پیش از}، بعد = {بعد})"
},
"xAndNMoreUpdatesAvailable": {
"one": "{} و 1 برنامه دیگر به‌روزرسانی دارند.",
"other": "{} و {} برنامه دیگر به روز رسانی دارند."
},
"xAndNMoreUpdatesInstalled": {
"one": "{} و 1 برنامه دیگر به روز شدند.",
"other": "{} و {} برنامه دیگر به روز شدند."
}
}

View File

@ -34,7 +34,7 @@
"githubStarredRepos": "GitHub Csillagos Repo-k",
"uname": "Felh.név",
"wrongArgNum": "Rossz számú argumentumot adott meg",
"xIsTrackOnly": "A(z) {} csak nyomkövethető",
"xIsTrackOnly": "A(z) {} csak nyomonkövethető",
"source": "Forrás",
"app": "App",
"appsFromSourceAreTrackOnly": "Az ebből a forrásból származó alkalmazások 'Csak nyomon követhetőek'.",
@ -78,7 +78,7 @@
"no": "Nem",
"yes": "Igen",
"markSelectedAppsUpdated": "Jelölje meg a kiválasztott appokat frissítettként",
"pinToTop": "Rögzítés a felülre",
"pinToTop": "Rögzítés felülre",
"unpinFromTop": "Eltávolít felülről",
"resetInstallStatusForSelectedAppsQuestion": "Visszaállítja a kiválasztott appok telepítési állapotát?",
"installStatusOfXWillBeResetExplanation": "A kiválasztott appok telepítési állapota visszaáll.\n\nEz akkor segíthet, ha az Obtainiumban megjelenített app verzió hibás, frissítések vagy egyéb problémák miatt.",
@ -208,10 +208,17 @@
"language": "Nyelv",
"storagePermissionDenied": "Tárhely engedély megtagadva",
"selectedCategorizeWarning": "Ez felváltja a kiválasztott alkalmazások meglévő kategória-beállításait.",
"filterAPKsByRegEx": "Filter APKs by Regular Expression",
"removeFromObtainium": "Remove from Obtainium",
"uninstallFromDevice": "Uninstall from Device",
"onlyWorksWithNonVersionDetectApps": "Only works for Apps with version detection disabled.",
"filterAPKsByRegEx": "Az APK-k szűrése reguláris kifejezéssel",
"removeFromObtainium": "Eltávolítás az Obtainiumból",
"uninstallFromDevice": "Eltávolítás a készülékről",
"onlyWorksWithNonVersionDetectApps": "Csak azoknál az alkalmazásoknál működik, amelyeknél a verzióérzékelés le van tiltva.",
"releaseDateAsVersion": "Használja a Kiadás dátumát, mint verziót",
"releaseDateAsVersionExplanation": "Ezt a beállítást csak olyan alkalmazásoknál szabad használni, ahol a verzió érzékelése nem működik megfelelően, de elérhető a kiadás dátuma.",
"changes": "Változtatások",
"releaseDate": "Kiadás dátuma",
"importFromURLsInFile": "Import from URLs in File (like OPML)",
"versionDetection": "Version Detection",
"standardVersionDetection": "Standard version detection",
"removeAppQuestion": {
"one": "Eltávolítja az alkalmazást?",
"other": "Eltávolítja az alkalmazást?"

View File

@ -56,9 +56,9 @@
"appsString": "App",
"noApps": "Nessuna App",
"noAppsForFilter": "Nessuna App per i filtri selezionati",
"byX": "Da {}",
"byX": "Di {}",
"percentProgress": "Progresso: {}%",
"pleaseWait": "Attendere prego",
"pleaseWait": "In attesa",
"updateAvailable": "Aggiornamento disponibile",
"estimateInBracketsShort": "(prev.)",
"notInstalled": "Non installato",
@ -94,7 +94,7 @@
"author": "Autore",
"upToDateApps": "App aggiornate",
"nonInstalledApps": "App non installate",
"importExport": "Importa - Esporta",
"importExport": "Importa/Esporta",
"settings": "Impostazioni",
"exportedTo": "Esportato in {}",
"obtainiumExport": "Esporta da Obtainium",
@ -207,15 +207,22 @@
"addCategory": "Aggiungi categoria",
"label": "Etichetta",
"language": "Lingua",
"storagePermissionDenied": "Storage permission denied",
"selectedCategorizeWarning": "This will replace any existing category settings for the selected Apps.",
"filterAPKsByRegEx": "Filter APKs by Regular Expression",
"removeFromObtainium": "Remove from Obtainium",
"uninstallFromDevice": "Uninstall from Device",
"onlyWorksWithNonVersionDetectApps": "Only works for Apps with version detection disabled.",
"storagePermissionDenied": "Accesso ai file non autorizzato",
"selectedCategorizeWarning": "Ciò sostituirà le impostazioni di categoria esistenti per le App selezionate.",
"filterAPKsByRegEx": "Filtra file APK con espressioni regolari",
"removeFromObtainium": "Rimuovi da Obtainium",
"uninstallFromDevice": "Disinstalla dal dispositivo",
"onlyWorksWithNonVersionDetectApps": "Funziona solo per le App con il rilevamento della versione disattivato.",
"releaseDateAsVersion": "Usa data di rilascio come versione",
"releaseDateAsVersionExplanation": "Questa opzione dovrebbe essere usata solo per le App in cui il rilevamento della versione non funziona correttamente, ma è disponibile una data di rilascio.",
"changes": "Novità",
"releaseDate": "Data di rilascio",
"importFromURLsInFile": "Import from URLs in File (like OPML)",
"versionDetection": "Version Detection",
"standardVersionDetection": "Standard version detection",
"removeAppQuestion": {
"one": "Rimuovere l'App?",
"other": "Rimuovere l'App?"
"other": "Rimuovere le App?"
},
"tooManyRequestsTryAgainInMinutes": {
"one": "Troppe richieste (traffico limitato) - riprova tra {} minuto",

View File

@ -210,9 +210,16 @@
"storagePermissionDenied": "ストレージ権限が拒否されました",
"selectedCategorizeWarning": "これにより、選択したアプリの既存のカテゴリ設定がすべて置き換えられます。",
"filterAPKsByRegEx": "正規表現でAPKを絞り込む",
"removeFromObtainium": "Remove from Obtainium",
"uninstallFromDevice": "Uninstall from Device",
"onlyWorksWithNonVersionDetectApps": "Only works for Apps with version detection disabled.",
"removeFromObtainium": "Obtainiumから削除する",
"uninstallFromDevice": "デバイスからアンインストールする",
"onlyWorksWithNonVersionDetectApps": "バージョン検出を無効にしているアプリにのみ動作します。",
"releaseDateAsVersion": "リリース日をバージョンとして使用する",
"releaseDateAsVersionExplanation": "このオプションは、バージョン検出が正しく機能しないアプリで、リリース日が利用可能な場合にのみ使用する必要があります。",
"changes": "変更点",
"releaseDate": "リリース日",
"importFromURLsInFile": "Import from URLs in File (like OPML)",
"versionDetection": "Version Detection",
"standardVersionDetection": "Standard version detection",
"removeAppQuestion": {
"one": "アプリを削除しますか?",
"other": "アプリを削除しますか?"

View File

@ -213,6 +213,13 @@
"filterAPKsByRegEx": "Filter APKs by Regular Expression",
"removeFromObtainium": "Remove from Obtainium",
"uninstallFromDevice": "Uninstall from Device",
"releaseDateAsVersion": "Use Release Date as Version",
"releaseDateAsVersionExplanation": "This option should only be used for Apps where version detection does not work correctly, but a release date is available.",
"changes": "Changes",
"releaseDate": "Release Date",
"importFromURLsInFile": "Import from URLs in File (like OPML)",
"versionDetection": "Version Detection",
"standardVersionDetection": "Standard version detection",
"removeAppQuestion": {
"one": "删除应用?",
"other": "删除应用?"

View File

@ -1,5 +1,9 @@
import 'dart:io';
import 'package:easy_localization/easy_localization.dart';
import 'package:html/parser.dart';
import 'package:http/http.dart';
import 'package:obtainium/components/generated_form.dart';
import 'package:obtainium/custom_errors.dart';
import 'package:obtainium/providers/source_provider.dart';
@ -7,6 +11,23 @@ class APKMirror extends AppSource {
APKMirror() {
host = 'apkmirror.com';
enforceTrackOnly = true;
additionalSourceAppSpecificSettingFormItems = [
[
GeneratedFormSwitch('fallbackToOlderReleases',
label: tr('fallbackToOlderReleases'), defaultValue: true)
],
[
GeneratedFormTextField('filterReleaseTitlesByRegEx',
label: tr('filterReleaseTitlesByRegEx'),
required: false,
additionalValidators: [
(value) {
return regExValidator(value);
}
])
]
];
}
@override
@ -28,12 +49,38 @@ class APKMirror extends AppSource {
String standardUrl,
Map<String, dynamic> additionalSettings,
) async {
bool fallbackToOlderReleases =
additionalSettings['fallbackToOlderReleases'] == true;
String? regexFilter =
(additionalSettings['filterReleaseTitlesByRegEx'] as String?)
?.isNotEmpty ==
true
? additionalSettings['filterReleaseTitlesByRegEx']
: null;
Response res = await get(Uri.parse('$standardUrl/feed'));
if (res.statusCode == 200) {
String? titleString = parse(res.body)
.querySelector('item')
?.querySelector('title')
?.innerHtml;
var items = parse(res.body).querySelectorAll('item');
dynamic targetRelease;
for (int i = 0; i < items.length; i++) {
if (!fallbackToOlderReleases && i > 0) break;
String? nameToFilter = items[i].querySelector('title')?.innerHtml;
if (regexFilter != null &&
nameToFilter != null &&
!RegExp(regexFilter).hasMatch(nameToFilter.trim())) {
continue;
}
targetRelease = items[i];
break;
}
String? titleString = targetRelease?.querySelector('title')?.innerHtml;
String? dateString = targetRelease
?.querySelector('pubDate')
?.innerHtml
.split(' ')
.sublist(0, 5)
.join(' ');
DateTime? releaseDate =
dateString != null ? HttpDate.parse('$dateString GMT') : null;
String? version = titleString
?.substring(RegExp('[0-9]').firstMatch(titleString)?.start ?? 0,
RegExp(' by ').firstMatch(titleString)?.start ?? 0)
@ -44,7 +91,8 @@ class APKMirror extends AppSource {
if (version == null || version.isEmpty) {
throw NoVersionError();
}
return APKDetails(version, [], getAppNames(standardUrl));
return APKDetails(version, [], getAppNames(standardUrl),
releaseDate: releaseDate);
} else {
throw getObtainiumHttpError(res);
}

View File

@ -54,9 +54,9 @@ class Codeberg extends AppSource {
String standardUrl,
Map<String, dynamic> additionalSettings,
) async {
bool includePrereleases = additionalSettings['includePrereleases'];
bool includePrereleases = additionalSettings['includePrereleases'] == true;
bool fallbackToOlderReleases =
additionalSettings['fallbackToOlderReleases'];
additionalSettings['fallbackToOlderReleases'] == true;
String? regexFilter =
(additionalSettings['filterReleaseTitlesByRegEx'] as String?)
?.isNotEmpty ==
@ -112,11 +112,15 @@ class Codeberg extends AppSource {
throw NoReleasesError();
}
String? version = targetRelease['tag_name'];
DateTime? releaseDate = targetRelease['published_at'] != null
? DateTime.parse(targetRelease['published_at'])
: null;
if (version == null) {
throw NoVersionError();
}
return APKDetails(version, targetRelease['apkUrls'] as List<String>,
getAppNames(standardUrl));
getAppNames(standardUrl),
releaseDate: releaseDate);
} else {
throw getObtainiumHttpError(res);
}

View File

@ -69,6 +69,8 @@ class FDroidRepo extends AppSource {
foundApps[0].querySelector('name')?.innerHtml ?? appIdOrName;
var releases = foundApps[0].querySelectorAll('package');
String? latestVersion = releases[0].querySelector('version')?.innerHtml;
String? added = releases[0].querySelector('added')?.innerHtml;
DateTime? releaseDate = added != null ? DateTime.parse(added) : null;
if (latestVersion == null) {
throw NoVersionError();
}
@ -78,7 +80,8 @@ class FDroidRepo extends AppSource {
element.querySelector('apkname') != null)
.map((e) => '$standardUrl/${e.querySelector('apkname')!.innerHtml}')
.toList();
return APKDetails(latestVersion, apkUrls, AppNames(authorName, appName));
return APKDetails(latestVersion, apkUrls, AppNames(authorName, appName),
releaseDate: releaseDate);
} else {
throw getObtainiumHttpError(res);
}

View File

@ -101,9 +101,9 @@ class GitHub extends AppSource {
String standardUrl,
Map<String, dynamic> additionalSettings,
) async {
bool includePrereleases = additionalSettings['includePrereleases'];
bool includePrereleases = additionalSettings['includePrereleases'] == true;
bool fallbackToOlderReleases =
additionalSettings['fallbackToOlderReleases'];
additionalSettings['fallbackToOlderReleases'] == true;
String? regexFilter =
(additionalSettings['filterReleaseTitlesByRegEx'] as String?)
?.isNotEmpty ==
@ -154,11 +154,15 @@ class GitHub extends AppSource {
throw NoReleasesError();
}
String? version = targetRelease['tag_name'];
DateTime? releaseDate = targetRelease['published_at'] != null
? DateTime.parse(targetRelease['published_at'])
: null;
if (version == null) {
throw NoVersionError();
}
return APKDetails(version, targetRelease['apkUrls'] as List<String>,
getAppNames(standardUrl));
getAppNames(standardUrl),
releaseDate: releaseDate);
} else {
rateLimitErrorCheck(res);
throw getObtainiumHttpError(res);

View File

@ -54,10 +54,14 @@ class GitLab extends AppSource {
var entryId = entry?.querySelector('id')?.innerHtml;
var version =
entryId == null ? null : Uri.parse(entryId).pathSegments.last;
var releaseDateString = entry?.querySelector('updated')?.innerHtml;
DateTime? releaseDate =
releaseDateString != null ? DateTime.parse(releaseDateString) : null;
if (version == null) {
throw NoVersionError();
}
return APKDetails(version, apkUrls, GitHub().getAppNames(standardUrl));
return APKDetails(version, apkUrls, GitHub().getAppNames(standardUrl),
releaseDate: releaseDate);
} else {
throw getObtainiumHttpError(res);
}

View File

@ -27,6 +27,10 @@ class HTML extends AppSource {
.where((element) => element.toLowerCase().endsWith('.apk'))
.toList();
links.sort((a, b) => a.split('/').last.compareTo(b.split('/').last));
if (additionalSettings['apkFilterRegEx'] != null) {
var reg = RegExp(additionalSettings['apkFilterRegEx']);
links = links.where((element) => reg.hasMatch(element)).toList();
}
if (links.isEmpty) {
throw NoReleasesError();
}
@ -37,7 +41,9 @@ class HTML extends AppSource {
.map((e) => e.toLowerCase().startsWith('http://') ||
e.toLowerCase().startsWith('https://')
? e
: '${uri.origin}/$e')
: e.startsWith('/')
? '${uri.origin}/$e'
: '${uri.origin}/${uri.path}/$e')
.toList();
return APKDetails(version, apkUrls, AppNames(uri.host, tr('app')));
} else {

View File

@ -10,7 +10,10 @@ class SteamMobile extends AppSource {
host = 'store.steampowered.com';
name = tr('steam');
additionalSourceAppSpecificSettingFormItems = [
[GeneratedFormDropdown('app', apks.entries.toList(), label: tr('app'))]
[
GeneratedFormDropdown('app', apks.entries.toList(),
label: tr('app'), defaultValue: apks.entries.toList()[0].key)
]
];
}
@ -35,7 +38,8 @@ class SteamMobile extends AppSource {
if (apkNamePrefix == null) {
throw NoReleasesError();
}
String apkInURLRegexPattern = '/$apkNamePrefix-[^/]+\\.apk\$';
String apkInURLRegexPattern =
'/$apkNamePrefix-([0-9]+\\.)*[0-9]+\\.apk\$';
var links = parse(res.body)
.querySelectorAll('a')
.map((e) => e.attributes['href'] ?? '')

View File

@ -21,7 +21,7 @@ import 'package:easy_localization/src/easy_localization_controller.dart';
// ignore: implementation_imports
import 'package:easy_localization/src/localization.dart';
const String currentVersion = '0.10.7';
const String currentVersion = '0.11.3';
const String currentReleaseTag =
'v$currentVersion-beta'; // KEEP THIS IN SYNC WITH GITHUB RELEASES
@ -33,7 +33,8 @@ const supportedLocales = [
Locale('it'),
Locale('ja'),
Locale('hu'),
Locale('de')
Locale('de'),
Locale('fa')
];
const fallbackLocale = Locale('en');
const localeDir = 'assets/translations';

View File

@ -71,8 +71,6 @@ class _AddAppPageState extends State<AddAppPage> {
var settingsProvider = context.read<SettingsProvider>();
() async {
var userPickedTrackOnly = additionalSettings['trackOnly'] == true;
var userPickedNoVersionDetection =
additionalSettings['noVersionDetection'] == true;
var cont = true;
if ((userPickedTrackOnly || pickedSource!.enforceTrackOnly) &&
// ignore: use_build_context_synchronously
@ -93,7 +91,21 @@ class _AddAppPageState extends State<AddAppPage> {
null) {
cont = false;
}
if (userPickedNoVersionDetection &&
if (additionalSettings['versionDetection'] == 'releaseDateAsVersion' &&
// ignore: use_build_context_synchronously
await showDialog(
context: context,
builder: (BuildContext ctx) {
return GeneratedFormModal(
title: tr('releaseDateAsVersion'),
items: const [],
message: tr('releaseDateAsVersionExplanation'),
);
}) ==
null) {
cont = false;
}
if (additionalSettings['versionDetection'] == 'noVersionDetection' &&
// ignore: use_build_context_synchronously
await showDialog(
context: context,
@ -112,13 +124,12 @@ class _AddAppPageState extends State<AddAppPage> {
var trackOnly = pickedSource!.enforceTrackOnly || userPickedTrackOnly;
App app = await sourceProvider.getApp(
pickedSource!, userInput, additionalSettings,
trackOnlyOverride: trackOnly,
noVersionDetectionOverride: userPickedNoVersionDetection);
trackOnlyOverride: trackOnly);
if (!trackOnly) {
await settingsProvider.getInstallPermission();
}
// Only download the APK here if you need to for the package ID
if (sourceProvider.isTempId(app.id) &&
if (sourceProvider.isTempId(app) &&
app.additionalSettings['trackOnly'] != true) {
// ignore: use_build_context_synchronously
var apkUrl = await appsProvider.confirmApkUrl(app, context);

View File

@ -42,8 +42,6 @@ class _AppPageState extends State<AppPage> {
getUpdate(app.app.id);
}
var trackOnly = app?.app.additionalSettings['trackOnly'] == true;
var noVersionDetection =
app?.app.additionalSettings['noVersionDetection'] == true;
var infoColumn = Column(
mainAxisAlignment: MainAxisAlignment.center,
@ -113,7 +111,7 @@ class _AppPageState extends State<AppPage> {
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
const SizedBox(height: 150),
const SizedBox(height: 125),
app?.installedInfo != null
? Row(mainAxisAlignment: MainAxisAlignment.center, children: [
Image.memory(
@ -136,6 +134,21 @@ class _AppPageState extends State<AppPage> {
textAlign: TextAlign.center,
style: Theme.of(context).textTheme.headlineMedium,
),
const SizedBox(
height: 8,
),
Text(
app?.app.id ?? '',
textAlign: TextAlign.center,
style: Theme.of(context).textTheme.labelSmall,
),
app?.app.releaseDate == null
? const SizedBox.shrink()
: Text(
app!.app.releaseDate.toString(),
textAlign: TextAlign.center,
style: Theme.of(context).textTheme.labelSmall,
),
const SizedBox(
height: 32,
),
@ -192,7 +205,8 @@ class _AppPageState extends State<AppPage> {
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
if (noVersionDetection &&
if (app?.app.additionalSettings['versionDetection'] !=
'standardVersionDetection' &&
!trackOnly &&
app?.app.installedVersion != null &&
app?.app.installedVersion != app?.app.latestVersion)
@ -268,19 +282,49 @@ class _AppPageState extends State<AppPage> {
);
}).then((values) {
if (app != null && values != null) {
var changedApp = app.app;
changedApp.additionalSettings =
values;
Map<String, dynamic>
originalSettings =
app.app.additionalSettings;
app.app.additionalSettings = values;
if (source.enforceTrackOnly) {
changedApp.additionalSettings[
app.app.additionalSettings[
'trackOnly'] = true;
showError(
tr('appsFromSourceAreTrackOnly'),
context);
}
appsProvider.saveApps(
[changedApp]).then((value) {
getUpdate(changedApp.id);
if (app.app.additionalSettings[
'versionDetection'] ==
'releaseDateAsVersion') {
if (originalSettings[
'versionDetection'] !=
'releaseDateAsVersion') {
if (app.app.releaseDate != null) {
bool isUpdated =
app.app.installedVersion ==
app.app.latestVersion;
app.app.latestVersion = app
.app
.releaseDate!
.microsecondsSinceEpoch
.toString();
if (isUpdated) {
app.app.installedVersion =
app.app.latestVersion;
}
}
}
} else if (originalSettings[
'versionDetection'] ==
'releaseDateAsVersion') {
app.app.installedVersion = app
.installedInfo
?.versionName ??
app.app.installedVersion;
}
appsProvider.saveApps([app.app]).then(
(value) {
getUpdate(app.app.id);
});
}
});
@ -347,6 +391,8 @@ class _AppPageState extends State<AppPage> {
if (res.isNotEmpty && mounted) {
Navigator.of(context).pop();
}
}).catchError((e) {
showError(e, context);
});
}).catchError((e) {
showError(e, context);
@ -391,18 +437,3 @@ class _AppPageState extends State<AppPage> {
);
}
}
class RemoveAppsModal extends StatefulWidget {
const RemoveAppsModal({super.key, this.apps = const []});
final List<App> apps;
@override
State<RemoveAppsModal> createState() => _RemoveAppsModalState();
}
class _RemoveAppsModalState extends State<RemoveAppsModal> {
@override
Widget build(BuildContext context) {
return const Placeholder();
}
}

View File

@ -54,12 +54,12 @@ class AppsPageState extends State<AppsPage> {
Widget build(BuildContext context) {
var appsProvider = context.watch<AppsProvider>();
var settingsProvider = context.watch<SettingsProvider>();
var sortedApps = appsProvider.apps.values.toList();
var listedApps = appsProvider.apps.values.toList();
var currentFilterIsUpdatesOnly =
filter.isIdenticalTo(updatesOnlyFilter, settingsProvider);
selectedApps = selectedApps
.where((element) => sortedApps.map((e) => e.app).contains(element))
.where((element) => listedApps.map((e) => e.app).contains(element))
.toSet();
toggleAppSelected(App app) {
@ -72,7 +72,7 @@ class AppsPageState extends State<AppsPage> {
});
}
sortedApps = sortedApps.where((app) {
listedApps = listedApps.where((app) {
if (app.app.installedVersion == app.app.latestVersion &&
!(filter.includeUptodate)) {
return false;
@ -111,7 +111,7 @@ class AppsPageState extends State<AppsPage> {
return true;
}).toList();
sortedApps.sort((a, b) {
listedApps.sort((a, b) {
var nameA = a.installedInfo?.name ?? a.app.name;
var nameB = b.installedInfo?.name ?? b.app.name;
int result = 0;
@ -119,25 +119,30 @@ class AppsPageState extends State<AppsPage> {
result = (a.app.author + nameA).compareTo(b.app.author + nameB);
} else if (settingsProvider.sortColumn == SortColumnSettings.nameAuthor) {
result = (nameA + a.app.author).compareTo(nameB + b.app.author);
} else if (settingsProvider.sortColumn ==
SortColumnSettings.releaseDate) {
result = (a.app.releaseDate)?.compareTo(
b.app.releaseDate ?? DateTime.fromMicrosecondsSinceEpoch(0)) ??
0;
}
return result;
});
if (settingsProvider.sortOrder == SortOrderSettings.descending) {
sortedApps = sortedApps.reversed.toList();
listedApps = listedApps.reversed.toList();
}
var existingUpdates = appsProvider.findExistingUpdates(installedOnly: true);
var existingUpdateIdsAllOrSelected = existingUpdates
.where((element) => selectedApps.isEmpty
? sortedApps.where((a) => a.app.id == element).isNotEmpty
? listedApps.where((a) => a.app.id == element).isNotEmpty
: selectedApps.map((e) => e.id).contains(element))
.toList();
var newInstallIdsAllOrSelected = appsProvider
.findExistingUpdates(nonInstalledOnly: true)
.where((element) => selectedApps.isEmpty
? sortedApps.where((a) => a.app.id == element).isNotEmpty
? listedApps.where((a) => a.app.id == element).isNotEmpty
: selectedApps.map((e) => e.id).contains(element))
.toList();
@ -159,26 +164,26 @@ class AppsPageState extends State<AppsPage> {
if (settingsProvider.pinUpdates) {
var temp = [];
sortedApps = sortedApps.where((sa) {
listedApps = listedApps.where((sa) {
if (existingUpdates.contains(sa.app.id)) {
temp.add(sa);
return false;
}
return true;
}).toList();
sortedApps = [...temp, ...sortedApps];
listedApps = [...temp, ...listedApps];
}
var tempPinned = [];
var tempNotPinned = [];
for (var a in sortedApps) {
for (var a in listedApps) {
if (a.app.pinned) {
tempPinned.add(a);
} else {
tempNotPinned.add(a);
}
}
sortedApps = [...tempPinned, ...tempNotPinned];
listedApps = [...tempPinned, ...tempNotPinned];
return Scaffold(
backgroundColor: Theme.of(context).colorScheme.surface,
@ -198,7 +203,7 @@ class AppsPageState extends State<AppsPage> {
},
child: CustomScrollView(slivers: <Widget>[
CustomAppBar(title: tr('appsString')),
if (appsProvider.loadingApps || sortedApps.isEmpty)
if (appsProvider.loadingApps || listedApps.isEmpty)
SliverFillRemaining(
child: Center(
child: appsProvider.loadingApps
@ -225,8 +230,8 @@ class AppsPageState extends State<AppsPage> {
delegate: SliverChildBuilderDelegate(
(BuildContext context, int index) {
String? changesUrl = SourceProvider()
.getSource(sortedApps[index].app.url)
.changeLogPageFromStandardUrl(sortedApps[index].app.url);
.getSource(listedApps[index].app.url)
.changeLogPageFromStandardUrl(listedApps[index].app.url);
var transparent = const Color.fromARGB(0, 0, 0, 0).value;
return Container(
decoration: BoxDecoration(
@ -234,52 +239,54 @@ class AppsPageState extends State<AppsPage> {
vertical: BorderSide(
width: 4,
color: Color(
sortedApps[index].app.categories.isNotEmpty
listedApps[index].app.categories.isNotEmpty
? settingsProvider.categories[
sortedApps[index]
listedApps[index]
.app
.categories
.first] ??
transparent
: transparent)))),
child: ListTile(
tileColor: sortedApps[index].app.pinned
tileColor: listedApps[index].app.pinned
? Colors.grey.withOpacity(0.1)
: Colors.transparent,
selectedTileColor: Theme.of(context)
.colorScheme
.primary
.withOpacity(sortedApps[index].app.pinned ? 0.2 : 0.1),
selected: selectedApps.contains(sortedApps[index].app),
.withOpacity(listedApps[index].app.pinned ? 0.2 : 0.1),
selected: selectedApps.contains(listedApps[index].app),
onLongPress: () {
toggleAppSelected(sortedApps[index].app);
toggleAppSelected(listedApps[index].app);
},
leading: sortedApps[index].installedInfo != null
leading: listedApps[index].installedInfo != null
? Image.memory(
sortedApps[index].installedInfo!.icon!,
listedApps[index].installedInfo!.icon!,
gaplessPlayback: true,
)
: null,
title: Text(
sortedApps[index].installedInfo?.name ??
sortedApps[index].app.name,
maxLines: 1,
listedApps[index].installedInfo?.name ??
listedApps[index].app.name,
style: TextStyle(
fontWeight: sortedApps[index].app.pinned
overflow: TextOverflow.ellipsis,
fontWeight: listedApps[index].app.pinned
? FontWeight.bold
: FontWeight.normal,
),
),
subtitle: Text(
tr('byX', args: [sortedApps[index].app.author]),
tr('byX', args: [listedApps[index].app.author]),
style: TextStyle(
fontWeight: sortedApps[index].app.pinned
fontWeight: listedApps[index].app.pinned
? FontWeight.bold
: FontWeight.normal)),
trailing: SingleChildScrollView(
reverse: true,
child: sortedApps[index].downloadProgress != null
child: listedApps[index].downloadProgress != null
? Text(tr('percentProgress', args: [
sortedApps[index]
listedApps[index]
.downloadProgress
?.toInt()
.toString() ??
@ -289,22 +296,19 @@ class AppsPageState extends State<AppsPage> {
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.end,
children: [
SizedBox(
width: 100,
Row(
mainAxisSize: MainAxisSize.min,
children: [
Container(
constraints: const BoxConstraints(
maxWidth: 150),
child: Text(
'${sortedApps[index].app.installedVersion ?? tr('notInstalled')}${sortedApps[index].app.additionalSettings['trackOnly'] == true ? ' ${tr('estimateInBrackets')}' : ''}',
overflow: TextOverflow.fade,
'${listedApps[index].app.installedVersion ?? tr('notInstalled')}${listedApps[index].app.additionalSettings['trackOnly'] == true ? ' ${tr('estimateInBrackets')}' : ''}',
overflow: TextOverflow.ellipsis,
textAlign: TextAlign.end,
)),
sortedApps[index].app.installedVersion !=
null &&
sortedApps[index]
.app
.installedVersion !=
sortedApps[index]
.app
.latestVersion
? GestureDetector(
))
]),
GestureDetector(
onTap: changesUrl == null
? null
: () {
@ -312,37 +316,84 @@ class AppsPageState extends State<AppsPage> {
mode: LaunchMode
.externalApplication);
},
child: appsProvider
.areDownloadsRunning()
? Text(tr('pleaseWait'))
: Text(
'${tr('updateAvailable')}${sortedApps[index].app.additionalSettings['trackOnly'] == true ? ' ${tr('estimateInBracketsShort')}' : ''}',
style: TextStyle(
fontStyle:
FontStyle.italic,
decoration: changesUrl ==
child: Text(
listedApps[index].app.releaseDate ==
null
? TextDecoration.none
: TextDecoration
.underline),
))
: const SizedBox(),
? tr('changes')
: DateFormat('yyyy-MM-dd').format(
listedApps[index]
.app
.releaseDate!),
style: const TextStyle(
fontStyle: FontStyle.italic,
decoration:
TextDecoration.underline),
)),
listedApps[index].app.installedVersion !=
null &&
listedApps[index]
.app
.installedVersion !=
listedApps[index]
.app
.latestVersion
? appsProvider.areDownloadsRunning()
? Text(tr('pleaseWait'))
: Row(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment:
MainAxisAlignment.end,
children: [
GestureDetector(
onTap: () {
appsProvider
.downloadAndInstallLatestApps(
[
listedApps[index]
.app
.id
],
globalNavigatorKey
.currentContext).catchError(
(e) {
showError(e, context);
});
},
child: Text(
listedApps[index]
.app
.additionalSettings[
'trackOnly'] ==
true
? tr('markUpdated')
: tr('update'),
style: TextStyle(
color:
Theme.of(context)
.colorScheme
.primary,
fontWeight:
FontWeight.bold),
)),
],
)
: const SizedBox.shrink(),
],
))),
onTap: () {
if (selectedApps.isNotEmpty) {
toggleAppSelected(sortedApps[index].app);
toggleAppSelected(listedApps[index].app);
} else {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) =>
AppPage(appId: sortedApps[index].app.id)),
AppPage(appId: listedApps[index].app.id)),
);
}
},
));
}, childCount: sortedApps.length))
}, childCount: listedApps.length))
])),
persistentFooterButtons: appsProvider.apps.isEmpty
? null
@ -354,20 +405,20 @@ class AppsPageState extends State<AppsPage> {
style: const ButtonStyle(
visualDensity: VisualDensity.compact),
onPressed: () {
selectThese(sortedApps.map((e) => e.app).toList());
selectThese(listedApps.map((e) => e.app).toList());
},
icon: Icon(
Icons.select_all_outlined,
color: Theme.of(context).colorScheme.primary,
),
label: Text(sortedApps.length.toString()))
label: Text(listedApps.length.toString()))
: TextButton.icon(
style: const ButtonStyle(
visualDensity: VisualDensity.compact),
onPressed: () {
selectedApps.isEmpty
? selectThese(
sortedApps.map((e) => e.app).toList())
listedApps.map((e) => e.app).toList())
: clearSelected();
},
icon: Icon(
@ -391,28 +442,6 @@ class AppsPageState extends State<AppsPage> {
: () {
appsProvider.removeAppsWithModal(
context, selectedApps.toList());
// showDialog<Map<String, dynamic>?>(
// context: context,
// builder: (BuildContext ctx) {
// return GeneratedFormModal(
// title: tr(
// 'removeSelectedAppsQuestion'),
// items: const [],
// initValid: true,
// message: tr(
// 'xWillBeRemovedButRemainInstalled',
// args: [
// plural('apps',
// selectedApps.length)
// ]),
// );
// }).then((values) {
// if (values != null) {
// appsProvider.removeApps(selectedApps
// .map((e) => e.id)
// .toList());
// }
// });
},
tooltip: tr('removeSelectedApps'),
icon: const Icon(Icons.delete_outline_outlined),
@ -675,7 +704,7 @@ class AppsPageState extends State<AppsPage> {
onPressed: () {
HapticFeedback.selectionClick();
appsProvider.saveApps(selectedApps.map((a) {
if (a.installedVersion != null && a.additionalSettings['noVersionDetection'] == true) {
if (a.installedVersion != null && a.additionalSettings['versionDetection'] != 'standardVersionDetection') {
a.installedVersion = a.latestVersion;
}
return a;

View File

@ -41,6 +41,66 @@ class _ImportExportPageState extends State<ImportExportPage> {
),
);
urlListImport({String? initValue, bool overrideInitValid = false}) {
showDialog<Map<String, dynamic>?>(
context: context,
builder: (BuildContext ctx) {
return GeneratedFormModal(
initValid: overrideInitValid,
title: tr('importFromURLList'),
items: [
[
GeneratedFormTextField('appURLList',
defaultValue: initValue ?? '',
label: tr('appURLList'),
max: 7,
additionalValidators: [
(dynamic value) {
if (value != null && value.isNotEmpty) {
var lines = value.trim().split('\n');
for (int i = 0; i < lines.length; i++) {
try {
sourceProvider.getSource(lines[i]);
} catch (e) {
return '${tr('line')} ${i + 1}: $e';
}
}
}
return null;
}
])
]
],
);
}).then((values) {
if (values != null) {
var urls = (values['appURLList'] as String).split('\n');
setState(() {
importInProgress = true;
});
appsProvider.addAppsByURL(urls).then((errors) {
if (errors.isEmpty) {
showError(tr('importedX', args: [plural('apps', urls.length)]),
context);
} else {
showDialog(
context: context,
builder: (BuildContext ctx) {
return ImportErrorDialog(
urlsLength: urls.length, errors: errors);
});
}
}).catchError((e) {
showError(e, context);
}).whenComplete(() {
setState(() {
importInProgress = false;
});
});
}
});
}
return Scaffold(
backgroundColor: Theme.of(context).colorScheme.surface,
body: CustomScrollView(slivers: <Widget>[
@ -150,6 +210,8 @@ class _ImportExportPageState extends State<ImportExportPage> {
],
)
else
Column(
children: [
const Divider(
height: 32,
),
@ -157,81 +219,51 @@ class _ImportExportPageState extends State<ImportExportPage> {
onPressed: importInProgress
? null
: () {
showDialog<Map<String, dynamic>?>(
context: context,
builder: (BuildContext ctx) {
return GeneratedFormModal(
title: tr('importFromURLList'),
items: [
[
GeneratedFormTextField(
'appURLList',
label: tr('appURLList'),
max: 7,
additionalValidators: [
(dynamic value) {
if (value != null &&
value.isNotEmpty) {
var lines = value
.trim()
.split('\n');
for (int i = 0;
i < lines.length;
i++) {
try {
sourceProvider
.getSource(
lines[i]);
} catch (e) {
return '${tr('line')} ${i + 1}: $e';
}
}
}
return null;
}
])
]
],
);
}).then((values) {
if (values != null) {
var urls =
(values['appURLList'] as String)
.split('\n');
setState(() {
importInProgress = true;
});
appsProvider
.addAppsByURL(urls)
.then((errors) {
if (errors.isEmpty) {
showError(
tr('importedX', args: [
plural('apps', urls.length)
]),
context);
} else {
showDialog(
context: context,
builder: (BuildContext ctx) {
return ImportErrorDialog(
urlsLength: urls.length,
errors: errors);
});
}
}).catchError((e) {
showError(e, context);
}).whenComplete(() {
setState(() {
importInProgress = false;
});
});
}
});
urlListImport();
},
child: Text(
tr('importFromURLList'),
)),
const SizedBox(height: 8),
TextButton(
onPressed: importInProgress
? null
: () {
FilePicker.platform
.pickFiles()
.then((result) {
if (result != null) {
urlListImport(
overrideInitValid: true,
initValue:
RegExp('https?://[^"]+')
.allMatches(File(result
.files
.single
.path!)
.readAsStringSync())
.map((e) =>
e.input.substring(
e.start, e.end))
.toSet()
.toList()
.where((url) {
try {
sourceProvider
.getSource(url);
return true;
} catch (e) {
return false;
}
}).join('\n'));
}
});
},
child: Text(
tr('importFromURLsInFile'),
)),
],
),
...sourceProvider.sources
.where((element) => element.canSearch)
.map((source) => Column(
@ -280,6 +312,7 @@ class _ImportExportPageState extends State<ImportExportPage> {
if (urlsWithDescriptions
.isNotEmpty) {
var selectedUrls =
// ignore: use_build_context_synchronously
await showDialog<
List<
String>?>(
@ -314,6 +347,7 @@ class _ImportExportPageState extends State<ImportExportPage> {
]),
context);
} else {
// ignore: use_build_context_synchronously
showDialog(
context: context,
builder:
@ -391,6 +425,7 @@ class _ImportExportPageState extends State<ImportExportPage> {
e.toString())
.toList());
var selectedUrls =
// ignore: use_build_context_synchronously
await showDialog<
List<String>?>(
context: context,
@ -418,6 +453,7 @@ class _ImportExportPageState extends State<ImportExportPage> {
]),
context);
} else {
// ignore: use_build_context_synchronously
showDialog(
context: context,
builder:

View File

@ -87,6 +87,7 @@ class _SettingsPageState extends State<SettingsPage> {
});
var sortDropdown = DropdownButtonFormField(
isExpanded: true,
decoration: InputDecoration(labelText: tr('appSortBy')),
value: settingsProvider.sortColumn,
items: [
@ -101,6 +102,10 @@ class _SettingsPageState extends State<SettingsPage> {
DropdownMenuItem(
value: SortColumnSettings.added,
child: Text(tr('asAdded')),
),
DropdownMenuItem(
value: SortColumnSettings.releaseDate,
child: Text(tr('releaseDate')),
)
],
onChanged: (value) {
@ -110,6 +115,7 @@ class _SettingsPageState extends State<SettingsPage> {
});
var orderDropdown = DropdownButtonFormField(
isExpanded: true,
decoration: InputDecoration(labelText: tr('appSortOrder')),
value: settingsProvider.sortOrder,
items: [

View File

@ -182,7 +182,7 @@ class AppsProvider with ChangeNotifier {
// The former case should be handled (give the App its real ID), the latter is a security issue
var newInfo = await PackageArchiveInfo.fromPath(downloadedFile.path);
if (app.id != newInfo.packageName) {
if (apps[app.id] != null && !SourceProvider().isTempId(app.id)) {
if (apps[app.id] != null && !SourceProvider().isTempId(app)) {
throw IDChangedError();
}
var originalAppId = app.id;
@ -467,8 +467,8 @@ class AppsProvider with ChangeNotifier {
App? getCorrectedInstallStatusAppIfPossible(App app, AppInfo? installedInfo) {
var modded = false;
var trackOnly = app.additionalSettings['trackOnly'] == true;
var noVersionDetection =
app.additionalSettings['noVersionDetection'] == true;
var noVersionDetection = app.additionalSettings['versionDetection'] !=
'standardVersionDetection';
if (installedInfo == null && app.installedVersion != null && !trackOnly) {
app.installedVersion = null;
modded = true;

View File

@ -6,7 +6,6 @@ import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:fluttertoast/fluttertoast.dart';
import 'package:obtainium/app_sources/github.dart';
import 'package:obtainium/components/generated_form.dart';
import 'package:obtainium/main.dart';
import 'package:permission_handler/permission_handler.dart';
import 'package:shared_preferences/shared_preferences.dart';
@ -18,7 +17,7 @@ enum ThemeSettings { system, light, dark }
enum ColourSettings { basic, materialYou }
enum SortColumnSettings { added, nameAuthor, authorName }
enum SortColumnSettings { added, nameAuthor, authorName, releaseDate }
enum SortOrderSettings { ascending, descending }

View File

@ -33,8 +33,9 @@ class APKDetails {
late String version;
late List<String> apkUrls;
late AppNames names;
late DateTime? releaseDate;
APKDetails(this.version, this.apkUrls, this.names);
APKDetails(this.version, this.apkUrls, this.names, {this.releaseDate});
}
class App {
@ -50,6 +51,7 @@ class App {
late DateTime? lastUpdateCheck;
bool pinned = false;
List<String> categories;
late DateTime? releaseDate;
App(
this.id,
this.url,
@ -62,7 +64,8 @@ class App {
this.additionalSettings,
this.lastUpdateCheck,
this.pinned,
{this.categories = const []});
{this.categories = const [],
this.releaseDate});
@override
String toString() {
@ -97,6 +100,20 @@ class App {
additionalSettings['noVersionDetection'] =
json['noVersionDetection'] == 'true' || json['trackOnly'] == true;
}
// Convert bool style version detection options to dropdown style
if (additionalSettings['noVersionDetection'] == true) {
additionalSettings['versionDetection'] = 'noVersionDetection';
}
if (additionalSettings['releaseDateAsVersion'] == true) {
additionalSettings['versionDetection'] = 'releaseDateAsVersion';
additionalSettings.remove('releaseDateAsVersion');
}
if (additionalSettings['noVersionDetection'] != null) {
additionalSettings.remove('noVersionDetection');
}
if (additionalSettings['releaseDateAsVersion'] != null) {
additionalSettings.remove('releaseDateAsVersion');
}
// Ensure additionalSettings are correctly typed
for (var item in formItems) {
if (additionalSettings[item.key] != null) {
@ -134,7 +151,11 @@ class App {
.toList()
: json['category'] != null
? [json['category'] as String]
: []);
: [],
releaseDate: json['releaseDate'] == null
? null
: DateTime.fromMicrosecondsSinceEpoch(json['releaseDate']),
);
}
Map<String, dynamic> toJson() => {
@ -149,7 +170,8 @@ class App {
'additionalSettings': jsonEncode(additionalSettings),
'lastUpdateCheck': lastUpdateCheck?.microsecondsSinceEpoch,
'pinned': pinned,
'categories': categories
'categories': categories,
'releaseDate': releaseDate?.microsecondsSinceEpoch
};
}
@ -226,7 +248,16 @@ class AppSource {
)
],
[
GeneratedFormSwitch('noVersionDetection', label: tr('noVersionDetection'))
GeneratedFormDropdown(
'versionDetection',
[
MapEntry(
'standardVersionDetection', tr('standardVersionDetection')),
MapEntry('releaseDateAsVersion', tr('releaseDateAsVersion')),
MapEntry('noVersionDetection', tr('noVersionDetection'))
],
label: tr('versionDetection'),
defaultValue: 'standardVersionDetection')
],
[
GeneratedFormTextField('apkFilterRegEx',
@ -350,41 +381,29 @@ class SourceProvider {
return false;
}
String generateTempID(AppNames names, AppSource source) =>
'${names.author.toLowerCase()}_${names.name.toLowerCase()}_${source.host}';
String generateTempID(
String standardUrl, Map<String, dynamic> additionalSettings) =>
(standardUrl + additionalSettings.toString()).hashCode.toString();
bool isTempId(String id) {
List<String> parts = id.split('_');
if (parts.length < 3) {
return false;
}
for (int i = 0; i < parts.length - 1; i++) {
if (RegExp('.*[A-Z].*').hasMatch(parts[i])) {
// TODO: Look into RegEx for non-Latin characters
return false;
}
}
return true;
bool isTempId(App app) {
// return app.id == generateTempID(app.url, app.additionalSettings);
return RegExp('^[0-9]+\$').hasMatch(app.id);
}
Future<App> getApp(
AppSource source,
String url,
Map<String, dynamic> additionalSettings, {
App? currentApp,
bool trackOnlyOverride = false,
noVersionDetectionOverride = false,
}) async {
AppSource source, String url, Map<String, dynamic> additionalSettings,
{App? currentApp, bool trackOnlyOverride = false}) async {
if (trackOnlyOverride || source.enforceTrackOnly) {
additionalSettings['trackOnly'] = true;
}
if (noVersionDetectionOverride) {
additionalSettings['noVersionDetection'] = true;
}
var trackOnly = additionalSettings['trackOnly'] == true;
String standardUrl = source.standardizeURL(preStandardizeUrl(url));
APKDetails apk =
await source.getLatestAPKDetails(standardUrl, additionalSettings);
if (additionalSettings['versionDetection'] == 'releaseDateAsVersion' &&
apk.releaseDate != null) {
apk.version = apk.releaseDate!.microsecondsSinceEpoch.toString();
}
if (additionalSettings['apkFilterRegEx'] != null) {
var reg = RegExp(additionalSettings['apkFilterRegEx']);
apk.apkUrls =
@ -400,7 +419,7 @@ class SourceProvider {
currentApp?.id ??
source.tryInferringAppId(standardUrl,
additionalSettings: additionalSettings) ??
generateTempID(apk.names, source),
generateTempID(standardUrl, additionalSettings),
standardUrl,
apk.names.author[0].toUpperCase() + apk.names.author.substring(1),
name.trim().isNotEmpty
@ -413,7 +432,8 @@ class SourceProvider {
additionalSettings,
DateTime.now(),
currentApp?.pinned ?? false,
categories: currentApp?.categories ?? const []);
categories: currentApp?.categories ?? const [],
releaseDate: apk.releaseDate);
}
// Returns errors in [results, errors] instead of throwing them

View File

@ -5,18 +5,18 @@ packages:
dependency: "direct main"
description:
name: android_alarm_manager_plus
sha256: "71e796198588e0038dd125bf8c91683b3237b938ffad037413245c689b87ae28"
sha256: "8647cc5f9339f3955a2bd9ec40e0f10c3a80049f31f80b3ffdd87e07bb73fce2"
url: "https://pub.dev"
source: hosted
version: "2.1.0"
version: "2.1.1"
android_intent_plus:
dependency: "direct main"
description:
name: android_intent_plus
sha256: ebd110b60723334bdc6eeb373116d6c52e9bed8feb9dcbd9f034531f56636e31
sha256: "54810cb33945c2c10742cd746ea994822c115e9dbe189919bc63cb436e45a6af"
url: "https://pub.dev"
source: hosted
version: "3.1.5"
version: "3.1.6"
animations:
dependency: "direct main"
description:
@ -37,10 +37,10 @@ packages:
dependency: transitive
description:
name: args
sha256: "139d809800a412ebb26a3892da228b2d0ba36f0ef5d9a82166e5e52ec8d61611"
sha256: "4cab82a83ffef80b262ddedf47a0a8e56ee6fbf7fe21e6e768b02792034dd440"
url: "https://pub.dev"
source: hosted
version: "2.3.2"
version: "2.4.0"
async:
dependency: transitive
description:
@ -149,10 +149,10 @@ packages:
dependency: "direct main"
description:
name: device_info_plus
sha256: "7ff671ed0a6356fa8f2e1ae7d3558d3fb7b6a41e24455e4f8df75b811fb8e4ab"
sha256: "1d6e5a61674ba3a68fb048a7c7b4ff4bebfed8d7379dbe8f2b718231be9a7c95"
url: "https://pub.dev"
source: hosted
version: "8.0.0"
version: "8.1.0"
device_info_plus_platform_interface:
dependency: transitive
description:
@ -234,10 +234,10 @@ packages:
dependency: "direct dev"
description:
name: flutter_launcher_icons
sha256: ce0e501cfc258907842238e4ca605e74b7fd1cdf04b3b43e86c43f3e40a1592c
sha256: "02dcaf49d405f652b7160e882bacfc02cb497041bb2eab2a49b1c393cf9aac12"
url: "https://pub.dev"
source: hosted
version: "0.11.0"
version: "0.12.0"
flutter_lints:
dependency: "direct dev"
description:
@ -258,10 +258,10 @@ packages:
dependency: transitive
description:
name: flutter_local_notifications_linux
sha256: "8f6c1611e0c4a88a382691a97bb3c3feb24cc0c0b54152b8b5fb7ffb837f7fbf"
sha256: ccb08b93703aeedb58856e5637450bf3ffec899adb66dc325630b68994734b89
url: "https://pub.dev"
source: hosted
version: "3.0.0"
version: "3.0.0+1"
flutter_local_notifications_platform_interface:
dependency: transitive
description:
@ -279,10 +279,10 @@ packages:
dependency: transitive
description:
name: flutter_plugin_android_lifecycle
sha256: "60fc7b78455b94e6de2333d2f95196d32cf5c22f4b0b0520a628804cb463503b"
sha256: "4bef634684b2c7f3468c77c766c831229af829a0cd2d4ee6c1b99558bd14e5d2"
url: "https://pub.dev"
source: hosted
version: "2.0.7"
version: "2.0.8"
flutter_test:
dependency: "direct dev"
description: flutter
@ -297,10 +297,10 @@ packages:
dependency: "direct main"
description:
name: fluttertoast
sha256: "7cc92eabe01e3f1babe1571c5560b135dfc762a34e41e9056881e2196b178ec1"
sha256: "2f9c4d3f4836421f7067a28f8939814597b27614e021da9d63e5d3fb6e212d25"
url: "https://pub.dev"
source: hosted
version: "8.1.2"
version: "8.2.1"
html:
dependency: "direct main"
description:
@ -329,10 +329,10 @@ packages:
dependency: transitive
description:
name: image
sha256: "8e9d133755c3e84c73288363e6343157c383a0c6c56fc51afcc5d4d7180306d6"
sha256: "483a389d6ccb292b570c31b3a193779b1b0178e7eb571986d9a49904b6861227"
url: "https://pub.dev"
source: hosted
version: "3.3.0"
version: "4.0.15"
install_plugin_v2:
dependency: "direct main"
description:
@ -449,50 +449,50 @@ packages:
dependency: "direct main"
description:
name: path_provider
sha256: dcea5feb97d8abf90cab9e9030b497fb7c3cbf26b7a1fe9e3ef7dcb0a1ddec95
sha256: "04890b994ee89bfa80bf3080bfec40d5a92c5c7a785ebb02c13084a099d2b6f9"
url: "https://pub.dev"
source: hosted
version: "2.0.12"
version: "2.0.13"
path_provider_android:
dependency: transitive
description:
name: path_provider_android
sha256: a776c088d671b27f6e3aa8881d64b87b3e80201c64e8869b811325de7a76c15e
sha256: "7623b7d4be0f0f7d9a8b5ee6879fc13e4522d4c875ab86801dee4af32b54b83e"
url: "https://pub.dev"
source: hosted
version: "2.0.22"
version: "2.0.23"
path_provider_foundation:
dependency: transitive
description:
name: path_provider_foundation
sha256: "62a68e7e1c6c459f9289859e2fae58290c981ce21d1697faf54910fe1faa4c74"
sha256: eec003594f19fe2456ea965ae36b3fc967bc5005f508890aafe31fa75e41d972
url: "https://pub.dev"
source: hosted
version: "2.1.1"
version: "2.1.2"
path_provider_linux:
dependency: transitive
description:
name: path_provider_linux
sha256: ab0987bf95bc591da42dffb38c77398fc43309f0b9b894dcc5d6f40c4b26c379
sha256: "525ad5e07622d19447ad740b1ed5070031f7a5437f44355ae915ff56e986429a"
url: "https://pub.dev"
source: hosted
version: "2.1.7"
version: "2.1.9"
path_provider_platform_interface:
dependency: transitive
description:
name: path_provider_platform_interface
sha256: f0abc8ebd7253741f05488b4813d936b4d07c6bae3e86148a09e342ee4b08e76
sha256: "57585299a729335f1298b43245842678cb9f43a6310351b18fb577d6e33165ec"
url: "https://pub.dev"
source: hosted
version: "2.0.5"
version: "2.0.6"
path_provider_windows:
dependency: transitive
description:
name: path_provider_windows
sha256: bcabbe399d4042b8ee687e17548d5d3f527255253b4a639f5f8d2094a9c2b45c
sha256: "642ddf65fde5404f83267e8459ddb4556316d3ee6d511ed193357e25caa3632d"
url: "https://pub.dev"
source: hosted
version: "2.1.3"
version: "2.1.4"
permission_handler:
dependency: "direct main"
description:
@ -553,10 +553,10 @@ packages:
dependency: transitive
description:
name: plugin_platform_interface
sha256: dbf0f707c78beedc9200146ad3cb0ab4d5da13c246336987be6940f026500d3a
sha256: "6a2128648c854906c53fa8e33986fc0247a1116122f9534dd20e3ab9e16a32bc"
url: "https://pub.dev"
source: hosted
version: "2.1.3"
version: "2.1.4"
pointycastle:
dependency: transitive
description:
@ -585,10 +585,10 @@ packages:
dependency: "direct main"
description:
name: share_plus
sha256: e387077716f80609bb979cd199331033326033ecd1c8f200a90c5f57b1c9f55e
sha256: "8c6892037b1824e2d7e8f59d54b3105932899008642e6372e5079c6939b4b625"
url: "https://pub.dev"
source: hosted
version: "6.3.0"
version: "6.3.1"
share_plus_platform_interface:
dependency: transitive
description:
@ -601,58 +601,58 @@ packages:
dependency: "direct main"
description:
name: shared_preferences
sha256: "5949029e70abe87f75cfe59d17bf5c397619c4b74a099b10116baeb34786fad9"
sha256: ee6257848f822b8481691f20c3e6d2bfee2e9eccb2a3d249907fcfb198c55b41
url: "https://pub.dev"
source: hosted
version: "2.0.17"
version: "2.0.18"
shared_preferences_android:
dependency: transitive
description:
name: shared_preferences_android
sha256: "955e9736a12ba776bdd261cf030232b30eadfcd9c79b32a3250dd4a494e8c8f7"
sha256: a51a4f9375097f94df1c6e0a49c0374440d31ab026b59d58a7e7660675879db4
url: "https://pub.dev"
source: hosted
version: "2.0.15"
version: "2.0.16"
shared_preferences_foundation:
dependency: transitive
description:
name: shared_preferences_foundation
sha256: "2b55c18636a4edc529fa5cd44c03d3f3100c00513f518c5127c951978efcccd0"
sha256: "6b84fdf06b32bb336f972d373cd38b63734f3461ba56ac2ba01b56d052796259"
url: "https://pub.dev"
source: hosted
version: "2.1.3"
version: "2.1.4"
shared_preferences_linux:
dependency: transitive
description:
name: shared_preferences_linux
sha256: f8ea038aa6da37090093974ebdcf4397010605fd2ff65c37a66f9d28394cb874
sha256: d7fb71e6e20cd3dfffcc823a28da3539b392e53ed5fc5c2b90b55fdaa8a7e8fa
url: "https://pub.dev"
source: hosted
version: "2.1.3"
version: "2.1.4"
shared_preferences_platform_interface:
dependency: transitive
description:
name: shared_preferences_platform_interface
sha256: da9431745ede5ece47bc26d5d73a9d3c6936ef6945c101a5aca46f62e52c1cf3
sha256: "824bfd02713e37603b2bdade0842e47d56e7db32b1dcdd1cae533fb88e2913fc"
url: "https://pub.dev"
source: hosted
version: "2.1.0"
version: "2.1.1"
shared_preferences_web:
dependency: transitive
description:
name: shared_preferences_web
sha256: a4b5bc37fe1b368bbc81f953197d55e12f49d0296e7e412dfe2d2d77d6929958
sha256: "6737b757e49ba93de2a233df229d0b6a87728cea1684da828cbc718b65dcf9d7"
url: "https://pub.dev"
source: hosted
version: "2.0.4"
version: "2.0.5"
shared_preferences_windows:
dependency: transitive
description:
name: shared_preferences_windows
sha256: "5eaf05ae77658d3521d0e993ede1af962d4b326cd2153d312df716dc250f00c9"
sha256: bd014168e8484837c39ef21065b78f305810ceabc1d4f90be6e3b392ce81b46d
url: "https://pub.dev"
source: hosted
version: "2.1.3"
version: "2.1.4"
sky_engine:
dependency: transitive
description: flutter
@ -750,66 +750,66 @@ packages:
dependency: "direct main"
description:
name: url_launcher
sha256: "698fa0b4392effdc73e9e184403b627362eb5fbf904483ac9defbb1c2191d809"
sha256: "75f2846facd11168d007529d6cd8fcb2b750186bea046af9711f10b907e1587e"
url: "https://pub.dev"
source: hosted
version: "6.1.8"
version: "6.1.10"
url_launcher_android:
dependency: transitive
description:
name: url_launcher_android
sha256: "3e2f6dfd2c7d9cd123296cab8ef66cfc2c1a13f5845f42c7a0f365690a8a7dd1"
sha256: "1f4d9ebe86f333c15d318f81dcdc08b01d45da44af74552608455ebdc08d9732"
url: "https://pub.dev"
source: hosted
version: "6.0.23"
version: "6.0.24"
url_launcher_ios:
dependency: transitive
description:
name: url_launcher_ios
sha256: bb328b24d3bccc20bdf1024a0990ac4f869d57663660de9c936fb8c043edefe3
sha256: c9cd648d2f7ab56968e049d4e9116f96a85517f1dd806b96a86ea1018a3a82e5
url: "https://pub.dev"
source: hosted
version: "6.0.18"
version: "6.1.1"
url_launcher_linux:
dependency: transitive
description:
name: url_launcher_linux
sha256: "318c42cba924e18180c029be69caf0a1a710191b9ec49bb42b5998fdcccee3cc"
sha256: e29039160ab3730e42f3d811dc2a6d5f2864b90a70fb765ea60144b03307f682
url: "https://pub.dev"
source: hosted
version: "3.0.2"
version: "3.0.3"
url_launcher_macos:
dependency: transitive
description:
name: url_launcher_macos
sha256: "41988b55570df53b3dd2a7fc90c76756a963de6a8c5f8e113330cb35992e2094"
sha256: "2dddb3291a57b074dade66b5e07e64401dd2487caefd4e9e2f467138d8c7eb06"
url: "https://pub.dev"
source: hosted
version: "3.0.2"
version: "3.0.3"
url_launcher_platform_interface:
dependency: transitive
description:
name: url_launcher_platform_interface
sha256: "4eae912628763eb48fc214522e58e942fd16ce195407dbf45638239523c759a6"
sha256: "6c9ca697a5ae218ce56cece69d46128169a58aa8653c1b01d26fcd4aad8c4370"
url: "https://pub.dev"
source: hosted
version: "2.1.1"
version: "2.1.2"
url_launcher_web:
dependency: transitive
description:
name: url_launcher_web
sha256: "44d79408ce9f07052095ef1f9a693c258d6373dc3944249374e30eff7219ccb0"
sha256: "574cfbe2390666003c3a1d129bdc4574aaa6728f0c00a4829a81c316de69dd9b"
url: "https://pub.dev"
source: hosted
version: "2.0.14"
version: "2.0.15"
url_launcher_windows:
dependency: transitive
description:
name: url_launcher_windows
sha256: b6217370f8eb1fd85c8890c539f5a639a01ab209a36db82c921ebeacefc7a615
sha256: "97c9067950a0d09cbd93e2e3f0383d1403989362b97102fbf446473a48079a4b"
url: "https://pub.dev"
source: hosted
version: "3.0.3"
version: "3.0.4"
uuid:
dependency: transitive
description:
@ -830,34 +830,34 @@ packages:
dependency: "direct main"
description:
name: webview_flutter
sha256: f7ec234830f86d0ef2bd664e8460b0038b8c1a83ff076035cad74ac70273753c
sha256: "9ba213434f13e760ea0f175fbc4d6bb6aeafd7dfc6c7d973f15d3e47a5d6686e"
url: "https://pub.dev"
source: hosted
version: "4.0.2"
version: "4.0.5"
webview_flutter_android:
dependency: transitive
description:
name: webview_flutter_android
sha256: "5f49a6e5fc59e21fcec5e1bbcd401afbee9792a24a4f3d9cef9b5bb0cd1e3767"
sha256: "48c8cfb023168473c0a3a4c21ffea6c23a32cc7156701c39f618b303c6a3c96e"
url: "https://pub.dev"
source: hosted
version: "3.2.4"
version: "3.3.1"
webview_flutter_platform_interface:
dependency: transitive
description:
name: webview_flutter_platform_interface
sha256: "8b2262dda5d26eabc600a7282a8c16a9473a0c765526afb0ffc33eef912f7968"
sha256: df6472164b3f4eaf3280422227f361dc8424b106726b7f21d79a8656ba53f71f
url: "https://pub.dev"
source: hosted
version: "2.0.1"
version: "2.0.2"
webview_flutter_wkwebview:
dependency: transitive
description:
name: webview_flutter_wkwebview
sha256: "92e7e7fa468f1df597fb9d37bcf1f303175cbe147c4dbdf06ecc323d950116eb"
sha256: "283a38c2a2544768033864c698e0133aa9eee0f2c800f494b538a3d1044f7ecb"
url: "https://pub.dev"
source: hosted
version: "3.0.5"
version: "3.1.1"
win32:
dependency: transitive
description:

View File

@ -17,7 +17,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
# In Windows, build-name is used as the major, minor, and patch parts
# of the product and file versions while build-number is used as the build suffix.
version: 0.10.7+113 # When changing this, update the tag in main() accordingly
version: 0.11.3+122 # When changing this, update the tag in main() accordingly
environment:
sdk: '>=2.18.2 <3.0.0'
@ -64,7 +64,7 @@ dependencies:
dev_dependencies:
flutter_test:
sdk: flutter
flutter_launcher_icons: ^0.11.0
flutter_launcher_icons: ^0.12.0
# The "flutter_lints" package below contains a set of recommended lints to
# encourage good coding practices. The lint set provided by the package is