Compare commits

...

17 Commits

Author SHA1 Message Date
Imran Remtulla
08aa04f812 Merge pull request #504 from ImranR98/dev
Less restrictive install permission requests (#495)
Add toggles to disable warnings (#495 
Fix a breaking bug on Add App page from the previous PR
2023-04-30 01:04:04 -04:00
Imran Remtulla
dd19fcf6da Add warning popup toggles + fix breaking bug in add app 2023-04-30 00:58:32 -04:00
Imran Remtulla
04b3c8ad7d Bugfix for prev. commit 2023-04-30 00:15:53 -04:00
Imran Remtulla
81f66683d2 Less restrictive install permission requests (#488) 2023-04-30 00:02:23 -04:00
Imran Remtulla
392554123b Added an (experimental) Source override option for URLs that work with an existing Source but use a custom host (#271, #393) (#502) 2023-04-29 23:50:12 -04:00
Imran Remtulla
3e4d5c26ac Merge pull request #501 from LilligantMatsuri/main
Update zh.json
2023-04-29 23:37:50 -04:00
Matsuri
86b7f6fef3 Update zh.json
- Overhaul Simplified Chinese translation
- Add a missing string ("onlyWorksWithNonVersionDetectApps")
- Remove an invalid string ("onlyAppliesToInstalledAndOutdatedApps")
2023-04-29 22:10:00 +08:00
Imran Remtulla
e1d914118f Merge pull request #496 from ImranR98/dev
Show "Reset Install Status" button on track-only apps
2023-04-28 22:46:13 -04:00
Imran Remtulla
4a07cf9951 Show "Reset Install Status" button on track-only apps 2023-04-28 22:45:38 -04:00
Imran Remtulla
ce44e200a5 Merge pull request #495 from ImranR98/dev
HTML Source Bugfix (#288) + Better Placeholder Icon Visibility (#490)
2023-04-28 22:25:30 -04:00
Imran Remtulla
e8ebf53626 Increment version, update packages 2023-04-28 22:24:41 -04:00
Imran Remtulla
cdd6a4124c Merge pull request #492 from gidano/main
Update hu.json
2023-04-28 22:23:26 -04:00
Imran Remtulla
09c71e4e9f Increase icon placeholder opacity (#490) 2023-04-28 22:22:55 -04:00
Imran Remtulla
28a996441c HTML Source Bugfix #288 2023-04-28 22:18:58 -04:00
gidano
396bf012c9 Update hu.json 2023-04-25 15:55:30 +02:00
Imran Remtulla
02da24aa75 Merge pull request #487 from ImranR98/dev
Fixed null error for imported Apps (#476)
2023-04-23 03:23:38 -04:00
Imran Remtulla
3c6e66ce12 Fixed null error for imported Apps (#476) 2023-04-23 03:22:56 -04:00
35 changed files with 630 additions and 407 deletions

View File

@@ -17,7 +17,6 @@ Currently supported App sources:
- [SourceForge](https://sourceforge.net/)
- [APKMirror](https://apkmirror.com/) (Track-Only)
- Third Party F-Droid Repos
- Any URLs ending with `/fdroid/<word>`, where `<word>` can be anything - most often `repo`
- [Steam](https://store.steampowered.com/mobile)
- [Telegram App](https://telegram.org)
- [VLC](https://www.videolan.org/vlc/download-android.html)

View File

@@ -179,7 +179,7 @@
"lastUpdateCheckX": "Letzte Aktualisierungsprüfung: {}",
"remove": "Entfernen",
"yesMarkUpdated": "Ja, als aktualisiert markieren",
"fdroid": "F-Droid",
"fdroid": "F-Droid Official",
"appIdOrName": "App ID oder Name",
"appWithIdOrNameNotFound": "Es wurde keine App mit dieser ID oder diesem Namen gefunden",
"reposHaveMultipleApps": "Repos können mehrere Apps enthalten",
@@ -224,6 +224,10 @@
"standardVersionDetection": "Standardversionserkennung",
"groupByCategory": "Nach Kategorie gruppieren",
"autoApkFilterByArch": "Nach Möglichkeit versuchen, APKs nach CPU-Architektur zu filtern",
"overrideSource": "Override Source",
"dontShowAgain": "Don't show this again",
"dontShowTrackOnlyWarnings": "Don't Show the 'Track-Only' Warning",
"dontShowAPKOriginWarnings": "Don't Show APK Origin Warnings",
"removeAppQuestion": {
"one": "App entfernen?",
"other": "Apps entfernen?"

View File

@@ -179,7 +179,7 @@
"lastUpdateCheckX": "Last Update Check: {}",
"remove": "Remove",
"yesMarkUpdated": "Yes, Mark as Updated",
"fdroid": "F-Droid",
"fdroid": "F-Droid Official",
"appIdOrName": "App ID or Name",
"appWithIdOrNameNotFound": "No App was found with that ID or Name",
"reposHaveMultipleApps": "Repos may contain multiple Apps",
@@ -224,6 +224,10 @@
"standardVersionDetection": "Standard version detection",
"groupByCategory": "Group by Category",
"autoApkFilterByArch": "Attempt to filter APKs by CPU architecture if possible",
"overrideSource": "Override Source",
"dontShowAgain": "Don't show this again",
"dontShowTrackOnlyWarnings": "Don't Show 'Track-Only' Warnings",
"dontShowAPKOriginWarnings": "Don't Show APK Origin Warnings",
"removeAppQuestion": {
"one": "Remove App?",
"other": "Remove Apps?"

View File

@@ -179,7 +179,7 @@
"lastUpdateCheckX": "بررسی آخرین به‌روزرسانی: {}",
"remove": "حذف",
"yesMarkUpdated": "بله، علامت گذاری به عنوان به روز شده",
"fdroid": "F-Droid",
"fdroid": "F-Droid Official",
"appIdOrName": "شناسه یا نام برنامه",
"appWithIdOrNameNotFound": "هیچ برنامه ای با آن شناسه یا نام یافت نشد",
"reposHaveMultipleApps": "مخازن ممکن است شامل چندین برنامه باشد",
@@ -224,6 +224,10 @@
"standardVersionDetection": "تشخیص نسخه استاندارد",
"groupByCategory": "گروه بر اساس دسته",
"autoApkFilterByArch": "در صورت امکان سعی کنید APKها را بر اساس معماری CPU فیلتر کنید",
"overrideSource": "Override Source",
"dontShowAgain": "Don't show this again",
"dontShowTrackOnlyWarnings": "Don't Show the 'Track-Only' Warning",
"dontShowAPKOriginWarnings": "Don't Show APK Origin Warnings",
"removeAppQuestion": {
"one": "برنامه حذف شود؟",
"other": "برنامه ها حذف شوند؟"

View File

@@ -179,7 +179,7 @@
"lastUpdateCheckX": "Vérification de la dernière mise à jour : {}",
"remove": "Retirer",
"yesMarkUpdated": "Oui, marquer comme mis à jour",
"fdroid": "F-Droid",
"fdroid": "F-Droid Official",
"appIdOrName": "ID ou nom de l'application",
"appWithIdOrNameNotFound": "Aucune application n'a été trouvée avec cet identifiant ou ce nom",
"reposHaveMultipleApps": "Les dépôts peuvent contenir plusieurs applications",
@@ -224,6 +224,10 @@
"standardVersionDetection": "Détection de version standard",
"groupByCategory": "Group by Category",
"autoApkFilterByArch": "Attempt to filter APKs by CPU architecture if possible",
"overrideSource": "Override Source",
"dontShowAgain": "Don't show this again",
"dontShowTrackOnlyWarnings": "Don't Show the 'Track-Only' Warning",
"dontShowAPKOriginWarnings": "Don't Show APK Origin Warnings",
"removeAppQuestion": {
"one": "Supprimer l'application ?",
"other": "Supprimer les applications ?"

View File

@@ -122,7 +122,7 @@
"followSystem": "Rendszer szerint",
"obtainium": "Obtainium",
"materialYou": "Material You",
"useBlackTheme": "Use pure black dark theme",
"useBlackTheme": "Használjon tiszta fekete sötét témát",
"appSortBy": "App rendezés...",
"authorName": "Szerző/Név",
"nameAuthor": "Név/Szerző",
@@ -179,7 +179,7 @@
"lastUpdateCheckX": "Frissítés ellenőrizve: {}",
"remove": "Eltávolítás",
"yesMarkUpdated": "Igen, megjelölés frissítettként",
"fdroid": "F-Droid",
"fdroid": "F-Droid Official",
"appIdOrName": "App ID vagy név",
"appWithIdOrNameNotFound": "Nem található app ezzel az azonosítóval vagy névvel",
"reposHaveMultipleApps": "A repók több alkalmazást is tartalmazhatnak",
@@ -207,7 +207,7 @@
"addCategory": "Új kategória",
"label": "Címke",
"language": "Nyelv",
"copiedToClipboard": "Copied to Clipboard",
"copiedToClipboard": "Másolva a vágólapra",
"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",
@@ -223,6 +223,10 @@
"standardVersionDetection": "Alapért. verzió érzékelés",
"groupByCategory": "Csoportosítás Kategória alapján",
"autoApkFilterByArch": "Ha lehetséges, próbálja CPU architektúra szerint szűrni az APK-kat",
"overrideSource": "Override Source",
"dontShowAgain": "Don't show this again",
"dontShowTrackOnlyWarnings": "Don't Show the 'Track-Only' Warning",
"dontShowAPKOriginWarnings": "Don't Show APK Origin Warnings",
"removeAppQuestion": {
"one": "Eltávolítja az alkalmazást?",
"other": "Eltávolítja az alkalmazást?"

View File

@@ -179,7 +179,7 @@
"lastUpdateCheckX": "Ultimo controllo degli aggiornamenti: {}",
"remove": "Rimuovi",
"yesMarkUpdated": "Sì, contrassegna come aggiornato",
"fdroid": "F-Droid",
"fdroid": "F-Droid Official",
"appIdOrName": "ID o nome dell'App",
"appWithIdOrNameNotFound": "Non è stata trovata alcuna App con quell'ID o nome",
"reposHaveMultipleApps": "I repository possono contenere più App",
@@ -224,6 +224,10 @@
"standardVersionDetection": "Rilevamento di versione standard",
"groupByCategory": "Raggruppa per categoria",
"autoApkFilterByArch": "Tenta di filtrare gli APK in base all'architettura della CPU, se possibile",
"overrideSource": "Override Source",
"dontShowAgain": "Don't show this again",
"dontShowTrackOnlyWarnings": "Don't Show the 'Track-Only' Warning",
"dontShowAPKOriginWarnings": "Don't Show APK Origin Warnings",
"removeAppQuestion": {
"one": "Rimuovere l'App?",
"other": "Rimuovere le App?"

View File

@@ -179,7 +179,7 @@
"lastUpdateCheckX": "最終アップデート確認: {}",
"remove": "削除",
"yesMarkUpdated": "はい、アップデート済みとしてマークします",
"fdroid": "F-Droid",
"fdroid": "F-Droid Official",
"appIdOrName": "アプリのIDまたは名前",
"appWithIdOrNameNotFound": "そのIDや名前を持つアプリは見つかりませんでした",
"reposHaveMultipleApps": "リポジトリには複数のアプリが含まれることがあります",
@@ -224,6 +224,10 @@
"standardVersionDetection": "標準のバージョン検出",
"groupByCategory": "カテゴリ別にグループ化する",
"autoApkFilterByArch": "可能であればCPUアーキテクチャによるAPKのフィルタリングを試みる",
"overrideSource": "Override Source",
"dontShowAgain": "Don't show this again",
"dontShowTrackOnlyWarnings": "Don't Show the 'Track-Only' Warning",
"dontShowAPKOriginWarnings": "Don't Show APK Origin Warnings",
"removeAppQuestion": {
"one": "アプリを削除しますか?",
"other": "アプリを削除しますか?"

View File

@@ -1,106 +1,105 @@
{
"invalidURLForSource": "不是一个有效的 {} URL",
"noReleaseFound": "找不到合适的更新",
"noVersionFound": "无法确定更新版本",
"urlMatchesNoSource": "URL 与已知来源不符",
"cantInstallOlderVersion": "无法安装旧版应用程序",
"appIdMismatch": "下载的软件包名与现有应用程序包名不一致",
"functionNotImplemented": "该类没有实现此功能",
"invalidURLForSource": "效的 {} URL",
"noReleaseFound": "找不到合适的发行版",
"noVersionFound": "无法确定发行版本",
"urlMatchesNoSource": "URL 与已知来源不符",
"cantInstallOlderVersion": "无法安装旧版本的应用",
"appIdMismatch": "下载 APK 的应用 ID 与现有应用不一致",
"functionNotImplemented": "该类实现此功能",
"placeholder": "占位符",
"someErrors": "出现了一些错误",
"unexpectedError": "意外错误",
"ok": "好的",
"and": "和",
"startedBgUpdateTask": "开始后台检查更新任务",
"bgUpdateIgnoreAfterIs": "下次后台更新检查 {}",
"startedActualBGUpdateCheck": "后台检查更新已开始",
"bgUpdateTaskFinished": "后台检查更新已完成",
"firstRun": "这是你第一次运行 Obtainium",
"settingUpdateCheckIntervalTo": "设置检查更新间隔为 {}",
"githubPATLabel": "GitHub 个人访问令牌 (提高 API 限制)",
"githubPATHint": "个人访问令牌必须为: username:token式",
"startedBgUpdateTask": "后台更新检查任务已启动",
"bgUpdateIgnoreAfterIs": "后台更新检查间隔为 {}",
"startedActualBGUpdateCheck": "开始后台更新检查",
"bgUpdateTaskFinished": "后台更新检查任务已完成",
"firstRun": "这是 Obtainium 首次启动",
"settingUpdateCheckIntervalTo": "更新检查间隔设置为 {}",
"githubPATLabel": "GitHub 个人访问令牌(提升 API 请求限额)",
"githubPATHint": "个人访问令牌必须为username:token”的格式",
"githubPATFormat": "username:token",
"githubPATLinkText": "关于 GitHub 个人访问令牌",
"includePrereleases": "包含预发版",
"fallbackToOlderReleases": "回退到旧版",
"filterReleaseTitlesByRegEx": "使用正则以过滤发布标题",
"invalidRegEx": "表达式无效",
"includePrereleases": "包含预发版",
"fallbackToOlderReleases": "将旧发行版作为备选",
"filterReleaseTitlesByRegEx": "使用正则表达式筛选发行标题",
"invalidRegEx": "无效的正则表达式",
"noDescription": "无描述",
"cancel": "取消",
"continue": "继续",
"requiredInBrackets": "(必须)",
"dropdownNoOptsError": "错误:下拉菜单必须至少一个选项",
"colour": "色",
"requiredInBrackets": "(必填)",
"dropdownNoOptsError": "错误:下拉菜单必须包含至少一个选项",
"colour": "色",
"githubStarredRepos": "GitHub 已星标仓库",
"uname": "用户名",
"wrongArgNum": "提供了错误的参数数量",
"xIsTrackOnly": "{} 仅追踪",
"source": "源码",
"app": "应用程序",
"appsFromSourceAreTrackOnly": "来自此来源的应用为仅追踪",
"youPickedTrackOnly": "你已选择仅追踪选项",
"trackOnlyAppDescription": "该应用程序将被跟踪更新,但 Obtainium 无法下载或安装它",
"wrongArgNum": "参数数量错误",
"xIsTrackOnly": "{} 为“仅追踪”模式",
"source": "源码",
"app": "应用",
"appsFromSourceAreTrackOnly": "此来源的应用为仅追踪”模式。",
"youPickedTrackOnly": "您选择了“仅追踪”。",
"trackOnlyAppDescription": "该应用的更新会被追踪,但 Obtainium 无法下载或安装它",
"cancelled": "已取消",
"appAlreadyAdded": "此应用程序已被添加",
"alreadyUpToDateQuestion": "应用已是最新",
"appAlreadyAdded": "此应用已经添加",
"alreadyUpToDateQuestion": "应用是否已经为最新版本",
"addApp": "添加应用",
"appSourceURL": "应用来源 URL",
"appSourceURL": "来源 URL",
"error": "错误",
"add": "添加",
"searchSomeSourcesLabel": "搜索 (仅部分来源)",
"searchSomeSourcesLabel": "搜索仅部分来源",
"search": "搜索",
"additionalOptsFor": "{} 的更多选项",
"supportedSourcesBelow": "支持的来源:",
"trackOnlyInBrackets": "(仅追踪)",
"searchableInBrackets": "(可被搜索)",
"appsString": "应用程序",
"noApps": "无应用程序",
"noAppsForFilter": "没有应用可被过滤",
"byX": "来自 {}",
"percentProgress": "进度: {}%",
"pleaseWait": "请等待...",
"supportedSourcesBelow": "支持的来源",
"trackOnlyInBrackets": "仅追踪",
"searchableInBrackets": "(可搜索",
"appsString": "应用列表",
"noApps": "无应用",
"noAppsForFilter": "没有符合条件的应用",
"byX": "作者:{}",
"percentProgress": "进度{}%",
"pleaseWait": "请稍候",
"updateAvailable": "更新可用",
"estimateInBracketsShort": "(预计.)",
"estimateInBracketsShort": "预计",
"notInstalled": "未安装",
"estimateInBrackets": "(预计)",
"estimateInBrackets": "预计",
"selectAll": "全选",
"deselectN": "取消选择 {}",
"xWillBeRemovedButRemainInstalled": "{} 将从 Obtainium 中删除,但仍安装在设备。",
"removeSelectedAppsQuestion": "删除已选择的应用程序吗",
"removeSelectedApps": "删除已选择的应用程序",
"xWillBeRemovedButRemainInstalled": "{} 将从 Obtainium 中删除,但仍安装在您的设备。",
"removeSelectedAppsQuestion": "是否删除选中的应用",
"removeSelectedApps": "删除选中的应用",
"updateX": "更新 {}",
"installX": "安装 {}",
"markXTrackOnlyAsUpdated": "将仅追踪编辑为已更新",
"markXTrackOnlyAsUpdated": "将 {}\n仅追踪\n标记为已更新",
"changeX": "更改 {}",
"installUpdateApps": "安装/更新应用程序",
"installUpdateSelectedApps": "安装/更新已选择的应用程序",
"onlyAppliesToInstalledAndOutdatedApps": "'只适用于已安装但已过时的应用程序",
"markXSelectedAppsAsUpdated": "将已选择的 {} 个应用程序标记为已更新?",
"installUpdateApps": "安装/更新应用",
"installUpdateSelectedApps": "安装/更新选中的应用",
"markXSelectedAppsAsUpdated": "是否将选中的 {} 个应用标记为已更新?",
"no": "不要",
"yes": "好的",
"markSelectedAppsUpdated": "标记已选择的应用程序为已更新",
"markSelectedAppsUpdated": "将选中的应用标记为已更新",
"pinToTop": "置顶",
"unpinFromTop": "取消置顶",
"resetInstallStatusForSelectedAppsQuestion": "为已选择的应用程序重置安装状态",
"installStatusOfXWillBeResetExplanation": " Obtainium 中显示的应用程序版本由于更新失败或其他问题而不正确时,这将有助于重置任何选定应用程序的安装状态。",
"shareSelectedAppURLs": "分享已选择的应用程序 URL",
"resetInstallStatusForSelectedAppsQuestion": "是否重置选中应用的安装状态?",
"installStatusOfXWillBeResetExplanation": "选中应用的安装状态将会被重置。\n\n当更新安装失败或其他问题导致 Obtainium 中的应用版本显示错误时,可以尝试通过此方法解决。",
"shareSelectedAppURLs": "分享选中应用的 URL",
"resetInstallStatus": "重置安装状态",
"more": "更多",
"removeOutdatedFilter": "删除过时的应用程序过滤器",
"showOutdatedOnly": "只显示过时的应用程序",
"filter": "过滤器",
"filterActive": "过滤器 *",
"filterApps": "过滤应用",
"removeOutdatedFilter": "删除失效的应用筛选",
"showOutdatedOnly": "只显示待更新应用",
"filter": "筛选",
"filterActive": "筛选 *",
"filterApps": "筛选应用",
"appName": "应用名称",
"author": "作者",
"upToDateApps": "更新的应用程序",
"nonInstalledApps": "未安装的应用程序",
"upToDateApps": "无需更新的应用",
"nonInstalledApps": "未安装的应用",
"importExport": "导入/导出",
"settings": "设置",
"exportedTo": "导出 {}",
"exportedTo": "导出 {}",
"obtainiumExport": "Obtainium 导出",
"invalidInput": "无效输入",
"importedX": "已导出到 {}",
"invalidInput": "无效输入",
"importedX": "已导 {}",
"obtainiumImport": "Obtainium 导入",
"importFromURLList": "从 URL 列表导入",
"searchQuery": "搜索查询",
@@ -109,13 +108,13 @@
"searchX": "搜索 {}",
"noResults": "无结果",
"importX": "导入 {}",
"importedAppsIdDisclaimer": "导入的应用程序可能显示为未安装。要解决这个问题,请通过 Obtainium 重新安装它们。",
"importedAppsIdDisclaimer": "导入的应用可能错误地显示为未安装”。\n请通过 Obtainium 重新安装这些应用来解决此问题。",
"importErrors": "导入错误",
"importedXOfYApps": "{} 中的 {} 个应用已导入",
"followingURLsHadErrors": "下 URL 错误:",
"importedXOfYApps": "已导入 {} 中的 {} 个应用",
"followingURLsHadErrors": "下 URL 存在错误:",
"okay": "好的",
"selectURL": "选择 URL",
"selectURLs": "选择 URL",
"selectURL": "选择 URL",
"selectURLs": "选择 URL",
"pick": "选择",
"theme": "主题",
"dark": "深色",
@@ -123,68 +122,68 @@
"followSystem": "跟随系统",
"obtainium": "Obtainium",
"materialYou": "Material You",
"useBlackTheme": "Use pure black dark theme",
"appSortBy": "排列方式",
"authorName": "作者 / 名字",
"nameAuthor": "名字 / 作者",
"asAdded": "添加序",
"appSortOrder": "排列顺序",
"useBlackTheme": "使用纯黑深色主题",
"appSortBy": "排序依据",
"authorName": "作者 / 应用名称",
"nameAuthor": "应用名称 / 作者",
"asAdded": "添加序",
"appSortOrder": "顺序",
"ascending": "升序",
"descending": "降序",
"bgUpdateCheckInterval": "后台更新检查间隔",
"neverManualOnly": "手动",
"appearance": "外观",
"showWebInAppView": "在应用来源页显示网页",
"pinUpdates": "更新应用置顶",
"updates": "检查间隔",
"sourceSpecific": "Github 访问令牌",
"showWebInAppView": "在应用详情页显示来源网页",
"pinUpdates": "将待更新应用置顶",
"updates": "更新",
"sourceSpecific": "来源相关",
"appSource": "源代码",
"noLogs": "无日志",
"appLogs": "应用日志",
"appLogs": "日志",
"close": "关闭",
"share": "分享",
"appNotFound": "未找到应用",
"obtainiumExportHyphenatedLowercase": "obtainium-导出",
"pickAnAPK": "选择一个安装包",
"pickAnAPK": "选择一个 APK 文件",
"appHasMoreThanOnePackage": "{} 有多个架构可用:",
"deviceSupportsXArch": "的设备支持 {} 架构",
"deviceSupportsFollowingArchs": "的设备支持下架构:",
"deviceSupportsXArch": "的设备支持 {} 架构",
"deviceSupportsFollowingArchs": "的设备支持下架构",
"warning": "警告",
"sourceIsXButPackageFromYPrompt": "此应用来源是 '{}' 但更新包来自 '{}'。 继续",
"sourceIsXButPackageFromYPrompt": "此应用来源是“{}”,但 APK 文件来自“{}”。是否继续?",
"updatesAvailable": "更新可用",
"updatesAvailableNotifDescription": "通知 Obtainium 所跟踪应用程序的更新",
"noNewUpdates": "你的应用已是最新。",
"xHasAnUpdate": "{} 有更新啦",
"updatesAvailableNotifDescription": "Obtainium 追踪的应用有更新时发出通知",
"noNewUpdates": "全部应用已是最新。",
"xHasAnUpdate": "{} 可以更新了。",
"appsUpdated": "应用已更新",
"appsUpdatedNotifDescription": "通知在后台安装应用程序的更新",
"xWasUpdatedToY": "{} 已更新 {}.",
"appsUpdatedNotifDescription": "当应用在后台安装更新时发出通知",
"xWasUpdatedToY": "{} 已更新 {}",
"errorCheckingUpdates": "检查更新出错",
"errorCheckingUpdatesNotifDescription": "当后台更新检查失败时显示的通知",
"errorCheckingUpdatesNotifDescription": "当后台检查更新失败时显示的通知",
"appsRemoved": "应用已删除",
"appsRemovedNotifDescription": "通知由于加载应用程序时出错而被删除",
"xWasRemovedDueToErrorY": "{} 已因以下错误被删除: {}",
"appsRemovedNotifDescription": "当应用因加载出错而被删除时发出通知",
"xWasRemovedDueToErrorY": "{} 由于以下错误被删除:{}",
"completeAppInstallation": "完成应用安装",
"obtainiumMustBeOpenToInstallApps": "Obtainium 需要被启动以安装更新",
"completeAppInstallationNotifDescription": "需要返回 Obtainium以完成应用程序的安装",
"checkingForUpdates": "检查更新",
"checkingForUpdatesNotifDescription": "检查更新时出现的瞬时通知",
"pleaseAllowInstallPerm": "请允许 Obtainium 安装应用程序",
"obtainiumMustBeOpenToInstallApps": "必须启动 Obtainium 才能安装应用",
"completeAppInstallationNotifDescription": "提示返回 Obtainium 以完成应用的安装",
"checkingForUpdates": "正在检查更新",
"checkingForUpdatesNotifDescription": "检查更新时短暂显示的通知",
"pleaseAllowInstallPerm": "请授予 Obtainium 安装应用的权限",
"trackOnly": "仅追踪",
"errorWithHttpStatusCode": "错误 {}",
"versionCorrectionDisabled": "禁用版本更正(插件似乎未起作用)",
"versionCorrectionDisabled": "禁用版本更正(插件似乎未起作用)",
"unknown": "未知",
"none": "无",
"never": "从不",
"latestVersionX": "最新: {}",
"installedVersionX": "已安装: {}",
"lastUpdateCheckX": "最后检查: {}",
"latestVersionX": "最新版本:{}",
"installedVersionX": "当前版本:{}",
"lastUpdateCheckX": "上次更新检查:{}",
"remove": "删除",
"yesMarkUpdated": "'是的,标为已更新",
"fdroid": "F-Droid",
"yesMarkUpdated": "是的,标为已更新",
"fdroid": "F-Droid Official",
"appIdOrName": "应用 ID 或名称",
"appWithIdOrNameNotFound": "没有发现具有此 ID 或名称的应用",
"reposHaveMultipleApps": "来源可能包含多个应用",
"fdroidThirdPartyRepo": "F-Droid 第三方",
"appWithIdOrNameNotFound": "未找到符合此 ID 或名称的应用",
"reposHaveMultipleApps": "存储库中可能包含多个应用",
"fdroidThirdPartyRepo": "F-Droid 第三方存储库",
"steam": "Steam",
"steamMobile": "Steam Mobile",
"steamChat": "Steam Chat",
@@ -193,52 +192,57 @@
"update": "更新",
"markUpdated": "标记为已更新",
"additionalOptions": "附加选项",
"disableVersionDetection": "关闭版本检测",
"noVersionDetectionExplanation": "此选项应只用于版本检测不能工作的应用程序",
"downloadingX": "下载 {}",
"downloadNotifDescription": "通知用户下载进度",
"noAPKFound": "未找到安装包",
"noVersionDetection": "版本检测",
"categorize": "归档",
"categories": "归档",
"disableVersionDetection": "禁用版本检测",
"noVersionDetectionExplanation": "此选项应该仅用于无法进行版本检测的应用",
"downloadingX": "正在下载 {}",
"downloadNotifDescription": "提示应用的下载进度",
"noAPKFound": "未找到 APK 文件",
"noVersionDetection": "禁用版本检测",
"categorize": "分类",
"categories": "类别",
"category": "类别",
"noCategory": "无类别",
"noCategories": "无类别",
"deleteCategoriesQuestion": "删除所有类别?",
"categoryDeleteWarning": "所有被删除类别的应用程序将被设置为无类别",
"deleteCategoriesQuestion": "是否删除选中的类别?",
"categoryDeleteWarning": "被删除类别的应用将恢复为未分类状态。",
"addCategory": "添加类别",
"label": "标签",
"language": "语言",
"copiedToClipboard": "Copied to Clipboard",
"storagePermissionDenied": "存储权限已被拒绝",
"selectedCategorizeWarning": "这将取代所选应用程序的任何现有类别",
"filterAPKsByRegEx": "Filter APKs by Regular Expression",
"removeFromObtainium": "Remove from Obtainium",
"uninstallFromDevice": "Uninstall from Device",
"releaseDateAsVersion": "Use Release Date as Version",
"releaseDateAsVersionExplanation": "This option should only be used for Apps where version detection does not work correctly, but a release date is available.",
"changes": "Changes",
"releaseDate": "Release Date",
"importFromURLsInFile": "Import from URLs in File (like OPML)",
"versionDetection": "Version Detection",
"standardVersionDetection": "Standard version detection",
"groupByCategory": "Group by Category",
"autoApkFilterByArch": "Attempt to filter APKs by CPU architecture if possible",
"copiedToClipboard": "已复制至剪贴板",
"storagePermissionDenied": "已拒绝授予存储权限",
"selectedCategorizeWarning": "这将覆盖选中应用当前的类别设置。",
"filterAPKsByRegEx": "使用正则表达式筛选 APK 文件",
"removeFromObtainium": " Obtainium 中删除",
"uninstallFromDevice": "从设备中卸载",
"onlyWorksWithNonVersionDetectApps": "仅适用于禁用版本检测的应用。",
"releaseDateAsVersion": "将发行日期作为版本号",
"releaseDateAsVersionExplanation": "此选项应该仅用于无法进行版本检测但能够获取发行日期的应用。",
"changes": "更新日志",
"releaseDate": "发行日期",
"importFromURLsInFile": "从文件中的 URL 导入(如 OPML",
"versionDetection": "版本检测",
"standardVersionDetection": "常规版本检测",
"groupByCategory": "按类别分组显示",
"autoApkFilterByArch": "如果可能,尝试按 CPU 架构筛选 APK 文件",
"overrideSource": "Override Source",
"dontShowAgain": "Don't show this again",
"dontShowTrackOnlyWarnings": "Don't Show the 'Track-Only' Warning",
"dontShowAPKOriginWarnings": "Don't Show APK Origin Warnings",
"removeAppQuestion": {
"one": "删除应用?",
"other": "删除应用?"
"one": "是否删除应用?",
"other": "是否删除应用?"
},
"tooManyRequestsTryAgainInMinutes": {
"one": "请求过多 (API 限制) - 在 {} 分钟后重试",
"other": "请求过多 (API 限制) - 在 {} 分钟后重试"
"one": "API 请求过于频繁(速率限制)- 在 {} 分钟后重试",
"other": "API 请求过于频繁(速率限制)- 在 {} 分钟后重试"
},
"bgUpdateGotErrorRetryInMinutes": {
"one": "后台更新检查遇到了 {} 问题, 将在 {} 分钟后重试",
"other": "后台更新检查遇到了 {} 问题, 将在 {} 分钟后重试"
"one": "后台更新检查遇到了{}问题,预定于 {} 分钟后重试",
"other": "后台更新检查遇到了{}问题,预定于 {} 分钟后重试"
},
"bgCheckFoundUpdatesWillNotifyIfNeeded": {
"one": "后台更新检查找到了 {} 个更新 - 将通知用户",
"other": "后台更新检查找到了 {} 个更新 - 将通知用户"
"one": "后台检查发现 {} 个应用更新 - 如有需要将发出通知",
"other": "后台检查发现 {} 个应用更新 - 如有需要将发出通知"
},
"apps": {
"one": "{} 个应用",
@@ -261,15 +265,15 @@
"other": "{} 天"
},
"clearedNLogsBeforeXAfterY": {
"one": "清除了 {n} 个日志 (清除前 = {before}, 清除后 = {after})",
"other": "清除了 {n} 个日志 (清除前 = {before}, 清除后 = {after})"
"one": "清除了 {n} 个日志{before} 之前,{after} 之后)",
"other": "清除了 {n} 个日志{before} 之前,{after} 之后)"
},
"xAndNMoreUpdatesAvailable": {
"one": "{} 和 {} 更多应用已被更新",
"other": "{} 和 {} 更多应用已被更新"
"one": "{} 和另外 1 个应用可以更新了。",
"other": "{} 和另外 {} 个应用可以更新了。"
},
"xAndNMoreUpdatesInstalled": {
"one": "{} 和 {} 更多应用已被安装",
"other": "{} 和 {} 更多应用已被安装"
"one": "{} 和另外 1 个应用已更新。",
"other": "{} 和另外 {} 应用已更新。"
}
}

View File

@@ -31,7 +31,7 @@ class APKMirror extends AppSource {
}
@override
String standardizeURL(String url) {
String sourceSpecificStandardizeURL(String url) {
RegExp standardUrlRegEx = RegExp('^https?://$host/apk/[^/]+/[^/]+');
RegExpMatch? match = standardUrlRegEx.firstMatch(url.toLowerCase());
if (match == null) {

View File

@@ -39,7 +39,7 @@ class Codeberg extends AppSource {
var gh = GitHub();
@override
String standardizeURL(String url) {
String sourceSpecificStandardizeURL(String url) {
RegExp standardUrlRegEx = RegExp('^https?://$host/[^/]+/[^/]+');
RegExpMatch? match = standardUrlRegEx.firstMatch(url.toLowerCase());
if (match == null) {

View File

@@ -12,16 +12,15 @@ class FDroid extends AppSource {
}
@override
String standardizeURL(String url) {
String sourceSpecificStandardizeURL(String url) {
RegExp standardUrlRegExB =
RegExp('^https?://(cloudflare\\.)?$host/+[^/]+/+packages/+[^/]+');
RegExp('^https?://$host/+[^/]+/+packages/+[^/]+');
RegExpMatch? match = standardUrlRegExB.firstMatch(url.toLowerCase());
if (match != null) {
url =
'https://${Uri.parse(url.substring(0, match.end)).host}/packages/${Uri.parse(url).pathSegments.last}';
}
RegExp standardUrlRegExA =
RegExp('^https?://(cloudflare\\.)?$host/+packages/+[^/]+');
RegExp standardUrlRegExA = RegExp('^https?://$host/+packages/+[^/]+');
match = standardUrlRegExA.firstMatch(url.toLowerCase());
if (match == null) {
throw InvalidURLError(name);

View File

@@ -19,17 +19,6 @@ class FDroidRepo extends AppSource {
];
}
@override
String standardizeURL(String url) {
RegExp standardUrlRegExp =
RegExp('^https?://.+/fdroid/([^/]+(/|\\?)|[^/]+\$)');
RegExpMatch? match = standardUrlRegExp.firstMatch(url.toLowerCase());
if (match == null) {
throw InvalidURLError(name);
}
return url.substring(0, match.end);
}
@override
Future<APKDetails> getLatestAPKDetails(
String standardUrl,

View File

@@ -75,7 +75,7 @@ class GitHub extends AppSource {
}
@override
String standardizeURL(String url) {
String sourceSpecificStandardizeURL(String url) {
RegExp standardUrlRegEx = RegExp('^https?://$host/[^/]+/[^/]+');
RegExpMatch? match = standardUrlRegEx.firstMatch(url.toLowerCase());
if (match == null) {

View File

@@ -19,7 +19,7 @@ class GitLab extends AppSource {
}
@override
String standardizeURL(String url) {
String sourceSpecificStandardizeURL(String url) {
RegExp standardUrlRegEx = RegExp('^https?://$host/[^/]+/[^/]+');
RegExpMatch? match = standardUrlRegEx.firstMatch(url.toLowerCase());
if (match == null) {

View File

@@ -6,7 +6,7 @@ import 'package:obtainium/providers/source_provider.dart';
class HTML extends AppSource {
@override
String standardizeURL(String url) {
String sourceSpecificStandardizeURL(String url) {
return url;
}
@@ -41,9 +41,14 @@ class HTML extends AppSource {
} catch (err) {
// is relative
}
var currPathSegments = uri.path.split('/');
var currPathSegments = uri.path
.split('/')
.where((element) => element.trim().isNotEmpty)
.toList();
if (e.startsWith('/') || currPathSegments.isEmpty) {
return '${uri.origin}/$e';
} else if (e.split('/').length == 1) {
return '${uri.origin}/${currPathSegments.join('/')}/$e';
} else {
return '${uri.origin}/${currPathSegments.sublist(0, currPathSegments.length - 1).join('/')}/$e';
}

View File

@@ -9,7 +9,7 @@ class IzzyOnDroid extends AppSource {
}
@override
String standardizeURL(String url) {
String sourceSpecificStandardizeURL(String url) {
RegExp standardUrlRegEx = RegExp('^https?://$host/repo/apk/[^/]+');
RegExpMatch? match = standardUrlRegEx.firstMatch(url.toLowerCase());
if (match == null) {

View File

@@ -10,7 +10,7 @@ class Mullvad extends AppSource {
}
@override
String standardizeURL(String url) {
String sourceSpecificStandardizeURL(String url) {
RegExp standardUrlRegEx = RegExp('^https?://$host');
RegExpMatch? match = standardUrlRegEx.firstMatch(url.toLowerCase());
if (match == null) {

View File

@@ -9,7 +9,7 @@ class NeutronCode extends AppSource {
}
@override
String standardizeURL(String url) {
String sourceSpecificStandardizeURL(String url) {
RegExp standardUrlRegEx = RegExp('^https?://$host/downloads/file/[^/]+');
RegExpMatch? match = standardUrlRegEx.firstMatch(url.toLowerCase());
if (match == null) {

View File

@@ -9,7 +9,7 @@ class Signal extends AppSource {
}
@override
String standardizeURL(String url) {
String sourceSpecificStandardizeURL(String url) {
return 'https://$host';
}

View File

@@ -9,7 +9,7 @@ class SourceForge extends AppSource {
}
@override
String standardizeURL(String url) {
String sourceSpecificStandardizeURL(String url) {
RegExp standardUrlRegEx = RegExp('^https?://$host/projects/[^/]+');
RegExpMatch? match = standardUrlRegEx.firstMatch(url.toLowerCase());
if (match == null) {

View File

@@ -20,7 +20,7 @@ class SteamMobile extends AppSource {
final apks = {'steam': tr('steamMobile'), 'steam-chat-app': tr('steamChat')};
@override
String standardizeURL(String url) {
String sourceSpecificStandardizeURL(String url) {
return 'https://$host';
}

View File

@@ -11,7 +11,7 @@ class TelegramApp extends AppSource {
}
@override
String standardizeURL(String url) {
String sourceSpecificStandardizeURL(String url) {
return 'https://$host';
}

View File

@@ -10,7 +10,7 @@ class VLC extends AppSource {
}
@override
String standardizeURL(String url) {
String sourceSpecificStandardizeURL(String url) {
return 'https://$host';
}

View File

@@ -9,7 +9,7 @@ class WhatsApp extends AppSource {
}
@override
String standardizeURL(String url) {
String sourceSpecificStandardizeURL(String url) {
return 'https://$host';
}

View File

@@ -21,7 +21,7 @@ import 'package:easy_localization/src/easy_localization_controller.dart';
// ignore: implementation_imports
import 'package:easy_localization/src/localization.dart';
const String currentVersion = '0.11.33';
const String currentVersion = '0.12.0';
const String currentReleaseTag =
'v$currentVersion-beta'; // KEEP THIS IN SYNC WITH GITHUB RELEASES

View File

@@ -1,6 +1,7 @@
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:obtainium/app_sources/html.dart';
import 'package:obtainium/components/custom_app_bar.dart';
import 'package:obtainium/components/generated_form.dart';
import 'package:obtainium/components/generated_form_modal.dart';
@@ -28,6 +29,7 @@ class _AddAppPageState extends State<AddAppPage> {
String userInput = '';
String searchQuery = '';
String? pickedSourceOverride;
AppSource? pickedSource;
Map<String, dynamic> additionalSettings = {};
bool additionalSettingsValid = true;
@@ -49,8 +51,25 @@ class _AddAppPageState extends State<AddAppPage> {
if (isSearch) {
searchnum++;
}
var source = valid ? sourceProvider.getSource(userInput) : null;
if (pickedSource.runtimeType != source.runtimeType) {
var prevHost = pickedSource?.host;
try {
var naturalSource =
valid ? sourceProvider.getSource(userInput) : null;
if (naturalSource != null &&
naturalSource.runtimeType.toString() !=
HTML().runtimeType.toString()) {
// If input has changed to match a regular source, reset the override
pickedSourceOverride = null;
}
} catch (e) {
// ignore
}
var source = valid
? sourceProvider.getSource(userInput,
overrideSource: pickedSourceOverride)
: null;
if (pickedSource.runtimeType != source.runtimeType ||
(prevHost != null && prevHost != source?.host)) {
pickedSource = source;
additionalSettings = source != null
? getDefaultValuesFromFormItems(
@@ -64,24 +83,35 @@ class _AddAppPageState extends State<AddAppPage> {
}
}
getTrackOnlyConfirmationIfNeeded(bool userPickedTrackOnly) async {
return (!((userPickedTrackOnly || pickedSource!.enforceTrackOnly) &&
// ignore: use_build_context_synchronously
await showDialog(
context: context,
builder: (BuildContext ctx) {
return GeneratedFormModal(
title: tr('xIsTrackOnly', args: [
pickedSource!.enforceTrackOnly
? tr('source')
: tr('app')
]),
items: const [],
message:
'${pickedSource!.enforceTrackOnly ? tr('appsFromSourceAreTrackOnly') : tr('youPickedTrackOnly')}\n\n${tr('trackOnlyAppDescription')}',
);
}) ==
null));
Future<bool> getTrackOnlyConfirmationIfNeeded(
bool userPickedTrackOnly, SettingsProvider settingsProvider,
{bool ignoreHideSetting = false}) async {
var useTrackOnly = userPickedTrackOnly || pickedSource!.enforceTrackOnly;
if (useTrackOnly &&
(!settingsProvider.hideTrackOnlyWarning || ignoreHideSetting)) {
// ignore: use_build_context_synchronously
var values = await showDialog(
context: context,
builder: (BuildContext ctx) {
return GeneratedFormModal(
initValid: true,
title: tr('xIsTrackOnly', args: [
pickedSource!.enforceTrackOnly ? tr('source') : tr('app')
]),
items: [
[GeneratedFormSwitch('hide', label: tr('dontShowAgain'))]
],
message:
'${pickedSource!.enforceTrackOnly ? tr('appsFromSourceAreTrackOnly') : tr('youPickedTrackOnly')}\n\n${tr('trackOnlyAppDescription')}',
);
});
if (values != null) {
settingsProvider.hideTrackOnlyWarning = values['hide'] == true;
}
return useTrackOnly && values != null;
} else {
return true;
}
}
getReleaseDateAsVersionConfirmationIfNeeded(
@@ -109,16 +139,15 @@ class _AddAppPageState extends State<AddAppPage> {
var settingsProvider = context.read<SettingsProvider>();
var userPickedTrackOnly = additionalSettings['trackOnly'] == true;
App? app;
if ((await getTrackOnlyConfirmationIfNeeded(userPickedTrackOnly)) &&
if ((await getTrackOnlyConfirmationIfNeeded(
userPickedTrackOnly, settingsProvider)) &&
(await getReleaseDateAsVersionConfirmationIfNeeded(
userPickedTrackOnly))) {
var trackOnly = pickedSource!.enforceTrackOnly || userPickedTrackOnly;
app = await sourceProvider.getApp(
pickedSource!, userInput, additionalSettings,
trackOnlyOverride: trackOnly);
if (!trackOnly) {
await settingsProvider.getInstallPermission();
}
trackOnlyOverride: trackOnly,
overrideSource: pickedSourceOverride);
// Only download the APK here if you need to for the package ID
if (sourceProvider.isTempId(app) &&
app.additionalSettings['trackOnly'] != true) {
@@ -173,9 +202,9 @@ class _AddAppPageState extends State<AddAppPage> {
(value) {
try {
sourceProvider
.getSource(value ?? '')
.standardizeURL(
preStandardizeUrl(value ?? ''));
.getSource(value ?? '',
overrideSource: pickedSourceOverride)
.standardizeUrl(value ?? '');
} catch (e) {
return e is String
? e
@@ -260,6 +289,48 @@ class _AddAppPageState extends State<AddAppPage> {
}
}
Widget getHTMLSourceOverrideDropdown() => Column(children: [
Row(
children: [
Expanded(
child: GeneratedForm(
items: [
[
GeneratedFormDropdown(
'overrideSource',
defaultValue: HTML().runtimeType.toString(),
[
...sourceProvider.sources.map(
(s) => MapEntry(s.runtimeType.toString(), s.name))
],
label: tr('overrideSource'))
]
],
onValueChanges: (values, valid, isBuilding) {
fn() {
pickedSourceOverride = (values['overrideSource'] == null ||
values['overrideSource'] == '')
? null
: values['overrideSource'];
}
if (!isBuilding) {
setState(() {
fn();
});
} else {
fn();
}
changeUserInput(userInput, valid, isBuilding);
},
))
],
),
const SizedBox(
height: 25,
),
]);
bool shouldShowSearchBar() =>
sourceProvider.sources.where((e) => e.canSearch).isNotEmpty &&
pickedSource == null &&
@@ -309,6 +380,10 @@ class _AddAppPageState extends State<AddAppPage> {
const SizedBox(
height: 16,
),
if (pickedSourceOverride != null ||
pickedSource.runtimeType.toString() ==
HTML().runtimeType.toString())
getHTMLSourceOverrideDropdown(),
GeneratedForm(
key: Key(pickedSource.runtimeType.toString()),
items: pickedSource!.combinedAppSpecificSettingFormItems,
@@ -379,6 +454,9 @@ class _AddAppPageState extends State<AddAppPage> {
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
getUrlInputRow(),
const SizedBox(
height: 16,
),
if (shouldShowSearchBar())
const SizedBox(
height: 16,

View File

@@ -39,7 +39,10 @@ class _AppPageState extends State<AppPage> {
var sourceProvider = SourceProvider();
AppInMemory? app = appsProvider.apps[widget.appId]?.deepCopy();
var source = app != null ? sourceProvider.getSource(app.app.url) : null;
var source = app != null
? sourceProvider.getSource(app.app.url,
overrideSource: app.app.overrideSource)
: null;
if (!areDownloadsRunning && prevApp == null && app != null) {
prevApp = app;
getUpdate(app.app.id);
@@ -312,7 +315,10 @@ class _AppPageState extends State<AppPage> {
app!.app.installedVersion = null;
appsProvider.saveApps([app.app]);
},
child: Text(tr('resetInstallStatus')));
child: Text(
tr('resetInstallStatus'),
textAlign: TextAlign.center,
));
getInstallOrUpdateButton() => TextButton(
onPressed: (app?.app.installedVersion == null ||
@@ -321,9 +327,6 @@ class _AppPageState extends State<AppPage> {
? () async {
try {
HapticFeedback.heavyImpact();
if (app?.app.additionalSettings['trackOnly'] != true) {
await settingsProvider.getInstallPermission();
}
var res = await appsProvider.downloadAndInstallLatestApps(
[app!.app.id], globalNavigatorKey.currentContext);
if (res.isNotEmpty && mounted) {
@@ -410,7 +413,7 @@ class _AppPageState extends State<AppPage> {
tooltip: tr('more')),
const SizedBox(width: 16.0),
Expanded(
child: !isVersionDetectionStandard &&
child: (!isVersionDetectionStandard || trackOnly) &&
app?.app.installedVersion != null &&
app?.app.installedVersion ==
app?.app.latestVersion

View File

@@ -111,7 +111,11 @@ class AppsPageState extends State<AppsPage> {
return false;
}
if (filter.sourceFilter.isNotEmpty &&
sourceProvider.getSource(app.app.url).runtimeType.toString() !=
sourceProvider
.getSource(app.app.url,
overrideSource: app.app.overrideSource)
.runtimeType
.toString() !=
filter.sourceFilter) {
return false;
}
@@ -306,8 +310,9 @@ class AppsPageState extends State<AppsPage> {
}
getChangeLogFn(int appIndex) {
AppSource appSource =
SourceProvider().getSource(listedApps[appIndex].app.url);
AppSource appSource = SourceProvider().getSource(
listedApps[appIndex].app.url,
overrideSource: listedApps[appIndex].app.overrideSource);
String? changesUrl =
appSource.changeLogPageFromStandardUrl(listedApps[appIndex].app.url);
String? changeLog = listedApps[appIndex].app.changeLog;
@@ -364,7 +369,7 @@ class AppsPageState extends State<AppsPage> {
child: Image(
image: const AssetImage(
'assets/graphics/icon_small.png'),
color: Colors.white.withOpacity(0.1),
color: Colors.white.withOpacity(0.3),
colorBlendMode: BlendMode.modulate,
gaplessPlayback: true,
),
@@ -610,7 +615,7 @@ class AppsPageState extends State<AppsPage> {
items: formItems.map((e) => [e]).toList(),
initValid: true,
);
}).then((values) {
}).then((values) async {
if (values != null) {
if (values.isEmpty) {
values = getDefaultValuesFromFormItems([formItems]);
@@ -618,28 +623,22 @@ class AppsPageState extends State<AppsPage> {
bool shouldInstallUpdates = values['updates'] == true;
bool shouldInstallNew = values['installs'] == true;
bool shouldMarkTrackOnlies = values['trackonlies'] == true;
(() async {
if (shouldInstallNew || shouldInstallUpdates) {
await settingsProvider.getInstallPermission();
}
})()
.then((_) {
List<String> toInstall = [];
if (shouldInstallUpdates) {
toInstall.addAll(existingUpdateIdsAllOrSelected);
}
if (shouldInstallNew) {
toInstall.addAll(newInstallIdsAllOrSelected);
}
if (shouldMarkTrackOnlies) {
toInstall.addAll(trackOnlyUpdateIdsAllOrSelected);
}
appsProvider
.downloadAndInstallLatestApps(
toInstall, globalNavigatorKey.currentContext)
.catchError((e) {
showError(e, context);
});
List<String> toInstall = [];
if (shouldInstallUpdates) {
toInstall.addAll(existingUpdateIdsAllOrSelected);
}
if (shouldInstallNew) {
toInstall.addAll(newInstallIdsAllOrSelected);
}
if (shouldMarkTrackOnlies) {
toInstall.addAll(trackOnlyUpdateIdsAllOrSelected);
}
appsProvider
.downloadAndInstallLatestApps(
toInstall, globalNavigatorKey.currentContext,
settingsProvider: settingsProvider)
.catchError((e) {
showError(e, context);
});
}
});

View File

@@ -286,6 +286,34 @@ class _SettingsPageState extends State<SettingsPage> {
})
],
),
height16,
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(tr('dontShowTrackOnlyWarnings')),
Switch(
value:
settingsProvider.hideTrackOnlyWarning,
onChanged: (value) {
settingsProvider.hideTrackOnlyWarning =
value;
})
],
),
height16,
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(tr('dontShowAPKOriginWarnings')),
Switch(
value:
settingsProvider.hideAPKOriginWarning,
onChanged: (value) {
settingsProvider.hideAPKOriginWarning =
value;
})
],
),
const Divider(
height: 16,
),

View File

@@ -172,7 +172,7 @@ class AppsProvider with ChangeNotifier {
}
try {
String downloadUrl = await SourceProvider()
.getSource(app.url)
.getSource(app.url, overrideSource: app.overrideSource)
.apkUrlPrefetchModifier(app.apkUrls[app.preferredApkIndex].value);
var fileName = '${app.id}-${downloadUrl.hashCode}.apk';
var notif = DownloadNotification(app.finalName, 100);
@@ -204,7 +204,8 @@ class AppsProvider with ChangeNotifier {
// The former case should be handled (give the App its real ID), the latter is a security issue
var newInfo = await PackageArchiveInfo.fromPath(downloadedFile.path);
if (app.id != newInfo.packageName) {
if (apps[app.id] != null && !SourceProvider().isTempId(app)) {
var isTempId = SourceProvider().isTempId(app);
if (apps[app.id] != null && !isTempId) {
throw IDChangedError();
}
var originalAppId = app.id;
@@ -213,7 +214,7 @@ class AppsProvider with ChangeNotifier {
'${downloadedFile.parent.path}/${app.id}-${downloadUrl.hashCode}.apk');
if (apps[originalAppId] != null) {
await removeApps([originalAppId]);
await saveApps([app]);
await saveApps([app], onlyIfExists: !isTempId);
}
}
return DownloadedApk(app.id, downloadedFile);
@@ -331,13 +332,16 @@ class AppsProvider with ChangeNotifier {
getHost(apkUrl.value) != getHost(app.url) &&
context != null) {
// ignore: use_build_context_synchronously
if (await showDialog(
context: context,
builder: (BuildContext ctx) {
return APKOriginWarningDialog(
sourceUrl: app.url, apkUrl: apkUrl!.value);
}) !=
true) {
var settingsProvider = context.read<SettingsProvider>();
if (!(settingsProvider.hideAPKOriginWarning) &&
// ignore: use_build_context_synchronously
await showDialog(
context: context,
builder: (BuildContext ctx) {
return APKOriginWarningDialog(
sourceUrl: app.url, apkUrl: apkUrl!.value);
}) !=
true) {
apkUrl = null;
}
}
@@ -350,7 +354,8 @@ class AppsProvider with ChangeNotifier {
// If user input is needed and the App is in the background, a notification is sent to get the user's attention
// Returns an array of Ids for Apps that were successfully downloaded, regardless of installation result
Future<List<String>> downloadAndInstallLatestApps(
List<String> appIds, BuildContext? context) async {
List<String> appIds, BuildContext? context,
{SettingsProvider? settingsProvider}) async {
List<String> appsToInstall = [];
List<String> trackOnlyAppsToUpdate = [];
// For all specified Apps, filter out those for which:
@@ -439,6 +444,11 @@ class AppsProvider with ChangeNotifier {
silentUpdates = moveObtainiumToStart(silentUpdates);
regularInstalls = moveObtainiumToStart(regularInstalls);
if (!(await settingsProvider?.getInstallPermission(enforce: false) ??
true)) {
throw ObtainiumError(tr('cancelled'));
}
// // Install silent updates (uncomment when it works - TODO)
// for (var u in silentUpdates) {
// await installApk(u, silent: true); // Would need to add silent option
@@ -646,7 +656,7 @@ class AppsProvider with ChangeNotifier {
for (int i = 0; i < newApps.length; i++) {
var info = await getInstalledInfo(newApps[i].id);
try {
sp.getSource(newApps[i].url);
sp.getSource(newApps[i].url, overrideSource: newApps[i].overrideSource);
apps[newApps[i].id] = AppInMemory(newApps[i], null, info);
} catch (e) {
errors.add([newApps[i].id, newApps[i].finalName, e.toString()]);
@@ -786,7 +796,8 @@ class AppsProvider with ChangeNotifier {
App? currentApp = apps[appId]!.app;
SourceProvider sourceProvider = SourceProvider();
App newApp = await sourceProvider.getApp(
sourceProvider.getSource(currentApp.url),
sourceProvider.getSource(currentApp.url,
overrideSource: currentApp.overrideSource),
currentApp.url,
currentApp.additionalSettings,
currentApp: currentApp);

View File

@@ -120,16 +120,20 @@ class SettingsProvider with ChangeNotifier {
return result;
}
Future<void> getInstallPermission() async {
Future<bool> getInstallPermission({bool enforce = false}) async {
while (!(await Permission.requestInstallPackages.isGranted)) {
// Explicit request as InstallPlugin request sometimes bugged
Fluttertoast.showToast(
msg: tr('pleaseAllowInstallPerm'), toastLength: Toast.LENGTH_LONG);
if ((await Permission.requestInstallPackages.request()) ==
PermissionStatus.granted) {
break;
return true;
}
if (!enforce) {
return false;
}
}
return true;
}
bool get showAppWebpage {
@@ -159,6 +163,24 @@ class SettingsProvider with ChangeNotifier {
notifyListeners();
}
bool get hideTrackOnlyWarning {
return prefs?.getBool('hideTrackOnlyWarning') ?? false;
}
set hideTrackOnlyWarning(bool show) {
prefs?.setBool('hideTrackOnlyWarning', show);
notifyListeners();
}
bool get hideAPKOriginWarning {
return prefs?.getBool('hideAPKOriginWarning') ?? false;
}
set hideAPKOriginWarning(bool show) {
prefs?.setBool('hideAPKOriginWarning', show);
notifyListeners();
}
String? getSettingString(String settingId) {
return prefs?.getString(settingId);
}

View File

@@ -44,6 +44,106 @@ class APKDetails {
{this.releaseDate, this.changeLog});
}
stringMapListTo2DList(List<MapEntry<String, String>> mapList) =>
mapList.map((e) => [e.key, e.value]).toList();
assumed2DlistToStringMapList(List<dynamic> arr) =>
arr.map((e) => MapEntry(e[0] as String, e[1] as String)).toList();
// App JSON schema has changed multiple times over the many versions of Obtainium
// This function takes an App JSON and modifies it if needed to conform to the latest (current) version
appJSONCompatibilityModifiers(Map<String, dynamic> json) {
var source = SourceProvider()
.getSource(json['url'], overrideSource: json['overrideSource']);
var formItems = source.combinedAppSpecificSettingFormItems
.reduce((value, element) => [...value, ...element]);
Map<String, dynamic> additionalSettings =
getDefaultValuesFromFormItems([formItems]);
if (json['additionalSettings'] != null) {
additionalSettings.addEntries(
Map<String, dynamic>.from(jsonDecode(json['additionalSettings']))
.entries);
}
// If needed, migrate old-style additionalData to newer-style additionalSettings (V1)
if (json['additionalData'] != null) {
List<String> temp = List<String>.from(jsonDecode(json['additionalData']));
temp.asMap().forEach((i, value) {
if (i < formItems.length) {
if (formItems[i] is GeneratedFormSwitch) {
additionalSettings[formItems[i].key] = value == 'true';
} else {
additionalSettings[formItems[i].key] = value;
}
}
});
additionalSettings['trackOnly'] =
json['trackOnly'] == 'true' || json['trackOnly'] == true;
additionalSettings['noVersionDetection'] =
json['noVersionDetection'] == 'true' || json['trackOnly'] == true;
}
// Convert bool style version detection options to dropdown style
if (additionalSettings['noVersionDetection'] == true) {
additionalSettings['versionDetection'] = 'noVersionDetection';
if (additionalSettings['releaseDateAsVersion'] == true) {
additionalSettings['versionDetection'] = 'releaseDateAsVersion';
additionalSettings.remove('releaseDateAsVersion');
}
if (additionalSettings['noVersionDetection'] != null) {
additionalSettings.remove('noVersionDetection');
}
if (additionalSettings['releaseDateAsVersion'] != null) {
additionalSettings.remove('releaseDateAsVersion');
}
}
// Ensure additionalSettings are correctly typed
for (var item in formItems) {
if (additionalSettings[item.key] != null) {
additionalSettings[item.key] =
item.ensureType(additionalSettings[item.key]);
}
}
int preferredApkIndex =
json['preferredApkIndex'] == null ? 0 : json['preferredApkIndex'] as int;
if (preferredApkIndex < 0) {
preferredApkIndex = 0;
}
json['preferredApkIndex'] = preferredApkIndex;
// apkUrls can either be old list or new named list apkUrls
List<MapEntry<String, String>> apkUrls = [];
if (json['apkUrls'] != null) {
var apkUrlJson = jsonDecode(json['apkUrls']);
try {
apkUrls = getApkUrlsFromUrls(List<String>.from(apkUrlJson));
} catch (e) {
apkUrls = assumed2DlistToStringMapList(List<dynamic>.from(apkUrlJson));
apkUrls = List<dynamic>.from(apkUrlJson)
.map((e) => MapEntry(e[0] as String, e[1] as String))
.toList();
}
json['apkUrls'] = jsonEncode(stringMapListTo2DList(apkUrls));
}
// Arch based APK filter option should be disabled if it previously did not exist
if (additionalSettings['autoApkFilterByArch'] == null) {
additionalSettings['autoApkFilterByArch'] = false;
}
json['additionalSettings'] = jsonEncode(additionalSettings);
// F-Droid no longer needs cloudflare exception since override can be used - migrate apps appropriately
// This allows us to reverse the changes made for issue #418 (support cloudflare.f-droid)
// While not causing problems for existing apps from that source that were added in a previous version
var overrideSourceWasUndefined = !json.keys.contains('overrideSource');
if ((json['url'] as String).startsWith('https://cloudflare.f-droid.org')) {
json['overrideSource'] = FDroid().runtimeType.toString();
} else if (overrideSourceWasUndefined) {
// Similar to above, but for third-party F-Droid repos
RegExpMatch? match = RegExp('^https?://.+/fdroid/([^/]+(/|\\?)|[^/]+\$)')
.firstMatch(json['url'] as String);
if (match != null) {
json['overrideSource'] = FDroidRepo().runtimeType.toString();
}
}
return json;
}
class App {
late String id;
late String url;
@@ -59,6 +159,7 @@ class App {
List<String> categories;
late DateTime? releaseDate;
late String? changeLog;
late String? overrideSource;
App(
this.id,
this.url,
@@ -73,7 +174,8 @@ class App {
this.pinned,
{this.categories = const [],
this.releaseDate,
this.changeLog});
this.changeLog,
this.overrideSource});
@override
String toString() {
@@ -103,80 +205,11 @@ class App {
pinned,
categories: categories,
changeLog: changeLog,
releaseDate: releaseDate);
releaseDate: releaseDate,
overrideSource: overrideSource);
factory App.fromJson(Map<String, dynamic> json) {
var source = SourceProvider().getSource(json['url']);
var formItems = source.combinedAppSpecificSettingFormItems
.reduce((value, element) => [...value, ...element]);
Map<String, dynamic> additionalSettings =
getDefaultValuesFromFormItems([formItems]);
if (json['additionalSettings'] != null) {
additionalSettings.addEntries(
Map<String, dynamic>.from(jsonDecode(json['additionalSettings']))
.entries);
}
// If needed, migrate old-style additionalData to newer-style additionalSettings (V1)
if (json['additionalData'] != null) {
List<String> temp = List<String>.from(jsonDecode(json['additionalData']));
temp.asMap().forEach((i, value) {
if (i < formItems.length) {
if (formItems[i] is GeneratedFormSwitch) {
additionalSettings[formItems[i].key] = value == 'true';
} else {
additionalSettings[formItems[i].key] = value;
}
}
});
additionalSettings['trackOnly'] =
json['trackOnly'] == 'true' || json['trackOnly'] == true;
additionalSettings['noVersionDetection'] =
json['noVersionDetection'] == 'true' || json['trackOnly'] == true;
}
// Convert bool style version detection options to dropdown style
if (additionalSettings['noVersionDetection'] == true) {
additionalSettings['versionDetection'] = 'noVersionDetection';
if (additionalSettings['releaseDateAsVersion'] == true) {
additionalSettings['versionDetection'] = 'releaseDateAsVersion';
additionalSettings.remove('releaseDateAsVersion');
}
if (additionalSettings['noVersionDetection'] != null) {
additionalSettings.remove('noVersionDetection');
}
if (additionalSettings['releaseDateAsVersion'] != null) {
additionalSettings.remove('releaseDateAsVersion');
}
}
// Ensure additionalSettings are correctly typed
for (var item in formItems) {
if (additionalSettings[item.key] != null) {
additionalSettings[item.key] =
item.ensureType(additionalSettings[item.key]);
}
}
int preferredApkIndex = json['preferredApkIndex'] == null
? 0
: json['preferredApkIndex'] as int;
if (preferredApkIndex < 0) {
preferredApkIndex = 0;
}
// apkUrls can either be old list or new named list apkUrls
List<MapEntry<String, String>> apkUrls = [];
if (json['apkUrls'] != null) {
var apkUrlJson = jsonDecode(json['apkUrls']);
try {
apkUrls = getApkUrlsFromUrls(List<String>.from(apkUrlJson));
} catch (e) {
apkUrls = List<dynamic>.from(apkUrlJson)
.map((e) => MapEntry(e[0] as String, e[1] as String))
.toList();
}
}
// Arch based APK filter option should be disabled if it previously did not exist
if (json['additionalSettings'] != null &&
jsonDecode(json['additionalSettings'])['autoApkFilterByArch'] == null) {
additionalSettings['autoApkFilterByArch'] = false;
}
json = appJSONCompatibilityModifiers(json);
return App(
json['id'] as String,
json['url'] as String,
@@ -186,9 +219,9 @@ class App {
? null
: json['installedVersion'] as String,
json['latestVersion'] as String,
apkUrls,
preferredApkIndex,
additionalSettings,
assumed2DlistToStringMapList(jsonDecode(json['apkUrls'])),
json['preferredApkIndex'] as int,
jsonDecode(json['additionalSettings']) as Map<String, dynamic>,
json['lastUpdateCheck'] == null
? null
: DateTime.fromMicrosecondsSinceEpoch(json['lastUpdateCheck']),
@@ -204,7 +237,8 @@ class App {
? null
: DateTime.fromMicrosecondsSinceEpoch(json['releaseDate']),
changeLog:
json['changeLog'] == null ? null : json['changeLog'] as String);
json['changeLog'] == null ? null : json['changeLog'] as String,
overrideSource: json['overrideSource']);
}
Map<String, dynamic> toJson() => {
@@ -214,14 +248,15 @@ class App {
'name': name,
'installedVersion': installedVersion,
'latestVersion': latestVersion,
'apkUrls': jsonEncode(apkUrls.map((e) => [e.key, e.value]).toList()),
'apkUrls': jsonEncode(stringMapListTo2DList(apkUrls)),
'preferredApkIndex': preferredApkIndex,
'additionalSettings': jsonEncode(additionalSettings),
'lastUpdateCheck': lastUpdateCheck?.microsecondsSinceEpoch,
'pinned': pinned,
'categories': categories,
'releaseDate': releaseDate?.microsecondsSinceEpoch,
'changeLog': changeLog
'changeLog': changeLog,
'overrideSource': overrideSource
};
}
@@ -273,8 +308,9 @@ List<MapEntry<String, String>> getApkUrlsFromUrls(List<String> urls) =>
return MapEntry(apkSegs.isNotEmpty ? apkSegs.last : segments.last, e);
}).toList();
class AppSource {
abstract class AppSource {
String? host;
bool hostChanged = false;
late String name;
bool enforceTrackOnly = false;
bool changeLogIfAnyIsMarkDown = true;
@@ -283,7 +319,15 @@ class AppSource {
name = runtimeType.toString();
}
String standardizeURL(String url) {
String standardizeUrl(String url) {
url = preStandardizeUrl(url);
if (!hostChanged) {
url = sourceSpecificStandardizeURL(url);
}
return url;
}
String sourceSpecificStandardizeURL(String url) {
throw NotImplementedError();
}
@@ -389,33 +433,44 @@ regExValidator(String? value) {
class SourceProvider {
// Add more source classes here so they are available via the service
List<AppSource> sources = [
GitHub(),
GitLab(),
Codeberg(),
FDroid(),
IzzyOnDroid(),
FDroidRepo(),
SourceForge(),
APKMirror(),
Mullvad(),
Signal(),
VLC(),
// WhatsApp(), // As of 2023-03-20 this is unusable as the version on the webpage is months out of date
TelegramApp(),
SteamMobile(),
NeutronCode(),
HTML() // This should ALWAYS be the last option as they are tried in order
];
List<AppSource> get sources => [
GitHub(),
GitLab(),
Codeberg(),
FDroid(),
IzzyOnDroid(),
FDroidRepo(),
SourceForge(),
APKMirror(),
Mullvad(),
Signal(),
VLC(),
// WhatsApp(), // As of 2023-03-20 this is unusable as the version on the webpage is months out of date
TelegramApp(),
SteamMobile(),
NeutronCode(),
HTML() // This should ALWAYS be the last option as they are tried in order
];
// Add more mass url source classes here so they are available via the service
List<MassAppUrlSource> massUrlSources = [GitHubStars()];
AppSource getSource(String url) {
AppSource getSource(String url, {String? overrideSource}) {
url = preStandardizeUrl(url);
if (overrideSource != null) {
var srcs =
sources.where((e) => e.runtimeType.toString() == overrideSource);
if (srcs.isEmpty) {
throw UnsupportedURLError();
}
var res = srcs.first;
res.host = Uri.parse(url).host;
res.hostChanged = true;
return srcs.first;
}
AppSource? source;
for (var s in sources.where((element) => element.host != null)) {
if (RegExp('://(.+\\.)?${s.host}').hasMatch(url)) {
if (RegExp('://${s.host}(/|\\z)?').hasMatch(url)) {
source = s;
break;
}
@@ -423,7 +478,7 @@ class SourceProvider {
if (source == null) {
for (var s in sources.where((element) => element.host == null)) {
try {
s.standardizeURL(url);
s.sourceSpecificStandardizeURL(url);
source = s;
break;
} catch (e) {
@@ -459,12 +514,14 @@ class SourceProvider {
Future<App> getApp(
AppSource source, String url, Map<String, dynamic> additionalSettings,
{App? currentApp, bool trackOnlyOverride = false}) async {
{App? currentApp,
bool trackOnlyOverride = false,
String? overrideSource}) async {
if (trackOnlyOverride || source.enforceTrackOnly) {
additionalSettings['trackOnly'] = true;
}
var trackOnly = additionalSettings['trackOnly'] == true;
String standardUrl = source.standardizeURL(preStandardizeUrl(url));
String standardUrl = source.standardizeUrl(url);
APKDetails apk =
await source.getLatestAPKDetails(standardUrl, additionalSettings);
if (additionalSettings['versionDetection'] == 'releaseDateAsVersion' &&
@@ -514,7 +571,8 @@ class SourceProvider {
currentApp?.pinned ?? false,
categories: currentApp?.categories ?? const [],
releaseDate: apk.releaseDate,
changeLog: apk.changeLog);
changeLog: apk.changeLog,
overrideSource: overrideSource ?? currentApp?.overrideSource);
}
// Returns errors in [results, errors] instead of throwing them

View File

@@ -5,18 +5,18 @@ packages:
dependency: "direct main"
description:
name: android_alarm_manager_plus
sha256: f6d0347734fa2ea716349a5a3e16ffdc1800ca64e5640112896d128c6815c178
sha256: "88a8001851fdc9bd54fa4e30d0277bb900a50f3d86ff244da7f027400bf23ac0"
url: "https://pub.dev"
source: hosted
version: "2.1.2"
version: "2.1.4"
android_intent_plus:
dependency: "direct main"
description:
name: android_intent_plus
sha256: "6bcdcd20461ac7a0c785f6298cdda96ad275d5bcbc1ecf28829cbe03ec6690be"
sha256: "04cbc7c332a6f0bba88fed354de78813e9d24049c1800aaf10f449c7adc22603"
url: "https://pub.dev"
source: hosted
version: "3.1.7"
version: "3.1.9"
animations:
dependency: "direct main"
description:
@@ -117,10 +117,10 @@ packages:
dependency: "direct main"
description:
name: device_info_plus
sha256: "435383ca05f212760b0a70426b5a90354fe6bd65992b3a5e27ab6ede74c02f5c"
sha256: f52ab3b76b36ede4d135aab80194df8925b553686f0fa12226b4e2d658e45903
url: "https://pub.dev"
source: hosted
version: "8.2.0"
version: "8.2.2"
device_info_plus_platform_interface:
dependency: transitive
description:
@@ -210,26 +210,26 @@ packages:
dependency: "direct main"
description:
name: flutter_local_notifications
sha256: "293995f94e120c8afce768981bd1fa9c5d6de67c547568e3b42ae2defdcbb4a0"
sha256: "2876372952b65ca7f684e698eba22bda1cf581fa071dd30ba2f01900f507d0d1"
url: "https://pub.dev"
source: hosted
version: "13.0.0"
version: "14.0.0+1"
flutter_local_notifications_linux:
dependency: transitive
description:
name: flutter_local_notifications_linux
sha256: ccb08b93703aeedb58856e5637450bf3ffec899adb66dc325630b68994734b89
sha256: "909bb95de05a2e793503a2437146285a2f600cd0b3f826e26b870a334d8586d7"
url: "https://pub.dev"
source: hosted
version: "3.0.0+1"
version: "4.0.0"
flutter_local_notifications_platform_interface:
dependency: transitive
description:
name: flutter_local_notifications_platform_interface
sha256: "5ec1feac5f7f7d9266759488bc5f76416152baba9aa1b26fe572246caa00d1ab"
sha256: "63235c42de5b6c99846969a27ad0209c401e6b77b0498939813725b5791c107c"
url: "https://pub.dev"
source: hosted
version: "6.0.0"
version: "7.0.0"
flutter_localizations:
dependency: transitive
description: flutter
@@ -247,10 +247,10 @@ packages:
dependency: transitive
description:
name: flutter_plugin_android_lifecycle
sha256: c224ac897bed083dabf11f238dd11a239809b446740be0c2044608c50029ffdf
sha256: "8ffe990dac54a4a5492747added38571a5ab474c8e5d196809ea08849c69b1bb"
url: "https://pub.dev"
source: hosted
version: "2.0.9"
version: "2.0.13"
flutter_test:
dependency: "direct dev"
description: flutter
@@ -417,10 +417,10 @@ packages:
dependency: transitive
description:
name: path_provider_android
sha256: da97262be945a72270513700a92b39dd2f4a54dad55d061687e2e37a6390366a
sha256: "2cec049d282c7f13c594b4a73976b0b4f2d7a1838a6dd5aaf7bd9719196bee86"
url: "https://pub.dev"
source: hosted
version: "2.0.25"
version: "2.0.27"
path_provider_foundation:
dependency: transitive
description:
@@ -449,10 +449,10 @@ packages:
dependency: transitive
description:
name: path_provider_windows
sha256: f53720498d5a543f9607db4b0e997c4b5438884de25b0f73098cc2671a51b130
sha256: d3f80b32e83ec208ac95253e0cd4d298e104fbc63cb29c5c69edaed43b0c69d6
url: "https://pub.dev"
source: hosted
version: "2.1.5"
version: "2.1.6"
permission_handler:
dependency: "direct main"
description:
@@ -537,10 +537,10 @@ packages:
dependency: "direct main"
description:
name: share_plus
sha256: "692261968a494e47323dcc8bc66d8d52e81bc27cb4b808e4e8d7e8079d4cc01a"
sha256: b1f15232d41e9701ab2f04181f21610c36c83a12ae426b79b4bd011c567934b1
url: "https://pub.dev"
source: hosted
version: "6.3.2"
version: "6.3.4"
share_plus_platform_interface:
dependency: transitive
description:
@@ -561,10 +561,10 @@ packages:
dependency: transitive
description:
name: shared_preferences_android
sha256: "7fa90471a6875d26ad78c7e4a675874b2043874586891128dc5899662c97db46"
sha256: "6478c6bbbecfe9aced34c483171e90d7c078f5883558b30ec3163cf18402c749"
url: "https://pub.dev"
source: hosted
version: "2.1.2"
version: "2.1.4"
shared_preferences_foundation:
dependency: transitive
description:
@@ -622,18 +622,18 @@ packages:
dependency: "direct main"
description:
name: sqflite
sha256: e7dfb6482d5d02b661d0b2399efa72b98909e5aa7b8336e1fb37e226264ade00
sha256: "8453780d1f703ead201a39673deb93decf85d543f359f750e2afc4908b55533f"
url: "https://pub.dev"
source: hosted
version: "2.2.7"
version: "2.2.8"
sqflite_common:
dependency: transitive
description:
name: sqflite_common
sha256: "220831bf0bd5333ff2445eee35ec131553b804e6b5d47a4a37ca6f5eb66e282c"
sha256: e77abf6ff961d69dfef41daccbb66b51e9983cdd5cb35bf30733598057401555
url: "https://pub.dev"
source: hosted
version: "2.4.4"
version: "2.4.5"
stack_trace:
dependency: transitive
description:
@@ -710,10 +710,10 @@ packages:
dependency: transitive
description:
name: url_launcher_android
sha256: a52628068d282d01a07cd86e6ba99e497aa45ce8c91159015b2416907d78e411
sha256: "22f8db4a72be26e9e3a4aa3f194b1f7afbc76d20ec141f84be1d787db2155cbd"
url: "https://pub.dev"
source: hosted
version: "6.0.27"
version: "6.0.31"
url_launcher_ios:
dependency: transitive
description:
@@ -726,10 +726,10 @@ packages:
dependency: transitive
description:
name: url_launcher_linux
sha256: "206fb8334a700ef7754d6a9ed119e7349bc830448098f21a69bf1b4ed038cabc"
sha256: "207f4ddda99b95b4d4868320a352d374b0b7e05eefad95a4a26f57da413443f5"
url: "https://pub.dev"
source: hosted
version: "3.0.4"
version: "3.0.5"
url_launcher_macos:
dependency: transitive
description:
@@ -758,10 +758,10 @@ packages:
dependency: transitive
description:
name: url_launcher_windows
sha256: a83ba3607a507758669cfafb03f9de09bf6e6280c14d9b9cb18f013e406dcacd
sha256: "254708f17f7c20a9c8c471f67d86d76d4a3f9c1591aad1e15292008aceb82771"
url: "https://pub.dev"
source: hosted
version: "3.0.5"
version: "3.0.6"
uuid:
dependency: transitive
description:
@@ -790,10 +790,10 @@ packages:
dependency: transitive
description:
name: webview_flutter_android
sha256: "134ed5d36127b6f5865e86a82174886eae0b983dacd8df14b0448371debde755"
sha256: d6cf18cd6c809c5a9294cd99707a21986aac4e08c87e1916ce2590315fb55d3a
url: "https://pub.dev"
source: hosted
version: "3.6.0"
version: "3.6.2"
webview_flutter_platform_interface:
dependency: transitive
description:
@@ -822,10 +822,10 @@ packages:
dependency: transitive
description:
name: xdg_directories
sha256: bd512f03919aac5f1313eb8249f223bacf4927031bf60b02601f81f687689e86
sha256: ee1505df1426458f7f60aac270645098d318a8b4766d85fde75f76f2e21807d1
url: "https://pub.dev"
source: hosted
version: "0.2.0+3"
version: "1.0.0"
xml:
dependency: transitive
description:

View File

@@ -17,7 +17,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
# In Windows, build-name is used as the major, minor, and patch parts
# of the product and file versions while build-number is used as the build suffix.
version: 0.11.33+155 # When changing this, update the tag in main() accordingly
version: 0.12.0+160 # When changing this, update the tag in main() accordingly
environment:
sdk: '>=2.18.2 <3.0.0'
@@ -38,7 +38,7 @@ dependencies:
cupertino_icons: ^1.0.5
path_provider: ^2.0.11
flutter_fgbg: ^0.2.0 # Try removing reliance on this
flutter_local_notifications: ^13.0.0
flutter_local_notifications: ^14.0.0+1
provider: ^6.0.3
http: ^0.13.5
webview_flutter: ^4.0.0