Compare commits
60 Commits
v0.10.2-be
...
v0.10.11-b
Author | SHA1 | Date | |
---|---|---|---|
c5ff1de950 | |||
56658abd60 | |||
b60622e2cb | |||
e149f0b225 | |||
d9729f08c0 | |||
eda5c1bac6 | |||
5574ea870b | |||
9f03234ac1 | |||
b2503dd43d | |||
e01ca704bc | |||
6aa4ace8e2 | |||
d762467a31 | |||
b07cce8ecd | |||
8002a946b2 | |||
fd9aebc5b2 | |||
1be38d361f | |||
32c40ae7b3 | |||
07223d81c7 | |||
78baee7265 | |||
348c33dfe9 | |||
c408d70ae6 | |||
3ae4e7cc8a | |||
dab0f2bb72 | |||
4baf6bcd3b | |||
db4517aa13 | |||
55d4d1f978 | |||
f89ac5965f | |||
d5ebaa161f | |||
a4c014a8bf | |||
bbaa42fb01 | |||
4fe311bc03 | |||
ea68b97ff7 | |||
6e0f6b528e | |||
a2c227931e | |||
15ad3bb439 | |||
b03d7fba1a | |||
31c491d7c5 | |||
71c80f11f5 | |||
eef4d33431 | |||
d56342e907 | |||
c72c0fdb57 | |||
ffe29009ed | |||
60e3b68ebd | |||
ee4d0f259f | |||
0ecfbef0a0 | |||
1b60e75ca7 | |||
abcfa389e8 | |||
a64bd67ef1 | |||
4252c2711b | |||
52913b0450 | |||
427b0ed8d2 | |||
a85d6d4f08 | |||
05f712603c | |||
fa2a80e34c | |||
f43e5a2ff1 | |||
b72aa8273e | |||
520f186e4a | |||
e1e97672cf | |||
1494bcd013 | |||
3457a0a12f |
11
README.md
@ -22,6 +22,15 @@ Currently supported App sources:
|
||||
- "HTML" (Fallback)
|
||||
- Any other URL that returns an HTML page with links to APK files (if multiple, the last file alphabetically is picked)
|
||||
|
||||
## Installation
|
||||
|
||||
[<img src="https://github.com/machiav3lli/oandbackupx/blob/034b226cea5c1b30eb4f6a6f313e4dadcbb0ece4/badge_github.png"
|
||||
alt="Get it on GitHub"
|
||||
height="80">](https://github.com/ImranR98/Obtainium/releases)
|
||||
[<img src="https://gitlab.com/IzzyOnDroid/repo/-/raw/master/assets/IzzyOnDroid.png"
|
||||
alt="Get it on IzzyOnDroid"
|
||||
height="80">](https://apt.izzysoft.de/fdroid/index/apk/dev.imranr.obtainium)
|
||||
|
||||
## Limitations
|
||||
- App installs happen asynchronously and the success/failure of an install cannot be determined directly. This results in install statuses and versions sometimes being out of sync with the OS until the next launch or until the problem is manually corrected.
|
||||
- Auto (unattended) updates are unsupported due to a lack of any capable Flutter plugin.
|
||||
@ -31,4 +40,4 @@ Currently supported App sources:
|
||||
|
||||
| <img src="./assets/screenshots/1.apps.png" alt="Apps Page" /> | <img src="./assets/screenshots/2.dark_theme.png" alt="Dark Theme" /> | <img src="./assets/screenshots/3.material_you.png" alt="Material You" /> |
|
||||
| ------------------------------------------------------ | ----------------------------------------------------------------------- | -------------------------------------------------------------------- |
|
||||
| <img src="./assets/screenshots/4.app.png" alt="App Page" /> | <img src="./assets/screenshots/5.apk_picker.png" alt="Multiple APK Support" /> | <img src="./assets/screenshots/6.apk_install.png" alt="App Installation" /> |
|
||||
| <img src="./assets/screenshots/4.app.png" alt="App Page" /> | <img src="./assets/screenshots/5.app_opts.png" alt="App Options" /> | <img src="./assets/screenshots/6.app_webview.png" alt="App Web View" /> |
|
||||
|
@ -3,7 +3,8 @@
|
||||
<application
|
||||
android:label="Obtainium"
|
||||
android:name="${applicationName}"
|
||||
android:icon="@mipmap/ic_launcher">
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:requestLegacyExternalStorage="true">
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
android:exported="true"
|
||||
@ -51,7 +52,8 @@
|
||||
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
|
||||
<uses-permission android:name="android.permission.WAKE_LOCK"/>
|
||||
<uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM"/>
|
||||
<uses-permission android:name="android.permission.REQUEST_DELETE_PACKAGES" />
|
||||
<uses-permission
|
||||
android:name="android.permission.WRITE_EXTERNAL_STORAGE"
|
||||
android:maxSdkVersion="28"/>
|
||||
android:maxSdkVersion="29"/>
|
||||
</manifest>
|
@ -2,4 +2,5 @@
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@color/ic_launcher_background"/>
|
||||
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
|
||||
<monochrome android:drawable="@drawable/ic_launcher_foreground"/>
|
||||
</adaptive-icon>
|
||||
|
Before Width: | Height: | Size: 228 KiB After Width: | Height: | Size: 234 KiB |
Before Width: | Height: | Size: 162 KiB After Width: | Height: | Size: 238 KiB |
Before Width: | Height: | Size: 170 KiB After Width: | Height: | Size: 140 KiB |
Before Width: | Height: | Size: 146 KiB After Width: | Height: | Size: 139 KiB |
Before Width: | Height: | Size: 188 KiB |
BIN
assets/screenshots/5.app_opts.png
Normal file
After Width: | Height: | Size: 118 KiB |
Before Width: | Height: | Size: 192 KiB |
BIN
assets/screenshots/6.app_webview.png
Normal file
After Width: | Height: | Size: 262 KiB |
@ -74,7 +74,6 @@
|
||||
"changeX": "Ändern {}",
|
||||
"installUpdateApps": "Apps installieren/aktualisieren",
|
||||
"installUpdateSelectedApps": "Ausgewählte Apps installieren/aktualisieren",
|
||||
"onlyWorksWithNonEVDApps": "Funktioniert nur bei Apps, deren Installationsstatus nicht automatisch erkannt werden kann (ungewöhnlich).",
|
||||
"markXSelectedAppsAsUpdated": "Markiere {} ausgewählte Apps als aktuell?",
|
||||
"no": "Nein",
|
||||
"yes": "Ja",
|
||||
@ -178,7 +177,6 @@
|
||||
"installedVersionX": "Installierte Version: {}",
|
||||
"lastUpdateCheckX": "Letzte Aktualisierungsprüfung: {}",
|
||||
"remove": "Entfernen",
|
||||
"removeAppQuestion": "App entfernen?",
|
||||
"yesMarkUpdated": "Ja, als aktualisiert markieren",
|
||||
"fdroid": "F-Droid",
|
||||
"appIdOrName": "App ID oder Name",
|
||||
@ -211,6 +209,14 @@
|
||||
"language": "Sprache",
|
||||
"storagePermissionDenied": "Storage permission denied",
|
||||
"selectedCategorizeWarning": "This will replace any existing category settings for the selected Apps.",
|
||||
"filterAPKsByRegEx": "Filter APKs by Regular Expression",
|
||||
"removeFromObtainium": "Remove from Obtainium",
|
||||
"uninstallFromDevice": "Uninstall from Device",
|
||||
"onlyWorksWithNonVersionDetectApps": "Only works for Apps with version detection disabled.",
|
||||
"removeAppQuestion": {
|
||||
"one": "App entfernen?",
|
||||
"other": "App entfernen?"
|
||||
},
|
||||
"tooManyRequestsTryAgainInMinutes": {
|
||||
"one": "Zu viele Anfragen (Rate begrenzt) - versuchen Sie es in {} Minute erneut",
|
||||
"other": "Zu viele Anfragen (Rate begrenzt) - versuchen Sie es in {} Minuten erneut"
|
||||
|
@ -74,7 +74,6 @@
|
||||
"changeX": "Change {}",
|
||||
"installUpdateApps": "Install/Update Apps",
|
||||
"installUpdateSelectedApps": "Install/Update Selected Apps",
|
||||
"onlyWorksWithNonEVDApps": "Only works for Apps whose install status cannot be automatically detected (uncommon).",
|
||||
"markXSelectedAppsAsUpdated": "Mark {} Selected Apps as Updated?",
|
||||
"no": "No",
|
||||
"yes": "Yes",
|
||||
@ -178,7 +177,6 @@
|
||||
"installedVersionX": "Installed Version: {}",
|
||||
"lastUpdateCheckX": "Last Update Check: {}",
|
||||
"remove": "Remove",
|
||||
"removeAppQuestion": "Remove App?",
|
||||
"yesMarkUpdated": "Yes, Mark as Updated",
|
||||
"fdroid": "F-Droid",
|
||||
"appIdOrName": "App ID or Name",
|
||||
@ -211,6 +209,14 @@
|
||||
"language": "Language",
|
||||
"storagePermissionDenied": "Storage permission denied",
|
||||
"selectedCategorizeWarning": "This will replace any existing category settings for the selected Apps.",
|
||||
"filterAPKsByRegEx": "Filter APKs by Regular Expression",
|
||||
"removeFromObtainium": "Remove from Obtainium",
|
||||
"uninstallFromDevice": "Uninstall from Device",
|
||||
"onlyWorksWithNonVersionDetectApps": "Only works for Apps with version detection disabled.",
|
||||
"removeAppQuestion": {
|
||||
"one": "Remove App?",
|
||||
"other": "Remove Apps?"
|
||||
},
|
||||
"tooManyRequestsTryAgainInMinutes": {
|
||||
"one": "Too many requests (rate limited) - try again in {} minute",
|
||||
"other": "Too many requests (rate limited) - try again in {} minutes"
|
||||
|
264
assets/translations/fa.json
Normal file
@ -0,0 +1,264 @@
|
||||
{
|
||||
"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": "فقط برای برنامههایی کار میکند که تشخیص نسخه غیرفعال است.",
|
||||
"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": "{} و {} برنامه دیگر به روز شدند."
|
||||
}
|
||||
}
|
@ -74,7 +74,6 @@
|
||||
"changeX": "Változás {}",
|
||||
"installUpdateApps": "Appok telepítése/frissítése",
|
||||
"installUpdateSelectedApps": "Telepítse/frissítse a kiválasztott appokat",
|
||||
"onlyWorksWithNonEVDApps": "Csak azoknál az alkalmazásoknál működik, amelyek telepítési állapota nem észlelhető autom. (nem gyakori).",
|
||||
"markXSelectedAppsAsUpdated": "Megjelöl {} kiválasztott alkalmazást frissítettként?",
|
||||
"no": "Nem",
|
||||
"yes": "Igen",
|
||||
@ -178,7 +177,6 @@
|
||||
"installedVersionX": "Telepített verzió: {}",
|
||||
"lastUpdateCheckX": "Frissítés ellenőrizve: {}",
|
||||
"remove": "Eltávolítás",
|
||||
"removeAppQuestion": "Eltávolítja az alkalmazást?",
|
||||
"yesMarkUpdated": "Igen, megjelölés frissítettként",
|
||||
"fdroid": "F-Droid",
|
||||
"appIdOrName": "App ID vagy név",
|
||||
@ -210,6 +208,14 @@
|
||||
"language": "Nyelv",
|
||||
"storagePermissionDenied": "Tárhely engedély megtagadva",
|
||||
"selectedCategorizeWarning": "Ez felváltja a kiválasztott alkalmazások meglévő kategória-beállításait.",
|
||||
"filterAPKsByRegEx": "Az APK-k szűrése reguláris kifejezéssel",
|
||||
"removeFromObtainium": "Eltávolítás az Obtainiumból",
|
||||
"uninstallFromDevice": "Eltávolítás a készülékről",
|
||||
"onlyWorksWithNonVersionDetectApps": "Csak azoknál az alkalmazásoknál működik, amelyeknél a verzióérzékelés le van tiltva.",
|
||||
"removeAppQuestion": {
|
||||
"one": "Eltávolítja az alkalmazást?",
|
||||
"other": "Eltávolítja az alkalmazást?"
|
||||
},
|
||||
"tooManyRequestsTryAgainInMinutes": {
|
||||
"one": "Túl sok kérés (korlátozott arány) – próbálja újra {} perc múlva",
|
||||
"other": "Túl sok kérés (korlátozott arány) – próbálja újra {} perc múlva"
|
||||
|
@ -74,7 +74,6 @@
|
||||
"changeX": "Modifica {}",
|
||||
"installUpdateApps": "Installa/Aggiorna App",
|
||||
"installUpdateSelectedApps": "Installa/Aggiorna le App selezionate",
|
||||
"onlyWorksWithNonEVDApps": "Funziona solo per le App il cui stato d'installazione non può essere rilevato automaticamente (inconsueto).",
|
||||
"markXSelectedAppsAsUpdated": "Contrassegnare le {} App selezionate come aggiornate?",
|
||||
"no": "No",
|
||||
"yes": "Sì",
|
||||
@ -178,7 +177,6 @@
|
||||
"installedVersionX": "Versione installata: {}",
|
||||
"lastUpdateCheckX": "Ultimo controllo degli aggiornamenti: {}",
|
||||
"remove": "Rimuovi",
|
||||
"removeAppQuestion": "Rimuovere l'App?",
|
||||
"yesMarkUpdated": "Sì, contrassegna come aggiornato",
|
||||
"fdroid": "F-Droid",
|
||||
"appIdOrName": "ID o nome dell'App",
|
||||
@ -209,8 +207,16 @@
|
||||
"addCategory": "Aggiungi categoria",
|
||||
"label": "Etichetta",
|
||||
"language": "Lingua",
|
||||
"storagePermissionDenied": "Storage permission denied",
|
||||
"selectedCategorizeWarning": "This will replace any existing category settings for the selected Apps.",
|
||||
"storagePermissionDenied": "Accesso ai file non autorizzato",
|
||||
"selectedCategorizeWarning": "Ciò sostituirà le impostazioni di categoria esistenti per le App selezionate.",
|
||||
"filterAPKsByRegEx": "Filtra file APK con espressioni regolari",
|
||||
"removeFromObtainium": "Rimuovi da Obtainium",
|
||||
"uninstallFromDevice": "Disinstalla dal dispositivo",
|
||||
"onlyWorksWithNonVersionDetectApps": "Funziona solo per le App con il rilevamento della versione disattivato.",
|
||||
"removeAppQuestion": {
|
||||
"one": "Rimuovere l'App?",
|
||||
"other": "Rimuovere le App?"
|
||||
},
|
||||
"tooManyRequestsTryAgainInMinutes": {
|
||||
"one": "Troppe richieste (traffico limitato) - riprova tra {} minuto",
|
||||
"other": "Troppe richieste (traffico limitato) - riprova tra {} minuti"
|
||||
|
@ -7,7 +7,7 @@
|
||||
"appIdMismatch": "ダウンロードしたパッケージのIDが既存のApp IDと一致しません",
|
||||
"functionNotImplemented": "このクラスはこの機能を実装していません",
|
||||
"placeholder": "プレースホルダー",
|
||||
"someErrors": "いくつかのエラーが発生しました",
|
||||
"someErrors": "何らかのエラーが発生しました",
|
||||
"unexpectedError": "予期せぬエラーが発生しました",
|
||||
"ok": "OK",
|
||||
"and": "と",
|
||||
@ -74,7 +74,6 @@
|
||||
"changeX": "{} を変更する",
|
||||
"installUpdateApps": "アプリのインストール/アップデート",
|
||||
"installUpdateSelectedApps": "選択したアプリのインストール/アップデート",
|
||||
"onlyWorksWithNonEVDApps": "インストール状況を自動検出できないアプリ(一般的でないもの)のみ動作します。",
|
||||
"markXSelectedAppsAsUpdated": "{}個の選択したアプリをアップデート済みとしてマークしますか?",
|
||||
"no": "いいえ",
|
||||
"yes": "はい",
|
||||
@ -82,7 +81,7 @@
|
||||
"pinToTop": "トップに固定",
|
||||
"unpinFromTop": "トップから固定解除",
|
||||
"resetInstallStatusForSelectedAppsQuestion": "選択したアプリのインストール状態をリセットしますか?",
|
||||
"installStatusOfXWillBeResetExplanation": "選択したアプリのインストール状態がリセットされます。\n\nアップデートに失敗するなどして、Obtainiumに表示されるアプリのバージョンが正しくない場合に役立ちます。",
|
||||
"installStatusOfXWillBeResetExplanation": "選択したアプリのインストール状態がリセットされます。\n\nアップデートに失敗した場合など、Obtainiumに表示されるアプリのバージョンが正しくない場合に有効です。",
|
||||
"shareSelectedAppURLs": "選択したアプリのURLを共有する",
|
||||
"resetInstallStatus": "インストール状態をリセットする",
|
||||
"more": "もっと見る",
|
||||
@ -109,7 +108,7 @@
|
||||
"searchX": "{}で検索",
|
||||
"noResults": "結果は見つかりませんでした",
|
||||
"importX": "{}をインポートする",
|
||||
"importedAppsIdDisclaimer": "インポートしたアプリが「未インストール」と表示されることがあります。\nこれを解決するには、Obtainiumから再インストールしてください。\nアプリのデータには影響しません。\n\nURLとサードパーティーのインポートメソッドにのみ影響します。",
|
||||
"importedAppsIdDisclaimer": "インポートしたアプリが「未インストール」と表示されることがあります。\nこれを解決するには、Obtainiumから再インストールしてください。\nアプリのデータには影響しません。\n\nURLとサードパーティのインポートメソッドにのみ影響します。",
|
||||
"importErrors": "インポートエラー",
|
||||
"importedXOfYApps": "{} / {} アプリをインポートしました",
|
||||
"followingURLsHadErrors": "以下のURLでエラーが発生しました:",
|
||||
@ -133,7 +132,7 @@
|
||||
"bgUpdateCheckInterval": "バックグラウンドでのアップデート確認の間隔",
|
||||
"neverManualOnly": "手動",
|
||||
"appearance": "外観",
|
||||
"showWebInAppView": "アプリビューにソースウェブページを表示する",
|
||||
"showWebInAppView": "アプリページにソースのWebページを表示する",
|
||||
"pinUpdates": "アップデートがあるアプリをトップに固定する",
|
||||
"updates": "アップデート",
|
||||
"sourceSpecific": "Github アクセストークン",
|
||||
@ -178,13 +177,12 @@
|
||||
"installedVersionX": "インストールされたバージョン: {}",
|
||||
"lastUpdateCheckX": "最終アップデート確認: {}",
|
||||
"remove": "削除",
|
||||
"removeAppQuestion": "アプリを削除しますか?",
|
||||
"yesMarkUpdated": "はい、アップデート済みとしてマークします",
|
||||
"fdroid": "F-Droid",
|
||||
"appIdOrName": "アプリのIDまたは名前",
|
||||
"appWithIdOrNameNotFound": "そのIDや名前を持つアプリは見つかりませんでした",
|
||||
"reposHaveMultipleApps": "リポジトリには複数のアプリが含まれることがあります",
|
||||
"fdroidThirdPartyRepo": "F-Droid Third-Party Repo",
|
||||
"fdroidThirdPartyRepo": "F-Droid サードパーティリポジトリ",
|
||||
"steam": "Steam",
|
||||
"steamMobile": "Steam Mobile",
|
||||
"steamChat": "Steam Chat",
|
||||
@ -211,6 +209,14 @@
|
||||
"language": "言語",
|
||||
"storagePermissionDenied": "ストレージ権限が拒否されました",
|
||||
"selectedCategorizeWarning": "これにより、選択したアプリの既存のカテゴリ設定がすべて置き換えられます。",
|
||||
"filterAPKsByRegEx": "正規表現でAPKを絞り込む",
|
||||
"removeFromObtainium": "Obtainiumから削除する",
|
||||
"uninstallFromDevice": "デバイスからアンインストールする",
|
||||
"onlyWorksWithNonVersionDetectApps": "バージョン検出を無効にしているアプリにのみ動作します。",
|
||||
"removeAppQuestion": {
|
||||
"one": "アプリを削除しますか?",
|
||||
"other": "アプリを削除しますか?"
|
||||
},
|
||||
"tooManyRequestsTryAgainInMinutes": {
|
||||
"one": "リクエストが多すぎます(レート制限)- {}分後に再試行してください",
|
||||
"other": "リクエストが多すぎます(レート制限)- {}分後に再試行してください"
|
||||
|
@ -178,7 +178,6 @@
|
||||
"installedVersionX": "已安装: {}",
|
||||
"lastUpdateCheckX": "最后检查: {}",
|
||||
"remove": "删除",
|
||||
"removeAppQuestion": "删除应用?",
|
||||
"yesMarkUpdated": "'是的,标为已更新",
|
||||
"fdroid": "F-Droid",
|
||||
"appIdOrName": "应用 ID 或名称",
|
||||
@ -211,6 +210,13 @@
|
||||
"language": "语言",
|
||||
"storagePermissionDenied": "存储权限已被拒绝",
|
||||
"selectedCategorizeWarning": "这将取代所选应用程序的任何现有类别",
|
||||
"filterAPKsByRegEx": "Filter APKs by Regular Expression",
|
||||
"removeFromObtainium": "Remove from Obtainium",
|
||||
"uninstallFromDevice": "Uninstall from Device",
|
||||
"removeAppQuestion": {
|
||||
"one": "删除应用?",
|
||||
"other": "删除应用?"
|
||||
},
|
||||
"tooManyRequestsTryAgainInMinutes": {
|
||||
"one": "请求过多 (API 限制) - 在 {} 分钟后重试",
|
||||
"other": "请求过多 (API 限制) - 在 {} 分钟后重试"
|
||||
|
@ -26,15 +26,7 @@ class Codeberg extends AppSource {
|
||||
required: false,
|
||||
additionalValidators: [
|
||||
(value) {
|
||||
if (value == null || value.isEmpty) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
RegExp(value);
|
||||
} catch (e) {
|
||||
return tr('invalidRegEx');
|
||||
}
|
||||
return null;
|
||||
return regExValidator(value);
|
||||
}
|
||||
])
|
||||
]
|
||||
@ -72,7 +64,7 @@ class Codeberg extends AppSource {
|
||||
? additionalSettings['filterReleaseTitlesByRegEx']
|
||||
: null;
|
||||
Response res = await get(Uri.parse(
|
||||
'https://$host/api/v1/repos${standardUrl.substring('https://$host'.length)}/releases'));
|
||||
'https://$host/api/v1/repos${standardUrl.substring('https://$host'.length)}/releases?per_page=100'));
|
||||
if (res.statusCode == 200) {
|
||||
var releases = jsonDecode(res.body) as List<dynamic>;
|
||||
|
||||
@ -99,8 +91,8 @@ class Codeberg extends AppSource {
|
||||
if (releases[i]['draft'] == true) {
|
||||
// Draft releases not supported
|
||||
}
|
||||
var nameToFilter = releases[i]['name'] as String;
|
||||
if (nameToFilter.trim().isEmpty) {
|
||||
var nameToFilter = releases[i]['name'] as String?;
|
||||
if (nameToFilter == null || nameToFilter.trim().isEmpty) {
|
||||
// Some leave titles empty so tag is used
|
||||
nameToFilter = releases[i]['tag_name'] as String;
|
||||
}
|
||||
|
@ -65,15 +65,7 @@ class GitHub extends AppSource {
|
||||
required: false,
|
||||
additionalValidators: [
|
||||
(value) {
|
||||
if (value == null || value.isEmpty) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
RegExp(value);
|
||||
} catch (e) {
|
||||
return tr('invalidRegEx');
|
||||
}
|
||||
return null;
|
||||
return regExValidator(value);
|
||||
}
|
||||
])
|
||||
]
|
||||
@ -119,7 +111,7 @@ class GitHub extends AppSource {
|
||||
? additionalSettings['filterReleaseTitlesByRegEx']
|
||||
: null;
|
||||
Response res = await get(Uri.parse(
|
||||
'https://${await getCredentialPrefixIfAny()}api.$host/repos${standardUrl.substring('https://$host'.length)}/releases'));
|
||||
'https://${await getCredentialPrefixIfAny()}api.$host/repos${standardUrl.substring('https://$host'.length)}/releases?per_page=100'));
|
||||
if (res.statusCode == 200) {
|
||||
var releases = jsonDecode(res.body) as List<dynamic>;
|
||||
|
||||
@ -141,8 +133,8 @@ class GitHub extends AppSource {
|
||||
if (!includePrereleases && releases[i]['prerelease'] == true) {
|
||||
continue;
|
||||
}
|
||||
var nameToFilter = releases[i]['name'] as String;
|
||||
if (nameToFilter.trim().isEmpty) {
|
||||
var nameToFilter = releases[i]['name'] as String?;
|
||||
if (nameToFilter == null || nameToFilter.trim().isEmpty) {
|
||||
// Some leave titles empty so tag is used
|
||||
nameToFilter = releases[i]['tag_name'] as String;
|
||||
}
|
||||
|
@ -37,7 +37,9 @@ class HTML extends AppSource {
|
||||
.map((e) => e.toLowerCase().startsWith('http://') ||
|
||||
e.toLowerCase().startsWith('https://')
|
||||
? e
|
||||
: '${uri.origin}/$e')
|
||||
: e.startsWith('/')
|
||||
? '${uri.origin}/$e'
|
||||
: '${uri.origin}/${uri.path}/$e')
|
||||
.toList();
|
||||
return APKDetails(version, apkUrls, AppNames(uri.host, tr('app')));
|
||||
} else {
|
||||
|
@ -10,7 +10,10 @@ class SteamMobile extends AppSource {
|
||||
host = 'store.steampowered.com';
|
||||
name = tr('steam');
|
||||
additionalSourceAppSpecificSettingFormItems = [
|
||||
[GeneratedFormDropdown('app', apks.entries.toList(), label: tr('app'))]
|
||||
[
|
||||
GeneratedFormDropdown('app', apks.entries.toList(),
|
||||
label: tr('app'), defaultValue: apks.entries.toList()[0].key)
|
||||
]
|
||||
];
|
||||
}
|
||||
|
||||
@ -35,7 +38,8 @@ class SteamMobile extends AppSource {
|
||||
if (apkNamePrefix == null) {
|
||||
throw NoReleasesError();
|
||||
}
|
||||
String apkInURLRegexPattern = '/$apkNamePrefix-[^/]+\\.apk\$';
|
||||
String apkInURLRegexPattern =
|
||||
'/$apkNamePrefix-([0-9]+\\.)*[0-9]+\\.apk\$';
|
||||
var links = parse(res.body)
|
||||
.querySelectorAll('a')
|
||||
.map((e) => e.attributes['href'] ?? '')
|
||||
|
@ -150,6 +150,7 @@ class _GeneratedFormState extends State<GeneratedForm> {
|
||||
Map<String, dynamic> values = {};
|
||||
late List<List<Widget>> formInputs;
|
||||
List<List<Widget>> rows = [];
|
||||
String? initKey;
|
||||
|
||||
// If any value changes, call this to update the parent with value and validity
|
||||
void someValueChanged({bool isBuilding = false}) {
|
||||
@ -169,13 +170,10 @@ class _GeneratedFormState extends State<GeneratedForm> {
|
||||
widget.onValueChanges(returnValues, valid, isBuilding);
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
||||
initForm() {
|
||||
initKey = widget.key.toString();
|
||||
// Initialize form values as all empty
|
||||
values.clear();
|
||||
int j = 0;
|
||||
for (var row in widget.items) {
|
||||
for (var e in row) {
|
||||
values[e.key] = e.defaultValue;
|
||||
@ -245,8 +243,17 @@ class _GeneratedFormState extends State<GeneratedForm> {
|
||||
someValueChanged(isBuilding: true);
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
initForm();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (widget.key.toString() != initKey) {
|
||||
initForm();
|
||||
}
|
||||
for (var r = 0; r < formInputs.length; r++) {
|
||||
for (var e = 0; e < formInputs[r].length; e++) {
|
||||
if (widget.items[r][e] is GeneratedFormSwitch) {
|
||||
|
@ -29,7 +29,7 @@ class NoReleasesError extends ObtainiumError {
|
||||
}
|
||||
|
||||
class NoAPKError extends ObtainiumError {
|
||||
NoAPKError() : super(tr('noReleaseFound'));
|
||||
NoAPKError() : super(tr('noAPKFound'));
|
||||
}
|
||||
|
||||
class NoVersionError extends ObtainiumError {
|
||||
|
@ -21,7 +21,7 @@ import 'package:easy_localization/src/easy_localization_controller.dart';
|
||||
// ignore: implementation_imports
|
||||
import 'package:easy_localization/src/localization.dart';
|
||||
|
||||
const String currentVersion = '0.10.2';
|
||||
const String currentVersion = '0.10.11';
|
||||
const String currentReleaseTag =
|
||||
'v$currentVersion-beta'; // KEEP THIS IN SYNC WITH GITHUB RELEASES
|
||||
|
||||
@ -33,7 +33,8 @@ const supportedLocales = [
|
||||
Locale('it'),
|
||||
Locale('ja'),
|
||||
Locale('hu'),
|
||||
Locale('de')
|
||||
Locale('de'),
|
||||
Locale('fa')
|
||||
];
|
||||
const fallbackLocale = Locale('en');
|
||||
const localeDir = 'assets/translations';
|
||||
|
@ -32,6 +32,7 @@ class _AddAppPageState extends State<AddAppPage> {
|
||||
Map<String, dynamic> additionalSettings = {};
|
||||
bool additionalSettingsValid = true;
|
||||
List<String> pickedCategories = [];
|
||||
int searchnum = 0;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@ -40,10 +41,14 @@ class _AddAppPageState extends State<AddAppPage> {
|
||||
|
||||
bool doingSomething = gettingAppInfo || searching;
|
||||
|
||||
changeUserInput(String input, bool valid, bool isBuilding) {
|
||||
changeUserInput(String input, bool valid, bool isBuilding,
|
||||
{bool isSearch = false}) {
|
||||
userInput = input;
|
||||
if (!isBuilding) {
|
||||
setState(() {
|
||||
if (isSearch) {
|
||||
searchnum++;
|
||||
}
|
||||
var source = valid ? sourceProvider.getSource(userInput) : null;
|
||||
if (pickedSource.runtimeType != source.runtimeType) {
|
||||
pickedSource = source;
|
||||
@ -70,6 +75,7 @@ class _AddAppPageState extends State<AddAppPage> {
|
||||
additionalSettings['noVersionDetection'] == true;
|
||||
var cont = true;
|
||||
if ((userPickedTrackOnly || pickedSource!.enforceTrackOnly) &&
|
||||
// ignore: use_build_context_synchronously
|
||||
await showDialog(
|
||||
context: context,
|
||||
builder: (BuildContext ctx) {
|
||||
@ -88,6 +94,7 @@ class _AddAppPageState extends State<AddAppPage> {
|
||||
cont = false;
|
||||
}
|
||||
if (userPickedNoVersionDetection &&
|
||||
// ignore: use_build_context_synchronously
|
||||
await showDialog(
|
||||
context: context,
|
||||
builder: (BuildContext ctx) {
|
||||
@ -167,30 +174,32 @@ class _AddAppPageState extends State<AddAppPage> {
|
||||
children: [
|
||||
Expanded(
|
||||
child: GeneratedForm(
|
||||
key: Key(searchnum.toString()),
|
||||
items: [
|
||||
[
|
||||
GeneratedFormTextField('appSourceURL',
|
||||
label: tr('appSourceURL'),
|
||||
additionalValidators: [
|
||||
(value) {
|
||||
try {
|
||||
sourceProvider
|
||||
.getSource(value ?? '')
|
||||
.standardizeURL(
|
||||
preStandardizeUrl(
|
||||
value ?? ''));
|
||||
} catch (e) {
|
||||
return e is String
|
||||
? e
|
||||
: e is ObtainiumError
|
||||
? e.toString()
|
||||
: tr('error');
|
||||
}
|
||||
return null;
|
||||
}
|
||||
])
|
||||
]
|
||||
],
|
||||
[
|
||||
GeneratedFormTextField('appSourceURL',
|
||||
label: tr('appSourceURL'),
|
||||
defaultValue: userInput,
|
||||
additionalValidators: [
|
||||
(value) {
|
||||
try {
|
||||
sourceProvider
|
||||
.getSource(value ?? '')
|
||||
.standardizeURL(
|
||||
preStandardizeUrl(
|
||||
value ?? ''));
|
||||
} catch (e) {
|
||||
return e is String
|
||||
? e
|
||||
: e is ObtainiumError
|
||||
? e.toString()
|
||||
: tr('error');
|
||||
}
|
||||
return null;
|
||||
}
|
||||
])
|
||||
]
|
||||
],
|
||||
onValueChanges: (values, valid, isBuilding) {
|
||||
changeUserInput(values['appSourceURL']!,
|
||||
valid, isBuilding);
|
||||
@ -294,8 +303,8 @@ class _AddAppPageState extends State<AddAppPage> {
|
||||
if (selectedUrls != null &&
|
||||
selectedUrls.isNotEmpty) {
|
||||
changeUserInput(
|
||||
selectedUrls[0], true, false);
|
||||
addApp(resetUserInputAfter: true);
|
||||
selectedUrls[0], true, false,
|
||||
isSearch: true);
|
||||
}
|
||||
}).catchError((e) {
|
||||
showError(e, context);
|
||||
@ -325,6 +334,7 @@ class _AddAppPageState extends State<AddAppPage> {
|
||||
height: 16,
|
||||
),
|
||||
GeneratedForm(
|
||||
key: Key(pickedSource.runtimeType.toString()),
|
||||
items: pickedSource!
|
||||
.combinedAppSpecificSettingFormItems,
|
||||
onValueChanges: (values, valid, isBuilding) {
|
||||
|
@ -42,6 +42,8 @@ class _AppPageState extends State<AppPage> {
|
||||
getUpdate(app.app.id);
|
||||
}
|
||||
var trackOnly = app?.app.additionalSettings['trackOnly'] == true;
|
||||
var noVersionDetection =
|
||||
app?.app.additionalSettings['noVersionDetection'] == true;
|
||||
|
||||
var infoColumn = Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
@ -190,8 +192,9 @@ class _AppPageState extends State<AppPage> {
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||
children: [
|
||||
if (app?.app.installedVersion != null &&
|
||||
if (noVersionDetection &&
|
||||
!trackOnly &&
|
||||
app?.app.installedVersion != null &&
|
||||
app?.app.installedVersion != app?.app.latestVersion)
|
||||
IconButton(
|
||||
onPressed: app?.downloadProgress != null
|
||||
@ -203,13 +206,6 @@ class _AppPageState extends State<AppPage> {
|
||||
return AlertDialog(
|
||||
title: Text(tr(
|
||||
'alreadyUpToDateQuestion')),
|
||||
content: Text(
|
||||
tr('onlyWorksWithNonEVDApps'),
|
||||
style: const TextStyle(
|
||||
fontWeight:
|
||||
FontWeight.bold,
|
||||
fontStyle:
|
||||
FontStyle.italic)),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
@ -267,8 +263,9 @@ class _AppPageState extends State<AppPage> {
|
||||
return row;
|
||||
}).toList();
|
||||
return GeneratedFormModal(
|
||||
title: tr('additionalOptions'),
|
||||
items: items);
|
||||
title: tr('additionalOptions'),
|
||||
items: items,
|
||||
);
|
||||
}).then((values) {
|
||||
if (app != null && values != null) {
|
||||
var changedApp = app.app;
|
||||
@ -289,7 +286,15 @@ class _AppPageState extends State<AppPage> {
|
||||
});
|
||||
},
|
||||
tooltip: tr('additionalOptions'),
|
||||
icon: const Icon(Icons.settings)),
|
||||
icon: const Icon(Icons.edit)),
|
||||
if (app != null && app.installedInfo != null)
|
||||
IconButton(
|
||||
onPressed: () {
|
||||
appsProvider.openAppSettings(app.app.id);
|
||||
},
|
||||
icon: const Icon(Icons.settings),
|
||||
tooltip: tr('settings'),
|
||||
),
|
||||
if (app != null && settingsProvider.showAppWebpage)
|
||||
IconButton(
|
||||
onPressed: () {
|
||||
@ -317,7 +322,7 @@ class _AppPageState extends State<AppPage> {
|
||||
tooltip: tr('more')),
|
||||
const SizedBox(width: 16.0),
|
||||
Expanded(
|
||||
child: ElevatedButton(
|
||||
child: TextButton(
|
||||
onPressed: (app?.app.installedVersion == null ||
|
||||
app?.app.installedVersion !=
|
||||
app?.app.latestVersion) &&
|
||||
@ -342,6 +347,8 @@ class _AppPageState extends State<AppPage> {
|
||||
if (res.isNotEmpty && mounted) {
|
||||
Navigator.of(context).pop();
|
||||
}
|
||||
}).catchError((e) {
|
||||
showError(e, context);
|
||||
});
|
||||
}).catchError((e) {
|
||||
showError(e, context);
|
||||
@ -356,44 +363,17 @@ class _AppPageState extends State<AppPage> {
|
||||
? tr('update')
|
||||
: tr('markUpdated')))),
|
||||
const SizedBox(width: 16.0),
|
||||
ElevatedButton(
|
||||
Expanded(
|
||||
child: TextButton(
|
||||
onPressed: app?.downloadProgress != null
|
||||
? null
|
||||
: () {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (BuildContext ctx) {
|
||||
return AlertDialog(
|
||||
title: Text(tr('removeAppQuestion')),
|
||||
content: Text(tr(
|
||||
'xWillBeRemovedButRemainInstalled',
|
||||
args: [
|
||||
app?.installedInfo?.name ??
|
||||
app?.app.name ??
|
||||
tr('app')
|
||||
])),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
HapticFeedback
|
||||
.selectionClick();
|
||||
appsProvider.removeApps(
|
||||
[app!.app.id]).then((_) {
|
||||
int count = 0;
|
||||
Navigator.of(context)
|
||||
.popUntil((_) =>
|
||||
count++ >= 2);
|
||||
});
|
||||
},
|
||||
child: Text(tr('remove'))),
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
child: Text(tr('cancel')))
|
||||
],
|
||||
);
|
||||
});
|
||||
appsProvider.removeAppsWithModal(
|
||||
context, [app!.app]).then((value) {
|
||||
if (value == true) {
|
||||
Navigator.of(context).pop();
|
||||
}
|
||||
});
|
||||
},
|
||||
style: TextButton.styleFrom(
|
||||
foregroundColor:
|
||||
@ -401,7 +381,7 @@ class _AppPageState extends State<AppPage> {
|
||||
surfaceTintColor:
|
||||
Theme.of(context).colorScheme.error),
|
||||
child: Text(tr('remove')),
|
||||
),
|
||||
)),
|
||||
])),
|
||||
if (app?.downloadProgress != null)
|
||||
Padding(
|
||||
|
1034
lib/pages/apps.dart
@ -63,21 +63,29 @@ class _HomePageState extends State<HomePage> {
|
||||
.map((e) =>
|
||||
NavigationDestination(icon: Icon(e.icon), label: e.title))
|
||||
.toList(),
|
||||
onDestinationSelected: (int index) {
|
||||
onDestinationSelected: (int index) async {
|
||||
HapticFeedback.selectionClick();
|
||||
setState(() {
|
||||
if (index == 0) {
|
||||
if (index == 0) {
|
||||
while ((pages[0].widget.key as GlobalKey<AppsPageState>)
|
||||
.currentState !=
|
||||
null) {
|
||||
// Avoid duplicate GlobalKey error
|
||||
await Future.delayed(const Duration(microseconds: 1));
|
||||
}
|
||||
setState(() {
|
||||
selectedIndexHistory.clear();
|
||||
} else if (selectedIndexHistory.isEmpty ||
|
||||
(selectedIndexHistory.isNotEmpty &&
|
||||
selectedIndexHistory.last != index)) {
|
||||
});
|
||||
} else if (selectedIndexHistory.isEmpty ||
|
||||
(selectedIndexHistory.isNotEmpty &&
|
||||
selectedIndexHistory.last != index)) {
|
||||
setState(() {
|
||||
int existingInd = selectedIndexHistory.indexOf(index);
|
||||
if (existingInd >= 0) {
|
||||
selectedIndexHistory.removeAt(existingInd);
|
||||
}
|
||||
selectedIndexHistory.add(index);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
},
|
||||
selectedIndex:
|
||||
selectedIndexHistory.isEmpty ? 0 : selectedIndexHistory.last,
|
||||
|
@ -564,18 +564,22 @@ class _UrlSelectionModalState extends State<UrlSelectionModal> {
|
||||
widget.onlyOneSelectionAllowed ? tr('selectURL') : tr('selectURLs')),
|
||||
content: Column(children: [
|
||||
...urlWithDescriptionSelections.keys.map((urlWithD) {
|
||||
select(bool? value) {
|
||||
setState(() {
|
||||
value ??= false;
|
||||
if (value! && widget.onlyOneSelectionAllowed) {
|
||||
selectOnlyOne(urlWithD.key);
|
||||
} else {
|
||||
urlWithDescriptionSelections[urlWithD] = value!;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return Row(children: [
|
||||
Checkbox(
|
||||
value: urlWithDescriptionSelections[urlWithD],
|
||||
onChanged: (value) {
|
||||
setState(() {
|
||||
value ??= false;
|
||||
if (value! && widget.onlyOneSelectionAllowed) {
|
||||
selectOnlyOne(urlWithD.key);
|
||||
} else {
|
||||
urlWithDescriptionSelections[urlWithD] = value!;
|
||||
}
|
||||
});
|
||||
select(value);
|
||||
}),
|
||||
const SizedBox(
|
||||
width: 8,
|
||||
@ -599,12 +603,17 @@ class _UrlSelectionModalState extends State<UrlSelectionModal> {
|
||||
const TextStyle(decoration: TextDecoration.underline),
|
||||
textAlign: TextAlign.start,
|
||||
)),
|
||||
Text(
|
||||
urlWithD.value.length > 128
|
||||
? '${urlWithD.value.substring(0, 128)}...'
|
||||
: urlWithD.value,
|
||||
style: const TextStyle(
|
||||
fontStyle: FontStyle.italic, fontSize: 12),
|
||||
GestureDetector(
|
||||
onTap: () {
|
||||
select(!(urlWithDescriptionSelections[urlWithD] ?? false));
|
||||
},
|
||||
child: Text(
|
||||
urlWithD.value.length > 128
|
||||
? '${urlWithD.value.substring(0, 128)}...'
|
||||
: urlWithD.value,
|
||||
style: const TextStyle(
|
||||
fontStyle: FontStyle.italic, fontSize: 12),
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
height: 8,
|
||||
|
@ -4,10 +4,8 @@ import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:obtainium/components/custom_app_bar.dart';
|
||||
import 'package:obtainium/components/generated_form.dart';
|
||||
import 'package:obtainium/components/generated_form_modal.dart';
|
||||
import 'package:obtainium/custom_errors.dart';
|
||||
import 'package:obtainium/main.dart';
|
||||
import 'package:obtainium/providers/apps_provider.dart';
|
||||
import 'package:obtainium/providers/logs_provider.dart';
|
||||
import 'package:obtainium/providers/settings_provider.dart';
|
||||
import 'package:obtainium/providers/source_provider.dart';
|
||||
|
@ -5,6 +5,7 @@ import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:android_intent_plus/flag.dart';
|
||||
import 'package:device_info_plus/device_info_plus.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
@ -12,6 +13,8 @@ import 'package:flutter/services.dart';
|
||||
import 'package:install_plugin_v2/install_plugin_v2.dart';
|
||||
import 'package:installed_apps/app_info.dart';
|
||||
import 'package:installed_apps/installed_apps.dart';
|
||||
import 'package:obtainium/components/generated_form.dart';
|
||||
import 'package:obtainium/components/generated_form_modal.dart';
|
||||
import 'package:obtainium/custom_errors.dart';
|
||||
import 'package:obtainium/providers/logs_provider.dart';
|
||||
import 'package:obtainium/providers/notifications_provider.dart';
|
||||
@ -23,6 +26,7 @@ import 'package:path_provider/path_provider.dart';
|
||||
import 'package:flutter_fgbg/flutter_fgbg.dart';
|
||||
import 'package:obtainium/providers/source_provider.dart';
|
||||
import 'package:http/http.dart';
|
||||
import 'package:android_intent_plus/android_intent.dart';
|
||||
|
||||
class AppInMemory {
|
||||
late App app;
|
||||
@ -247,7 +251,11 @@ class AppsProvider with ChangeNotifier {
|
||||
!(await canDowngradeApps())) {
|
||||
throw DowngradeError();
|
||||
}
|
||||
await InstallPlugin.installApk(file.file.path, 'dev.imranr.obtainium');
|
||||
await InstallPlugin.installApk(file.file.path, obtainiumId);
|
||||
if (file.appId == obtainiumId) {
|
||||
// Obtainium prompt should be lowest
|
||||
await Future.delayed(const Duration(milliseconds: 500));
|
||||
}
|
||||
apps[file.appId]!.app.installedVersion =
|
||||
apps[file.appId]!.app.latestVersion;
|
||||
// Don't correct install status as installation may not be done yet
|
||||
@ -255,6 +263,15 @@ class AppsProvider with ChangeNotifier {
|
||||
attemptToCorrectInstallStatus: false);
|
||||
}
|
||||
|
||||
void uninstallApp(String appId) async {
|
||||
var intent = AndroidIntent(
|
||||
action: 'android.intent.action.DELETE',
|
||||
data: 'package:$appId',
|
||||
flags: <int>[Flag.FLAG_ACTIVITY_NEW_TASK],
|
||||
package: 'vnd.android.package-archive');
|
||||
await intent.launch();
|
||||
}
|
||||
|
||||
Future<String?> confirmApkUrl(App app, BuildContext? context) async {
|
||||
// If the App has more than one APK, the user should pick one (if context provided)
|
||||
String? apkUrl = app.apkUrls[app.preferredApkIndex];
|
||||
@ -262,6 +279,7 @@ class AppsProvider with ChangeNotifier {
|
||||
List<String> archs = (await DeviceInfoPlugin().androidInfo).supportedAbis;
|
||||
|
||||
if (app.apkUrls.length > 1 && context != null) {
|
||||
// ignore: use_build_context_synchronously
|
||||
apkUrl = await showDialog(
|
||||
context: context,
|
||||
builder: (BuildContext ctx) {
|
||||
@ -281,6 +299,7 @@ class AppsProvider with ChangeNotifier {
|
||||
if (apkUrl != null &&
|
||||
getHost(apkUrl) != getHost(app.url) &&
|
||||
context != null) {
|
||||
// ignore: use_build_context_synchronously
|
||||
if (await showDialog(
|
||||
context: context,
|
||||
builder: (BuildContext ctx) {
|
||||
@ -438,9 +457,6 @@ class AppsProvider with ChangeNotifier {
|
||||
} catch (e) {
|
||||
//
|
||||
}
|
||||
if (!res) {
|
||||
logs.add(tr('versionCorrectionDisabled'));
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
@ -619,6 +635,57 @@ class AppsProvider with ChangeNotifier {
|
||||
}
|
||||
}
|
||||
|
||||
Future<bool> removeAppsWithModal(BuildContext context, List<App> apps) async {
|
||||
var showUninstallOption =
|
||||
apps.where((a) => a.installedVersion != null).isNotEmpty;
|
||||
var values = await showDialog(
|
||||
context: context,
|
||||
builder: (BuildContext ctx) {
|
||||
return GeneratedFormModal(
|
||||
title: plural('removeAppQuestion', apps.length),
|
||||
items: !showUninstallOption
|
||||
? []
|
||||
: [
|
||||
[
|
||||
GeneratedFormSwitch('rmAppEntry',
|
||||
label: tr('removeFromObtainium'), defaultValue: true)
|
||||
],
|
||||
[
|
||||
GeneratedFormSwitch('uninstallApp',
|
||||
label: tr('uninstallFromDevice'))
|
||||
]
|
||||
],
|
||||
initValid: true,
|
||||
);
|
||||
});
|
||||
if (values != null) {
|
||||
bool uninstall = values['uninstallApp'] == true && showUninstallOption;
|
||||
bool remove = values['rmAppEntry'] == true || !showUninstallOption;
|
||||
if (uninstall) {
|
||||
for (var i = 0; i < apps.length; i++) {
|
||||
if (apps[i].installedVersion != null) {
|
||||
uninstallApp(apps[i].id);
|
||||
apps[i].installedVersion = null;
|
||||
}
|
||||
}
|
||||
await saveApps(apps, attemptToCorrectInstallStatus: false);
|
||||
}
|
||||
if (remove) {
|
||||
await removeApps(apps.map((e) => e.id).toList());
|
||||
}
|
||||
return uninstall || remove;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
Future<void> openAppSettings(String appId) async {
|
||||
final AndroidIntent intent = AndroidIntent(
|
||||
action: 'action_application_details_settings',
|
||||
data: 'package:$appId',
|
||||
);
|
||||
await intent.launch();
|
||||
}
|
||||
|
||||
Future<App?> checkUpdate(String appId) async {
|
||||
App? currentApp = apps[appId]!.app;
|
||||
SourceProvider sourceProvider = SourceProvider();
|
||||
@ -704,7 +771,7 @@ class AppsProvider with ChangeNotifier {
|
||||
exportDir = await getExternalStorageDirectory();
|
||||
path = exportDir!.path;
|
||||
}
|
||||
if ((await DeviceInfoPlugin().androidInfo).version.sdkInt <= 28) {
|
||||
if ((await DeviceInfoPlugin().androidInfo).version.sdkInt <= 29) {
|
||||
if (await Permission.storage.isDenied) {
|
||||
await Permission.storage.request();
|
||||
}
|
||||
|
@ -225,7 +225,19 @@ class AppSource {
|
||||
label: tr('trackOnly'),
|
||||
)
|
||||
],
|
||||
[GeneratedFormSwitch('noVersionDetection', label: tr('noVersionDetection'))]
|
||||
[
|
||||
GeneratedFormSwitch('noVersionDetection', label: tr('noVersionDetection'))
|
||||
],
|
||||
[
|
||||
GeneratedFormTextField('apkFilterRegEx',
|
||||
label: tr('filterAPKsByRegEx'),
|
||||
required: false,
|
||||
additionalValidators: [
|
||||
(value) {
|
||||
return regExValidator(value);
|
||||
}
|
||||
])
|
||||
]
|
||||
];
|
||||
|
||||
// Previous 2 variables combined into one at runtime for convenient usage
|
||||
@ -269,6 +281,18 @@ abstract class MassAppUrlSource {
|
||||
Future<Map<String, String>> getUrlsWithDescriptions(List<String> args);
|
||||
}
|
||||
|
||||
regExValidator(String? value) {
|
||||
if (value == null || value.isEmpty) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
RegExp(value);
|
||||
} catch (e) {
|
||||
return tr('invalidRegEx');
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
class SourceProvider {
|
||||
// Add more source classes here so they are available via the service
|
||||
List<AppSource> sources = [
|
||||
@ -344,10 +368,13 @@ class SourceProvider {
|
||||
}
|
||||
|
||||
Future<App> getApp(
|
||||
AppSource source, String url, Map<String, dynamic> additionalSettings,
|
||||
{App? currentApp,
|
||||
bool trackOnlyOverride = false,
|
||||
noVersionDetectionOverride = false}) async {
|
||||
AppSource source,
|
||||
String url,
|
||||
Map<String, dynamic> additionalSettings, {
|
||||
App? currentApp,
|
||||
bool trackOnlyOverride = false,
|
||||
noVersionDetectionOverride = false,
|
||||
}) async {
|
||||
if (trackOnlyOverride || source.enforceTrackOnly) {
|
||||
additionalSettings['trackOnly'] = true;
|
||||
}
|
||||
@ -358,6 +385,11 @@ class SourceProvider {
|
||||
String standardUrl = source.standardizeURL(preStandardizeUrl(url));
|
||||
APKDetails apk =
|
||||
await source.getLatestAPKDetails(standardUrl, additionalSettings);
|
||||
if (additionalSettings['apkFilterRegEx'] != null) {
|
||||
var reg = RegExp(additionalSettings['apkFilterRegEx']);
|
||||
apk.apkUrls =
|
||||
apk.apkUrls.where((element) => reg.hasMatch(element)).toList();
|
||||
}
|
||||
if (apk.apkUrls.isEmpty && !trackOnly) {
|
||||
throw NoAPKError();
|
||||
}
|
||||
|
416
pubspec.lock
@ -17,7 +17,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev
|
||||
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
|
||||
# In Windows, build-name is used as the major, minor, and patch parts
|
||||
# of the product and file versions while build-number is used as the build suffix.
|
||||
version: 0.10.2+108 # When changing this, update the tag in main() accordingly
|
||||
version: 0.10.11+117 # When changing this, update the tag in main() accordingly
|
||||
|
||||
environment:
|
||||
sdk: '>=2.18.2 <3.0.0'
|
||||
@ -58,6 +58,7 @@ dependencies:
|
||||
android_alarm_manager_plus: ^2.1.0
|
||||
sqflite: ^2.2.0+3
|
||||
easy_localization: ^3.0.1
|
||||
android_intent_plus: ^3.1.5
|
||||
|
||||
|
||||
dev_dependencies:
|
||||
|