From cc3c4cc79f6ceb48f0cf5ee5bc9e9a5b161e3b5a Mon Sep 17 00:00:00 2001 From: Imran Remtulla Date: Sat, 6 May 2023 13:20:58 -0400 Subject: [PATCH 1/4] Add XAPK support (incomplete - OBB not copied) --- lib/app_sources/apkpure.dart | 4 +- lib/pages/add_app.dart | 11 ++- lib/providers/apps_provider.dart | 131 ++++++++++++++++++++++++------- pubspec.lock | 24 ++++++ pubspec.yaml | 1 + 5 files changed, 139 insertions(+), 32 deletions(-) diff --git a/lib/app_sources/apkpure.dart b/lib/app_sources/apkpure.dart index 9f75f7d..1ab017d 100644 --- a/lib/app_sources/apkpure.dart +++ b/lib/app_sources/apkpure.dart @@ -57,9 +57,9 @@ class APKPure extends AppSource { } catch (err) { // ignore } - + String type = html.querySelector('a.info-tag')?.text.trim() ?? 'APK'; List> 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') diff --git a/lib/pages/add_app.dart b/lib/pages/add_app.dart index ee0b2e4..bdb18e4 100644 --- a/lib/pages/add_app.dart +++ b/lib/pages/add_app.dart @@ -159,9 +159,16 @@ class _AddAppPageState extends State { 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')); diff --git a/lib/providers/apps_provider.dart b/lib/providers/apps_provider.dart index e4f611c..9691428 100644 --- a/lib/providers/apps_provider.dart +++ b/lib/providers/apps_provider.dart @@ -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 generateStandardVersionRegExStrings() { // TODO: Look into RegEx for non-Latin characters / non-Arabic numerals var basics = [ @@ -164,7 +172,27 @@ class AppsProvider with ChangeNotifier { return downloadedFile; } - Future downloadApp(App app, BuildContext? context) async { + 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}.apk'); + if (apps[originalAppId] != null) { + await removeApps([originalAppId]); + await saveApps([app], onlyIfExists: !isTempId); + } + } + } + + Future downloadApp(App app, BuildContext? context) async { NotificationsProvider? notificationsProvider = context?.read(); var notifId = DownloadNotification(app.finalName, 0).id; @@ -194,33 +222,42 @@ 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); - } + PackageArchiveInfo? newInfo; + try { + newInfo = await PackageArchiveInfo.fromPath(downloadedFile.path); + } catch (e) { + // Assume it's an XAPK + fileName = '${app.id}-${downloadUrl.hashCode}.xapk'; + String newPath = '${downloadedFile.parent.path}/$fileName'; + downloadedFile.renameSync(newPath); + downloadedFile = File(newPath); } - // Delete older versions of the APK if any + Directory? xapkDir; + if (newInfo == null) { + 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); + } + 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.toLowerCase().endsWith(xapkDir == null ? '.apk' : '.xapk') && fn != downloadedFile.path.split('/').last) { file.delete(); } } - return DownloadedApk(app.id, downloadedFile); + if (xapkDir != null) { + return DownloadedXApkDir(app.id, downloadedFile, xapkDir); + } else { + return DownloadedApk(app.id, downloadedFile); + } } finally { notificationsProvider?.cancel(notifId); if (apps[app.id] != null) { @@ -267,10 +304,37 @@ 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 + 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; + File(filename) + ..createSync(recursive: true) + ..writeAsBytesSync(data); + } else { + Directory(filename).create(recursive: true); + } + } + } + + Future installXApkDir(DownloadedXApkDir dir, + {bool silent = false}) async { + try { + for (var apk in dir.extracted + .listSync() + .where((f) => f is File && f.path.toLowerCase().endsWith('.apk'))) { + await installApk(DownloadedApk(dir.appId, apk as File), silent: silent); + } + dir.file.delete(); + } finally { + dir.extracted.delete(recursive: true); + } + } + Future installApk(DownloadedApk file, {bool silent = false}) async { // TODO: Use 'silent' when/if ever possible var newInfo = await PackageArchiveInfo.fromPath(file.file.path); @@ -420,9 +484,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 +503,11 @@ class AppsProvider with ChangeNotifier { // ignore: use_build_context_synchronously await waitForUserToReturnToForeground(context); } - await installApk(downloadedFile, silent: willBeSilent); + if (downloadedFile != null) { + await installApk(downloadedFile, silent: willBeSilent); + } else { + await installXApkDir(downloadedDir!, silent: willBeSilent); + } installedIds.add(id); } catch (e) { errors.add(id, e.toString()); diff --git a/pubspec.lock b/pubspec.lock index fc6d56b..c5513ac 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -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: @@ -82,6 +90,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.17.0" + convert: + dependency: transitive + description: + name: convert + sha256: "0f08b14755d163f6e2134cb58222dd25ea2a2ee8a195e53983d57c075324d592" + url: "https://pub.dev" + source: hosted + version: "3.1.1" cross_file: dependency: transitive description: @@ -518,6 +534,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: diff --git a/pubspec.yaml b/pubspec.yaml index d1197a8..36665bd 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -63,6 +63,7 @@ dependencies: easy_localization: ^3.0.1 android_intent_plus: ^3.1.5 flutter_markdown: ^0.6.14 + archive: ^3.3.7 dev_dependencies: From d5f7eced8beb8b3af626293d49e15eb20e62a28b Mon Sep 17 00:00:00 2001 From: Imran Remtulla Date: Sat, 6 May 2023 13:28:41 -0400 Subject: [PATCH 2/4] UI tweaks --- lib/pages/apps.dart | 10 ++++++---- lib/providers/apps_provider.dart | 2 +- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/lib/pages/apps.dart b/lib/pages/apps.dart index 6a0a947..f7a8159 100644 --- a/lib/pages/apps.dart +++ b/lib/pages/apps.dart @@ -515,10 +515,12 @@ class AppsPageState extends State { ? FontWeight.bold : FontWeight.normal)), trailing: listedApps[index].downloadProgress != null - ? Text(tr('percentProgress', args: [ - listedApps[index].downloadProgress?.toInt().toString() ?? - '100' - ])) + ? SizedBox( + width: 110, + child: Text(tr('percentProgress', args: [ + listedApps[index].downloadProgress?.toInt().toString() ?? + '100' + ]))) : trailingRow, onTap: () { if (selectedAppIds.isNotEmpty) { diff --git a/lib/providers/apps_provider.dart b/lib/providers/apps_provider.dart index 9691428..3e6f5ea 100644 --- a/lib/providers/apps_provider.dart +++ b/lib/providers/apps_provider.dart @@ -809,7 +809,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()); From cc268aeeda7c7351d27a0d94c434b9df31b915d4 Mon Sep 17 00:00:00 2001 From: Imran Remtulla Date: Sat, 6 May 2023 14:25:17 -0400 Subject: [PATCH 3/4] "Check updates on start" toggle --- assets/translations/de.json | 3 +- assets/translations/en.json | 3 +- assets/translations/es.json | 1 + assets/translations/fa.json | 1 + assets/translations/fr.json | 3 +- assets/translations/hu.json | 1 + assets/translations/it.json | 3 +- assets/translations/ja.json | 1 + assets/translations/zh.json | 1 + lib/pages/apps.dart | 41 ++++++++++++++++++---------- lib/pages/settings.dart | 12 ++++++++ lib/providers/settings_provider.dart | 18 ++++++++++++ 12 files changed, 70 insertions(+), 18 deletions(-) diff --git a/assets/translations/de.json b/assets/translations/de.json index fb3135a..5f53337 100644 --- a/assets/translations/de.json +++ b/assets/translations/de.json @@ -121,7 +121,7 @@ "followSystem": "System folgen", "obtainium": "Obtainium", "materialYou": "Material You", - "useBlackTheme": "Use pure black dark theme", + "useBlackTheme": "Use Pure Black Dark Theme", "appSortBy": "App sortieren nach", "authorName": "Autor/Name", "nameAuthor": "Name/Autor", @@ -231,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": "App entfernen?", "other": "Apps entfernen?" diff --git a/assets/translations/en.json b/assets/translations/en.json index dd027a9..eb59399 100644 --- a/assets/translations/en.json +++ b/assets/translations/en.json @@ -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", @@ -231,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": "Remove App?", "other": "Remove Apps?" diff --git a/assets/translations/es.json b/assets/translations/es.json index d3fae41..c013d01 100644 --- a/assets/translations/es.json +++ b/assets/translations/es.json @@ -231,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": "¿Eliminar Aplicación?", "other": "¿Eliminar Aplicaciones?" diff --git a/assets/translations/fa.json b/assets/translations/fa.json index 36e25d4..5685c1e 100644 --- a/assets/translations/fa.json +++ b/assets/translations/fa.json @@ -231,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": "برنامه حذف شود؟", "other": "برنامه ها حذف شوند؟" diff --git a/assets/translations/fr.json b/assets/translations/fr.json index 4e29966..90c060f 100644 --- a/assets/translations/fr.json +++ b/assets/translations/fr.json @@ -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", @@ -231,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": "Supprimer l'application ?", "other": "Supprimer les applications ?" diff --git a/assets/translations/hu.json b/assets/translations/hu.json index 85134f0..c73ef70 100644 --- a/assets/translations/hu.json +++ b/assets/translations/hu.json @@ -230,6 +230,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?" diff --git a/assets/translations/it.json b/assets/translations/it.json index 728bce8..0cff11d 100644 --- a/assets/translations/it.json +++ b/assets/translations/it.json @@ -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", @@ -231,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": "Rimuovere l'App?", "other": "Rimuovere le App?" diff --git a/assets/translations/ja.json b/assets/translations/ja.json index 02ee143..621b831 100644 --- a/assets/translations/ja.json +++ b/assets/translations/ja.json @@ -231,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": "アプリを削除しますか?", "other": "アプリを削除しますか?" diff --git a/assets/translations/zh.json b/assets/translations/zh.json index 1cf5267..cd6f101 100644 --- a/assets/translations/zh.json +++ b/assets/translations/zh.json @@ -231,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": "是否删除应用?", "other": "是否删除应用?" diff --git a/lib/pages/apps.dart b/lib/pages/apps.dart index f7a8159..5fcee6a 100644 --- a/lib/pages/apps.dart +++ b/lib/pages/apps.dart @@ -52,6 +52,9 @@ class AppsPageState extends State { } } + final GlobalKey _refreshIndicatorKey = + GlobalKey(); + @override Widget build(BuildContext context) { var appsProvider = context.watch(); @@ -61,6 +64,27 @@ class AppsPageState extends State { var currentFilterIsUpdatesOnly = 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 .where((element) => listedApps.map((e) => e.app.id).contains(element)) .toSet(); @@ -315,7 +339,7 @@ class AppsPageState extends State { ?.isBefore(refreshingSince!) ?? true)) .length / - appsProvider.apps.length, + (appsProvider.apps.isNotEmpty ? appsProvider.apps.length : 1), ), ) ]; @@ -1019,19 +1043,8 @@ class AppsPageState extends State { 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: [ CustomAppBar(title: tr('appsString')), ...getLoadingWidgets(), diff --git a/lib/pages/settings.dart b/lib/pages/settings.dart index 65684cf..7827111 100644 --- a/lib/pages/settings.dart +++ b/lib/pages/settings.dart @@ -228,6 +228,18 @@ class _SettingsPageState extends State { 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'), diff --git a/lib/providers/settings_provider.dart b/lib/providers/settings_provider.dart index 162d7f9..8ddfdf2 100644 --- a/lib/providers/settings_provider.dart +++ b/lib/providers/settings_provider.dart @@ -35,6 +35,7 @@ List 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 getInstallPermission({bool enforce = false}) async { while (!(await Permission.requestInstallPackages.isGranted)) { // Explicit request as InstallPlugin request sometimes bugged From e1db024034a14169acb7c805aaf2d8c3a2854473 Mon Sep 17 00:00:00 2001 From: Imran Remtulla Date: Sat, 6 May 2023 14:40:14 -0400 Subject: [PATCH 4/4] Increment version --- lib/main.dart | 2 +- pubspec.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/main.dart b/lib/main.dart index c7ca28b..b9044c4 100644 --- a/lib/main.dart +++ b/lib/main.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.1'; const String currentReleaseTag = 'v$currentVersion-beta'; // KEEP THIS IN SYNC WITH GITHUB RELEASES diff --git a/pubspec.yaml b/pubspec.yaml index 36665bd..5d786c4 100644 --- a/pubspec.yaml +++ b/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.1+165 # When changing this, update the tag in main() accordingly environment: sdk: '>=2.18.2 <3.0.0'