Compare commits

...

17 Commits

Author SHA1 Message Date
0cd4385de7 Merge pull request #544 from LilligantMatsuri/main
Update zh.json
2023-05-12 18:02:58 -04:00
0774b3ddc3 Merge pull request #558 from iDazai/patch-1
Update de.json
2023-05-12 18:02:52 -04:00
b60b1ed058 Merge pull request #560 from ImranR98/dev
XAPK Bugfixes #541, HTML User-Agent #545, Better APK Cleanup #551, Search UI Improvements #550
2023-05-12 18:02:21 -04:00
b196715d60 Search UI improvements 2023-05-12 18:00:21 -04:00
0673e90dff Better APK cleanup 2023-05-12 17:53:07 -04:00
59cfa242fb Update de.json
translate newly added English text
improved some German text
2023-05-10 18:20:40 +02:00
65ab72ba90 Increment version 2023-05-09 00:40:39 -04:00
408bca8951 XAPK bugfixes, HTML default User-Agent 2023-05-09 00:37:06 -04:00
480467492a Update zh.json
- Translate new strings
- Slight improvements

Signed-off-by: Matsuri <matsuri@vmoe.info>
2023-05-07 22:00:02 +08:00
219b04aedb Merge pull request #538 from bluefly000/japanese-translation
Update ja.json
2023-05-06 14:43:26 -04:00
a0709856ef Merge branch 'main' into japanese-translation 2023-05-06 14:43:14 -04:00
577642850f Merge pull request #542 from ImranR98/dev
Add (Incomplete) XAPK Support (#541), Auto-Check Updates on Start (#539), UI Tweaks (#540)
2023-05-06 14:42:46 -04:00
e1db024034 Increment version 2023-05-06 14:40:14 -04:00
cc268aeeda "Check updates on start" toggle 2023-05-06 14:25:17 -04:00
d5f7eced8b UI tweaks 2023-05-06 13:28:41 -04:00
cc3c4cc79f Add XAPK support (incomplete - OBB not copied) 2023-05-06 13:20:58 -04:00
89b61884f1 Update ja.json 2023-05-06 15:52:23 +09:00
21 changed files with 334 additions and 117 deletions

View File

@ -121,12 +121,12 @@
"followSystem": "System folgen", "followSystem": "System folgen",
"obtainium": "Obtainium", "obtainium": "Obtainium",
"materialYou": "Material You", "materialYou": "Material You",
"useBlackTheme": "Use pure black dark theme", "useBlackTheme": "Verwende Pure Black Dark Theme",
"appSortBy": "App sortieren nach", "appSortBy": "App sortieren nach",
"authorName": "Autor/Name", "authorName": "Autor/Name",
"nameAuthor": "Name/Autor", "nameAuthor": "Name/Autor",
"asAdded": "Wie hinzugefügt", "asAdded": "Wie hinzugefügt",
"appSortOrder": "App Sortierung nach", "appSortOrder": "App sortieren nach",
"ascending": "Aufsteigend", "ascending": "Aufsteigend",
"descending": "Absteigend", "descending": "Absteigend",
"bgUpdateCheckInterval": "Prüfintervall für Hintergrundaktualisierung", "bgUpdateCheckInterval": "Prüfintervall für Hintergrundaktualisierung",
@ -207,7 +207,7 @@
"addCategory": "Kategorie hinzufügen", "addCategory": "Kategorie hinzufügen",
"label": "Bezeichnung", "label": "Bezeichnung",
"language": "Sprache", "language": "Sprache",
"copiedToClipboard": "Copied to Clipboard", "copiedToClipboard": "In die Zwischenablage kopiert",
"storagePermissionDenied": "Speicherberechtigung verweigert", "storagePermissionDenied": "Speicherberechtigung verweigert",
"selectedCategorizeWarning": "Dadurch werden alle bestehenden Kategorieeinstellungen für die ausgewählten Apps ersetzt.", "selectedCategorizeWarning": "Dadurch werden alle bestehenden Kategorieeinstellungen für die ausgewählten Apps ersetzt.",
"filterAPKsByRegEx": "APKs nach regulärem Ausdruck filtern", "filterAPKsByRegEx": "APKs nach regulärem Ausdruck filtern",
@ -218,7 +218,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.", "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", "changes": "Änderungen",
"releaseDate": "Veröffentlichungsdatum", "releaseDate": "Veröffentlichungsdatum",
"importFromURLsInFile": "Importieren von URLs aus Datei ( z.B. OPML)", "importFromURLsInFile": "Importieren von URLs aus Datei (z. B. OPML)",
"versionDetection": "Versionserkennung", "versionDetection": "Versionserkennung",
"standardVersionDetection": "Standardversionserkennung", "standardVersionDetection": "Standardversionserkennung",
"groupByCategory": "Nach Kategorie gruppieren", "groupByCategory": "Nach Kategorie gruppieren",
@ -227,10 +227,11 @@
"dontShowAgain": "Nicht noch einmal zeigen", "dontShowAgain": "Nicht noch einmal zeigen",
"dontShowTrackOnlyWarnings": "Warnung für 'Nur Nachverfolgen' nicht anzeigen", "dontShowTrackOnlyWarnings": "Warnung für 'Nur Nachverfolgen' nicht anzeigen",
"dontShowAPKOriginWarnings": "Warnung für APK-Herkunft nicht anzeigen", "dontShowAPKOriginWarnings": "Warnung für APK-Herkunft nicht anzeigen",
"moveNonInstalledAppsToBottom": "Move Non-Installed Apps to Bottom of Apps View", "moveNonInstalledAppsToBottom": "Nicht installierte Apps ans Ende der Apps Ansicht verschieben",
"gitlabPATLabel": "GitLab Personal Access Token (Enables Search)", "gitlabPATLabel": "GitLab Personal Access Token (Aktiviert Suche)",
"about": "About", "about": "Über",
"requiresCredentialsInSettings": "This needs additional credentials (in Settings)", "requiresCredentialsInSettings": "Benötigt zusätzliche Anmeldedaten (in den Einstellungen)",
"checkOnStart": "Überprüfe einmalig beim Start",
"removeAppQuestion": { "removeAppQuestion": {
"one": "App entfernen?", "one": "App entfernen?",
"other": "Apps entfernen?" "other": "Apps entfernen?"
@ -257,7 +258,7 @@
}, },
"minute": { "minute": {
"one": "{} Minute", "one": "{} Minute",
"other": "{} Minutes" "other": "{} Minuten"
}, },
"hour": { "hour": {
"one": "{} Stunde", "one": "{} Stunde",

View File

@ -121,7 +121,7 @@
"followSystem": "Follow System", "followSystem": "Follow System",
"obtainium": "Obtainium", "obtainium": "Obtainium",
"materialYou": "Material You", "materialYou": "Material You",
"useBlackTheme": "Use pure black dark theme", "useBlackTheme": "Use Pure Black Dark Theme",
"appSortBy": "App Sort By", "appSortBy": "App Sort By",
"authorName": "Author/Name", "authorName": "Author/Name",
"nameAuthor": "Name/Author", "nameAuthor": "Name/Author",
@ -231,6 +231,7 @@
"gitlabPATLabel": "GitLab Personal Access Token (Enables Search)", "gitlabPATLabel": "GitLab Personal Access Token (Enables Search)",
"about": "About", "about": "About",
"requiresCredentialsInSettings": "This needs additional credentials (in Settings)", "requiresCredentialsInSettings": "This needs additional credentials (in Settings)",
"checkOnStart": "Check Once on Start",
"removeAppQuestion": { "removeAppQuestion": {
"one": "Remove App?", "one": "Remove App?",
"other": "Remove Apps?" "other": "Remove Apps?"

View File

@ -231,6 +231,7 @@
"gitlabPATLabel": "GitLab Personal Access Token (Enables Search)", "gitlabPATLabel": "GitLab Personal Access Token (Enables Search)",
"about": "About", "about": "About",
"requiresCredentialsInSettings": "This needs additional credentials (in Settings)", "requiresCredentialsInSettings": "This needs additional credentials (in Settings)",
"checkOnStart": "Check Once on Start",
"removeAppQuestion": { "removeAppQuestion": {
"one": "¿Eliminar Aplicación?", "one": "¿Eliminar Aplicación?",
"other": "¿Eliminar Aplicaciones?" "other": "¿Eliminar Aplicaciones?"

View File

@ -231,6 +231,7 @@
"gitlabPATLabel": "GitLab Personal Access Token (Enables Search)", "gitlabPATLabel": "GitLab Personal Access Token (Enables Search)",
"about": "About", "about": "About",
"requiresCredentialsInSettings": "This needs additional credentials (in Settings)", "requiresCredentialsInSettings": "This needs additional credentials (in Settings)",
"checkOnStart": "Check Once on Start",
"removeAppQuestion": { "removeAppQuestion": {
"one": "برنامه حذف شود؟", "one": "برنامه حذف شود؟",
"other": "برنامه ها حذف شوند؟" "other": "برنامه ها حذف شوند؟"

View File

@ -121,7 +121,7 @@
"followSystem": "Suivre le système", "followSystem": "Suivre le système",
"obtainium": "Obtainium", "obtainium": "Obtainium",
"materialYou": "Material You", "materialYou": "Material You",
"useBlackTheme": "Use pure black dark theme", "useBlackTheme": "Use Pure Black Dark Theme",
"appSortBy": "Applications triées par", "appSortBy": "Applications triées par",
"authorName": "Auteur/Nom", "authorName": "Auteur/Nom",
"nameAuthor": "Nom/Auteur", "nameAuthor": "Nom/Auteur",
@ -231,6 +231,7 @@
"gitlabPATLabel": "GitLab Personal Access Token (Enables Search)", "gitlabPATLabel": "GitLab Personal Access Token (Enables Search)",
"about": "About", "about": "About",
"requiresCredentialsInSettings": "This needs additional credentials (in Settings)", "requiresCredentialsInSettings": "This needs additional credentials (in Settings)",
"checkOnStart": "Check Once on Start",
"removeAppQuestion": { "removeAppQuestion": {
"one": "Supprimer l'application ?", "one": "Supprimer l'application ?",
"other": "Supprimer les applications ?" "other": "Supprimer les applications ?"

View File

@ -230,6 +230,7 @@
"gitlabPATLabel": "GitLab Personal Access Token (Enables Search)", "gitlabPATLabel": "GitLab Personal Access Token (Enables Search)",
"about": "About", "about": "About",
"requiresCredentialsInSettings": "This needs additional credentials (in Settings)", "requiresCredentialsInSettings": "This needs additional credentials (in Settings)",
"checkOnStart": "Check Once on Start",
"removeAppQuestion": { "removeAppQuestion": {
"one": "Eltávolítja az alkalmazást?", "one": "Eltávolítja az alkalmazást?",
"other": "Eltávolítja az alkalmazást?" "other": "Eltávolítja az alkalmazást?"

View File

@ -121,7 +121,7 @@
"followSystem": "Segui sistema", "followSystem": "Segui sistema",
"obtainium": "Obtainium", "obtainium": "Obtainium",
"materialYou": "Material You", "materialYou": "Material You",
"useBlackTheme": "Use pure black dark theme", "useBlackTheme": "Use Pure Black Dark Theme",
"appSortBy": "App ordinate per", "appSortBy": "App ordinate per",
"authorName": "Autore/Nome", "authorName": "Autore/Nome",
"nameAuthor": "Nome/Autore", "nameAuthor": "Nome/Autore",
@ -231,6 +231,7 @@
"gitlabPATLabel": "GitLab Personal Access Token (Enables Search)", "gitlabPATLabel": "GitLab Personal Access Token (Enables Search)",
"about": "About", "about": "About",
"requiresCredentialsInSettings": "This needs additional credentials (in Settings)", "requiresCredentialsInSettings": "This needs additional credentials (in Settings)",
"checkOnStart": "Check Once on Start",
"removeAppQuestion": { "removeAppQuestion": {
"one": "Rimuovere l'App?", "one": "Rimuovere l'App?",
"other": "Rimuovere le App?" "other": "Rimuovere le App?"

View File

@ -227,10 +227,11 @@
"dontShowAgain": "二度と表示しない", "dontShowAgain": "二度と表示しない",
"dontShowTrackOnlyWarnings": "「追跡のみ」の警告を表示しない", "dontShowTrackOnlyWarnings": "「追跡のみ」の警告を表示しない",
"dontShowAPKOriginWarnings": "APK Originの警告を表示しない", "dontShowAPKOriginWarnings": "APK Originの警告を表示しない",
"moveNonInstalledAppsToBottom": "Move Non-Installed Apps to Bottom of Apps View", "moveNonInstalledAppsToBottom": "未インストールのアプリをアプリ一覧の下部に移動させる",
"gitlabPATLabel": "GitLab Personal Access Token (Enables Search)", "gitlabPATLabel": "GitLab パーソナルアクセストークン (検索を有効化する)",
"about": "About", "about": "概要",
"requiresCredentialsInSettings": "This needs additional credentials (in Settings)", "requiresCredentialsInSettings": "これには追加の認証が必要です (設定にて)",
"checkOnStart": "Check Once on Start",
"removeAppQuestion": { "removeAppQuestion": {
"one": "アプリを削除しますか?", "one": "アプリを削除しますか?",
"other": "アプリを削除しますか?" "other": "アプリを削除しますか?"

View File

@ -33,7 +33,7 @@
"githubStarredRepos": "GitHub 已星标仓库", "githubStarredRepos": "GitHub 已星标仓库",
"uname": "用户名", "uname": "用户名",
"wrongArgNum": "参数数量错误", "wrongArgNum": "参数数量错误",
"xIsTrackOnly": "{} 为“仅追踪”模式", "xIsTrackOnly": "{}为“仅追踪”模式",
"source": "源代码", "source": "源代码",
"app": "应用", "app": "应用",
"appsFromSourceAreTrackOnly": "此来源的应用为“仅追踪”模式。", "appsFromSourceAreTrackOnly": "此来源的应用为“仅追踪”模式。",
@ -50,8 +50,8 @@
"search": "搜索", "search": "搜索",
"additionalOptsFor": "{} 的更多选项", "additionalOptsFor": "{} 的更多选项",
"supportedSourcesBelow": "支持的来源:", "supportedSourcesBelow": "支持的来源:",
"trackOnlyInBrackets": "仅追踪", "trackOnlyInBrackets": "(仅追踪)",
"searchableInBrackets": "可搜索", "searchableInBrackets": "(可搜索)",
"appsString": "应用列表", "appsString": "应用列表",
"noApps": "无应用", "noApps": "无应用",
"noAppsForFilter": "没有符合条件的应用", "noAppsForFilter": "没有符合条件的应用",
@ -59,9 +59,9 @@
"percentProgress": "进度:{}%", "percentProgress": "进度:{}%",
"pleaseWait": "请稍候", "pleaseWait": "请稍候",
"updateAvailable": "更新可用", "updateAvailable": "更新可用",
"estimateInBracketsShort": "(预计)", "estimateInBracketsShort": "(推测)",
"notInstalled": "未安装", "notInstalled": "未安装",
"estimateInBrackets": "(预计)", "estimateInBrackets": "(推测)",
"selectAll": "全选", "selectAll": "全选",
"deselectN": "取消选择 {}", "deselectN": "取消选择 {}",
"xWillBeRemovedButRemainInstalled": "{} 将从 Obtainium 中删除,但仍安装在您的设备中。", "xWillBeRemovedButRemainInstalled": "{} 将从 Obtainium 中删除,但仍安装在您的设备中。",
@ -74,8 +74,8 @@
"installUpdateApps": "安装/更新应用", "installUpdateApps": "安装/更新应用",
"installUpdateSelectedApps": "安装/更新选中的应用", "installUpdateSelectedApps": "安装/更新选中的应用",
"markXSelectedAppsAsUpdated": "是否将选中的 {} 个应用标记为已更新?", "markXSelectedAppsAsUpdated": "是否将选中的 {} 个应用标记为已更新?",
"no": "不要", "no": "",
"yes": "好的", "yes": "",
"markSelectedAppsUpdated": "将选中的应用标记为已更新", "markSelectedAppsUpdated": "将选中的应用标记为已更新",
"pinToTop": "置顶", "pinToTop": "置顶",
"unpinFromTop": "取消置顶", "unpinFromTop": "取消置顶",
@ -142,7 +142,7 @@
"close": "关闭", "close": "关闭",
"share": "分享", "share": "分享",
"appNotFound": "未找到应用", "appNotFound": "未找到应用",
"obtainiumExportHyphenatedLowercase": "obtainium-导出", "obtainiumExportHyphenatedLowercase": "obtainium-export",
"pickAnAPK": "选择一个 APK 文件", "pickAnAPK": "选择一个 APK 文件",
"appHasMoreThanOnePackage": "{} 有多个架构可用:", "appHasMoreThanOnePackage": "{} 有多个架构可用:",
"deviceSupportsXArch": "您的设备支持 {} 架构。", "deviceSupportsXArch": "您的设备支持 {} 架构。",
@ -172,13 +172,13 @@
"versionCorrectionDisabled": "禁用版本号更正(插件似乎未起作用)", "versionCorrectionDisabled": "禁用版本号更正(插件似乎未起作用)",
"unknown": "未知", "unknown": "未知",
"none": "无", "none": "无",
"never": "从", "never": "从",
"latestVersionX": "最新版本:{}", "latestVersionX": "最新版本:{}",
"installedVersionX": "当前版本:{}", "installedVersionX": "当前版本:{}",
"lastUpdateCheckX": "上次更新检查:{}", "lastUpdateCheckX": "上次更新检查:{}",
"remove": "删除", "remove": "删除",
"yesMarkUpdated": "是,标记为已更新", "yesMarkUpdated": "是,标记为已更新",
"fdroid": "F-Droid Official", "fdroid": "F-Droid 官方存储库",
"appIdOrName": "应用 ID 或名称", "appIdOrName": "应用 ID 或名称",
"appWithIdOrNameNotFound": "未找到符合此 ID 或名称的应用", "appWithIdOrNameNotFound": "未找到符合此 ID 或名称的应用",
"reposHaveMultipleApps": "存储库中可能包含多个应用", "reposHaveMultipleApps": "存储库中可能包含多个应用",
@ -193,7 +193,7 @@
"additionalOptions": "附加选项", "additionalOptions": "附加选项",
"disableVersionDetection": "禁用版本检测", "disableVersionDetection": "禁用版本检测",
"noVersionDetectionExplanation": "此选项应该仅用于无法进行版本检测的应用。", "noVersionDetectionExplanation": "此选项应该仅用于无法进行版本检测的应用。",
"downloadingX": "正在下载 {}", "downloadingX": "正在下载{}",
"downloadNotifDescription": "提示应用的下载进度", "downloadNotifDescription": "提示应用的下载进度",
"noAPKFound": "未找到 APK 文件", "noAPKFound": "未找到 APK 文件",
"noVersionDetection": "禁用版本检测", "noVersionDetection": "禁用版本检测",
@ -222,15 +222,16 @@
"versionDetection": "版本检测", "versionDetection": "版本检测",
"standardVersionDetection": "常规版本检测", "standardVersionDetection": "常规版本检测",
"groupByCategory": "按类别分组显示", "groupByCategory": "按类别分组显示",
"autoApkFilterByArch": "如果可能,尝试按 CPU 架构筛选 APK 文件", "autoApkFilterByArch": "如果可能,尝试按设备支持的 CPU 架构筛选 APK 文件",
"overrideSource": "Override Source", "overrideSource": "覆盖来源",
"dontShowAgain": "Don't show this again", "dontShowAgain": "不再显示",
"dontShowTrackOnlyWarnings": "Don't Show the 'Track-Only' Warning", "dontShowTrackOnlyWarnings": "不显示“仅追踪”模式警告",
"dontShowAPKOriginWarnings": "Don't Show APK Origin Warnings", "dontShowAPKOriginWarnings": "不显示 APK 文件来源警告",
"moveNonInstalledAppsToBottom": "Move Non-Installed Apps to Bottom of Apps View", "moveNonInstalledAppsToBottom": "将未安装应用置底",
"gitlabPATLabel": "GitLab Personal Access Token (Enables Search)", "gitlabPATLabel": "GitLab 个人访问令牌(用于搜索)",
"about": "About", "about": "相关文档",
"requiresCredentialsInSettings": "This needs additional credentials (in Settings)", "requiresCredentialsInSettings": "此功能需要额外的凭据(在“设置”中添加)",
"checkOnStart": "启动时进行一次检查",
"removeAppQuestion": { "removeAppQuestion": {
"one": "是否删除应用?", "one": "是否删除应用?",
"other": "是否删除应用?" "other": "是否删除应用?"

View File

@ -57,9 +57,9 @@ class APKPure extends AppSource {
} catch (err) { } catch (err) {
// ignore // ignore
} }
String type = html.querySelector('a.info-tag')?.text.trim() ?? 'APK';
List<MapEntry<String, String>> apkUrls = [ 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 String author = html
.querySelector('span.info-sdk') .querySelector('span.info-sdk')

View File

@ -89,6 +89,13 @@ class HTML extends AppSource {
overrideEligible = true; 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 @override
String sourceSpecificStandardizeURL(String url) { String sourceSpecificStandardizeURL(String url) {
return url; return url;

View File

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

View File

@ -159,9 +159,16 @@ class _AddAppPageState extends State<AddAppPage> {
app.preferredApkIndex = app.preferredApkIndex =
app.apkUrls.map((e) => e.value).toList().indexOf(apkUrl.value); app.apkUrls.map((e) => e.value).toList().indexOf(apkUrl.value);
// ignore: use_build_context_synchronously // ignore: use_build_context_synchronously
var downloadedApk = await appsProvider.downloadApp( var downloadedArtifact = await appsProvider.downloadApp(
app, globalNavigatorKey.currentContext); 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)) { if (appsProvider.apps.containsKey(app.id)) {
throw ObtainiumError(tr('appAlreadyAdded')); throw ObtainiumError(tr('appAlreadyAdded'));
@ -276,6 +283,9 @@ class _AddAppPageState extends State<AddAppPage> {
} }
si++; si++;
} }
if (res.isEmpty) {
throw ObtainiumError(tr('noResults'));
}
List<String>? selectedUrls = res.isEmpty List<String>? selectedUrls = res.isEmpty
? [] ? []
// ignore: use_build_context_synchronously // ignore: use_build_context_synchronously
@ -370,7 +380,9 @@ class _AddAppPageState extends State<AddAppPage> {
const SizedBox( const SizedBox(
width: 16, width: 16,
), ),
ElevatedButton( searching
? const CircularProgressIndicator()
: ElevatedButton(
onPressed: searchQuery.isEmpty || doingSomething onPressed: searchQuery.isEmpty || doingSomething
? null ? null
: () { : () {

View File

@ -444,7 +444,9 @@ class _AppPageState extends State<AppPage> {
Padding( Padding(
padding: const EdgeInsets.fromLTRB(0, 8, 0, 0), padding: const EdgeInsets.fromLTRB(0, 8, 0, 0),
child: LinearProgressIndicator( child: LinearProgressIndicator(
value: app!.downloadProgress! / 100)) value: app!.downloadProgress! >= 0
? app.downloadProgress! / 100
: null))
], ],
)); ));

View File

@ -52,6 +52,9 @@ class AppsPageState extends State<AppsPage> {
} }
} }
final GlobalKey<RefreshIndicatorState> _refreshIndicatorKey =
GlobalKey<RefreshIndicatorState>();
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
var appsProvider = context.watch<AppsProvider>(); var appsProvider = context.watch<AppsProvider>();
@ -61,6 +64,27 @@ class AppsPageState extends State<AppsPage> {
var currentFilterIsUpdatesOnly = var currentFilterIsUpdatesOnly =
filter.isIdenticalTo(updatesOnlyFilter, settingsProvider); filter.isIdenticalTo(updatesOnlyFilter, settingsProvider);
refresh() {
HapticFeedback.lightImpact();
setState(() {
refreshingSince = DateTime.now();
});
return appsProvider.checkUpdates().catchError((e) {
showError(e, context);
}).whenComplete(() {
setState(() {
refreshingSince = null;
});
});
}
if (!appsProvider.loadingApps &&
appsProvider.apps.isNotEmpty &&
settingsProvider.checkJustStarted() &&
settingsProvider.checkOnStart) {
_refreshIndicatorKey.currentState?.show();
}
selectedAppIds = selectedAppIds selectedAppIds = selectedAppIds
.where((element) => listedApps.map((e) => e.app.id).contains(element)) .where((element) => listedApps.map((e) => e.app.id).contains(element))
.toSet(); .toSet();
@ -315,7 +339,7 @@ class AppsPageState extends State<AppsPage> {
?.isBefore(refreshingSince!) ?? ?.isBefore(refreshingSince!) ??
true)) true))
.length / .length /
appsProvider.apps.length, (appsProvider.apps.isNotEmpty ? appsProvider.apps.length : 1),
), ),
) )
]; ];
@ -515,10 +539,16 @@ class AppsPageState extends State<AppsPage> {
? FontWeight.bold ? FontWeight.bold
: FontWeight.normal)), : FontWeight.normal)),
trailing: listedApps[index].downloadProgress != null trailing: listedApps[index].downloadProgress != null
? Text(tr('percentProgress', args: [ ? SizedBox(
listedApps[index].downloadProgress?.toInt().toString() ?? width: 110,
'100' child: Text(tr('percentProgress', args: [
])) listedApps[index].downloadProgress! >= 0
? listedApps[index]
.downloadProgress!
.toInt()
.toString()
: tr('pleaseWait')
])))
: trailingRow, : trailingRow,
onTap: () { onTap: () {
if (selectedAppIds.isNotEmpty) { if (selectedAppIds.isNotEmpty) {
@ -1017,19 +1047,8 @@ class AppsPageState extends State<AppsPage> {
return Scaffold( return Scaffold(
backgroundColor: Theme.of(context).colorScheme.surface, backgroundColor: Theme.of(context).colorScheme.surface,
body: RefreshIndicator( body: RefreshIndicator(
onRefresh: () { key: _refreshIndicatorKey,
HapticFeedback.lightImpact(); onRefresh: refresh,
setState(() {
refreshingSince = DateTime.now();
});
return appsProvider.checkUpdates().catchError((e) {
showError(e, context);
}).whenComplete(() {
setState(() {
refreshingSince = null;
});
});
},
child: CustomScrollView(slivers: <Widget>[ child: CustomScrollView(slivers: <Widget>[
CustomAppBar(title: tr('appsString')), CustomAppBar(title: tr('appsString')),
...getLoadingWidgets(), ...getLoadingWidgets(),

View File

@ -228,6 +228,18 @@ class _SettingsPageState extends State<SettingsPage> {
color: Theme.of(context).colorScheme.primary), color: Theme.of(context).colorScheme.primary),
), ),
intervalDropdown, intervalDropdown,
height16,
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Flexible(child: Text(tr('checkOnStart'))),
Switch(
value: settingsProvider.checkOnStart,
onChanged: (value) {
settingsProvider.checkOnStart = value;
})
],
),
height32, height32,
Text( Text(
tr('sourceSpecific'), tr('sourceSpecific'),

View File

@ -27,6 +27,7 @@ import 'package:flutter_fgbg/flutter_fgbg.dart';
import 'package:obtainium/providers/source_provider.dart'; import 'package:obtainium/providers/source_provider.dart';
import 'package:http/http.dart'; import 'package:http/http.dart';
import 'package:android_intent_plus/android_intent.dart'; import 'package:android_intent_plus/android_intent.dart';
import 'package:archive/archive.dart';
class AppInMemory { class AppInMemory {
late App app; late App app;
@ -46,6 +47,13 @@ class DownloadedApk {
DownloadedApk(this.appId, this.file); DownloadedApk(this.appId, this.file);
} }
class DownloadedXApkDir {
String appId;
File file;
Directory extracted;
DownloadedXApkDir(this.appId, this.file, this.extracted);
}
List<String> generateStandardVersionRegExStrings() { List<String> generateStandardVersionRegExStrings() {
// TODO: Look into RegEx for non-Latin characters / non-Arabic numerals // TODO: Look into RegEx for non-Latin characters / non-Arabic numerals
var basics = [ var basics = [
@ -114,25 +122,34 @@ class AppsProvider with ChangeNotifier {
// Load Apps into memory (in background, this is done later instead of in the constructor) // Load Apps into memory (in background, this is done later instead of in the constructor)
await loadApps(); await loadApps();
// Delete any partial APKs // Delete any partial APKs
var cutoff = DateTime.now().subtract(const Duration(days: 7));
(await getExternalCacheDirectories()) (await getExternalCacheDirectories())
?.first ?.first
.listSync() .listSync()
.where((element) => element.path.endsWith('.apk.part')) .where((element) =>
element.path.endsWith('.part') ||
element.statSync().modified.isBefore(cutoff))
.forEach((partialApk) { .forEach((partialApk) {
partialApk.delete(); partialApk.delete();
}); });
}(); }();
} }
downloadFile(String url, String fileName, Function? onProgress, Future<File> downloadFile(
String url, String fileNameNoExt, Function? onProgress,
{bool useExisting = true, Map<String, String>? headers}) async { {bool useExisting = true, Map<String, String>? headers}) async {
var destDir = (await getExternalCacheDirectories())!.first.path; var destDir = (await getExternalCacheDirectories())!.first.path;
var req = Request('GET', Uri.parse(url)); var req = Request('GET', Uri.parse(url));
if (headers != null) { if (headers != null) {
req.headers.addAll(headers); req.headers.addAll(headers);
} }
StreamedResponse response = await Client().send(req); var client = Client();
File downloadedFile = File('$destDir/$fileName'); StreamedResponse response = await client.send(req);
var ext = response.headers['content-disposition']!.split('.').last;
if (ext.endsWith('"') || ext.endsWith("other")) {
ext = ext.substring(0, ext.length - 1);
}
File downloadedFile = File('$destDir/$fileNameNoExt.$ext');
if (!(downloadedFile.existsSync() && useExisting)) { if (!(downloadedFile.existsSync() && useExisting)) {
File tempDownloadedFile = File('${downloadedFile.path}.part'); File tempDownloadedFile = File('${downloadedFile.path}.part');
if (tempDownloadedFile.existsSync()) { if (tempDownloadedFile.existsSync()) {
@ -160,11 +177,34 @@ class AppsProvider with ChangeNotifier {
throw response.reasonPhrase ?? tr('unexpectedError'); throw response.reasonPhrase ?? tr('unexpectedError');
} }
tempDownloadedFile.renameSync(downloadedFile.path); tempDownloadedFile.renameSync(downloadedFile.path);
} else {
client.close();
} }
return downloadedFile; 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 = NotificationsProvider? notificationsProvider =
context?.read<NotificationsProvider>(); context?.read<NotificationsProvider>();
var notifId = DownloadNotification(app.finalName, 0).id; var notifId = DownloadNotification(app.finalName, 0).id;
@ -177,11 +217,11 @@ class AppsProvider with ChangeNotifier {
.getSource(app.url, overrideSource: app.overrideSource); .getSource(app.url, overrideSource: app.overrideSource);
String downloadUrl = await source.apkUrlPrefetchModifier( String downloadUrl = await source.apkUrlPrefetchModifier(
app.apkUrls[app.preferredApkIndex].value, app.url); app.apkUrls[app.preferredApkIndex].value, app.url);
var fileName = '${app.id}-${downloadUrl.hashCode}.apk';
var notif = DownloadNotification(app.finalName, 100); var notif = DownloadNotification(app.finalName, 100);
notificationsProvider?.cancel(notif.id); notificationsProvider?.cancel(notif.id);
int? prevProg; 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) { headers: source.requestHeaders, (double? progress) {
int? prog = progress?.ceil(); int? prog = progress?.ceil();
if (apps[app.id] != null) { if (apps[app.id] != null) {
@ -194,33 +234,45 @@ class AppsProvider with ChangeNotifier {
} }
prevProg = prog; 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 // Set to 90 for remaining steps, will make null in 'finally'
// The former case should be handled (give the App its real ID), the latter is a security issue if (apps[app.id] != null) {
var newInfo = await PackageArchiveInfo.fromPath(downloadedFile.path); apps[app.id]!.downloadProgress = -1;
if (app.id != newInfo.packageName) { notifyListeners();
var isTempId = SourceProvider().isTempId(app); notif = DownloadNotification(app.finalName, -1);
if (apps[app.id] != null && !isTempId) { notificationsProvider?.notify(notif);
throw IDChangedError();
} }
var originalAppId = app.id; PackageArchiveInfo? newInfo;
app.id = newInfo.packageName; var isAPK = downloadedFile.path.toLowerCase().endsWith('.apk');
downloadedFile = downloadedFile.renameSync( Directory? xapkDir;
'${downloadedFile.parent.path}/${app.id}-${downloadUrl.hashCode}.apk'); if (isAPK) {
if (apps[originalAppId] != null) { newInfo = await PackageArchiveInfo.fromPath(downloadedFile.path);
await removeApps([originalAppId]); } else {
await saveApps([app], onlyIfExists: !isTempId); // 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 =
// Delete older versions of the APK if any await handleAPKIDChange(app, newInfo, downloadedFile, downloadUrl);
// Delete older versions of the file if any
for (var file in downloadedFile.parent.listSync()) { for (var file in downloadedFile.parent.listSync()) {
var fn = file.path.split('/').last; var fn = file.path.split('/').last;
if (fn.startsWith('${app.id}-') && if (fn.startsWith('${app.id}-') &&
fn.endsWith('.apk') && FileSystemEntity.isFileSync(file.path) &&
fn != downloadedFile.path.split('/').last) { file.path != downloadedFile.path) {
file.delete(); file.delete();
} }
} }
if (isAPK) {
return DownloadedApk(app.id, downloadedFile); return DownloadedApk(app.id, downloadedFile);
} else {
return DownloadedXApkDir(app.id, downloadedFile, xapkDir!);
}
} finally { } finally {
notificationsProvider?.cancel(notifId); notificationsProvider?.cancel(notifId);
if (apps[app.id] != null) { if (apps[app.id] != null) {
@ -267,11 +319,43 @@ class AppsProvider with ChangeNotifier {
} }
} }
// Unfortunately this 'await' does not actually wait for the APK to finish installing void unzipFile(String filePath, String destinationPath) {
// So we only know that the install prompt was shown, but the user could still cancel w/o us knowing final bytes = File(filePath).readAsBytesSync();
// If appropriate criteria are met, the update (never a fresh install) happens silently in the background final archive = ZipDecoder().decodeBytes(bytes);
// But even then, we don't know if it actually succeeded
Future<void> installApk(DownloadedApk file, {bool silent = false}) async { 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();
}
} finally {
dir.extracted.delete(recursive: true);
}
}
Future<bool> installApk(DownloadedApk file, {bool silent = false}) async {
// TODO: Use 'silent' when/if ever possible // TODO: Use 'silent' when/if ever possible
var newInfo = await PackageArchiveInfo.fromPath(file.file.path); var newInfo = await PackageArchiveInfo.fromPath(file.file.path);
AppInfo? appInfo; AppInfo? appInfo;
@ -287,14 +371,17 @@ class AppsProvider with ChangeNotifier {
} }
int? code = int? code =
await AndroidPackageInstaller.installApk(apkFilePath: file.file.path); await AndroidPackageInstaller.installApk(apkFilePath: file.file.path);
bool installed = false;
if (code != null && code != 0 && code != 3) { if (code != null && code != 0 && code != 3) {
throw InstallError(code); throw InstallError(code);
} else if (code == 0) { } else if (code == 0) {
installed = true;
apps[file.appId]!.app.installedVersion = apps[file.appId]!.app.installedVersion =
apps[file.appId]!.app.latestVersion; apps[file.appId]!.app.latestVersion;
file.file.delete(); file.file.delete();
} }
await saveApps([apps[file.appId]!.app]); await saveApps([apps[file.appId]!.app]);
return installed;
} }
void uninstallApp(String appId) async { void uninstallApp(String appId) async {
@ -420,9 +507,16 @@ class AppsProvider with ChangeNotifier {
for (var id in appsToInstall) { for (var id in appsToInstall) {
try { try {
// ignore: use_build_context_synchronously // ignore: use_build_context_synchronously
var downloadedFile = await downloadApp(apps[id]!.app, context); var downloadedArtifact = await downloadApp(apps[id]!.app, context);
bool willBeSilent = DownloadedApk? downloadedFile;
await canInstallSilently(apps[downloadedFile.appId]!.app); 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 willBeSilent = false; // TODO: Remove this when silent updates work
if (!(await settingsProvider?.getInstallPermission(enforce: false) ?? if (!(await settingsProvider?.getInstallPermission(enforce: false) ??
true)) { true)) {
@ -432,7 +526,18 @@ class AppsProvider with ChangeNotifier {
// ignore: use_build_context_synchronously // ignore: use_build_context_synchronously
await waitForUserToReturnToForeground(context); await waitForUserToReturnToForeground(context);
} }
apps[id]?.downloadProgress = -1;
notifyListeners();
try {
if (downloadedFile != null) {
await installApk(downloadedFile, silent: willBeSilent); await installApk(downloadedFile, silent: willBeSilent);
} else {
await installXApkDir(downloadedDir!, silent: willBeSilent);
}
} finally {
apps[id]?.downloadProgress = null;
notifyListeners();
}
installedIds.add(id); installedIds.add(id);
} catch (e) { } catch (e) {
errors.add(id, e.toString()); errors.add(id, e.toString());
@ -684,11 +789,18 @@ class AppsProvider with ChangeNotifier {
} }
Future<void> removeApps(List<String> appIds) async { Future<void> removeApps(List<String> appIds) async {
var apkFiles = (await getExternalCacheDirectories())?.first.listSync();
for (var appId in appIds) { for (var appId in appIds) {
File file = File('${(await getAppsDir()).path}/$appId.json'); File file = File('${(await getAppsDir()).path}/$appId.json');
if (file.existsSync()) { if (file.existsSync()) {
file.deleteSync(); file.deleteSync();
} }
apkFiles
?.where(
(element) => element.path.split('/').last.startsWith('$appId-'))
.forEach((element) {
element.delete();
});
if (apps.containsKey(appId)) { if (apps.containsKey(appId)) {
apps.remove(appId); apps.remove(appId);
} }
@ -734,7 +846,7 @@ class AppsProvider with ChangeNotifier {
apps[i].installedVersion = null; apps[i].installedVersion = null;
} }
} }
await saveApps(apps, attemptToCorrectInstallStatus: !remove); await saveApps(apps, attemptToCorrectInstallStatus: false);
} }
if (remove) { if (remove) {
await removeApps(apps.map((e) => e.id).toList()); await removeApps(apps.map((e) => e.id).toList());

View File

@ -167,7 +167,8 @@ class NotificationsProvider {
progress: progPercent ?? 0, progress: progPercent ?? 0,
maxProgress: 100, maxProgress: 100,
showProgress: progPercent != null, showProgress: progPercent != null,
onlyAlertOnce: onlyAlertOnce))); onlyAlertOnce: onlyAlertOnce,
indeterminate: progPercent != null && progPercent < 0)));
} }
Future<void> notify(ObtainiumNotification notif, Future<void> notify(ObtainiumNotification notif,

View File

@ -35,6 +35,7 @@ List<int> updateIntervals = [15, 30, 60, 120, 180, 360, 720, 1440, 4320, 0]
class SettingsProvider with ChangeNotifier { class SettingsProvider with ChangeNotifier {
SharedPreferences? prefs; SharedPreferences? prefs;
bool justStarted = true;
String sourceUrl = 'https://github.com/ImranR98/Obtainium'; String sourceUrl = 'https://github.com/ImranR98/Obtainium';
@ -92,6 +93,15 @@ class SettingsProvider with ChangeNotifier {
notifyListeners(); notifyListeners();
} }
bool get checkOnStart {
return prefs?.getBool('checkOnStart') ?? false;
}
set checkOnStart(bool checkOnStart) {
prefs?.setBool('checkOnStart', checkOnStart);
notifyListeners();
}
SortColumnSettings get sortColumn { SortColumnSettings get sortColumn {
return SortColumnSettings.values[ return SortColumnSettings.values[
prefs?.getInt('sortColumn') ?? SortColumnSettings.nameAuthor.index]; prefs?.getInt('sortColumn') ?? SortColumnSettings.nameAuthor.index];
@ -120,6 +130,14 @@ class SettingsProvider with ChangeNotifier {
return result; return result;
} }
bool checkJustStarted() {
if (justStarted) {
justStarted = false;
return true;
}
return false;
}
Future<bool> getInstallPermission({bool enforce = false}) async { Future<bool> getInstallPermission({bool enforce = false}) async {
while (!(await Permission.requestInstallPackages.isGranted)) { while (!(await Permission.requestInstallPackages.isGranted)) {
// Explicit request as InstallPlugin request sometimes bugged // Explicit request as InstallPlugin request sometimes bugged

View File

@ -34,6 +34,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.0.7" version: "2.0.7"
archive:
dependency: "direct main"
description:
name: archive
sha256: "0c8368c9b3f0abbc193b9d6133649a614204b528982bebc7026372d61677ce3a"
url: "https://pub.dev"
source: hosted
version: "3.3.7"
args: args:
dependency: transitive dependency: transitive
description: description:
@ -82,6 +90,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.17.0" version: "1.17.0"
convert:
dependency: transitive
description:
name: convert
sha256: "0f08b14755d163f6e2134cb58222dd25ea2a2ee8a195e53983d57c075324d592"
url: "https://pub.dev"
source: hosted
version: "3.1.1"
cross_file: cross_file:
dependency: transitive dependency: transitive
description: description:
@ -410,10 +426,10 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: path_provider name: path_provider
sha256: c7edf82217d4b2952b2129a61d3ad60f1075b9299e629e149a8d2e39c2e6aad4 sha256: "3087813781ab814e4157b172f1a11c46be20179fcc9bea043e0fba36bc0acaa2"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.0.14" version: "2.0.15"
path_provider_android: path_provider_android:
dependency: transitive dependency: transitive
description: description:
@ -426,10 +442,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: path_provider_foundation name: path_provider_foundation
sha256: ad4c4d011830462633f03eb34445a45345673dfd4faf1ab0b4735fbd93b19183 sha256: "1995d88ec2948dac43edf8fe58eb434d35d22a2940ecee1a9fefcd62beee6eb3"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.2.2" version: "2.2.3"
path_provider_linux: path_provider_linux:
dependency: transitive dependency: transitive
description: description:
@ -518,6 +534,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.1.4" version: "2.1.4"
pointycastle:
dependency: transitive
description:
name: pointycastle
sha256: "7c1e5f0d23c9016c5bbd8b1473d0d3fb3fc851b876046039509e18e0c7485f2c"
url: "https://pub.dev"
source: hosted
version: "3.7.3"
process: process:
dependency: transitive dependency: transitive
description: description:
@ -554,10 +578,10 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: shared_preferences name: shared_preferences
sha256: "858aaa72d8f61637d64e776aca82e1c67e6d9ee07979123c5d17115031c1b13b" sha256: "16d3fb6b3692ad244a695c0183fca18cf81fd4b821664394a781de42386bf022"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.1.0" version: "2.1.1"
shared_preferences_android: shared_preferences_android:
dependency: transitive dependency: transitive
description: description:
@ -570,10 +594,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: shared_preferences_foundation name: shared_preferences_foundation
sha256: "0c1c16c56c9708aa9c361541a6f0e5cc6fc12a3232d866a687a7b7db30032b07" sha256: e014107bb79d6d3297196f4f2d0db54b5d1f85b8ea8ff63b8e8b391a02700feb
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.2.1" version: "2.2.2"
shared_preferences_linux: shared_preferences_linux:
dependency: transitive dependency: transitive
description: description:
@ -623,10 +647,10 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: sqflite name: sqflite
sha256: acf091c6e55c50d00b30b8532b2dd23e393cf775861665ebd0f15cdd6ebfb079 sha256: "3a82c9a216b46b88617e3714dd74227eaca20c501c4abcc213e56db26b9caa00"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.2.8+1" version: "2.2.8+2"
sqflite_common: sqflite_common:
dependency: transitive dependency: transitive
description: description:

View File

@ -17,7 +17,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
# In Windows, build-name is used as the major, minor, and patch parts # In Windows, build-name is used as the major, minor, and patch parts
# of the product and file versions while build-number is used as the build suffix. # of the product and file versions while build-number is used as the build suffix.
version: 0.13.0+164 # When changing this, update the tag in main() accordingly version: 0.13.2+166 # When changing this, update the tag in main() accordingly
environment: environment:
sdk: '>=2.18.2 <3.0.0' sdk: '>=2.18.2 <3.0.0'
@ -63,6 +63,7 @@ dependencies:
easy_localization: ^3.0.1 easy_localization: ^3.0.1
android_intent_plus: ^3.1.5 android_intent_plus: ^3.1.5
flutter_markdown: ^0.6.14 flutter_markdown: ^0.6.14
archive: ^3.3.7
dev_dependencies: dev_dependencies: