Compare commits

...

77 Commits

Author SHA1 Message Date
5f3eeb9971 Merge pull request #333 from ImranR98/dev
Increment version
2023-02-25 15:57:28 -05:00
e67a6b8627 Increment version 2023-02-25 15:57:04 -05:00
f8e99bb0cb Merge pull request #329 from bluefly000/japanese-translation
Update Japanese translation
2023-02-25 15:55:44 -05:00
09b5dd41d3 Merge pull request #331 from gidano/main
Update hu.json
2023-02-25 15:55:38 -05:00
b1bd36408c Merge pull request #332 from mehdijahann/main
Update fa.json
2023-02-25 15:55:26 -05:00
54d8dff32f Update fa.json 2023-02-25 20:52:22 +03:30
7b1416e28e Update hu.json 2023-02-25 11:17:17 +01:00
926e7b89ce Update Japanese translation 2023-02-25 13:57:07 +09:00
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 909 additions and 381 deletions

View File

@ -22,6 +22,15 @@ Currently supported App sources:
- "HTML" (Fallback) - "HTML" (Fallback)
- Any other URL that returns an HTML page with links to APK files (if multiple, the last file alphabetically is picked) - 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 ## 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. - 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. - Auto (unattended) updates are unsupported due to a lack of any capable Flutter plugin.

View File

