mirror of
https://github.com/ImranR98/Obtainium.git
synced 2025-07-13 13:26:43 +02:00
Compare commits
38 Commits
v0.13.0-be
...
v0.13.5-be
Author | SHA1 | Date | |
---|---|---|---|
4c6303f783 | |||
ce6e6c47db | |||
2ccff15525 | |||
d24f2b4e6d | |||
03fc6a530f | |||
4136734a60 | |||
ca1371260c | |||
03c2ce9a01 | |||
eda5fec37c | |||
e21c6297ff | |||
c6297ea449 | |||
e33cc00266 | |||
96c92c8df9 | |||
e256ada2dc | |||
eb0be196da | |||
1606ad3442 | |||
d212f13345 | |||
f80c9ec33e | |||
7681e23de9 | |||
22a60df40e | |||
431a01f2a5 | |||
0cd4385de7 | |||
0774b3ddc3 | |||
b60b1ed058 | |||
b196715d60 | |||
0673e90dff | |||
59cfa242fb | |||
65ab72ba90 | |||
408bca8951 | |||
480467492a | |||
219b04aedb | |||
a0709856ef | |||
577642850f | |||
e1db024034 | |||
cc268aeeda | |||
d5f7eced8b | |||
cc3c4cc79f | |||
89b61884f1 |
BIN
android/app/src/main/res/mipmap-hdpi/ic_launcher.png
Normal file
BIN
android/app/src/main/res/mipmap-hdpi/ic_launcher.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 3.4 KiB |
BIN
android/app/src/main/res/mipmap-mdpi/ic_launcher.png
Normal file
BIN
android/app/src/main/res/mipmap-mdpi/ic_launcher.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.3 KiB |
BIN
android/app/src/main/res/mipmap-xhdpi/ic_launcher.png
Normal file
BIN
android/app/src/main/res/mipmap-xhdpi/ic_launcher.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 4.5 KiB |
BIN
android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
Normal file
BIN
android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 7.1 KiB |
BIN
android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
Normal file
BIN
android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 9.6 KiB |
@ -26,6 +26,6 @@ subprojects {
|
||||
project.evaluationDependsOn(':app')
|
||||
}
|
||||
|
||||
task clean(type: Delete) {
|
||||
tasks.register("clean", Delete) {
|
||||
delete rootProject.buildDir
|
||||
}
|
||||
|
BIN
assets/graphics/icon.png
Normal file
BIN
assets/graphics/icon.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 66 KiB |
@ -121,12 +121,12 @@
|
||||
"followSystem": "System folgen",
|
||||
"obtainium": "Obtainium",
|
||||
"materialYou": "Material You",
|
||||
"useBlackTheme": "Use pure black dark theme",
|
||||
"useBlackTheme": "Verwende Pure Black Dark Theme",
|
||||
"appSortBy": "App sortieren nach",
|
||||
"authorName": "Autor/Name",
|
||||
"nameAuthor": "Name/Autor",
|
||||
"asAdded": "Wie hinzugefügt",
|
||||
"appSortOrder": "App Sortierung nach",
|
||||
"appSortOrder": "App sortieren nach",
|
||||
"ascending": "Aufsteigend",
|
||||
"descending": "Absteigend",
|
||||
"bgUpdateCheckInterval": "Prüfintervall für Hintergrundaktualisierung",
|
||||
@ -180,6 +180,7 @@
|
||||
"yesMarkUpdated": "Ja, als aktualisiert markieren",
|
||||
"fdroid": "F-Droid Official",
|
||||
"appIdOrName": "App ID oder Name",
|
||||
"appId": "App ID",
|
||||
"appWithIdOrNameNotFound": "Es wurde keine App mit dieser ID oder diesem Namen gefunden",
|
||||
"reposHaveMultipleApps": "Repos können mehrere Apps enthalten",
|
||||
"fdroidThirdPartyRepo": "F-Droid Third-Party Repo",
|
||||
@ -207,7 +208,7 @@
|
||||
"addCategory": "Kategorie hinzufügen",
|
||||
"label": "Bezeichnung",
|
||||
"language": "Sprache",
|
||||
"copiedToClipboard": "Copied to Clipboard",
|
||||
"copiedToClipboard": "In die Zwischenablage kopiert",
|
||||
"storagePermissionDenied": "Speicherberechtigung verweigert",
|
||||
"selectedCategorizeWarning": "Dadurch werden alle bestehenden Kategorieeinstellungen für die ausgewählten Apps ersetzt.",
|
||||
"filterAPKsByRegEx": "APKs nach regulärem Ausdruck filtern",
|
||||
@ -218,7 +219,7 @@
|
||||
"releaseDateAsVersionExplanation": "Diese Option sollte nur für Apps verwendet werden, bei denen die Versionserkennung nicht korrekt funktioniert, aber ein Veröffentlichungsdatum verfügbar ist.",
|
||||
"changes": "Änderungen",
|
||||
"releaseDate": "Veröffentlichungsdatum",
|
||||
"importFromURLsInFile": "Importieren von URLs aus Datei ( z.B. OPML)",
|
||||
"importFromURLsInFile": "Importieren von URLs aus Datei (z. B. OPML)",
|
||||
"versionDetection": "Versionserkennung",
|
||||
"standardVersionDetection": "Standardversionserkennung",
|
||||
"groupByCategory": "Nach Kategorie gruppieren",
|
||||
@ -227,10 +228,11 @@
|
||||
"dontShowAgain": "Nicht noch einmal zeigen",
|
||||
"dontShowTrackOnlyWarnings": "Warnung für 'Nur Nachverfolgen' nicht anzeigen",
|
||||
"dontShowAPKOriginWarnings": "Warnung für APK-Herkunft nicht anzeigen",
|
||||
"moveNonInstalledAppsToBottom": "Move Non-Installed Apps to Bottom of Apps View",
|
||||
"gitlabPATLabel": "GitLab Personal Access Token (Enables Search)",
|
||||
"about": "About",
|
||||
"requiresCredentialsInSettings": "This needs additional credentials (in Settings)",
|
||||
"moveNonInstalledAppsToBottom": "Nicht installierte Apps ans Ende der Apps Ansicht verschieben",
|
||||
"gitlabPATLabel": "GitLab Personal Access Token (Aktiviert Suche)",
|
||||
"about": "Über",
|
||||
"requiresCredentialsInSettings": "Benötigt zusätzliche Anmeldedaten (in den Einstellungen)",
|
||||
"checkOnStart": "Überprüfe einmalig beim Start",
|
||||
"removeAppQuestion": {
|
||||
"one": "App entfernen?",
|
||||
"other": "Apps entfernen?"
|
||||
@ -257,7 +259,7 @@
|
||||
},
|
||||
"minute": {
|
||||
"one": "{} Minute",
|
||||
"other": "{} Minutes"
|
||||
"other": "{} Minuten"
|
||||
},
|
||||
"hour": {
|
||||
"one": "{} Stunde",
|
||||
|
@ -121,7 +121,7 @@
|
||||
"followSystem": "Follow System",
|
||||
"obtainium": "Obtainium",
|
||||
"materialYou": "Material You",
|
||||
"useBlackTheme": "Use pure black dark theme",
|
||||
"useBlackTheme": "Use Pure Black Dark Theme",
|
||||
"appSortBy": "App Sort By",
|
||||
"authorName": "Author/Name",
|
||||
"nameAuthor": "Name/Author",
|
||||
@ -180,6 +180,7 @@
|
||||
"yesMarkUpdated": "Yes, Mark as Updated",
|
||||
"fdroid": "F-Droid Official",
|
||||
"appIdOrName": "App ID or Name",
|
||||
"appId": "App ID",
|
||||
"appWithIdOrNameNotFound": "No App was found with that ID or Name",
|
||||
"reposHaveMultipleApps": "Repos may contain multiple Apps",
|
||||
"fdroidThirdPartyRepo": "F-Droid Third-Party Repo",
|
||||
@ -231,6 +232,7 @@
|
||||
"gitlabPATLabel": "GitLab Personal Access Token (Enables Search)",
|
||||
"about": "About",
|
||||
"requiresCredentialsInSettings": "This needs additional credentials (in Settings)",
|
||||
"checkOnStart": "Check Once on Start",
|
||||
"removeAppQuestion": {
|
||||
"one": "Remove App?",
|
||||
"other": "Remove Apps?"
|
||||
|
@ -180,6 +180,7 @@
|
||||
"yesMarkUpdated": "Sí, Marcar como Actualizada",
|
||||
"fdroid": "Repositorio oficial de F-Droid",
|
||||
"appIdOrName": "ID o Nombre de la Aplicación",
|
||||
"appId": "ID de la Aplicación",
|
||||
"appWithIdOrNameNotFound": "No se han encontrado aplicaciones con esa ID o nombre",
|
||||
"reposHaveMultipleApps": "Los repositorios pueden contener varias aplicaciones",
|
||||
"fdroidThirdPartyRepo": "Rpositorios de terceros de F-Droid",
|
||||
@ -231,6 +232,7 @@
|
||||
"gitlabPATLabel": "GitLab Personal Access Token (Enables Search)",
|
||||
"about": "About",
|
||||
"requiresCredentialsInSettings": "This needs additional credentials (in Settings)",
|
||||
"checkOnStart": "Check Once on Start",
|
||||
"removeAppQuestion": {
|
||||
"one": "¿Eliminar Aplicación?",
|
||||
"other": "¿Eliminar Aplicaciones?"
|
||||
|
@ -180,6 +180,7 @@
|
||||
"yesMarkUpdated": "بله، علامت گذاری به عنوان به روز شده",
|
||||
"fdroid": "F-Droid Official",
|
||||
"appIdOrName": "شناسه یا نام برنامه",
|
||||
"appId": "App ID",
|
||||
"appWithIdOrNameNotFound": "هیچ برنامه ای با آن شناسه یا نام یافت نشد",
|
||||
"reposHaveMultipleApps": "مخازن ممکن است شامل چندین برنامه باشد",
|
||||
"fdroidThirdPartyRepo": "مخازن شخص ثالث F-Droid",
|
||||
@ -231,6 +232,7 @@
|
||||
"gitlabPATLabel": "GitLab Personal Access Token (Enables Search)",
|
||||
"about": "About",
|
||||
"requiresCredentialsInSettings": "This needs additional credentials (in Settings)",
|
||||
"checkOnStart": "Check Once on Start",
|
||||
"removeAppQuestion": {
|
||||
"one": "برنامه حذف شود؟",
|
||||
"other": "برنامه ها حذف شوند؟"
|
||||
|
@ -121,7 +121,7 @@
|
||||
"followSystem": "Suivre le système",
|
||||
"obtainium": "Obtainium",
|
||||
"materialYou": "Material You",
|
||||
"useBlackTheme": "Use pure black dark theme",
|
||||
"useBlackTheme": "Use Pure Black Dark Theme",
|
||||
"appSortBy": "Applications triées par",
|
||||
"authorName": "Auteur/Nom",
|
||||
"nameAuthor": "Nom/Auteur",
|
||||
@ -180,6 +180,7 @@
|
||||
"yesMarkUpdated": "Oui, marquer comme mis à jour",
|
||||
"fdroid": "F-Droid Official",
|
||||
"appIdOrName": "ID ou nom de l'application",
|
||||
"appId": "ID 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",
|
||||
"fdroidThirdPartyRepo": "Dépôt tiers F-Droid",
|
||||
@ -231,6 +232,7 @@
|
||||
"gitlabPATLabel": "GitLab Personal Access Token (Enables Search)",
|
||||
"about": "About",
|
||||
"requiresCredentialsInSettings": "This needs additional credentials (in Settings)",
|
||||
"checkOnStart": "Check Once on Start",
|
||||
"removeAppQuestion": {
|
||||
"one": "Supprimer l'application ?",
|
||||
"other": "Supprimer les applications ?"
|
||||
|
@ -180,6 +180,7 @@
|
||||
"yesMarkUpdated": "Igen, megjelölés frissítettként",
|
||||
"fdroid": "F-Droid Official",
|
||||
"appIdOrName": "App ID vagy név",
|
||||
"appId": "App ID",
|
||||
"appWithIdOrNameNotFound": "Nem található app ezzel az azonosítóval vagy névvel",
|
||||
"reposHaveMultipleApps": "A repók több alkalmazást is tartalmazhatnak",
|
||||
"fdroidThirdPartyRepo": "F-Droid Harmadik-fél Repo",
|
||||
@ -230,6 +231,7 @@
|
||||
"gitlabPATLabel": "GitLab Personal Access Token (Enables Search)",
|
||||
"about": "About",
|
||||
"requiresCredentialsInSettings": "This needs additional credentials (in Settings)",
|
||||
"checkOnStart": "Check Once on Start",
|
||||
"removeAppQuestion": {
|
||||
"one": "Eltávolítja az alkalmazást?",
|
||||
"other": "Eltávolítja az alkalmazást?"
|
||||
|
@ -121,7 +121,7 @@
|
||||
"followSystem": "Segui sistema",
|
||||
"obtainium": "Obtainium",
|
||||
"materialYou": "Material You",
|
||||
"useBlackTheme": "Use pure black dark theme",
|
||||
"useBlackTheme": "Use Pure Black Dark Theme",
|
||||
"appSortBy": "App ordinate per",
|
||||
"authorName": "Autore/Nome",
|
||||
"nameAuthor": "Nome/Autore",
|
||||
@ -180,6 +180,7 @@
|
||||
"yesMarkUpdated": "Sì, contrassegna come aggiornato",
|
||||
"fdroid": "F-Droid Official",
|
||||
"appIdOrName": "ID o nome dell'App",
|
||||
"appId": "ID dell'App",
|
||||
"appWithIdOrNameNotFound": "Non è stata trovata alcuna App con quell'ID o nome",
|
||||
"reposHaveMultipleApps": "I repository possono contenere più App",
|
||||
"fdroidThirdPartyRepo": "Repository F-Droid di terze parti",
|
||||
@ -231,6 +232,7 @@
|
||||
"gitlabPATLabel": "GitLab Personal Access Token (Enables Search)",
|
||||
"about": "About",
|
||||
"requiresCredentialsInSettings": "This needs additional credentials (in Settings)",
|
||||
"checkOnStart": "Check Once on Start",
|
||||
"removeAppQuestion": {
|
||||
"one": "Rimuovere l'App?",
|
||||
"other": "Rimuovere le App?"
|
||||
|
@ -180,6 +180,7 @@
|
||||
"yesMarkUpdated": "はい、アップデート済みとしてマークします",
|
||||
"fdroid": "F-Droid Official",
|
||||
"appIdOrName": "アプリのIDまたは名前",
|
||||
"appId": "App ID",
|
||||
"appWithIdOrNameNotFound": "そのIDや名前を持つアプリは見つかりませんでした",
|
||||
"reposHaveMultipleApps": "リポジトリには複数のアプリが含まれることがあります",
|
||||
"fdroidThirdPartyRepo": "F-Droid サードパーティリポジトリ",
|
||||
@ -227,10 +228,11 @@
|
||||
"dontShowAgain": "二度と表示しない",
|
||||
"dontShowTrackOnlyWarnings": "「追跡のみ」の警告を表示しない",
|
||||
"dontShowAPKOriginWarnings": "APK Originの警告を表示しない",
|
||||
"moveNonInstalledAppsToBottom": "Move Non-Installed Apps to Bottom of Apps View",
|
||||
"gitlabPATLabel": "GitLab Personal Access Token (Enables Search)",
|
||||
"about": "About",
|
||||
"requiresCredentialsInSettings": "This needs additional credentials (in Settings)",
|
||||
"moveNonInstalledAppsToBottom": "未インストールのアプリをアプリ一覧の下部に移動させる",
|
||||
"gitlabPATLabel": "GitLab パーソナルアクセストークン (検索を有効化する)",
|
||||
"about": "概要",
|
||||
"requiresCredentialsInSettings": "これには追加の認証が必要です (設定にて)",
|
||||
"checkOnStart": "Check Once on Start",
|
||||
"removeAppQuestion": {
|
||||
"one": "アプリを削除しますか?",
|
||||
"other": "アプリを削除しますか?"
|
||||
|
@ -33,7 +33,7 @@
|
||||
"githubStarredRepos": "GitHub 已星标仓库",
|
||||
"uname": "用户名",
|
||||
"wrongArgNum": "参数数量错误",
|
||||
"xIsTrackOnly": "{} 为“仅追踪”模式",
|
||||
"xIsTrackOnly": "{}为“仅追踪”模式",
|
||||
"source": "源代码",
|
||||
"app": "应用",
|
||||
"appsFromSourceAreTrackOnly": "此来源的应用为“仅追踪”模式。",
|
||||
@ -50,8 +50,8 @@
|
||||
"search": "搜索",
|
||||
"additionalOptsFor": "{} 的更多选项",
|
||||
"supportedSourcesBelow": "支持的来源:",
|
||||
"trackOnlyInBrackets": "(仅追踪)",
|
||||
"searchableInBrackets": "(可搜索)",
|
||||
"trackOnlyInBrackets": "(仅追踪)",
|
||||
"searchableInBrackets": "(可搜索)",
|
||||
"appsString": "应用列表",
|
||||
"noApps": "无应用",
|
||||
"noAppsForFilter": "没有符合条件的应用",
|
||||
@ -59,9 +59,9 @@
|
||||
"percentProgress": "进度:{}%",
|
||||
"pleaseWait": "请稍候",
|
||||
"updateAvailable": "更新可用",
|
||||
"estimateInBracketsShort": "(预计)",
|
||||
"estimateInBracketsShort": "(推测)",
|
||||
"notInstalled": "未安装",
|
||||
"estimateInBrackets": "(预计)",
|
||||
"estimateInBrackets": "(推测)",
|
||||
"selectAll": "全选",
|
||||
"deselectN": "取消选择 {}",
|
||||
"xWillBeRemovedButRemainInstalled": "{} 将从 Obtainium 中删除,但仍安装在您的设备中。",
|
||||
@ -74,8 +74,8 @@
|
||||
"installUpdateApps": "安装/更新应用",
|
||||
"installUpdateSelectedApps": "安装/更新选中的应用",
|
||||
"markXSelectedAppsAsUpdated": "是否将选中的 {} 个应用标记为已更新?",
|
||||
"no": "不要",
|
||||
"yes": "好的",
|
||||
"no": "否",
|
||||
"yes": "是",
|
||||
"markSelectedAppsUpdated": "将选中的应用标记为已更新",
|
||||
"pinToTop": "置顶",
|
||||
"unpinFromTop": "取消置顶",
|
||||
@ -142,7 +142,7 @@
|
||||
"close": "关闭",
|
||||
"share": "分享",
|
||||
"appNotFound": "未找到应用",
|
||||
"obtainiumExportHyphenatedLowercase": "obtainium-导出",
|
||||
"obtainiumExportHyphenatedLowercase": "obtainium-export",
|
||||
"pickAnAPK": "选择一个 APK 文件",
|
||||
"appHasMoreThanOnePackage": "{} 有多个架构可用:",
|
||||
"deviceSupportsXArch": "您的设备支持 {} 架构。",
|
||||
@ -172,14 +172,15 @@
|
||||
"versionCorrectionDisabled": "禁用版本号更正(插件似乎未起作用)",
|
||||
"unknown": "未知",
|
||||
"none": "无",
|
||||
"never": "从不",
|
||||
"never": "从未",
|
||||
"latestVersionX": "最新版本:{}",
|
||||
"installedVersionX": "当前版本:{}",
|
||||
"lastUpdateCheckX": "上次更新检查:{}",
|
||||
"remove": "删除",
|
||||
"yesMarkUpdated": "是的,标记为已更新",
|
||||
"fdroid": "F-Droid Official",
|
||||
"yesMarkUpdated": "是,标记为已更新",
|
||||
"fdroid": "F-Droid 官方存储库",
|
||||
"appIdOrName": "应用 ID 或名称",
|
||||
"appId": "App ID",
|
||||
"appWithIdOrNameNotFound": "未找到符合此 ID 或名称的应用",
|
||||
"reposHaveMultipleApps": "存储库中可能包含多个应用",
|
||||
"fdroidThirdPartyRepo": "F-Droid 第三方存储库",
|
||||
@ -193,7 +194,7 @@
|
||||
"additionalOptions": "附加选项",
|
||||
"disableVersionDetection": "禁用版本检测",
|
||||
"noVersionDetectionExplanation": "此选项应该仅用于无法进行版本检测的应用。",
|
||||
"downloadingX": "正在下载 {}",
|
||||
"downloadingX": "正在下载{}",
|
||||
"downloadNotifDescription": "提示应用的下载进度",
|
||||
"noAPKFound": "未找到 APK 文件",
|
||||
"noVersionDetection": "禁用版本检测",
|
||||
@ -222,15 +223,16 @@
|
||||
"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",
|
||||
"moveNonInstalledAppsToBottom": "Move Non-Installed Apps to Bottom of Apps View",
|
||||
"gitlabPATLabel": "GitLab Personal Access Token (Enables Search)",
|
||||
"about": "About",
|
||||
"requiresCredentialsInSettings": "This needs additional credentials (in Settings)",
|
||||
"autoApkFilterByArch": "如果可能,尝试按设备支持的 CPU 架构筛选 APK 文件",
|
||||
"overrideSource": "覆盖来源",
|
||||
"dontShowAgain": "不再显示",
|
||||
"dontShowTrackOnlyWarnings": "不显示“仅追踪”模式警告",
|
||||
"dontShowAPKOriginWarnings": "不显示 APK 文件来源警告",
|
||||
"moveNonInstalledAppsToBottom": "将未安装应用置底",
|
||||
"gitlabPATLabel": "GitLab 个人访问令牌(用于搜索)",
|
||||
"about": "相关文档",
|
||||
"requiresCredentialsInSettings": "此功能需要额外的凭据(在“设置”中添加)",
|
||||
"checkOnStart": "启动时进行一次检查",
|
||||
"removeAppQuestion": {
|
||||
"one": "是否删除应用?",
|
||||
"other": "是否删除应用?"
|
||||
|
@ -1,6 +1,5 @@
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:html/parser.dart';
|
||||
import 'package:http/http.dart';
|
||||
import 'package:obtainium/custom_errors.dart';
|
||||
import 'package:obtainium/providers/source_provider.dart';
|
||||
|
||||
@ -85,7 +84,6 @@ class APKCombo extends AppSource {
|
||||
Map<String, dynamic> additionalSettings,
|
||||
) async {
|
||||
String appId = tryInferringAppId(standardUrl)!;
|
||||
String host = Uri.parse(standardUrl).host;
|
||||
var preres = await sourceRequest(standardUrl);
|
||||
if (preres.statusCode != 200) {
|
||||
throw getObtainiumHttpError(preres);
|
||||
|
@ -1,6 +1,5 @@
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:html/parser.dart';
|
||||
import 'package:http/http.dart';
|
||||
import 'package:obtainium/custom_errors.dart';
|
||||
import 'package:obtainium/providers/source_provider.dart';
|
||||
|
||||
@ -57,9 +56,9 @@ class APKPure extends AppSource {
|
||||
} catch (err) {
|
||||
// ignore
|
||||
}
|
||||
|
||||
String type = html.querySelector('a.info-tag')?.text.trim() ?? 'APK';
|
||||
List<MapEntry<String, String>> apkUrls = [
|
||||
MapEntry('$appId.apk', 'https://d.$host/b/APK/$appId?version=latest')
|
||||
MapEntry('$appId.apk', 'https://d.$host/b/$type/$appId?version=latest')
|
||||
];
|
||||
String author = html
|
||||
.querySelector('span.info-sdk')
|
||||
|
@ -1,6 +1,4 @@
|
||||
import 'dart:convert';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:http/http.dart';
|
||||
import 'package:obtainium/app_sources/github.dart';
|
||||
import 'package:obtainium/components/generated_form.dart';
|
||||
import 'package:obtainium/custom_errors.dart';
|
||||
@ -9,7 +7,6 @@ import 'package:obtainium/providers/source_provider.dart';
|
||||
class Codeberg extends AppSource {
|
||||
Codeberg() {
|
||||
host = 'codeberg.org';
|
||||
overrideEligible = true;
|
||||
|
||||
additionalSourceSpecificSettingFormItems = [];
|
||||
|
||||
@ -58,10 +55,10 @@ class Codeberg extends AppSource {
|
||||
String standardUrl,
|
||||
Map<String, dynamic> additionalSettings,
|
||||
) async {
|
||||
return gh.getLatestAPKDetailsCommon(
|
||||
'https://$host/api/v1/repos${standardUrl.substring('https://$host'.length)}/releases?per_page=100',
|
||||
standardUrl,
|
||||
additionalSettings);
|
||||
return await gh.getLatestAPKDetailsCommon2(standardUrl, additionalSettings,
|
||||
(bool useTagUrl) async {
|
||||
return 'https://$host/api/v1/repos${standardUrl.substring('https://$host'.length)}/${useTagUrl ? 'tags' : 'releases'}?per_page=100';
|
||||
}, null);
|
||||
}
|
||||
|
||||
AppNames getAppNames(String standardUrl) {
|
||||
|
@ -1,6 +1,5 @@
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:html/parser.dart';
|
||||
import 'package:http/http.dart';
|
||||
import 'package:obtainium/components/generated_form.dart';
|
||||
import 'package:obtainium/custom_errors.dart';
|
||||
import 'package:obtainium/providers/source_provider.dart';
|
||||
@ -8,7 +7,6 @@ import 'package:obtainium/providers/source_provider.dart';
|
||||
class FDroidRepo extends AppSource {
|
||||
FDroidRepo() {
|
||||
name = tr('fdroidThirdPartyRepo');
|
||||
overrideEligible = true;
|
||||
|
||||
additionalSourceAppSpecificSettingFormItems = [
|
||||
[
|
||||
|
@ -13,7 +13,6 @@ import 'package:url_launcher/url_launcher_string.dart';
|
||||
class GitHub extends AppSource {
|
||||
GitHub() {
|
||||
host = 'github.com';
|
||||
overrideEligible = true;
|
||||
|
||||
additionalSourceSpecificSettingFormItems = [
|
||||
GeneratedFormTextField('github-creds',
|
||||
@ -143,15 +142,17 @@ class GitHub extends AppSource {
|
||||
} else if (b == null) {
|
||||
return 1;
|
||||
} else {
|
||||
var stdFormats = findStandardFormatsForVersion(a['tag_name'], true)
|
||||
.intersection(findStandardFormatsForVersion(b['tag_name'], true));
|
||||
var nameA = a['tag_name'] ?? a['name'];
|
||||
var nameB = b['tag_name'] ?? b['name'];
|
||||
var stdFormats = findStandardFormatsForVersion(nameA, true)
|
||||
.intersection(findStandardFormatsForVersion(nameB, true));
|
||||
if (stdFormats.isNotEmpty) {
|
||||
var reg = RegExp(stdFormats.first);
|
||||
var matchA = reg.firstMatch(a['tag_name']);
|
||||
var matchB = reg.firstMatch(b['tag_name']);
|
||||
var matchA = reg.firstMatch(nameA);
|
||||
var matchB = reg.firstMatch(nameB);
|
||||
return compareAlphaNumeric(
|
||||
(a['tag_name'] as String).substring(matchA!.start, matchA.end),
|
||||
(b['tag_name'] as String).substring(matchB!.start, matchB.end));
|
||||
(nameA as String).substring(matchA!.start, matchA.end),
|
||||
(nameB as String).substring(matchB!.start, matchB.end));
|
||||
} else {
|
||||
return getReleaseDateFromRelease(a)!
|
||||
.compareTo(getReleaseDateFromRelease(b)!);
|
||||
@ -191,7 +192,7 @@ class GitHub extends AppSource {
|
||||
if (targetRelease == null) {
|
||||
throw NoReleasesError();
|
||||
}
|
||||
String? version = targetRelease['tag_name'];
|
||||
String? version = targetRelease['tag_name'] ?? targetRelease['name'];
|
||||
DateTime? releaseDate = getReleaseDateFromRelease(targetRelease);
|
||||
if (version == null) {
|
||||
throw NoVersionError();
|
||||
@ -211,15 +212,35 @@ class GitHub extends AppSource {
|
||||
}
|
||||
}
|
||||
|
||||
getLatestAPKDetailsCommon2(
|
||||
String standardUrl,
|
||||
Map<String, dynamic> additionalSettings,
|
||||
Future<String> Function(bool) reqUrlGenerator,
|
||||
dynamic Function(Response)? onHttpErrorCode) async {
|
||||
try {
|
||||
return await getLatestAPKDetailsCommon(
|
||||
await reqUrlGenerator(false), standardUrl, additionalSettings,
|
||||
onHttpErrorCode: onHttpErrorCode);
|
||||
} catch (err) {
|
||||
if (err is NoReleasesError && additionalSettings['trackOnly'] == true) {
|
||||
return await getLatestAPKDetailsCommon(
|
||||
await reqUrlGenerator(true), standardUrl, additionalSettings,
|
||||
onHttpErrorCode: onHttpErrorCode);
|
||||
} else {
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<APKDetails> getLatestAPKDetails(
|
||||
String standardUrl,
|
||||
Map<String, dynamic> additionalSettings,
|
||||
) async {
|
||||
return getLatestAPKDetailsCommon(
|
||||
'https://${await getCredentialPrefixIfAny()}api.$host/repos${standardUrl.substring('https://$host'.length)}/releases?per_page=100',
|
||||
standardUrl,
|
||||
additionalSettings, onHttpErrorCode: (Response res) {
|
||||
return await getLatestAPKDetailsCommon2(standardUrl, additionalSettings,
|
||||
(bool useTagUrl) async {
|
||||
return 'https://${await getCredentialPrefixIfAny()}api.$host/repos${standardUrl.substring('https://$host'.length)}/${useTagUrl ? 'tags' : 'releases'}?per_page=100';
|
||||
}, (Response res) {
|
||||
rateLimitErrorCheck(res);
|
||||
});
|
||||
}
|
||||
|
@ -14,7 +14,6 @@ import 'package:url_launcher/url_launcher_string.dart';
|
||||
class GitLab extends AppSource {
|
||||
GitLab() {
|
||||
host = 'gitlab.com';
|
||||
overrideEligible = true;
|
||||
canSearch = true;
|
||||
|
||||
additionalSourceSpecificSettingFormItems = [
|
||||
@ -83,12 +82,12 @@ class GitLab extends AppSource {
|
||||
}
|
||||
var json = jsonDecode(res.body) as List<dynamic>;
|
||||
Map<String, List<String>> results = {};
|
||||
json.forEach((element) {
|
||||
for (var element in json) {
|
||||
results['https://$host/${element['path_with_namespace']}'] = [
|
||||
element['name_with_namespace'],
|
||||
element['description'] ?? tr('noDescription')
|
||||
];
|
||||
});
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
|
@ -85,9 +85,12 @@ bool _isNumeric(String s) {
|
||||
}
|
||||
|
||||
class HTML extends AppSource {
|
||||
HTML() {
|
||||
overrideEligible = true;
|
||||
}
|
||||
@override
|
||||
// TODO: implement requestHeaders choice, hardcoded for now
|
||||
Map<String, String>? get requestHeaders => {
|
||||
"User-Agent":
|
||||
"Mozilla/5.0 (X11; Linux x86_64; rv:60.0) Gecko/20100101 Firefox/81.0"
|
||||
};
|
||||
|
||||
@override
|
||||
String sourceSpecificStandardizeURL(String url) {
|
||||
|
@ -1,4 +1,3 @@
|
||||
import 'package:http/http.dart';
|
||||
import 'package:obtainium/app_sources/fdroid.dart';
|
||||
import 'package:obtainium/custom_errors.dart';
|
||||
import 'package:obtainium/providers/source_provider.dart';
|
||||
|
@ -6,11 +6,9 @@ import 'package:obtainium/providers/source_provider.dart';
|
||||
|
||||
class Jenkins extends AppSource {
|
||||
Jenkins() {
|
||||
overrideEligible = true;
|
||||
overrideVersionDetectionFormDefault('releaseDateAsVersion', true);
|
||||
}
|
||||
|
||||
@override
|
||||
String trimJobUrl(String url) {
|
||||
RegExp standardUrlRegEx = RegExp('.*/job/[^/]+');
|
||||
RegExpMatch? match = standardUrlRegEx.firstMatch(url);
|
||||
|
@ -6,7 +6,6 @@ import 'package:obtainium/providers/source_provider.dart';
|
||||
class SourceForge extends AppSource {
|
||||
SourceForge() {
|
||||
host = 'sourceforge.net';
|
||||
overrideEligible = true;
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -1,6 +1,5 @@
|
||||
import 'package:html/parser.dart';
|
||||
import 'package:http/http.dart';
|
||||
import 'package:obtainium/app_sources/github.dart';
|
||||
import 'package:obtainium/app_sources/html.dart';
|
||||
import 'package:obtainium/custom_errors.dart';
|
||||
import 'package:obtainium/providers/source_provider.dart';
|
||||
@ -10,7 +9,6 @@ import 'package:easy_localization/easy_localization.dart';
|
||||
class SourceHut extends AppSource {
|
||||
SourceHut() {
|
||||
host = 'git.sr.ht';
|
||||
overrideEligible = true;
|
||||
|
||||
additionalSourceAppSpecificSettingFormItems = [
|
||||
[
|
||||
@ -58,11 +56,19 @@ class SourceHut extends AppSource {
|
||||
throw NoVersionError();
|
||||
}
|
||||
String? releaseDateString = entry.querySelector('pubDate')?.innerHtml;
|
||||
var link = entry.querySelector('link');
|
||||
String releasePage = '$standardUrl/refs/$version';
|
||||
DateTime? releaseDate = releaseDateString != null
|
||||
? DateFormat('EEE, dd MMM yyyy HH:mm:ss Z').parse(releaseDateString)
|
||||
: null;
|
||||
DateTime? releaseDate;
|
||||
try {
|
||||
releaseDate = releaseDateString != null
|
||||
? DateFormat('E, dd MMM yyyy HH:mm:ss Z').parse(releaseDateString)
|
||||
: null;
|
||||
releaseDate = releaseDateString != null
|
||||
? DateFormat('EEE, dd MMM yyyy HH:mm:ss Z')
|
||||
.parse(releaseDateString)
|
||||
: null;
|
||||
} catch (e) {
|
||||
// ignore
|
||||
}
|
||||
var res2 = await sourceRequest(releasePage);
|
||||
List<MapEntry<String, String>> apkUrls = [];
|
||||
if (res2.statusCode == 200) {
|
||||
|
@ -1,6 +1,5 @@
|
||||
import 'package:html/parser.dart';
|
||||
import 'package:http/http.dart';
|
||||
import 'package:obtainium/app_sources/html.dart';
|
||||
import 'package:obtainium/custom_errors.dart';
|
||||
import 'package:obtainium/providers/source_provider.dart';
|
||||
|
||||
|
@ -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.13.0';
|
||||
const String currentVersion = '0.13.5';
|
||||
const String currentReleaseTag =
|
||||
'v$currentVersion-beta'; // KEEP THIS IN SYNC WITH GITHUB RELEASES
|
||||
|
||||
|
@ -159,9 +159,16 @@ class _AddAppPageState extends State<AddAppPage> {
|
||||
app.preferredApkIndex =
|
||||
app.apkUrls.map((e) => e.value).toList().indexOf(apkUrl.value);
|
||||
// ignore: use_build_context_synchronously
|
||||
var downloadedApk = await appsProvider.downloadApp(
|
||||
var downloadedArtifact = await appsProvider.downloadApp(
|
||||
app, globalNavigatorKey.currentContext);
|
||||
app.id = downloadedApk.appId;
|
||||
DownloadedApk? downloadedFile;
|
||||
DownloadedXApkDir? downloadedDir;
|
||||
if (downloadedArtifact is DownloadedApk) {
|
||||
downloadedFile = downloadedArtifact;
|
||||
} else {
|
||||
downloadedDir = downloadedArtifact as DownloadedXApkDir;
|
||||
}
|
||||
app.id = downloadedFile?.appId ?? downloadedDir!.appId;
|
||||
}
|
||||
if (appsProvider.apps.containsKey(app.id)) {
|
||||
throw ObtainiumError(tr('appAlreadyAdded'));
|
||||
@ -276,6 +283,9 @@ class _AddAppPageState extends State<AddAppPage> {
|
||||
}
|
||||
si++;
|
||||
}
|
||||
if (res.isEmpty) {
|
||||
throw ObtainiumError(tr('noResults'));
|
||||
}
|
||||
List<String>? selectedUrls = res.isEmpty
|
||||
? []
|
||||
// ignore: use_build_context_synchronously
|
||||
@ -311,10 +321,8 @@ class _AddAppPageState extends State<AddAppPage> {
|
||||
'overrideSource',
|
||||
defaultValue: HTML().runtimeType.toString(),
|
||||
[
|
||||
...sourceProvider.sources
|
||||
.where((s) => s.overrideEligible)
|
||||
.map((s) =>
|
||||
MapEntry(s.runtimeType.toString(), s.name))
|
||||
...sourceProvider.sources.map(
|
||||
(s) => MapEntry(s.runtimeType.toString(), s.name))
|
||||
],
|
||||
label: tr('overrideSource'))
|
||||
]
|
||||
@ -370,13 +378,15 @@ class _AddAppPageState extends State<AddAppPage> {
|
||||
const SizedBox(
|
||||
width: 16,
|
||||
),
|
||||
ElevatedButton(
|
||||
onPressed: searchQuery.isEmpty || doingSomething
|
||||
? null
|
||||
: () {
|
||||
runSearch();
|
||||
},
|
||||
child: Text(tr('search')))
|
||||
searching
|
||||
? const CircularProgressIndicator()
|
||||
: ElevatedButton(
|
||||
onPressed: searchQuery.isEmpty || doingSomething
|
||||
? null
|
||||
: () {
|
||||
runSearch();
|
||||
},
|
||||
child: Text(tr('search')))
|
||||
],
|
||||
);
|
||||
|
||||
|
@ -32,6 +32,7 @@ class _AppPageState extends State<AppPage> {
|
||||
getUpdate(String id) {
|
||||
appsProvider.checkUpdate(id).catchError((e) {
|
||||
showError(e, context);
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
@ -444,7 +445,9 @@ class _AppPageState extends State<AppPage> {
|
||||
Padding(
|
||||
padding: const EdgeInsets.fromLTRB(0, 8, 0, 0),
|
||||
child: LinearProgressIndicator(
|
||||
value: app!.downloadProgress! / 100))
|
||||
value: app!.downloadProgress! >= 0
|
||||
? app.downloadProgress! / 100
|
||||
: null))
|
||||
],
|
||||
));
|
||||
|
||||
|
@ -52,14 +52,37 @@ class AppsPageState extends State<AppsPage> {
|
||||
}
|
||||
}
|
||||
|
||||
final GlobalKey<RefreshIndicatorState> _refreshIndicatorKey =
|
||||
GlobalKey<RefreshIndicatorState>();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
var appsProvider = context.watch<AppsProvider>();
|
||||
var settingsProvider = context.watch<SettingsProvider>();
|
||||
var sourceProvider = SourceProvider();
|
||||
var listedApps = appsProvider.getAppValues().toList();
|
||||
var currentFilterIsUpdatesOnly =
|
||||
filter.isIdenticalTo(updatesOnlyFilter, settingsProvider);
|
||||
|
||||
refresh() {
|
||||
HapticFeedback.lightImpact();
|
||||
setState(() {
|
||||
refreshingSince = DateTime.now();
|
||||
});
|
||||
return appsProvider.checkUpdates().catchError((e) {
|
||||
showError(e, context);
|
||||
return <App>[];
|
||||
}).whenComplete(() {
|
||||
setState(() {
|
||||
refreshingSince = null;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
if (!appsProvider.loadingApps &&
|
||||
appsProvider.apps.isNotEmpty &&
|
||||
settingsProvider.checkJustStarted() &&
|
||||
settingsProvider.checkOnStart) {
|
||||
_refreshIndicatorKey.currentState?.show();
|
||||
}
|
||||
|
||||
selectedAppIds = selectedAppIds
|
||||
.where((element) => listedApps.map((e) => e.app.id).contains(element))
|
||||
@ -104,6 +127,11 @@ class AppsPageState extends State<AppsPage> {
|
||||
}
|
||||
}
|
||||
}
|
||||
if (filter.idFilter.isNotEmpty) {
|
||||
if (!app.app.id.contains(filter.idFilter)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (filter.categoryFilter.isNotEmpty &&
|
||||
filter.categoryFilter
|
||||
.intersection(app.app.categories.toSet())
|
||||
@ -294,28 +322,28 @@ class AppsPageState extends State<AppsPage> {
|
||||
|
||||
getLoadingWidgets() {
|
||||
return [
|
||||
if (appsProvider.loadingApps || listedApps.isEmpty)
|
||||
if (listedApps.isEmpty)
|
||||
SliverFillRemaining(
|
||||
child: Center(
|
||||
child: appsProvider.loadingApps
|
||||
? const CircularProgressIndicator()
|
||||
: Text(
|
||||
appsProvider.apps.isEmpty
|
||||
? tr('noApps')
|
||||
: tr('noAppsForFilter'),
|
||||
style: Theme.of(context).textTheme.headlineMedium,
|
||||
textAlign: TextAlign.center,
|
||||
))),
|
||||
if (refreshingSince != null)
|
||||
child: Text(
|
||||
appsProvider.apps.isEmpty ? tr('noApps') : tr('noAppsForFilter'),
|
||||
style: Theme.of(context).textTheme.headlineMedium,
|
||||
textAlign: TextAlign.center,
|
||||
))),
|
||||
if (refreshingSince != null || appsProvider.loadingApps)
|
||||
SliverToBoxAdapter(
|
||||
child: LinearProgressIndicator(
|
||||
value: appsProvider
|
||||
.getAppValues()
|
||||
.where((element) => !(element.app.lastUpdateCheck
|
||||
?.isBefore(refreshingSince!) ??
|
||||
true))
|
||||
.length /
|
||||
appsProvider.apps.length,
|
||||
value: appsProvider.loadingApps
|
||||
? null
|
||||
: appsProvider
|
||||
.getAppValues()
|
||||
.where((element) => !(element.app.lastUpdateCheck
|
||||
?.isBefore(refreshingSince!) ??
|
||||
true))
|
||||
.length /
|
||||
(appsProvider.apps.isNotEmpty
|
||||
? appsProvider.apps.length
|
||||
: 1),
|
||||
),
|
||||
)
|
||||
];
|
||||
@ -355,6 +383,7 @@ class AppsPageState extends State<AppsPage> {
|
||||
[listedApps[appIndex].app.id],
|
||||
globalNavigatorKey.currentContext).catchError((e) {
|
||||
showError(e, context);
|
||||
return <String>[];
|
||||
});
|
||||
},
|
||||
icon: Icon(
|
||||
@ -417,37 +446,35 @@ class AppsPageState extends State<AppsPage> {
|
||||
width: 10,
|
||||
)
|
||||
: const SizedBox.shrink(),
|
||||
Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
crossAxisAlignment: CrossAxisAlignment.end,
|
||||
children: [
|
||||
Row(mainAxisSize: MainAxisSize.min, children: [
|
||||
Container(
|
||||
constraints: BoxConstraints(
|
||||
maxWidth: MediaQuery.of(context).size.width / 4),
|
||||
child: Text(
|
||||
getVersionText(index),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
textAlign: TextAlign.end,
|
||||
)),
|
||||
]),
|
||||
Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
GestureDetector(
|
||||
onTap: showChangesFn,
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
crossAxisAlignment: CrossAxisAlignment.end,
|
||||
children: [
|
||||
GestureDetector(
|
||||
onTap: showChangesFn,
|
||||
child: Text(
|
||||
Row(mainAxisSize: MainAxisSize.min, children: [
|
||||
Container(
|
||||
constraints: BoxConstraints(
|
||||
maxWidth: MediaQuery.of(context).size.width / 4),
|
||||
child: Text(getVersionText(index),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
textAlign: TextAlign.end)),
|
||||
]),
|
||||
Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Text(
|
||||
getChangesButtonString(index, showChangesFn != null),
|
||||
style: TextStyle(
|
||||
fontStyle: FontStyle.italic,
|
||||
decoration: showChangesFn != null
|
||||
? TextDecoration.underline
|
||||
: TextDecoration.none),
|
||||
))
|
||||
)
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
)
|
||||
))
|
||||
],
|
||||
);
|
||||
|
||||
@ -515,10 +542,21 @@ class AppsPageState extends State<AppsPage> {
|
||||
? FontWeight.bold
|
||||
: FontWeight.normal)),
|
||||
trailing: listedApps[index].downloadProgress != null
|
||||
? Text(tr('percentProgress', args: [
|
||||
listedApps[index].downloadProgress?.toInt().toString() ??
|
||||
'100'
|
||||
]))
|
||||
? SizedBox(
|
||||
width: 90,
|
||||
child: Text(
|
||||
listedApps[index].downloadProgress! >= 0
|
||||
? tr('percentProgress', args: [
|
||||
listedApps[index]
|
||||
.downloadProgress!
|
||||
.toInt()
|
||||
.toString()
|
||||
])
|
||||
: tr('pleaseWait'),
|
||||
textAlign: (listedApps[index].downloadProgress! >= 0)
|
||||
? TextAlign.start
|
||||
: TextAlign.end,
|
||||
))
|
||||
: trailingRow,
|
||||
onTap: () {
|
||||
if (selectedAppIds.isNotEmpty) {
|
||||
@ -651,6 +689,7 @@ class AppsPageState extends State<AppsPage> {
|
||||
settingsProvider: settingsProvider)
|
||||
.catchError((e) {
|
||||
showError(e, context);
|
||||
return <String>[];
|
||||
});
|
||||
}
|
||||
});
|
||||
@ -846,44 +885,41 @@ class AppsPageState extends State<AppsPage> {
|
||||
});
|
||||
}
|
||||
|
||||
getMainBottomButtonsRow() {
|
||||
return Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||
children: [
|
||||
IconButton(
|
||||
getMainBottomButtons() {
|
||||
return [
|
||||
IconButton(
|
||||
visualDensity: VisualDensity.compact,
|
||||
onPressed: selectedAppIds.isEmpty
|
||||
? null
|
||||
: () {
|
||||
appsProvider.removeAppsWithModal(
|
||||
context, selectedApps.toList());
|
||||
},
|
||||
tooltip: tr('removeSelectedApps'),
|
||||
icon: const Icon(Icons.delete_outline_outlined),
|
||||
),
|
||||
IconButton(
|
||||
visualDensity: VisualDensity.compact,
|
||||
onPressed: getMassObtainFunction(),
|
||||
tooltip: selectedAppIds.isEmpty
|
||||
? tr('installUpdateApps')
|
||||
: tr('installUpdateSelectedApps'),
|
||||
icon: const Icon(
|
||||
Icons.file_download_outlined,
|
||||
)),
|
||||
IconButton(
|
||||
visualDensity: VisualDensity.compact,
|
||||
onPressed: selectedAppIds.isEmpty ? null : launchCategorizeDialog(),
|
||||
tooltip: tr('categorize'),
|
||||
icon: const Icon(Icons.category_outlined),
|
||||
),
|
||||
IconButton(
|
||||
visualDensity: VisualDensity.compact,
|
||||
onPressed: selectedAppIds.isEmpty ? null : showMoreOptionsDialog,
|
||||
tooltip: tr('more'),
|
||||
icon: const Icon(Icons.more_horiz),
|
||||
),
|
||||
],
|
||||
);
|
||||
onPressed: getMassObtainFunction(),
|
||||
tooltip: selectedAppIds.isEmpty
|
||||
? tr('installUpdateApps')
|
||||
: tr('installUpdateSelectedApps'),
|
||||
icon: const Icon(
|
||||
Icons.file_download_outlined,
|
||||
)),
|
||||
IconButton(
|
||||
visualDensity: VisualDensity.compact,
|
||||
onPressed: selectedAppIds.isEmpty
|
||||
? null
|
||||
: () {
|
||||
appsProvider.removeAppsWithModal(
|
||||
context, selectedApps.toList());
|
||||
},
|
||||
tooltip: tr('removeSelectedApps'),
|
||||
icon: const Icon(Icons.delete_outline_outlined),
|
||||
),
|
||||
IconButton(
|
||||
visualDensity: VisualDensity.compact,
|
||||
onPressed: selectedAppIds.isEmpty ? null : launchCategorizeDialog(),
|
||||
tooltip: tr('categorize'),
|
||||
icon: const Icon(Icons.category_outlined),
|
||||
),
|
||||
IconButton(
|
||||
visualDensity: VisualDensity.compact,
|
||||
onPressed: selectedAppIds.isEmpty ? null : showMoreOptionsDialog,
|
||||
tooltip: tr('more'),
|
||||
icon: const Icon(Icons.more_horiz),
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
showFilterDialog() async {
|
||||
@ -905,6 +941,12 @@ class AppsPageState extends State<AppsPage> {
|
||||
required: false,
|
||||
defaultValue: vals['author'])
|
||||
],
|
||||
[
|
||||
GeneratedFormTextField('appId',
|
||||
label: tr('appId'),
|
||||
required: false,
|
||||
defaultValue: vals['appId'])
|
||||
],
|
||||
[
|
||||
GeneratedFormSwitch('upToDateApps',
|
||||
label: tr('upToDateApps'),
|
||||
@ -950,50 +992,33 @@ class AppsPageState extends State<AppsPage> {
|
||||
}
|
||||
|
||||
getFilterButtonsRow() {
|
||||
var isFilterOff = filter.isIdenticalTo(neutralFilter, settingsProvider);
|
||||
return Row(
|
||||
children: [
|
||||
getSelectAllButton(),
|
||||
IconButton(
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
style: const ButtonStyle(visualDensity: VisualDensity.compact),
|
||||
tooltip: isFilterOff ? tr('filter') : tr('filterActive'),
|
||||
onPressed: isFilterOff
|
||||
? showFilterDialog
|
||||
: () {
|
||||
setState(() {
|
||||
filter = AppsFilter();
|
||||
});
|
||||
},
|
||||
icon: Icon(isFilterOff
|
||||
? Icons.filter_list_rounded
|
||||
: Icons.filter_list_off_rounded)),
|
||||
const SizedBox(
|
||||
width: 10,
|
||||
),
|
||||
const VerticalDivider(),
|
||||
Expanded(
|
||||
child: SingleChildScrollView(
|
||||
scrollDirection: Axis.horizontal,
|
||||
child: getMainBottomButtonsRow())),
|
||||
const VerticalDivider(),
|
||||
IconButton(
|
||||
visualDensity: VisualDensity.compact,
|
||||
onPressed: () {
|
||||
setState(() {
|
||||
if (currentFilterIsUpdatesOnly) {
|
||||
filter = AppsFilter();
|
||||
} else {
|
||||
filter = updatesOnlyFilter;
|
||||
}
|
||||
});
|
||||
},
|
||||
tooltip: currentFilterIsUpdatesOnly
|
||||
? tr('removeOutdatedFilter')
|
||||
: tr('showOutdatedOnly'),
|
||||
icon: Icon(
|
||||
currentFilterIsUpdatesOnly
|
||||
? Icons.update_disabled_rounded
|
||||
: Icons.update_rounded,
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
),
|
||||
),
|
||||
TextButton.icon(
|
||||
style: const ButtonStyle(visualDensity: VisualDensity.compact),
|
||||
label: Text(
|
||||
filter.isIdenticalTo(neutralFilter, settingsProvider)
|
||||
? tr('filter')
|
||||
: tr('filterActive'),
|
||||
style: TextStyle(
|
||||
fontWeight:
|
||||
filter.isIdenticalTo(neutralFilter, settingsProvider)
|
||||
? FontWeight.normal
|
||||
: FontWeight.bold),
|
||||
),
|
||||
onPressed: showFilterDialog,
|
||||
icon: const Icon(Icons.filter_list_rounded))
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||
children: getMainBottomButtons(),
|
||||
)),
|
||||
],
|
||||
);
|
||||
}
|
||||
@ -1017,19 +1042,8 @@ class AppsPageState extends State<AppsPage> {
|
||||
return Scaffold(
|
||||
backgroundColor: Theme.of(context).colorScheme.surface,
|
||||
body: RefreshIndicator(
|
||||
onRefresh: () {
|
||||
HapticFeedback.lightImpact();
|
||||
setState(() {
|
||||
refreshingSince = DateTime.now();
|
||||
});
|
||||
return appsProvider.checkUpdates().catchError((e) {
|
||||
showError(e, context);
|
||||
}).whenComplete(() {
|
||||
setState(() {
|
||||
refreshingSince = null;
|
||||
});
|
||||
});
|
||||
},
|
||||
key: _refreshIndicatorKey,
|
||||
onRefresh: refresh,
|
||||
child: CustomScrollView(slivers: <Widget>[
|
||||
CustomAppBar(title: tr('appsString')),
|
||||
...getLoadingWidgets(),
|
||||
@ -1047,6 +1061,7 @@ class AppsPageState extends State<AppsPage> {
|
||||
class AppsFilter {
|
||||
late String nameFilter;
|
||||
late String authorFilter;
|
||||
late String idFilter;
|
||||
late bool includeUptodate;
|
||||
late bool includeNonInstalled;
|
||||
late Set<String> categoryFilter;
|
||||
@ -1055,6 +1070,7 @@ class AppsFilter {
|
||||
AppsFilter(
|
||||
{this.nameFilter = '',
|
||||
this.authorFilter = '',
|
||||
this.idFilter = '',
|
||||
this.includeUptodate = true,
|
||||
this.includeNonInstalled = true,
|
||||
this.categoryFilter = const {},
|
||||
@ -1064,6 +1080,7 @@ class AppsFilter {
|
||||
return {
|
||||
'appName': nameFilter,
|
||||
'author': authorFilter,
|
||||
'appId': idFilter,
|
||||
'upToDateApps': includeUptodate,
|
||||
'nonInstalledApps': includeNonInstalled,
|
||||
'sourceFilter': sourceFilter
|
||||
@ -1073,6 +1090,7 @@ class AppsFilter {
|
||||
setFormValuesFromMap(Map<String, dynamic> values) {
|
||||
nameFilter = values['appName']!;
|
||||
authorFilter = values['author']!;
|
||||
idFilter = values['appId']!;
|
||||
includeUptodate = values['upToDateApps'];
|
||||
includeNonInstalled = values['nonInstalledApps'];
|
||||
sourceFilter = values['sourceFilter'];
|
||||
@ -1081,6 +1099,7 @@ class AppsFilter {
|
||||
bool isIdenticalTo(AppsFilter other, SettingsProvider settingsProvider) =>
|
||||
authorFilter.trim() == other.authorFilter.trim() &&
|
||||
nameFilter.trim() == other.nameFilter.trim() &&
|
||||
idFilter.trim() == other.idFilter.trim() &&
|
||||
includeUptodate == other.includeUptodate &&
|
||||
includeNonInstalled == other.includeNonInstalled &&
|
||||
settingsProvider.setEqual(categoryFilter, other.categoryFilter) &&
|
||||
|
@ -27,6 +27,7 @@ class NavigationPageItem {
|
||||
class _HomePageState extends State<HomePage> {
|
||||
List<int> selectedIndexHistory = [];
|
||||
int prevAppCount = -1;
|
||||
bool prevIsLoading = true;
|
||||
|
||||
List<NavigationPageItem> pages = [
|
||||
NavigationPageItem(tr('appsString'), Icons.apps,
|
||||
@ -64,13 +65,15 @@ class _HomePageState extends State<HomePage> {
|
||||
}
|
||||
}
|
||||
|
||||
if (prevAppCount >= 0 &&
|
||||
if (!prevIsLoading &&
|
||||
prevAppCount >= 0 &&
|
||||
appsProvider.apps.length > prevAppCount &&
|
||||
selectedIndexHistory.isNotEmpty &&
|
||||
selectedIndexHistory.last == 1) {
|
||||
switchToPage(0);
|
||||
}
|
||||
prevAppCount = appsProvider.apps.length;
|
||||
prevIsLoading = appsProvider.loadingApps;
|
||||
|
||||
return WillPopScope(
|
||||
child: Scaffold(
|
||||
|
@ -323,8 +323,8 @@ class _ImportExportPageState extends State<ImportExportPage> {
|
||||
],
|
||||
),
|
||||
if (importInProgress)
|
||||
Column(
|
||||
children: const [
|
||||
const Column(
|
||||
children: [
|
||||
SizedBox(
|
||||
height: 14,
|
||||
),
|
||||
|
@ -228,6 +228,18 @@ class _SettingsPageState extends State<SettingsPage> {
|
||||
color: Theme.of(context).colorScheme.primary),
|
||||
),
|
||||
intervalDropdown,
|
||||
height16,
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Flexible(child: Text(tr('checkOnStart'))),
|
||||
Switch(
|
||||
value: settingsProvider.checkOnStart,
|
||||
onChanged: (value) {
|
||||
settingsProvider.checkOnStart = value;
|
||||
})
|
||||
],
|
||||
),
|
||||
height32,
|
||||
Text(
|
||||
tr('sourceSpecific'),
|
||||
|
@ -27,6 +27,7 @@ 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';
|
||||
import 'package:archive/archive.dart';
|
||||
|
||||
class AppInMemory {
|
||||
late App app;
|
||||
@ -46,6 +47,13 @@ class DownloadedApk {
|
||||
DownloadedApk(this.appId, this.file);
|
||||
}
|
||||
|
||||
class DownloadedXApkDir {
|
||||
String appId;
|
||||
File file;
|
||||
Directory extracted;
|
||||
DownloadedXApkDir(this.appId, this.file, this.extracted);
|
||||
}
|
||||
|
||||
List<String> generateStandardVersionRegExStrings() {
|
||||
// TODO: Look into RegEx for non-Latin characters / non-Arabic numerals
|
||||
var basics = [
|
||||
@ -100,6 +108,7 @@ class AppsProvider with ChangeNotifier {
|
||||
bool isForeground = true;
|
||||
late Stream<FGBGType>? foregroundStream;
|
||||
late StreamSubscription<FGBGType>? foregroundSubscription;
|
||||
late Directory APKDir;
|
||||
|
||||
Iterable<AppInMemory> getAppValues() => apps.values.map((a) => a.deepCopy());
|
||||
|
||||
@ -108,35 +117,53 @@ class AppsProvider with ChangeNotifier {
|
||||
foregroundStream = FGBGEvents.stream.asBroadcastStream();
|
||||
foregroundSubscription = foregroundStream?.listen((event) async {
|
||||
isForeground = event == FGBGType.foreground;
|
||||
if (isForeground) await loadApps();
|
||||
if (isForeground) await refreshInstallStatuses();
|
||||
});
|
||||
() async {
|
||||
var cacheDirs = await getExternalCacheDirectories();
|
||||
if (cacheDirs?.isNotEmpty ?? false) {
|
||||
APKDir = cacheDirs!.first;
|
||||
} else {
|
||||
APKDir =
|
||||
Directory('${(await getExternalStorageDirectory())!.path}/apks');
|
||||
if (!APKDir.existsSync()) {
|
||||
APKDir.createSync();
|
||||
}
|
||||
}
|
||||
// Load Apps into memory (in background, this is done later instead of in the constructor)
|
||||
await loadApps();
|
||||
// Delete any partial APKs
|
||||
(await getExternalCacheDirectories())
|
||||
?.first
|
||||
.listSync()
|
||||
.where((element) => element.path.endsWith('.apk.part'))
|
||||
var cutoff = DateTime.now().subtract(const Duration(days: 7));
|
||||
APKDir.listSync()
|
||||
.where((element) =>
|
||||
element.path.endsWith('.part') ||
|
||||
element.statSync().modified.isBefore(cutoff))
|
||||
.forEach((partialApk) {
|
||||
partialApk.delete();
|
||||
partialApk.delete(recursive: true);
|
||||
});
|
||||
}();
|
||||
}
|
||||
|
||||
downloadFile(String url, String fileName, Function? onProgress,
|
||||
Future<File> downloadFile(
|
||||
String url, String fileNameNoExt, Function? onProgress,
|
||||
{bool useExisting = true, Map<String, String>? headers}) async {
|
||||
var destDir = (await getExternalCacheDirectories())!.first.path;
|
||||
var destDir = APKDir.path;
|
||||
var req = Request('GET', Uri.parse(url));
|
||||
if (headers != null) {
|
||||
req.headers.addAll(headers);
|
||||
}
|
||||
StreamedResponse response = await Client().send(req);
|
||||
File downloadedFile = File('$destDir/$fileName');
|
||||
var client = Client();
|
||||
StreamedResponse response = await client.send(req);
|
||||
String ext =
|
||||
response.headers['content-disposition']?.split('.').last ?? 'apk';
|
||||
if (ext.endsWith('"') || ext.endsWith("other")) {
|
||||
ext = ext.substring(0, ext.length - 1);
|
||||
}
|
||||
File downloadedFile = File('$destDir/$fileNameNoExt.$ext');
|
||||
if (!(downloadedFile.existsSync() && useExisting)) {
|
||||
File tempDownloadedFile = File('${downloadedFile.path}.part');
|
||||
if (tempDownloadedFile.existsSync()) {
|
||||
tempDownloadedFile.deleteSync();
|
||||
tempDownloadedFile.deleteSync(recursive: true);
|
||||
}
|
||||
var length = response.contentLength;
|
||||
var received = 0;
|
||||
@ -156,15 +183,38 @@ class AppsProvider with ChangeNotifier {
|
||||
onProgress(progress);
|
||||
}
|
||||
if (response.statusCode != 200) {
|
||||
tempDownloadedFile.deleteSync();
|
||||
tempDownloadedFile.deleteSync(recursive: true);
|
||||
throw response.reasonPhrase ?? tr('unexpectedError');
|
||||
}
|
||||
tempDownloadedFile.renameSync(downloadedFile.path);
|
||||
} else {
|
||||
client.close();
|
||||
}
|
||||
return downloadedFile;
|
||||
}
|
||||
|
||||
Future<DownloadedApk> downloadApp(App app, BuildContext? context) async {
|
||||
Future<File> handleAPKIDChange(App app, PackageArchiveInfo newInfo,
|
||||
File downloadedFile, String downloadUrl) async {
|
||||
// If the APK package ID is different from the App ID, it is either new (using a placeholder ID) or the ID has changed
|
||||
// The former case should be handled (give the App its real ID), the latter is a security issue
|
||||
if (app.id != newInfo.packageName) {
|
||||
var isTempId = SourceProvider().isTempId(app);
|
||||
if (apps[app.id] != null && !isTempId) {
|
||||
throw IDChangedError();
|
||||
}
|
||||
var originalAppId = app.id;
|
||||
app.id = newInfo.packageName;
|
||||
downloadedFile = downloadedFile.renameSync(
|
||||
'${downloadedFile.parent.path}/${app.id}-${downloadUrl.hashCode}.${downloadedFile.path.split('.').last}');
|
||||
if (apps[originalAppId] != null) {
|
||||
await removeApps([originalAppId]);
|
||||
await saveApps([app], onlyIfExists: !isTempId);
|
||||
}
|
||||
}
|
||||
return downloadedFile;
|
||||
}
|
||||
|
||||
Future<Object> downloadApp(App app, BuildContext? context) async {
|
||||
NotificationsProvider? notificationsProvider =
|
||||
context?.read<NotificationsProvider>();
|
||||
var notifId = DownloadNotification(app.finalName, 0).id;
|
||||
@ -177,11 +227,11 @@ class AppsProvider with ChangeNotifier {
|
||||
.getSource(app.url, overrideSource: app.overrideSource);
|
||||
String downloadUrl = await source.apkUrlPrefetchModifier(
|
||||
app.apkUrls[app.preferredApkIndex].value, app.url);
|
||||
var fileName = '${app.id}-${downloadUrl.hashCode}.apk';
|
||||
var notif = DownloadNotification(app.finalName, 100);
|
||||
notificationsProvider?.cancel(notif.id);
|
||||
int? prevProg;
|
||||
File downloadedFile = await downloadFile(downloadUrl, fileName,
|
||||
var fileNameNoExt = '${app.id}-${downloadUrl.hashCode}';
|
||||
var downloadedFile = await downloadFile(downloadUrl, fileNameNoExt,
|
||||
headers: source.requestHeaders, (double? progress) {
|
||||
int? prog = progress?.ceil();
|
||||
if (apps[app.id] != null) {
|
||||
@ -194,33 +244,45 @@ class AppsProvider with ChangeNotifier {
|
||||
}
|
||||
prevProg = prog;
|
||||
});
|
||||
// If the APK package ID is different from the App ID, it is either new (using a placeholder ID) or the ID has changed
|
||||
// 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) {
|
||||
var isTempId = SourceProvider().isTempId(app);
|
||||
if (apps[app.id] != null && !isTempId) {
|
||||
throw IDChangedError();
|
||||
}
|
||||
var originalAppId = app.id;
|
||||
app.id = newInfo.packageName;
|
||||
downloadedFile = downloadedFile.renameSync(
|
||||
'${downloadedFile.parent.path}/${app.id}-${downloadUrl.hashCode}.apk');
|
||||
if (apps[originalAppId] != null) {
|
||||
await removeApps([originalAppId]);
|
||||
await saveApps([app], onlyIfExists: !isTempId);
|
||||
}
|
||||
// Set to 90 for remaining steps, will make null in 'finally'
|
||||
if (apps[app.id] != null) {
|
||||
apps[app.id]!.downloadProgress = -1;
|
||||
notifyListeners();
|
||||
notif = DownloadNotification(app.finalName, -1);
|
||||
notificationsProvider?.notify(notif);
|
||||
}
|
||||
// Delete older versions of the APK if any
|
||||
PackageArchiveInfo? newInfo;
|
||||
var isAPK = downloadedFile.path.toLowerCase().endsWith('.apk');
|
||||
Directory? xapkDir;
|
||||
if (isAPK) {
|
||||
newInfo = await PackageArchiveInfo.fromPath(downloadedFile.path);
|
||||
} else {
|
||||
// Assume XAPK
|
||||
String xapkDirPath = '${downloadedFile.path}-dir';
|
||||
unzipFile(downloadedFile.path, '${downloadedFile.path}-dir');
|
||||
xapkDir = Directory(xapkDirPath);
|
||||
var apks = xapkDir
|
||||
.listSync()
|
||||
.where((e) => e.path.toLowerCase().endsWith('.apk'))
|
||||
.toList();
|
||||
newInfo = await PackageArchiveInfo.fromPath(apks.first.path);
|
||||
}
|
||||
downloadedFile =
|
||||
await handleAPKIDChange(app, newInfo, downloadedFile, downloadUrl);
|
||||
// Delete older versions of the file if any
|
||||
for (var file in downloadedFile.parent.listSync()) {
|
||||
var fn = file.path.split('/').last;
|
||||
if (fn.startsWith('${app.id}-') &&
|
||||
fn.endsWith('.apk') &&
|
||||
fn != downloadedFile.path.split('/').last) {
|
||||
file.delete();
|
||||
FileSystemEntity.isFileSync(file.path) &&
|
||||
file.path != downloadedFile.path) {
|
||||
file.delete(recursive: true);
|
||||
}
|
||||
}
|
||||
return DownloadedApk(app.id, downloadedFile);
|
||||
if (isAPK) {
|
||||
return DownloadedApk(app.id, downloadedFile);
|
||||
} else {
|
||||
return DownloadedXApkDir(app.id, downloadedFile, xapkDir!);
|
||||
}
|
||||
} finally {
|
||||
notificationsProvider?.cancel(notifId);
|
||||
if (apps[app.id] != null) {
|
||||
@ -267,11 +329,43 @@ class AppsProvider with ChangeNotifier {
|
||||
}
|
||||
}
|
||||
|
||||
// Unfortunately this 'await' does not actually wait for the APK to finish installing
|
||||
// So we only know that the install prompt was shown, but the user could still cancel w/o us knowing
|
||||
// If appropriate criteria are met, the update (never a fresh install) happens silently in the background
|
||||
// But even then, we don't know if it actually succeeded
|
||||
Future<void> installApk(DownloadedApk file, {bool silent = false}) async {
|
||||
void unzipFile(String filePath, String destinationPath) {
|
||||
final bytes = File(filePath).readAsBytesSync();
|
||||
final archive = ZipDecoder().decodeBytes(bytes);
|
||||
|
||||
for (final file in archive) {
|
||||
final filename = '$destinationPath/${file.name}';
|
||||
if (file.isFile) {
|
||||
final data = file.content as List<int>;
|
||||
File(filename)
|
||||
..createSync(recursive: true)
|
||||
..writeAsBytesSync(data);
|
||||
} else {
|
||||
Directory(filename).create(recursive: true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> installXApkDir(DownloadedXApkDir dir,
|
||||
{bool silent = false}) async {
|
||||
try {
|
||||
var somethingInstalled = false;
|
||||
for (var apk in dir.extracted
|
||||
.listSync()
|
||||
.where((f) => f is File && f.path.toLowerCase().endsWith('.apk'))) {
|
||||
somethingInstalled = somethingInstalled ||
|
||||
await installApk(DownloadedApk(dir.appId, apk as File),
|
||||
silent: silent);
|
||||
}
|
||||
if (somethingInstalled) {
|
||||
dir.file.delete(recursive: true);
|
||||
}
|
||||
} finally {
|
||||
dir.extracted.delete(recursive: true);
|
||||
}
|
||||
}
|
||||
|
||||
Future<bool> installApk(DownloadedApk file, {bool silent = false}) async {
|
||||
// TODO: Use 'silent' when/if ever possible
|
||||
var newInfo = await PackageArchiveInfo.fromPath(file.file.path);
|
||||
AppInfo? appInfo;
|
||||
@ -287,14 +381,17 @@ class AppsProvider with ChangeNotifier {
|
||||
}
|
||||
int? code =
|
||||
await AndroidPackageInstaller.installApk(apkFilePath: file.file.path);
|
||||
bool installed = false;
|
||||
if (code != null && code != 0 && code != 3) {
|
||||
throw InstallError(code);
|
||||
} else if (code == 0) {
|
||||
installed = true;
|
||||
apps[file.appId]!.app.installedVersion =
|
||||
apps[file.appId]!.app.latestVersion;
|
||||
file.file.delete();
|
||||
file.file.delete(recursive: true);
|
||||
}
|
||||
await saveApps([apps[file.appId]!.app]);
|
||||
return installed;
|
||||
}
|
||||
|
||||
void uninstallApp(String appId) async {
|
||||
@ -420,9 +517,16 @@ class AppsProvider with ChangeNotifier {
|
||||
for (var id in appsToInstall) {
|
||||
try {
|
||||
// ignore: use_build_context_synchronously
|
||||
var downloadedFile = await downloadApp(apps[id]!.app, context);
|
||||
bool willBeSilent =
|
||||
await canInstallSilently(apps[downloadedFile.appId]!.app);
|
||||
var downloadedArtifact = await downloadApp(apps[id]!.app, context);
|
||||
DownloadedApk? downloadedFile;
|
||||
DownloadedXApkDir? downloadedDir;
|
||||
if (downloadedArtifact is DownloadedApk) {
|
||||
downloadedFile = downloadedArtifact;
|
||||
} else {
|
||||
downloadedDir = downloadedArtifact as DownloadedXApkDir;
|
||||
}
|
||||
bool willBeSilent = await canInstallSilently(
|
||||
apps[downloadedFile?.appId ?? downloadedDir!.appId]!.app);
|
||||
willBeSilent = false; // TODO: Remove this when silent updates work
|
||||
if (!(await settingsProvider?.getInstallPermission(enforce: false) ??
|
||||
true)) {
|
||||
@ -432,7 +536,18 @@ class AppsProvider with ChangeNotifier {
|
||||
// ignore: use_build_context_synchronously
|
||||
await waitForUserToReturnToForeground(context);
|
||||
}
|
||||
await installApk(downloadedFile, silent: willBeSilent);
|
||||
apps[id]?.downloadProgress = -1;
|
||||
notifyListeners();
|
||||
try {
|
||||
if (downloadedFile != null) {
|
||||
await installApk(downloadedFile, silent: willBeSilent);
|
||||
} else {
|
||||
await installXApkDir(downloadedDir!, silent: willBeSilent);
|
||||
}
|
||||
} finally {
|
||||
apps[id]?.downloadProgress = null;
|
||||
notifyListeners();
|
||||
}
|
||||
installedIds.add(id);
|
||||
} catch (e) {
|
||||
errors.add(id, e.toString());
|
||||
@ -597,41 +712,30 @@ class AppsProvider with ChangeNotifier {
|
||||
}
|
||||
loadingApps = true;
|
||||
notifyListeners();
|
||||
List<App> newApps = (await getAppsDir())
|
||||
.listSync()
|
||||
.where((item) => item.path.toLowerCase().endsWith('.json'))
|
||||
.map((e) {
|
||||
try {
|
||||
return App.fromJson(jsonDecode(File(e.path).readAsStringSync()));
|
||||
} catch (err) {
|
||||
if (err is FormatException) {
|
||||
logs.add('Corrupt JSON when loading App (will be ignored): $e');
|
||||
e.renameSync('${e.path}.corrupt');
|
||||
return App(
|
||||
'', '', '', '', '', '', [], 0, {}, DateTime.now(), false);
|
||||
} else {
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
})
|
||||
.where((element) => element.id.isNotEmpty)
|
||||
.toList();
|
||||
var idsToDelete = apps.values
|
||||
.map((e) => e.app.id)
|
||||
.toSet()
|
||||
.difference(newApps.map((e) => e.id).toSet());
|
||||
for (var id in idsToDelete) {
|
||||
apps.remove(id);
|
||||
}
|
||||
var sp = SourceProvider();
|
||||
List<List<String>> errors = [];
|
||||
for (int i = 0; i < newApps.length; i++) {
|
||||
var info = await getInstalledInfo(newApps[i].id);
|
||||
List<FileSystemEntity> newApps = (await getAppsDir())
|
||||
.listSync()
|
||||
.where((item) => item.path.toLowerCase().endsWith('.json'))
|
||||
.toList();
|
||||
for (var e in newApps) {
|
||||
try {
|
||||
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()]);
|
||||
var app = App.fromJson(jsonDecode(File(e.path).readAsStringSync()));
|
||||
try {
|
||||
var info = await getInstalledInfo(app.id);
|
||||
sp.getSource(app.url, overrideSource: app.overrideSource);
|
||||
apps[app.id] = AppInMemory(app, null, info);
|
||||
notifyListeners();
|
||||
} catch (e) {
|
||||
errors.add([app.id, app.finalName, e.toString()]);
|
||||
}
|
||||
} catch (err) {
|
||||
if (err is FormatException) {
|
||||
logs.add('Corrupt JSON when loading App (will be ignored): $e');
|
||||
e.renameSync('${e.path}.corrupt');
|
||||
} else {
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (errors.isNotEmpty) {
|
||||
@ -641,6 +745,10 @@ class AppsProvider with ChangeNotifier {
|
||||
}
|
||||
loadingApps = false;
|
||||
notifyListeners();
|
||||
refreshInstallStatuses();
|
||||
}
|
||||
|
||||
Future<void> refreshInstallStatuses() async {
|
||||
if (await doesInstalledAppsPluginWork()) {
|
||||
List<App> modifiedApps = [];
|
||||
for (var app in apps.values) {
|
||||
@ -684,11 +792,18 @@ class AppsProvider with ChangeNotifier {
|
||||
}
|
||||
|
||||
Future<void> removeApps(List<String> appIds) async {
|
||||
var apkFiles = APKDir.listSync();
|
||||
for (var appId in appIds) {
|
||||
File file = File('${(await getAppsDir()).path}/$appId.json');
|
||||
if (file.existsSync()) {
|
||||
file.deleteSync();
|
||||
file.deleteSync(recursive: true);
|
||||
}
|
||||
apkFiles
|
||||
.where(
|
||||
(element) => element.path.split('/').last.startsWith('$appId-'))
|
||||
.forEach((element) {
|
||||
element.delete(recursive: true);
|
||||
});
|
||||
if (apps.containsKey(appId)) {
|
||||
apps.remove(appId);
|
||||
}
|
||||
@ -734,7 +849,7 @@ class AppsProvider with ChangeNotifier {
|
||||
apps[i].installedVersion = null;
|
||||
}
|
||||
}
|
||||
await saveApps(apps, attemptToCorrectInstallStatus: !remove);
|
||||
await saveApps(apps, attemptToCorrectInstallStatus: false);
|
||||
}
|
||||
if (remove) {
|
||||
await removeApps(apps.map((e) => e.id).toList());
|
||||
|
@ -167,7 +167,8 @@ class NotificationsProvider {
|
||||
progress: progPercent ?? 0,
|
||||
maxProgress: 100,
|
||||
showProgress: progPercent != null,
|
||||
onlyAlertOnce: onlyAlertOnce)));
|
||||
onlyAlertOnce: onlyAlertOnce,
|
||||
indeterminate: progPercent != null && progPercent < 0)));
|
||||
}
|
||||
|
||||
Future<void> notify(ObtainiumNotification notif,
|
||||
|
@ -35,6 +35,7 @@ List<int> updateIntervals = [15, 30, 60, 120, 180, 360, 720, 1440, 4320, 0]
|
||||
|
||||
class SettingsProvider with ChangeNotifier {
|
||||
SharedPreferences? prefs;
|
||||
bool justStarted = true;
|
||||
|
||||
String sourceUrl = 'https://github.com/ImranR98/Obtainium';
|
||||
|
||||
@ -92,6 +93,15 @@ class SettingsProvider with ChangeNotifier {
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
bool get checkOnStart {
|
||||
return prefs?.getBool('checkOnStart') ?? false;
|
||||
}
|
||||
|
||||
set checkOnStart(bool checkOnStart) {
|
||||
prefs?.setBool('checkOnStart', checkOnStart);
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
SortColumnSettings get sortColumn {
|
||||
return SortColumnSettings.values[
|
||||
prefs?.getInt('sortColumn') ?? SortColumnSettings.nameAuthor.index];
|
||||
@ -120,6 +130,14 @@ class SettingsProvider with ChangeNotifier {
|
||||
return result;
|
||||
}
|
||||
|
||||
bool checkJustStarted() {
|
||||
if (justStarted) {
|
||||
justStarted = false;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
Future<bool> getInstallPermission({bool enforce = false}) async {
|
||||
while (!(await Permission.requestInstallPackages.isGranted)) {
|
||||
// Explicit request as InstallPlugin request sometimes bugged
|
||||
|
@ -7,7 +7,6 @@ import 'package:device_info_plus/device_info_plus.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:html/dom.dart';
|
||||
import 'package:http/http.dart';
|
||||
import 'package:obtainium/app_sources/apkcombo.dart';
|
||||
import 'package:obtainium/app_sources/apkmirror.dart';
|
||||
import 'package:obtainium/app_sources/apkpure.dart';
|
||||
import 'package:obtainium/app_sources/codeberg.dart';
|
||||
@ -318,7 +317,6 @@ abstract class AppSource {
|
||||
late String name;
|
||||
bool enforceTrackOnly = false;
|
||||
bool changeLogIfAnyIsMarkDown = true;
|
||||
bool overrideEligible = false;
|
||||
|
||||
AppSource() {
|
||||
name = runtimeType.toString();
|
||||
|
226
pubspec.lock
226
pubspec.lock
@ -5,18 +5,18 @@ packages:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: android_alarm_manager_plus
|
||||
sha256: "88a8001851fdc9bd54fa4e30d0277bb900a50f3d86ff244da7f027400bf23ac0"
|
||||
sha256: "80f963d47cb7ab0818144c7b0668aea4c038f9cb8626626e89a4ea77375defb7"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.4"
|
||||
version: "3.0.1"
|
||||
android_intent_plus:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: android_intent_plus
|
||||
sha256: "04cbc7c332a6f0bba88fed354de78813e9d24049c1800aaf10f449c7adc22603"
|
||||
sha256: "2c87d8330ba5deef5fe20e77f4d178190b3b24531dce08368030ab4be40a9d4e"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.1.9"
|
||||
version: "4.0.1"
|
||||
android_package_installer:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@ -34,6 +34,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.7"
|
||||
archive:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: archive
|
||||
sha256: "0c8368c9b3f0abbc193b9d6133649a614204b528982bebc7026372d61677ce3a"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.3.7"
|
||||
args:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -46,10 +54,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: async
|
||||
sha256: bfe67ef28df125b7dddcea62755991f807aa39a2492a23e1550161692950bbe0
|
||||
sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.10.0"
|
||||
version: "2.11.0"
|
||||
boolean_selector:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -62,10 +70,26 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: characters
|
||||
sha256: e6a326c8af69605aec75ed6c187d06b349707a27fbff8222ca9cc2cff167975c
|
||||
sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.2.1"
|
||||
version: "1.3.0"
|
||||
checked_yaml:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: checked_yaml
|
||||
sha256: feb6bed21949061731a7a75fc5d2aa727cf160b91af9a3e464c5e3a32e28b5ff
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.3"
|
||||
cli_util:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: cli_util
|
||||
sha256: b8db3080e59b2503ca9e7922c3df2072cf13992354d5e944074ffa836fba43b7
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.4.0"
|
||||
clock:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -78,10 +102,18 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: collection
|
||||
sha256: cfc915e6923fe5ce6e153b0723c753045de46de1b4d63771530504004a45fae0
|
||||
sha256: "4a07be6cb69c84d677a6c3096fcf960cc3285a8330b4603e0d463d15d9bd934c"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.17.0"
|
||||
version: "1.17.1"
|
||||
convert:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: convert
|
||||
sha256: "0f08b14755d163f6e2134cb58222dd25ea2a2ee8a195e53983d57c075324d592"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.1.1"
|
||||
cross_file:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -126,10 +158,10 @@ packages:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: device_info_plus
|
||||
sha256: f52ab3b76b36ede4d135aab80194df8925b553686f0fa12226b4e2d658e45903
|
||||
sha256: "499c61743e13909c13374a8c209075385858c614b9c0f2487b5f9995eeaf7369"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "8.2.2"
|
||||
version: "9.0.1"
|
||||
device_info_plus_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -142,18 +174,18 @@ packages:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: dynamic_color
|
||||
sha256: bbebb1b7ebed819e0ec83d4abdc2a8482d934f6a85289ffc1c6acf7589fa2aad
|
||||
sha256: "74dff1435a695887ca64899b8990004f8d1232b0e84bfc4faa1fdda7c6f57cc1"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.6.3"
|
||||
version: "1.6.5"
|
||||
easy_localization:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: easy_localization
|
||||
sha256: "6a2e99fa0bfe5765bf4c6ca9b137d5de2c75593007178c5e4cd2ae985f870080"
|
||||
sha256: "30ebf25448ffe169e0bd9bc4b5da94faa8398967a2ad2ca09f438be8b6953645"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.1"
|
||||
version: "3.0.2"
|
||||
easy_logger:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -174,10 +206,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: ffi
|
||||
sha256: a38574032c5f1dd06c4aee541789906c12ccaab8ba01446e800d9c5b79c4a978
|
||||
sha256: ed5337a5660c506388a9f012be0288fb38b49020ce2b45fe1f8b8323fe429f99
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.1"
|
||||
version: "2.0.2"
|
||||
file:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -190,10 +222,10 @@ packages:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: file_picker
|
||||
sha256: b85eb92b175767fdaa0c543bf3b0d1f610fe966412ea72845fe5ba7801e763ff
|
||||
sha256: c7a8e25ca60e7f331b153b0cb3d405828f18d3e72a6fa1d9440c86556fffc877
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "5.2.10"
|
||||
version: "5.3.0"
|
||||
flutter:
|
||||
dependency: "direct main"
|
||||
description: flutter
|
||||
@ -207,6 +239,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.2.2"
|
||||
flutter_launcher_icons:
|
||||
dependency: "direct dev"
|
||||
description:
|
||||
name: flutter_launcher_icons
|
||||
sha256: "526faf84284b86a4cb36d20a5e45147747b7563d921373d4ee0559c54fcdbcea"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.13.1"
|
||||
flutter_lints:
|
||||
dependency: "direct dev"
|
||||
description:
|
||||
@ -219,26 +259,26 @@ packages:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: flutter_local_notifications
|
||||
sha256: "2876372952b65ca7f684e698eba22bda1cf581fa071dd30ba2f01900f507d0d1"
|
||||
sha256: "12f8abacca8bf29c042ec50c554f967da4c6f88ec99fc215e0325e5b43a25188"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "14.0.0+1"
|
||||
version: "14.1.0"
|
||||
flutter_local_notifications_linux:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: flutter_local_notifications_linux
|
||||
sha256: "909bb95de05a2e793503a2437146285a2f600cd0b3f826e26b870a334d8586d7"
|
||||
sha256: "33f741ef47b5f63cc7f78fe75eeeac7e19f171ff3c3df054d84c1e38bedb6a03"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.0.0"
|
||||
version: "4.0.0+1"
|
||||
flutter_local_notifications_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: flutter_local_notifications_platform_interface
|
||||
sha256: "63235c42de5b6c99846969a27ad0209c401e6b77b0498939813725b5791c107c"
|
||||
sha256: "7cf643d6d5022f3baed0be777b0662cce5919c0a7b86e700299f22dc4ae660ef"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "7.0.0"
|
||||
version: "7.0.0+1"
|
||||
flutter_localizations:
|
||||
dependency: transitive
|
||||
description: flutter
|
||||
@ -256,10 +296,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: flutter_plugin_android_lifecycle
|
||||
sha256: "96af49aa6b57c10a312106ad6f71deed5a754029c24789bbf620ba784f0bd0b0"
|
||||
sha256: "950e77c2bbe1692bc0874fc7fb491b96a4dc340457f4ea1641443d0a6c1ea360"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.14"
|
||||
version: "2.0.15"
|
||||
flutter_test:
|
||||
dependency: "direct dev"
|
||||
description: flutter
|
||||
@ -290,10 +330,10 @@ packages:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: http
|
||||
sha256: "5895291c13fa8a3bd82e76d5627f69e0d85ca6a30dcac95c4ea19a5d555879c2"
|
||||
sha256: "4c3f04bfb64d3efd508d06b41b825542f08122d30bda4933fb95c069d22a4fa3"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.13.6"
|
||||
version: "1.0.0"
|
||||
http_parser:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -302,6 +342,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.0.2"
|
||||
image:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: image
|
||||
sha256: a72242c9a0ffb65d03de1b7113bc4e189686fc07c7147b8b41811d0dd0e0d9bf
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.0.17"
|
||||
installed_apps:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@ -314,26 +362,34 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: intl
|
||||
sha256: "910f85bce16fb5c6f614e117efa303e85a1731bb0081edf3604a2ae6e9a3cc91"
|
||||
sha256: a3715e3bc90294e971cb7dc063fbf3cd9ee0ebf8604ffeafabd9e6f16abbdbe6
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.17.0"
|
||||
version: "0.18.0"
|
||||
js:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: js
|
||||
sha256: "5528c2f391ededb7775ec1daa69e65a2d61276f7552de2b5f7b8d34ee9fd4ab7"
|
||||
sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.6.5"
|
||||
version: "0.6.7"
|
||||
json_annotation:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: json_annotation
|
||||
sha256: b10a7b2ff83d83c777edba3c6a0f97045ddadd56c944e1a23a3fdf43a1bf4467
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.8.1"
|
||||
lints:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: lints
|
||||
sha256: "5e4a9cd06d447758280a8ac2405101e0e2094d2a1dbdd3756aec3fe7775ba593"
|
||||
sha256: "6b0206b0bf4f04961fc5438198ccb3a885685cd67d4d4a32cc20ad7f8adbe015"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.1"
|
||||
version: "2.1.0"
|
||||
markdown:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -346,10 +402,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: matcher
|
||||
sha256: "16db949ceee371e9b99d22f88fa3a73c4e59fd0afed0bd25fc336eb76c198b72"
|
||||
sha256: "6501fbd55da300384b768785b83e5ce66991266cec21af89ab9ae7f5ce1c4cbb"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.12.13"
|
||||
version: "0.12.15"
|
||||
material_color_utilities:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -362,10 +418,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: meta
|
||||
sha256: "6c268b42ed578a53088d834796959e4a1814b5e9e164f147f580a386e5decf42"
|
||||
sha256: "3c74dbf8763d36539f114c799d8a2d87343b5067e9d796ca22b5eb8437090ee3"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.8.0"
|
||||
version: "1.9.1"
|
||||
mime:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -402,18 +458,18 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: path
|
||||
sha256: db9d4f58c908a4ba5953fcee2ae317c94889433e5024c27ce74a37f94267945b
|
||||
sha256: "8829d8a55c13fc0e37127c29fedf290c102f4e40ae94ada574091fe0ff96c917"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.8.2"
|
||||
version: "1.8.3"
|
||||
path_provider:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: path_provider
|
||||
sha256: c7edf82217d4b2952b2129a61d3ad60f1075b9299e629e149a8d2e39c2e6aad4
|
||||
sha256: "3087813781ab814e4157b172f1a11c46be20179fcc9bea043e0fba36bc0acaa2"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.14"
|
||||
version: "2.0.15"
|
||||
path_provider_android:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -426,10 +482,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: path_provider_foundation
|
||||
sha256: ad4c4d011830462633f03eb34445a45345673dfd4faf1ab0b4735fbd93b19183
|
||||
sha256: "1995d88ec2948dac43edf8fe58eb434d35d22a2940ecee1a9fefcd62beee6eb3"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.2.2"
|
||||
version: "2.2.3"
|
||||
path_provider_linux:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -466,10 +522,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: permission_handler_android
|
||||
sha256: "8028362b40c4a45298f1cbfccd227c8dd6caf0e27088a69f2ba2ab15464159e2"
|
||||
sha256: d8cc6a62ded6d0f49c6eac337e080b066ee3bce4d405bd9439a61e1f1927bfe8
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "10.2.0"
|
||||
version: "10.2.1"
|
||||
permission_handler_apple:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -498,10 +554,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: petitparser
|
||||
sha256: "49392a45ced973e8d94a85fdb21293fbb40ba805fc49f2965101ae748a3683b4"
|
||||
sha256: cb3798bef7fc021ac45b308f4b51208a152792445cce0448c9a4ba5879dd8750
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "5.1.0"
|
||||
version: "5.4.0"
|
||||
platform:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -518,6 +574,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.4"
|
||||
pointycastle:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: pointycastle
|
||||
sha256: "7c1e5f0d23c9016c5bbd8b1473d0d3fb3fc851b876046039509e18e0c7485f2c"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.7.3"
|
||||
process:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -538,10 +602,10 @@ packages:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: share_plus
|
||||
sha256: b1f15232d41e9701ab2f04181f21610c36c83a12ae426b79b4bd011c567934b1
|
||||
sha256: "44fc0bc2d35a8fafa1b564e1c6888bdc4fbb2d0197e4a4c21bac0e66123be9cd"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.3.4"
|
||||
version: "7.0.1"
|
||||
share_plus_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -554,10 +618,10 @@ packages:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: shared_preferences
|
||||
sha256: "858aaa72d8f61637d64e776aca82e1c67e6d9ee07979123c5d17115031c1b13b"
|
||||
sha256: "16d3fb6b3692ad244a695c0183fca18cf81fd4b821664394a781de42386bf022"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.0"
|
||||
version: "2.1.1"
|
||||
shared_preferences_android:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -570,10 +634,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shared_preferences_foundation
|
||||
sha256: "0c1c16c56c9708aa9c361541a6f0e5cc6fc12a3232d866a687a7b7db30032b07"
|
||||
sha256: e014107bb79d6d3297196f4f2d0db54b5d1f85b8ea8ff63b8e8b391a02700feb
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.2.1"
|
||||
version: "2.2.2"
|
||||
shared_preferences_linux:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -623,10 +687,10 @@ packages:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: sqflite
|
||||
sha256: acf091c6e55c50d00b30b8532b2dd23e393cf775861665ebd0f15cdd6ebfb079
|
||||
sha256: b4d6710e1200e96845747e37338ea8a819a12b51689a3bcf31eff0003b37a0b9
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.2.8+1"
|
||||
version: "2.2.8+4"
|
||||
sqflite_common:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -679,10 +743,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: test_api
|
||||
sha256: ad540f65f92caa91bf21dfc8ffb8c589d6e4dc0c2267818b4cc2792857706206
|
||||
sha256: eb6ac1540b26de412b3403a163d919ba86f6a973fe6cc50ae3541b80092fdcfb
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.4.16"
|
||||
version: "0.5.1"
|
||||
timezone:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -695,26 +759,26 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: typed_data
|
||||
sha256: "26f87ade979c47a150c9eaab93ccd2bebe70a27dc0b4b29517f2904f04eb11a5"
|
||||
sha256: facc8d6582f16042dd49f2463ff1bd6e2c9ef9f3d5da3d9b087e244a7b564b3c
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.3.1"
|
||||
version: "1.3.2"
|
||||
url_launcher:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: url_launcher
|
||||
sha256: "75f2846facd11168d007529d6cd8fcb2b750186bea046af9711f10b907e1587e"
|
||||
sha256: eb1e00ab44303d50dd487aab67ebc575456c146c6af44422f9c13889984c00f3
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.1.10"
|
||||
version: "6.1.11"
|
||||
url_launcher_android:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: url_launcher_android
|
||||
sha256: "22f8db4a72be26e9e3a4aa3f194b1f7afbc76d20ec141f84be1d787db2155cbd"
|
||||
sha256: "1a5848f598acc5b7d8f7c18b8cb834ab667e59a13edc3c93e9d09cf38cc6bc87"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.0.31"
|
||||
version: "6.0.34"
|
||||
url_launcher_ios:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -807,18 +871,26 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: webview_flutter_wkwebview
|
||||
sha256: "61f33512810bf1ee9ac89761a4b02663ff64e8227b7dc80654642acd660fd49d"
|
||||
sha256: "4646bb68297803bdbb96d46853e8fcb560d6cb5e04153fa64581535767875dfe"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.4.2"
|
||||
version: "3.4.3"
|
||||
win32:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: win32
|
||||
sha256: a6f0236dbda0f63aa9a25ad1ff9a9d8a4eaaa5012da0dc59d21afdb1dc361ca4
|
||||
sha256: "5a751eddf9db89b3e5f9d50c20ab8612296e4e8db69009788d6c8b060a84191c"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.1.4"
|
||||
version: "4.1.4"
|
||||
win32_registry:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: win32_registry
|
||||
sha256: "1c52f994bdccb77103a6231ad4ea331a244dbcef5d1f37d8462f713143b0bfae"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.0"
|
||||
xdg_directories:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -831,10 +903,18 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: xml
|
||||
sha256: "979ee37d622dec6365e2efa4d906c37470995871fe9ae080d967e192d88286b5"
|
||||
sha256: "5bc72e1e45e941d825fd7468b9b4cc3b9327942649aeb6fc5cdbf135f0a86e84"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.2.2"
|
||||
version: "6.3.0"
|
||||
yaml:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: yaml
|
||||
sha256: "75769501ea3489fca56601ff33454fe45507ea3bfb014161abc3b43ae25989d5"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.1.2"
|
||||
sdks:
|
||||
dart: ">=2.19.0 <3.0.0"
|
||||
dart: ">=3.0.0 <4.0.0"
|
||||
flutter: ">=3.4.0-17.0.pre"
|
||||
|
18
pubspec.yaml
18
pubspec.yaml
@ -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.13.0+164 # When changing this, update the tag in main() accordingly
|
||||
version: 0.13.5+169 # When changing this, update the tag in main() accordingly
|
||||
|
||||
environment:
|
||||
sdk: '>=2.18.2 <3.0.0'
|
||||
@ -40,7 +40,7 @@ dependencies:
|
||||
flutter_fgbg: ^0.2.0 # Try removing reliance on this
|
||||
flutter_local_notifications: ^14.0.0+1
|
||||
provider: ^6.0.3
|
||||
http: ^0.13.5
|
||||
http: ^1.0.0
|
||||
webview_flutter: ^4.0.0
|
||||
dynamic_color: ^1.5.4
|
||||
html: ^0.15.0
|
||||
@ -48,26 +48,28 @@ dependencies:
|
||||
url_launcher: ^6.1.5
|
||||
permission_handler: ^10.0.0
|
||||
fluttertoast: ^8.0.9
|
||||
device_info_plus: ^8.0.0
|
||||
device_info_plus: ^9.0.0
|
||||
file_picker: ^5.2.10
|
||||
animations: ^2.0.4
|
||||
android_package_installer:
|
||||
git:
|
||||
url: https://github.com/ImranR98/android_package_installer
|
||||
ref: main
|
||||
share_plus: ^6.0.1
|
||||
share_plus: ^7.0.0
|
||||
installed_apps: ^1.3.1
|
||||
package_archive_info: ^0.1.0
|
||||
android_alarm_manager_plus: ^2.1.0
|
||||
android_alarm_manager_plus: ^3.0.0
|
||||
sqflite: ^2.2.0+3
|
||||
easy_localization: ^3.0.1
|
||||
android_intent_plus: ^3.1.5
|
||||
android_intent_plus: ^4.0.0
|
||||
flutter_markdown: ^0.6.14
|
||||
archive: ^3.3.7
|
||||
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
sdk: flutter
|
||||
flutter_launcher_icons: ^0.13.1
|
||||
|
||||
# The "flutter_lints" package below contains a set of recommended lints to
|
||||
# encourage good coding practices. The lint set provided by the package is
|
||||
@ -76,6 +78,10 @@ dev_dependencies:
|
||||
# rules and activating additional ones.
|
||||
flutter_lints: ^2.0.1
|
||||
|
||||
flutter_launcher_icons:
|
||||
android: "ic_launcher"
|
||||
image_path: "assets/graphics/icon.png"
|
||||
|
||||
# For information on the generic Dart part of this file, see the
|
||||
# following page: https://dart.dev/tools/pub/pubspec
|
||||
|
||||
|
Reference in New Issue
Block a user