Compare commits

...

69 Commits

Author SHA1 Message Date
2679d5a1aa Merge pull request #340 from ImranR98/dev
UI Improvements (#330, #337)
2023-03-01 21:55:47 -05:00
e49c09c0ff Increment version 2023-03-01 21:55:04 -05:00
c9318ef2b5 Merge pull request #336 from markus-gitdev/main
Update de.json
2023-03-01 21:54:05 -05:00
2e88c8eede UI improvements
Update button for individual apps on list is now an icon.
Less clipping on small screens on apps list page.
2023-03-01 21:37:18 -05:00
8648c1bea7 Added icon for non-installed Apps 2023-03-01 20:20:34 -05:00
b22e2bab0c Update de.json
Updated the following strings to a German translation:
- importFromURLsInFile
- versionDetection
- standardVersionDetection
2023-03-01 08:20:04 +01:00
57f7bf44c2 Merge pull request #335 from ImranR98/dev
Language bugfix + package upgrades + incr. ver.
2023-02-27 19:01:20 -05:00
ce526d8d26 Language bugfix + package upgrades + incr. ver. 2023-02-27 19:00:50 -05:00
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
25 changed files with 928 additions and 344 deletions

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": "Importieren von URLs aus Datei ( z.B. OPML)",
"versionDetection": "Versionserkennung",
"standardVersionDetection": "Standardversionserkennung",
"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",
@ -212,6 +212,13 @@
"removeFromObtainium": "Eltávolítás az Obtainiumból", "removeFromObtainium": "Eltávolítás az Obtainiumból",
"uninstallFromDevice": "Eltávolítás a készülékről", "uninstallFromDevice": "Eltávolítás a készülékről",
"onlyWorksWithNonVersionDetectApps": "Csak azoknál az alkalmazásoknál működik, amelyeknél a verzióérzékelés le van tiltva.", "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",
@ -213,6 +213,13 @@
"removeFromObtainium": "Rimuovi da Obtainium", "removeFromObtainium": "Rimuovi da Obtainium",
"uninstallFromDevice": "Disinstalla dal dispositivo", "uninstallFromDevice": "Disinstalla dal dispositivo",
"onlyWorksWithNonVersionDetectApps": "Funziona solo per le App con il rilevamento della versione disattivato.", "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 le App?" "other": "Rimuovere le App?"

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": "{} / {} アプリをインポートしました",
@ -213,6 +213,13 @@
"removeFromObtainium": "Obtainiumから削除する", "removeFromObtainium": "Obtainiumから削除する",
"uninstallFromDevice": "デバイスからアンインストールする", "uninstallFromDevice": "デバイスからアンインストールする",
"onlyWorksWithNonVersionDetectApps": "バージョン検出を無効にしているアプリにのみ動作します。", "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.9'; const String currentVersion = '0.11.6';
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';
@ -210,6 +211,14 @@ class _ObtainiumState extends State<Obtainium> {
false) false)
]); ]);
} }
if (!supportedLocales
.map((e) => e.languageCode)
.contains(context.locale.languageCode) ||
settingsProvider.forcedLocale == null &&
context.deviceLocale.languageCode !=
context.locale.languageCode) {
settingsProvider.resetLocaleSafe(context);
}
// Register the background update task according to the user's setting // Register the background update task according to the user's setting
if (existingUpdateInterval != settingsProvider.updateInterval) { if (existingUpdateInterval != settingsProvider.updateInterval) {
if (existingUpdateInterval != -1) { if (existingUpdateInterval != -1) {

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

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,86 +230,140 @@ 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;
var hasUpdate = listedApps[index].app.installedVersion != null &&
listedApps[index].app.installedVersion !=
listedApps[index].app.latestVersion;
var updateButton = IconButton(
visualDensity: VisualDensity.compact,
color: Theme.of(context).colorScheme.primary,
tooltip:
listedApps[index].app.additionalSettings['trackOnly'] ==
true
? tr('markUpdated')
: tr('update'),
onPressed: appsProvider.areDownloadsRunning()
? null
: () {
appsProvider.downloadAndInstallLatestApps([
listedApps[index].app.id
], globalNavigatorKey.currentContext).catchError((e) {
showError(e, context);
});
},
icon: Icon(
listedApps[index].app.additionalSettings['trackOnly'] ==
true
? Icons.check_circle_outline
: Icons.install_mobile));
return Container( return Container(
decoration: BoxDecoration( decoration: BoxDecoration(
border: Border.symmetric( border: Border.symmetric(
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, : Row(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Transform(
alignment: Alignment.center,
transform: Matrix4.rotationZ(0.31),
child: Image(
image: const AssetImage(
'assets/graphics/icon.png'),
color: Colors.white.withOpacity(0.1),
colorBlendMode: BlendMode.modulate,
gaplessPlayback: true,
),
),
]),
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]),
maxLines: 1,
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)),
trailing: SingleChildScrollView( trailing: listedApps[index].downloadProgress != null
reverse: true, ? Text(tr('percentProgress', args: [
child: sortedApps[index].downloadProgress != null listedApps[index]
? Text(tr('percentProgress', args: [ .downloadProgress
sortedApps[index] ?.toInt()
.downloadProgress .toString() ??
?.toInt() '100'
.toString() ?? ]))
'100' : (Row(
])) mainAxisSize: MainAxisSize.min,
: (Column( crossAxisAlignment: CrossAxisAlignment.start,
children: [
hasUpdate
? updateButton
: const SizedBox.shrink(),
hasUpdate
? const SizedBox(
width: 10,
)
: const SizedBox.shrink(),
Column(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.end, crossAxisAlignment: CrossAxisAlignment.end,
children: [ children: [
SizedBox( Row(
width: 100, mainAxisSize: MainAxisSize.min,
child: Text( children: [
'${sortedApps[index].app.installedVersion ?? tr('notInstalled')}${sortedApps[index].app.additionalSettings['trackOnly'] == true ? ' ${tr('estimateInBrackets')}' : ''}', Container(
overflow: TextOverflow.fade, constraints: const BoxConstraints(
textAlign: TextAlign.end, maxWidth: 150),
)), child: Text(
sortedApps[index].app.installedVersion != '${listedApps[index].app.installedVersion ?? tr('notInstalled')}${listedApps[index].app.additionalSettings['trackOnly'] == true ? ' ${tr('estimateInBrackets')}' : ''}',
null && overflow: TextOverflow.ellipsis,
sortedApps[index] textAlign: TextAlign.end,
.app )),
.installedVersion != ]),
sortedApps[index] Row(
.app mainAxisSize: MainAxisSize.min,
.latestVersion children: [
? GestureDetector( GestureDetector(
onTap: changesUrl == null onTap: changesUrl == null
? null ? null
: () { : () {
@ -312,37 +371,39 @@ class AppsPageState extends State<AppsPage> {
mode: LaunchMode mode: LaunchMode
.externalApplication); .externalApplication);
}, },
child: appsProvider child: Text(
.areDownloadsRunning() listedApps[index].app.releaseDate ==
? Text(tr('pleaseWait')) null
: Text( ? tr('changes')
'${tr('updateAvailable')}${sortedApps[index].app.additionalSettings['trackOnly'] == true ? ' ${tr('estimateInBracketsShort')}' : ''}', : DateFormat('yyyy-MM-dd')
style: TextStyle( .format(listedApps[index]
fontStyle: .app
FontStyle.italic, .releaseDate!),
decoration: changesUrl == style: const TextStyle(
null fontStyle: FontStyle.italic,
? TextDecoration.none decoration:
: TextDecoration TextDecoration.underline),
.underline), ))
)) ],
: const SizedBox(), ),
], ],
))), )
],
)),
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 +415,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(
@ -653,7 +714,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: [
@ -146,7 +152,7 @@ class _SettingsPageState extends State<SettingsPage> {
if (value != null) { if (value != null) {
context.setLocale(Locale(value)); context.setLocale(Locale(value));
} else { } else {
context.resetLocale(); settingsProvider.resetLocaleSafe(context);
} }
}); });

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 }
@ -179,4 +178,15 @@ class SettingsProvider with ChangeNotifier {
bool setEqual(Set<String> a, Set<String> b) => bool setEqual(Set<String> a, Set<String> b) =>
a.length == b.length && a.union(b).length == a.length; a.length == b.length && a.union(b).length == a.length;
void resetLocaleSafe(BuildContext context) {
if (context.supportedLocales
.map((e) => e.languageCode)
.contains(context.deviceLocale.languageCode)) {
context.resetLocale();
} else {
context.setLocale(context.fallbackLocale!);
context.deleteSaveLocale();
}
}
} }

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
@ -670,10 +670,10 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: sqflite name: sqflite
sha256: "78324387dc81df14f78df06019175a86a2ee0437624166c382e145d0a7fd9a4f" sha256: "851d5040552cf911f4cabda08d003eca76b27da3ed0002978272e27c8fbf8ecc"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.2.4+1" version: "2.2.5"
sqflite_common: sqflite_common:
dependency: transitive dependency: transitive
description: description:
@ -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.9+115 # When changing this, update the tag in main() accordingly version: 0.11.6+125 # 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
@ -97,6 +97,7 @@ flutter:
assets: assets:
- assets/translations/ - assets/translations/
- assets/graphics/
# An image asset can refer to one or more resolution-specific "variants", see # An image asset can refer to one or more resolution-specific "variants", see
# https://flutter.dev/assets-and-images/#resolution-aware # https://flutter.dev/assets-and-images/#resolution-aware