@ -207,12 +207,19 @@
"addCategory": "Kategorie hinzufügen", "addCategory": "Kategorie hinzufügen",
"label": "Bezeichnung", "label": "Bezeichnung",
"language": "Sprache", "language": "Sprache",
"storagePermissionDenied": "Storage permission denied", "storagePermissionDenied": "Speicherberechtigung verweigert",
"selectedCategorizeWarning": "This will replace any existing category settings for the selected Apps.", "selectedCategorizeWarning": "Dadurch werden alle bestehenden Kategorieeinstellungen für die ausgewählten Apps ersetzt.",
"filterAPKsByRegEx": "Filter APKs by Regular Expression", "filterAPKsByRegEx": "APKs nach regulärem Ausdruck filtern",
"removeFromObtainium": "Remove from Obtainium", "removeFromObtainium": "Aus Obtainium entfernen",
"uninstallFromDevice": "Uninstall from Device", "uninstallFromDevice": "Vom Gerät deinstallieren",
"onlyWorksWithNonVersionDetectApps": "Only works for Apps with version detection disabled.", "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": { "removeAppQuestion": {
"one": "App entfernen?", "one": "App entfernen?",
"other": "App entfernen?" "other": "App entfernen?"

View File

@ -213,6 +213,13 @@
"removeFromObtainium": "Remove from Obtainium", "removeFromObtainium": "Remove from Obtainium",
"uninstallFromDevice": "Uninstall from Device", "uninstallFromDevice": "Uninstall from Device",
"onlyWorksWithNonVersionDetectApps": "Only works for Apps with version detection disabled.", "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": { "removeAppQuestion": {
"one": "Remove App?", "one": "Remove App?",
"other": "Remove Apps?" "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": "از تاریخ انتشار به عنوان نسخه استفاده کنید",
"releaseDateAsVersionExplanation": "این گزینه فقط باید برای برنامه هایی استفاده شود که تشخیص نسخه به درستی کار نمی کند، اما تاریخ انتشار در دسترس است.",
"changes": "تغییرات",
"releaseDate": "تاریخ انتشار",
"importFromURLsInFile": "وارد کردن از آدرس های اینترنتی موجود در فایل (مانند OPML)",
"versionDetection": "تشخیص نسخه",
"standardVersionDetection": "تشخیص نسخه استاندارد",
"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", "githubStarredRepos": "GitHub Csillagos Repo-k",
"uname": "Felh.név", "uname": "Felh.név",
"wrongArgNum": "Rossz számú argumentumot adott meg", "wrongArgNum": "Rossz számú argumentumot adott meg",
"xIsTrackOnly": "A(z) {} csak nyomkövethető", "xIsTrackOnly": "A(z) {} csak nyomonkövethető",
"source": "Forrás", "source": "Forrás",
"app": "App", "app": "App",
"appsFromSourceAreTrackOnly": "Az ebből a forrásból származó alkalmazások 'Csak nyomon követhetőek'.", "appsFromSourceAreTrackOnly": "Az ebből a forrásból származó alkalmazások 'Csak nyomon követhetőek'.",
@ -56,7 +56,7 @@
"appsString": "Appok", "appsString": "Appok",
"noApps": "Nincs App", "noApps": "Nincs App",
"noAppsForFilter": "Nincsenek appok a szűrőhöz", "noAppsForFilter": "Nincsenek appok a szűrőhöz",
"byX": "{} által", "byX": "Fejlesztő: {}",
"percentProgress": "Folyamat: {}%", "percentProgress": "Folyamat: {}%",
"pleaseWait": "Kis türelmet", "pleaseWait": "Kis türelmet",
"updateAvailable": "Frissítés érhető el", "updateAvailable": "Frissítés érhető el",
@ -78,7 +78,7 @@
"no": "Nem", "no": "Nem",
"yes": "Igen", "yes": "Igen",
"markSelectedAppsUpdated": "Jelölje meg a kiválasztott appokat frissítettként", "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", "unpinFromTop": "Eltávolít felülről",
"resetInstallStatusForSelectedAppsQuestion": "Visszaállítja a kiválasztott appok telepítési állapotát?", "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.", "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.",
@ -191,7 +191,7 @@
"update": "Frissít", "update": "Frissít",
"markUpdated": "Frissítettnek jelöl", "markUpdated": "Frissítettnek jelöl",
"additionalOptions": "További lehetőségek", "additionalOptions": "További lehetőségek",
"disableVersionDetection": "Verzióérzékelés letiltása", "disableVersionDetection": "Verzió érzékelés letiltása",
"noVersionDetectionExplanation": "Ezt a beállítást csak olyan alkalmazásoknál szabad használni, ahol a verzióérzékelés nem működik megfelelően.", "noVersionDetectionExplanation": "Ezt a beállítást csak olyan alkalmazásoknál szabad használni, ahol a verzióérzékelés nem működik megfelelően.",
"downloadingX": "{} letöltés", "downloadingX": "{} letöltés",
"downloadNotifDescription": "Értesíti a felhasználót az app letöltésének előrehaladásáról", "downloadNotifDescription": "Értesíti a felhasználót az app letöltésének előrehaladásáról",
@ -208,10 +208,17 @@
"language": "Nyelv", "language": "Nyelv",
"storagePermissionDenied": "Tárhely engedély megtagadva", "storagePermissionDenied": "Tárhely engedély megtagadva",
"selectedCategorizeWarning": "Ez felváltja a kiválasztott alkalmazások meglévő kategória-beállításait.", "selectedCategorizeWarning": "Ez felváltja a kiválasztott alkalmazások meglévő kategória-beállításait.",
"filterAPKsByRegEx": "Filter APKs by Regular Expression", "filterAPKsByRegEx": "Az APK-k szűrése reguláris kifejezéssel",
"removeFromObtainium": "Remove from Obtainium", "removeFromObtainium": "Eltávolítás az Obtainiumból",
"uninstallFromDevice": "Uninstall from Device", "uninstallFromDevice": "Eltávolítás a készülékről",
"onlyWorksWithNonVersionDetectApps": "Only works for Apps with version detection disabled.", "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álás fájlban található URL-ből (mint pl. OPML)",
"versionDetection": "Verzió érzékelés",
"standardVersionDetection": "Alapért. verzió érzékelés",
"removeAppQuestion": { "removeAppQuestion": {
"one": "Eltávolítja az alkalmazást?", "one": "Eltávolítja az alkalmazást?",
"other": "Eltávolítja az alkalmazást?" "other": "Eltávolítja az alkalmazást?"

View File

@ -56,9 +56,9 @@
"appsString": "App", "appsString": "App",
"noApps": "Nessuna App", "noApps": "Nessuna App",
"noAppsForFilter": "Nessuna App per i filtri selezionati", "noAppsForFilter": "Nessuna App per i filtri selezionati",
"byX": "Da {}", "byX": "Di {}",
"percentProgress": "Progresso: {}%", "percentProgress": "Progresso: {}%",
"pleaseWait": "Attendere prego", "pleaseWait": "In attesa",
"updateAvailable": "Aggiornamento disponibile", "updateAvailable": "Aggiornamento disponibile",
"estimateInBracketsShort": "(prev.)", "estimateInBracketsShort": "(prev.)",
"notInstalled": "Non installato", "notInstalled": "Non installato",
@ -94,7 +94,7 @@
"author": "Autore", "author": "Autore",
"upToDateApps": "App aggiornate", "upToDateApps": "App aggiornate",
"nonInstalledApps": "App non installate", "nonInstalledApps": "App non installate",
"importExport": "Importa - Esporta", "importExport": "Importa/Esporta",
"settings": "Impostazioni", "settings": "Impostazioni",
"exportedTo": "Esportato in {}", "exportedTo": "Esportato in {}",
"obtainiumExport": "Esporta da Obtainium", "obtainiumExport": "Esporta da Obtainium",
@ -207,15 +207,22 @@
"addCategory": "Aggiungi categoria", "addCategory": "Aggiungi categoria",
"label": "Etichetta", "label": "Etichetta",
"language": "Lingua", "language": "Lingua",
"storagePermissionDenied": "Storage permission denied", "storagePermissionDenied": "Accesso ai file non autorizzato",
"selectedCategorizeWarning": "This will replace any existing category settings for the selected Apps.", "selectedCategorizeWarning": "Ciò sostituirà le impostazioni di categoria esistenti per le App selezionate.",
"filterAPKsByRegEx": "Filter APKs by Regular Expression", "filterAPKsByRegEx": "Filtra file APK con espressioni regolari",
"removeFromObtainium": "Remove from Obtainium", "removeFromObtainium": "Rimuovi da Obtainium",
"uninstallFromDevice": "Uninstall from Device", "uninstallFromDevice": "Disinstalla dal dispositivo",
"onlyWorksWithNonVersionDetectApps": "Only works for Apps with version detection disabled.", "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": { "removeAppQuestion": {
"one": "Rimuovere l'App?", "one": "Rimuovere l'App?",
"other": "Rimuovere l'App?" "other": "Rimuovere le App?"
}, },
"tooManyRequestsTryAgainInMinutes": { "tooManyRequestsTryAgainInMinutes": {
"one": "Troppe richieste (traffico limitato) - riprova tra {} minuto", "one": "Troppe richieste (traffico limitato) - riprova tra {} minuto",

View File

@ -107,7 +107,7 @@
"line": "行", "line": "行",
"searchX": "{}で検索", "searchX": "{}で検索",
"noResults": "結果は見つかりませんでした", "noResults": "結果は見つかりませんでした",
"importX": "{}をインポートする", "importX": "{}をインポート",
"importedAppsIdDisclaimer": "インポートしたアプリが「未インストール」と表示されることがあります。\nこれを解決するには、Obtainiumから再インストールしてください。\nアプリのデータには影響しません。\n\nURLとサードパーティのインポートメソッドにのみ影響します。", "importedAppsIdDisclaimer": "インポートしたアプリが「未インストール」と表示されることがあります。\nこれを解決するには、Obtainiumから再インストールしてください。\nアプリのデータには影響しません。\n\nURLとサードパーティのインポートメソッドにのみ影響します。",
"importErrors": "インポートエラー", "importErrors": "インポートエラー",
"importedXOfYApps": "{} / {} アプリをインポートしました", "importedXOfYApps": "{} / {} アプリをインポートしました",
@ -210,9 +210,16 @@
"storagePermissionDenied": "ストレージ権限が拒否されました", "storagePermissionDenied": "ストレージ権限が拒否されました",
"selectedCategorizeWarning": "これにより、選択したアプリの既存のカテゴリ設定がすべて置き換えられます。", "selectedCategorizeWarning": "これにより、選択したアプリの既存のカテゴリ設定がすべて置き換えられます。",
"filterAPKsByRegEx": "正規表現でAPKを絞り込む", "filterAPKsByRegEx": "正規表現でAPKを絞り込む",
"removeFromObtainium": "Remove from Obtainium", "removeFromObtainium": "Obtainiumから削除する",
"uninstallFromDevice": "Uninstall from Device", "uninstallFromDevice": "デバイスからアンインストールする",
"onlyWorksWithNonVersionDetectApps": "Only works for Apps with version detection disabled.", "onlyWorksWithNonVersionDetectApps": "バージョン検出を無効にしているアプリにのみ動作します。",
"releaseDateAsVersion": "リリース日をバージョンとして使用する",
"releaseDateAsVersionExplanation": "このオプションは、バージョン検出が正しく機能しないアプリで、リリース日が利用可能な場合にのみ使用する必要があります。",
"changes": "変更点",
"releaseDate": "リリース日",
"importFromURLsInFile": "ファイルOPMLなど内のURLからインポート",
"versionDetection": "バージョン検出",
"standardVersionDetection": "標準のバージョン検出",
"removeAppQuestion": { "removeAppQuestion": {
"one": "アプリを削除しますか?", "one": "アプリを削除しますか?",
"other": "アプリを削除しますか?" "other": "アプリを削除しますか?"

View File

@ -213,6 +213,13 @@
"filterAPKsByRegEx": "Filter APKs by Regular Expression", "filterAPKsByRegEx": "Filter APKs by Regular Expression",
"removeFromObtainium": "Remove from Obtainium", "removeFromObtainium": "Remove from Obtainium",
"uninstallFromDevice": "Uninstall from Device", "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": { "removeAppQuestion": {
"one": "删除应用?", "one": "删除应用?",
"other": "删除应用?" "other": "删除应用?"

View File

@ -1,5 +1,9 @@
import 'dart:io';
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/components/generated_form.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';
@ -7,6 +11,23 @@ class APKMirror extends AppSource {
APKMirror() { APKMirror() {
host = 'apkmirror.com'; host = 'apkmirror.com';
enforceTrackOnly = true; enforceTrackOnly = true;
additionalSourceAppSpecificSettingFormItems = [
[
GeneratedFormSwitch('fallbackToOlderReleases',
label: tr('fallbackToOlderReleases'), defaultValue: true)
],
[
GeneratedFormTextField('filterReleaseTitlesByRegEx',
label: tr('filterReleaseTitlesByRegEx'),
required: false,
additionalValidators: [
(value) {
return regExValidator(value);
}
])
]
];
} }
@override @override
@ -28,12 +49,38 @@ class APKMirror extends AppSource {
String standardUrl, String standardUrl,
Map<String, dynamic> additionalSettings, Map<String, dynamic> additionalSettings,
) async { ) 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')); Response res = await get(Uri.parse('$standardUrl/feed'));
if (res.statusCode == 200) { if (res.statusCode == 200) {
String? titleString = parse(res.body) var items = parse(res.body).querySelectorAll('item');
.querySelector('item') dynamic targetRelease;
?.querySelector('title') for (int i = 0; i < items.length; i++) {
?.innerHtml; 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 String? version = titleString
?.substring(RegExp('[0-9]').firstMatch(titleString)?.start ?? 0, ?.substring(RegExp('[0-9]').firstMatch(titleString)?.start ?? 0,
RegExp(' by ').firstMatch(titleString)?.start ?? 0) RegExp(' by ').firstMatch(titleString)?.start ?? 0)
@ -44,7 +91,8 @@ class APKMirror extends AppSource {
if (version == null || version.isEmpty) { if (version == null || version.isEmpty) {
throw NoVersionError(); throw NoVersionError();
} }
return APKDetails(version, [], getAppNames(standardUrl)); return APKDetails(version, [], getAppNames(standardUrl),
releaseDate: releaseDate);
} else { } else {
throw getObtainiumHttpError(res); throw getObtainiumHttpError(res);
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -10,7 +10,10 @@ class SteamMobile extends AppSource {
host = 'store.steampowered.com'; host = 'store.steampowered.com';
name = tr('steam'); name = tr('steam');
additionalSourceAppSpecificSettingFormItems = [ 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) { if (apkNamePrefix == null) {
throw NoReleasesError(); throw NoReleasesError();
} }
String apkInURLRegexPattern = '/$apkNamePrefix-[^/]+\\.apk\$'; String apkInURLRegexPattern =
'/$apkNamePrefix-([0-9]+\\.)*[0-9]+\\.apk\$';
var links = parse(res.body) var links = parse(res.body)
.querySelectorAll('a') .querySelectorAll('a')
.map((e) => e.attributes['href'] ?? '') .map((e) => e.attributes['href'] ?? '')

View File

@ -21,7 +21,7 @@ import 'package:easy_localization/src/easy_localization_controller.dart';
// ignore: implementation_imports // ignore: implementation_imports
import 'package:easy_localization/src/localization.dart'; import 'package:easy_localization/src/localization.dart';
const String currentVersion = '0.10.7'; const String currentVersion = '0.11.4';
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
@ -33,7 +33,8 @@ const supportedLocales = [
Locale('it'), Locale('it'),
Locale('ja'), Locale('ja'),
Locale('hu'), Locale('hu'),
Locale('de') Locale('de'),
Locale('fa')
]; ];
const fallbackLocale = Locale('en'); const fallbackLocale = Locale('en');
const localeDir = 'assets/translations'; const localeDir = 'assets/translations';

View File

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

View File

@ -42,8 +42,6 @@ class _AppPageState extends State<AppPage> {
getUpdate(app.app.id); getUpdate(app.app.id);
} }
var trackOnly = app?.app.additionalSettings['trackOnly'] == true; var trackOnly = app?.app.additionalSettings['trackOnly'] == true;
var noVersionDetection =
app?.app.additionalSettings['noVersionDetection'] == true;
var infoColumn = Column( var infoColumn = Column(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
@ -113,7 +111,7 @@ class _AppPageState extends State<AppPage> {
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.stretch, crossAxisAlignment: CrossAxisAlignment.stretch,
children: [ children: [
const SizedBox(height: 150), const SizedBox(height: 125),
app?.installedInfo != null app?.installedInfo != null
? Row(mainAxisAlignment: MainAxisAlignment.center, children: [ ? Row(mainAxisAlignment: MainAxisAlignment.center, children: [
Image.memory( Image.memory(
@ -136,6 +134,21 @@ class _AppPageState extends State<AppPage> {
textAlign: TextAlign.center, textAlign: TextAlign.center,
style: Theme.of(context).textTheme.headlineMedium, 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( const SizedBox(
height: 32, height: 32,
), ),
@ -192,7 +205,8 @@ class _AppPageState extends State<AppPage> {
child: Row( child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly, mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [ children: [
if (noVersionDetection && if (app?.app.additionalSettings['versionDetection'] !=
'standardVersionDetection' &&
!trackOnly && !trackOnly &&
app?.app.installedVersion != null && app?.app.installedVersion != null &&
app?.app.installedVersion != app?.app.latestVersion) app?.app.installedVersion != app?.app.latestVersion)
@ -268,19 +282,49 @@ class _AppPageState extends State<AppPage> {
); );
}).then((values) { }).then((values) {
if (app != null && values != null) { if (app != null && values != null) {
var changedApp = app.app; Map<String, dynamic>
changedApp.additionalSettings = originalSettings =
values; app.app.additionalSettings;
app.app.additionalSettings = values;
if (source.enforceTrackOnly) { if (source.enforceTrackOnly) {
changedApp.additionalSettings[ app.app.additionalSettings[
'trackOnly'] = true; 'trackOnly'] = true;
showError( showError(
tr('appsFromSourceAreTrackOnly'), tr('appsFromSourceAreTrackOnly'),
context); context);
} }
appsProvider.saveApps( if (app.app.additionalSettings[
[changedApp]).then((value) { 'versionDetection'] ==
getUpdate(changedApp.id); '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) { if (res.isNotEmpty && mounted) {
Navigator.of(context).pop(); Navigator.of(context).pop();
} }
}).catchError((e) {
showError(e, context);
}); });
}).catchError((e) { }).catchError((e) {
showError(e, context); 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) { Widget build(BuildContext context) {
var appsProvider = context.watch<AppsProvider>(); var appsProvider = context.watch<AppsProvider>();
var settingsProvider = context.watch<SettingsProvider>(); var settingsProvider = context.watch<SettingsProvider>();
var sortedApps = appsProvider.apps.values.toList(); var listedApps = appsProvider.apps.values.toList();
var currentFilterIsUpdatesOnly = var currentFilterIsUpdatesOnly =
filter.isIdenticalTo(updatesOnlyFilter, settingsProvider); filter.isIdenticalTo(updatesOnlyFilter, settingsProvider);
selectedApps = selectedApps selectedApps = selectedApps
.where((element) => sortedApps.map((e) => e.app).contains(element)) .where((element) => listedApps.map((e) => e.app).contains(element))
.toSet(); .toSet();
toggleAppSelected(App app) { 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 && if (app.app.installedVersion == app.app.latestVersion &&
!(filter.includeUptodate)) { !(filter.includeUptodate)) {
return false; return false;
@ -111,7 +111,7 @@ class AppsPageState extends State<AppsPage> {
return true; return true;
}).toList(); }).toList();
sortedApps.sort((a, b) { listedApps.sort((a, b) {
var nameA = a.installedInfo?.name ?? a.app.name; var nameA = a.installedInfo?.name ?? a.app.name;
var nameB = b.installedInfo?.name ?? b.app.name; var nameB = b.installedInfo?.name ?? b.app.name;
int result = 0; int result = 0;
@ -119,25 +119,30 @@ class AppsPageState extends State<AppsPage> {
result = (a.app.author + nameA).compareTo(b.app.author + nameB); result = (a.app.author + nameA).compareTo(b.app.author + nameB);
} else if (settingsProvider.sortColumn == SortColumnSettings.nameAuthor) { } else if (settingsProvider.sortColumn == SortColumnSettings.nameAuthor) {
result = (nameA + a.app.author).compareTo(nameB + b.app.author); 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; return result;
}); });
if (settingsProvider.sortOrder == SortOrderSettings.descending) { if (settingsProvider.sortOrder == SortOrderSettings.descending) {
sortedApps = sortedApps.reversed.toList(); listedApps = listedApps.reversed.toList();
} }
var existingUpdates = appsProvider.findExistingUpdates(installedOnly: true); var existingUpdates = appsProvider.findExistingUpdates(installedOnly: true);
var existingUpdateIdsAllOrSelected = existingUpdates var existingUpdateIdsAllOrSelected = existingUpdates
.where((element) => selectedApps.isEmpty .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)) : selectedApps.map((e) => e.id).contains(element))
.toList(); .toList();
var newInstallIdsAllOrSelected = appsProvider var newInstallIdsAllOrSelected = appsProvider
.findExistingUpdates(nonInstalledOnly: true) .findExistingUpdates(nonInstalledOnly: true)
.where((element) => selectedApps.isEmpty .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)) : selectedApps.map((e) => e.id).contains(element))
.toList(); .toList();
@ -159,26 +164,26 @@ class AppsPageState extends State<AppsPage> {
if (settingsProvider.pinUpdates) { if (settingsProvider.pinUpdates) {
var temp = []; var temp = [];
sortedApps = sortedApps.where((sa) { listedApps = listedApps.where((sa) {
if (existingUpdates.contains(sa.app.id)) { if (existingUpdates.contains(sa.app.id)) {
temp.add(sa); temp.add(sa);
return false; return false;
} }
return true; return true;
}).toList(); }).toList();
sortedApps = [...temp, ...sortedApps]; listedApps = [...temp, ...listedApps];
} }
var tempPinned = []; var tempPinned = [];
var tempNotPinned = []; var tempNotPinned = [];
for (var a in sortedApps) { for (var a in listedApps) {
if (a.app.pinned) { if (a.app.pinned) {
tempPinned.add(a); tempPinned.add(a);
} else { } else {
tempNotPinned.add(a); tempNotPinned.add(a);
} }
} }
sortedApps = [...tempPinned, ...tempNotPinned]; listedApps = [...tempPinned, ...tempNotPinned];
return Scaffold( return Scaffold(
backgroundColor: Theme.of(context).colorScheme.surface, backgroundColor: Theme.of(context).colorScheme.surface,
@ -198,7 +203,7 @@ class AppsPageState extends State<AppsPage> {
}, },
child: CustomScrollView(slivers: <Widget>[ child: CustomScrollView(slivers: <Widget>[
CustomAppBar(title: tr('appsString')), CustomAppBar(title: tr('appsString')),
if (appsProvider.loadingApps || sortedApps.isEmpty) if (appsProvider.loadingApps || listedApps.isEmpty)
SliverFillRemaining( SliverFillRemaining(
child: Center( child: Center(
child: appsProvider.loadingApps child: appsProvider.loadingApps
@ -225,8 +230,8 @@ class AppsPageState extends State<AppsPage> {
delegate: SliverChildBuilderDelegate( delegate: SliverChildBuilderDelegate(
(BuildContext context, int index) { (BuildContext context, int index) {
String? changesUrl = SourceProvider() String? changesUrl = SourceProvider()
.getSource(sortedApps[index].app.url) .getSource(listedApps[index].app.url)
.changeLogPageFromStandardUrl(sortedApps[index].app.url); .changeLogPageFromStandardUrl(listedApps[index].app.url);
var transparent = const Color.fromARGB(0, 0, 0, 0).value; var transparent = const Color.fromARGB(0, 0, 0, 0).value;
return Container( return Container(
decoration: BoxDecoration( decoration: BoxDecoration(
@ -234,52 +239,54 @@ class AppsPageState extends State<AppsPage> {
vertical: BorderSide( vertical: BorderSide(
width: 4, width: 4,
color: Color( color: Color(
sortedApps[index].app.categories.isNotEmpty listedApps[index].app.categories.isNotEmpty
? settingsProvider.categories[ ? settingsProvider.categories[
sortedApps[index] listedApps[index]
.app .app
.categories .categories
.first] ?? .first] ??
transparent transparent
: transparent)))), : transparent)))),
child: ListTile( child: ListTile(
tileColor: sortedApps[index].app.pinned tileColor: listedApps[index].app.pinned
? Colors.grey.withOpacity(0.1) ? Colors.grey.withOpacity(0.1)
: Colors.transparent, : Colors.transparent,
selectedTileColor: Theme.of(context) selectedTileColor: Theme.of(context)
.colorScheme .colorScheme
.primary .primary
.withOpacity(sortedApps[index].app.pinned ? 0.2 : 0.1), .withOpacity(listedApps[index].app.pinned ? 0.2 : 0.1),
selected: selectedApps.contains(sortedApps[index].app), selected: selectedApps.contains(listedApps[index].app),
onLongPress: () { onLongPress: () {
toggleAppSelected(sortedApps[index].app); toggleAppSelected(listedApps[index].app);
}, },
leading: sortedApps[index].installedInfo != null leading: listedApps[index].installedInfo != null
? Image.memory( ? Image.memory(
sortedApps[index].installedInfo!.icon!, listedApps[index].installedInfo!.icon!,
gaplessPlayback: true, gaplessPlayback: true,
) )
: null, : null,
title: Text( title: Text(
sortedApps[index].installedInfo?.name ?? maxLines: 1,
sortedApps[index].app.name, listedApps[index].installedInfo?.name ??
listedApps[index].app.name,
style: TextStyle( style: TextStyle(
fontWeight: sortedApps[index].app.pinned overflow: TextOverflow.ellipsis,
fontWeight: listedApps[index].app.pinned
? FontWeight.bold ? FontWeight.bold
: FontWeight.normal, : FontWeight.normal,
), ),
), ),
subtitle: Text( subtitle: Text(
tr('byX', args: [sortedApps[index].app.author]), tr('byX', args: [listedApps[index].app.author]),
style: TextStyle( style: TextStyle(
fontWeight: sortedApps[index].app.pinned fontWeight: listedApps[index].app.pinned
? FontWeight.bold ? FontWeight.bold
: FontWeight.normal)), : FontWeight.normal)),
trailing: SingleChildScrollView( trailing: SingleChildScrollView(
reverse: true, reverse: true,
child: sortedApps[index].downloadProgress != null child: listedApps[index].downloadProgress != null
? Text(tr('percentProgress', args: [ ? Text(tr('percentProgress', args: [
sortedApps[index] listedApps[index]
.downloadProgress .downloadProgress
?.toInt() ?.toInt()
.toString() ?? .toString() ??
@ -289,60 +296,104 @@ class AppsPageState extends State<AppsPage> {
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.end, crossAxisAlignment: CrossAxisAlignment.end,
children: [ children: [
SizedBox( Row(
width: 100, mainAxisSize: MainAxisSize.min,
children: [
Container(
constraints: const BoxConstraints(
maxWidth: 150),
child: Text(
'${listedApps[index].app.installedVersion ?? tr('notInstalled')}${listedApps[index].app.additionalSettings['trackOnly'] == true ? ' ${tr('estimateInBrackets')}' : ''}',
overflow: TextOverflow.ellipsis,
textAlign: TextAlign.end,
))
]),
GestureDetector(
onTap: changesUrl == null
? null
: () {
launchUrlString(changesUrl,
mode: LaunchMode
.externalApplication);
},
child: Text( child: Text(
'${sortedApps[index].app.installedVersion ?? tr('notInstalled')}${sortedApps[index].app.additionalSettings['trackOnly'] == true ? ' ${tr('estimateInBrackets')}' : ''}', listedApps[index].app.releaseDate ==
overflow: TextOverflow.fade, null
textAlign: TextAlign.end, ? tr('changes')
: DateFormat('yyyy-MM-dd').format(
listedApps[index]
.app
.releaseDate!),
style: const TextStyle(
fontStyle: FontStyle.italic,
decoration:
TextDecoration.underline),
)), )),
sortedApps[index].app.installedVersion != listedApps[index].app.installedVersion !=
null && null &&
sortedApps[index] listedApps[index]
.app .app
.installedVersion != .installedVersion !=
sortedApps[index] listedApps[index]
.app .app
.latestVersion .latestVersion
? GestureDetector( ? appsProvider.areDownloadsRunning()
onTap: changesUrl == null ? Text(tr('pleaseWait'))
? null : Row(
: () { mainAxisSize: MainAxisSize.min,
launchUrlString(changesUrl, mainAxisAlignment:
mode: LaunchMode MainAxisAlignment.end,
.externalApplication); children: [
}, GestureDetector(
child: appsProvider onTap: () {
.areDownloadsRunning() appsProvider
? Text(tr('pleaseWait')) .downloadAndInstallLatestApps(
: Text( [
'${tr('updateAvailable')}${sortedApps[index].app.additionalSettings['trackOnly'] == true ? ' ${tr('estimateInBracketsShort')}' : ''}', listedApps[index]
style: TextStyle( .app
fontStyle: .id
FontStyle.italic, ],
decoration: changesUrl == globalNavigatorKey
null .currentContext).catchError(
? TextDecoration.none (e) {
: TextDecoration showError(e, context);
.underline), });
)) },
: const SizedBox(), 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: () { onTap: () {
if (selectedApps.isNotEmpty) { if (selectedApps.isNotEmpty) {
toggleAppSelected(sortedApps[index].app); toggleAppSelected(listedApps[index].app);
} else { } else {
Navigator.push( Navigator.push(
context, context,
MaterialPageRoute( MaterialPageRoute(
builder: (context) => builder: (context) =>
AppPage(appId: sortedApps[index].app.id)), AppPage(appId: listedApps[index].app.id)),
); );
} }
}, },
)); ));
}, childCount: sortedApps.length)) }, childCount: listedApps.length))
])), ])),
persistentFooterButtons: appsProvider.apps.isEmpty persistentFooterButtons: appsProvider.apps.isEmpty
? null ? null
@ -354,20 +405,20 @@ class AppsPageState extends State<AppsPage> {
style: const ButtonStyle( style: const ButtonStyle(
visualDensity: VisualDensity.compact), visualDensity: VisualDensity.compact),
onPressed: () { onPressed: () {
selectThese(sortedApps.map((e) => e.app).toList()); selectThese(listedApps.map((e) => e.app).toList());
}, },
icon: Icon( icon: Icon(
Icons.select_all_outlined, Icons.select_all_outlined,
color: Theme.of(context).colorScheme.primary, color: Theme.of(context).colorScheme.primary,
), ),
label: Text(sortedApps.length.toString())) label: Text(listedApps.length.toString()))
: TextButton.icon( : TextButton.icon(
style: const ButtonStyle( style: const ButtonStyle(
visualDensity: VisualDensity.compact), visualDensity: VisualDensity.compact),
onPressed: () { onPressed: () {
selectedApps.isEmpty selectedApps.isEmpty
? selectThese( ? selectThese(
sortedApps.map((e) => e.app).toList()) listedApps.map((e) => e.app).toList())
: clearSelected(); : clearSelected();
}, },
icon: Icon( icon: Icon(
@ -391,28 +442,6 @@ class AppsPageState extends State<AppsPage> {
: () { : () {
appsProvider.removeAppsWithModal( appsProvider.removeAppsWithModal(
context, selectedApps.toList()); 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'), tooltip: tr('removeSelectedApps'),
icon: const Icon(Icons.delete_outline_outlined), icon: const Icon(Icons.delete_outline_outlined),
@ -675,7 +704,7 @@ class AppsPageState extends State<AppsPage> {
onPressed: () { onPressed: () {
HapticFeedback.selectionClick(); HapticFeedback.selectionClick();
appsProvider.saveApps(selectedApps.map((a) { 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; a.installedVersion = a.latestVersion;
} }
return a; 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( return Scaffold(
backgroundColor: Theme.of(context).colorScheme.surface, backgroundColor: Theme.of(context).colorScheme.surface,
body: CustomScrollView(slivers: <Widget>[ body: CustomScrollView(slivers: <Widget>[
@ -150,88 +210,60 @@ class _ImportExportPageState extends State<ImportExportPage> {
], ],
) )
else else
const Divider( Column(
height: 32, children: [
), const Divider(
TextButton( height: 32,
onPressed: importInProgress ),
? null TextButton(
: () { onPressed: importInProgress
showDialog<Map<String, dynamic>?>( ? null
context: context, : () {
builder: (BuildContext ctx) { urlListImport();
return GeneratedFormModal( },
title: tr('importFromURLList'), child: Text(
items: [ tr('importFromURLList'),
[ )),
GeneratedFormTextField( const SizedBox(height: 8),
'appURLList', TextButton(
label: tr('appURLList'), onPressed: importInProgress
max: 7, ? null
additionalValidators: [ : () {
(dynamic value) { FilePicker.platform
if (value != null && .pickFiles()
value.isNotEmpty) { .then((result) {
var lines = value if (result != null) {
.trim() urlListImport(
.split('\n'); overrideInitValid: true,
for (int i = 0; initValue:
i < lines.length; RegExp('https?://[^"]+')
i++) { .allMatches(File(result
try { .files
sourceProvider .single
.getSource( .path!)
lines[i]); .readAsStringSync())
} catch (e) { .map((e) =>
return '${tr('line')} ${i + 1}: $e'; e.input.substring(
} e.start, e.end))
} .toSet()
} .toList()
return null; .where((url) {
} try {
]) sourceProvider
] .getSource(url);
], return true;
); } catch (e) {
}).then((values) { return false;
if (values != null) { }
var urls = }).join('\n'));
(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;
}); });
}); },
} child: Text(
}); tr('importFromURLsInFile'),
}, )),
child: Text( ],
tr('importFromURLList'), ),
)),
...sourceProvider.sources ...sourceProvider.sources
.where((element) => element.canSearch) .where((element) => element.canSearch)
.map((source) => Column( .map((source) => Column(
@ -280,6 +312,7 @@ class _ImportExportPageState extends State<ImportExportPage> {
if (urlsWithDescriptions if (urlsWithDescriptions
.isNotEmpty) { .isNotEmpty) {
var selectedUrls = var selectedUrls =
// ignore: use_build_context_synchronously
await showDialog< await showDialog<
List< List<
String>?>( String>?>(
@ -314,6 +347,7 @@ class _ImportExportPageState extends State<ImportExportPage> {
]), ]),
context); context);
} else { } else {
// ignore: use_build_context_synchronously
showDialog( showDialog(
context: context, context: context,
builder: builder:
@ -391,6 +425,7 @@ class _ImportExportPageState extends State<ImportExportPage> {
e.toString()) e.toString())
.toList()); .toList());
var selectedUrls = var selectedUrls =
// ignore: use_build_context_synchronously
await showDialog< await showDialog<
List<String>?>( List<String>?>(
context: context, context: context,
@ -418,6 +453,7 @@ class _ImportExportPageState extends State<ImportExportPage> {
]), ]),
context); context);
} else { } else {
// ignore: use_build_context_synchronously
showDialog( showDialog(
context: context, context: context,
builder: builder:

View File

@ -87,6 +87,7 @@ class _SettingsPageState extends State<SettingsPage> {
}); });
var sortDropdown = DropdownButtonFormField( var sortDropdown = DropdownButtonFormField(
isExpanded: true,
decoration: InputDecoration(labelText: tr('appSortBy')), decoration: InputDecoration(labelText: tr('appSortBy')),
value: settingsProvider.sortColumn, value: settingsProvider.sortColumn,
items: [ items: [
@ -101,6 +102,10 @@ class _SettingsPageState extends State<SettingsPage> {
DropdownMenuItem( DropdownMenuItem(
value: SortColumnSettings.added, value: SortColumnSettings.added,
child: Text(tr('asAdded')), child: Text(tr('asAdded')),
),
DropdownMenuItem(
value: SortColumnSettings.releaseDate,
child: Text(tr('releaseDate')),
) )
], ],
onChanged: (value) { onChanged: (value) {
@ -110,6 +115,7 @@ class _SettingsPageState extends State<SettingsPage> {
}); });
var orderDropdown = DropdownButtonFormField( var orderDropdown = DropdownButtonFormField(
isExpanded: true,
decoration: InputDecoration(labelText: tr('appSortOrder')), decoration: InputDecoration(labelText: tr('appSortOrder')),
value: settingsProvider.sortOrder, value: settingsProvider.sortOrder,
items: [ 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 // 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); var newInfo = await PackageArchiveInfo.fromPath(downloadedFile.path);
if (app.id != newInfo.packageName) { if (app.id != newInfo.packageName) {
if (apps[app.id] != null && !SourceProvider().isTempId(app.id)) { if (apps[app.id] != null && !SourceProvider().isTempId(app)) {
throw IDChangedError(); throw IDChangedError();
} }
var originalAppId = app.id; var originalAppId = app.id;
@ -467,8 +467,8 @@ class AppsProvider with ChangeNotifier {
App? getCorrectedInstallStatusAppIfPossible(App app, AppInfo? installedInfo) { App? getCorrectedInstallStatusAppIfPossible(App app, AppInfo? installedInfo) {
var modded = false; var modded = false;
var trackOnly = app.additionalSettings['trackOnly'] == true; var trackOnly = app.additionalSettings['trackOnly'] == true;
var noVersionDetection = var noVersionDetection = app.additionalSettings['versionDetection'] !=
app.additionalSettings['noVersionDetection'] == true; 'standardVersionDetection';
if (installedInfo == null && app.installedVersion != null && !trackOnly) { if (installedInfo == null && app.installedVersion != null && !trackOnly) {
app.installedVersion = null; app.installedVersion = null;
modded = true; modded = true;

View File

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

View File

@ -33,8 +33,9 @@ class APKDetails {
late String version; late String version;
late List<String> apkUrls; late List<String> apkUrls;
late AppNames names; late AppNames names;
late DateTime? releaseDate;
APKDetails(this.version, this.apkUrls, this.names); APKDetails(this.version, this.apkUrls, this.names, {this.releaseDate});
} }
class App { class App {
@ -50,6 +51,7 @@ class App {
late DateTime? lastUpdateCheck; late DateTime? lastUpdateCheck;
bool pinned = false; bool pinned = false;
List<String> categories; List<String> categories;
late DateTime? releaseDate;
App( App(
this.id, this.id,
this.url, this.url,
@ -62,7 +64,8 @@ class App {
this.additionalSettings, this.additionalSettings,
this.lastUpdateCheck, this.lastUpdateCheck,
this.pinned, this.pinned,
{this.categories = const []}); {this.categories = const [],
this.releaseDate});
@override @override
String toString() { String toString() {
@ -97,6 +100,20 @@ class App {
additionalSettings['noVersionDetection'] = additionalSettings['noVersionDetection'] =
json['noVersionDetection'] == 'true' || json['trackOnly'] == true; 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 // Ensure additionalSettings are correctly typed
for (var item in formItems) { for (var item in formItems) {
if (additionalSettings[item.key] != null) { if (additionalSettings[item.key] != null) {
@ -111,30 +128,34 @@ class App {
preferredApkIndex = 0; preferredApkIndex = 0;
} }
return App( return App(
json['id'] as String, json['id'] as String,
json['url'] as String, json['url'] as String,
json['author'] as String, json['author'] as String,
json['name'] as String, json['name'] as String,
json['installedVersion'] == null json['installedVersion'] == null
? null ? null
: json['installedVersion'] as String, : json['installedVersion'] as String,
json['latestVersion'] as String, json['latestVersion'] as String,
json['apkUrls'] == null json['apkUrls'] == null
? [] ? []
: List<String>.from(jsonDecode(json['apkUrls'])), : List<String>.from(jsonDecode(json['apkUrls'])),
preferredApkIndex, preferredApkIndex,
additionalSettings, additionalSettings,
json['lastUpdateCheck'] == null json['lastUpdateCheck'] == null
? null ? null
: DateTime.fromMicrosecondsSinceEpoch(json['lastUpdateCheck']), : DateTime.fromMicrosecondsSinceEpoch(json['lastUpdateCheck']),
json['pinned'] ?? false, json['pinned'] ?? false,
categories: json['categories'] != null categories: json['categories'] != null
? (json['categories'] as List<dynamic>) ? (json['categories'] as List<dynamic>)
.map((e) => e.toString()) .map((e) => e.toString())
.toList() .toList()
: json['category'] != null : json['category'] != null
? [json['category'] as String] ? [json['category'] as String]
: []); : [],
releaseDate: json['releaseDate'] == null
? null
: DateTime.fromMicrosecondsSinceEpoch(json['releaseDate']),
);
} }
Map<String, dynamic> toJson() => { Map<String, dynamic> toJson() => {
@ -149,7 +170,8 @@ class App {
'additionalSettings': jsonEncode(additionalSettings), 'additionalSettings': jsonEncode(additionalSettings),
'lastUpdateCheck': lastUpdateCheck?.microsecondsSinceEpoch, 'lastUpdateCheck': lastUpdateCheck?.microsecondsSinceEpoch,
'pinned': pinned, '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', GeneratedFormTextField('apkFilterRegEx',
@ -350,41 +381,29 @@ class SourceProvider {
return false; return false;
} }
String generateTempID(AppNames names, AppSource source) => String generateTempID(
'${names.author.toLowerCase()}_${names.name.toLowerCase()}_${source.host}'; String standardUrl, Map<String, dynamic> additionalSettings) =>
(standardUrl + additionalSettings.toString()).hashCode.toString();
bool isTempId(String id) { bool isTempId(App app) {
List<String> parts = id.split('_'); // return app.id == generateTempID(app.url, app.additionalSettings);
if (parts.length < 3) { return RegExp('^[0-9]+\$').hasMatch(app.id);
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;
} }
Future<App> getApp( Future<App> getApp(
AppSource source, AppSource source, String url, Map<String, dynamic> additionalSettings,
String url, {App? currentApp, bool trackOnlyOverride = false}) async {
Map<String, dynamic> additionalSettings, {
App? currentApp,
bool trackOnlyOverride = false,
noVersionDetectionOverride = false,
}) async {
if (trackOnlyOverride || source.enforceTrackOnly) { if (trackOnlyOverride || source.enforceTrackOnly) {
additionalSettings['trackOnly'] = true; additionalSettings['trackOnly'] = true;
} }
if (noVersionDetectionOverride) {
additionalSettings['noVersionDetection'] = true;
}
var trackOnly = additionalSettings['trackOnly'] == true; var trackOnly = additionalSettings['trackOnly'] == true;
String standardUrl = source.standardizeURL(preStandardizeUrl(url)); String standardUrl = source.standardizeURL(preStandardizeUrl(url));
APKDetails apk = APKDetails apk =
await source.getLatestAPKDetails(standardUrl, additionalSettings); await source.getLatestAPKDetails(standardUrl, additionalSettings);
if (additionalSettings['versionDetection'] == 'releaseDateAsVersion' &&
apk.releaseDate != null) {
apk.version = apk.releaseDate!.microsecondsSinceEpoch.toString();
}
if (additionalSettings['apkFilterRegEx'] != null) { if (additionalSettings['apkFilterRegEx'] != null) {
var reg = RegExp(additionalSettings['apkFilterRegEx']); var reg = RegExp(additionalSettings['apkFilterRegEx']);
apk.apkUrls = apk.apkUrls =
@ -400,7 +419,7 @@ class SourceProvider {
currentApp?.id ?? currentApp?.id ??
source.tryInferringAppId(standardUrl, source.tryInferringAppId(standardUrl,
additionalSettings: additionalSettings) ?? additionalSettings: additionalSettings) ??
generateTempID(apk.names, source), generateTempID(standardUrl, additionalSettings),
standardUrl, standardUrl,
apk.names.author[0].toUpperCase() + apk.names.author.substring(1), apk.names.author[0].toUpperCase() + apk.names.author.substring(1),
name.trim().isNotEmpty name.trim().isNotEmpty
@ -413,7 +432,8 @@ class SourceProvider {
additionalSettings, additionalSettings,
DateTime.now(), DateTime.now(),
currentApp?.pinned ?? false, currentApp?.pinned ?? false,
categories: currentApp?.categories ?? const []); categories: currentApp?.categories ?? const [],
releaseDate: apk.releaseDate);
} }
// Returns errors in [results, errors] instead of throwing them // Returns errors in [results, errors] instead of throwing them

View File

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