diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml
index 3e13a38..90ccc57 100644
--- a/android/app/src/main/AndroidManifest.xml
+++ b/android/app/src/main/AndroidManifest.xml
@@ -5,7 +5,8 @@
android:label="Obtainium"
android:name="${applicationName}"
android:icon="@mipmap/ic_launcher"
- android:requestLegacyExternalStorage="true">
+ android:requestLegacyExternalStorage="true"
+ android:usesCleartextTraffic="true">
-
-
-
-
-
-
-
reg.hasMatch(element.key)).toList();
+ }
if (apkUrls.isEmpty && additionalSettings['trackOnly'] != true) {
continue;
}
diff --git a/lib/app_sources/html.dart b/lib/app_sources/html.dart
index 7ed84d6..45c2048 100644
--- a/lib/app_sources/html.dart
+++ b/lib/app_sources/html.dart
@@ -1,5 +1,4 @@
import 'package:easy_localization/easy_localization.dart';
-import 'package:flutter/material.dart';
import 'package:html/parser.dart';
import 'package:http/http.dart';
import 'package:obtainium/components/generated_form.dart';
@@ -100,28 +99,6 @@ class HTML extends AppSource {
}
])
],
- [
- GeneratedFormTextField('versionExtractionRegEx',
- label: tr('versionExtractionRegEx'),
- required: false,
- additionalValidators: [(value) => regExValidator(value)]),
- ],
- [
- GeneratedFormTextField('matchGroupToUse',
- label: tr('matchGroupToUse'),
- required: false,
- hint: '0',
- textInputType: const TextInputType.numberWithOptions(),
- additionalValidators: [
- (value) {
- if (value?.isEmpty == true) {
- value = null;
- }
- value ??= '0';
- return intValidator(value);
- }
- ])
- ],
[
GeneratedFormSwitch('versionExtractWholePage',
label: tr('versionExtractWholePage'))
@@ -242,9 +219,14 @@ class HTML extends AppSource {
Map additionalSettings,
) async {
var currentUrl = standardUrl;
- for (int i = 0;
- i < (additionalSettings['intermediateLink']?.length ?? 0);
- i++) {
+ if (additionalSettings['intermediateLink']?.isNotEmpty != true) {
+ additionalSettings['intermediateLink'] = [];
+ }
+ additionalSettings['intermediateLink'] =
+ additionalSettings['intermediateLink']
+ .where((l) => l['customLinkFilterRegex'].isNotEmpty == true)
+ .toList();
+ for (int i = 0; i < (additionalSettings['intermediateLink'].length); i++) {
var intLinks = await grabLinksCommon(await sourceRequest(currentUrl),
additionalSettings['intermediateLink'][i]);
if (intLinks.isEmpty) {
@@ -270,26 +252,12 @@ class HTML extends AppSource {
if (additionalSettings['supportFixedAPKURL'] != true) {
version = rel.hashCode.toString();
}
- var versionExtractionRegEx =
- additionalSettings['versionExtractionRegEx'] as String?;
- if (versionExtractionRegEx?.isNotEmpty == true) {
- var match = RegExp(versionExtractionRegEx!).allMatches(
- additionalSettings['versionExtractWholePage'] == true
- ? res.body.split('\r\n').join('\n').split('\n').join('\\n')
- : rel);
- if (match.isEmpty) {
- throw NoVersionError();
- }
- String matchGroupString =
- (additionalSettings['matchGroupToUse'] as String).trim();
- if (matchGroupString.isEmpty) {
- matchGroupString = "0";
- }
- version = match.last.group(int.parse(matchGroupString));
- if (version?.isEmpty == true) {
- throw NoVersionError();
- }
- }
+ version = extractVersion(
+ additionalSettings['versionExtractionRegEx'] as String?,
+ additionalSettings['matchGroupToUse'] as String?,
+ additionalSettings['versionExtractWholePage'] == true
+ ? res.body.split('\r\n').join('\n').split('\n').join('\\n')
+ : rel);
rel = ensureAbsoluteUrl(rel, uri);
version ??= (await checkDownloadHash(rel)).toString();
return APKDetails(version, [rel].map((e) => MapEntry(e, e)).toList(),
diff --git a/lib/main.dart b/lib/main.dart
index b8a8d8c..4ddb3e9 100644
--- a/lib/main.dart
+++ b/lib/main.dart
@@ -19,7 +19,7 @@ import 'package:easy_localization/src/easy_localization_controller.dart';
// ignore: implementation_imports
import 'package:easy_localization/src/localization.dart';
-const String currentVersion = '0.15.3';
+const String currentVersion = '0.15.4';
const String currentReleaseTag =
'v$currentVersion-beta'; // KEEP THIS IN SYNC WITH GITHUB RELEASES
diff --git a/lib/pages/home.dart b/lib/pages/home.dart
index 0bba900..6d87a02 100644
--- a/lib/pages/home.dart
+++ b/lib/pages/home.dart
@@ -5,6 +5,7 @@ import 'package:app_links/app_links.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
+import 'package:obtainium/components/generated_form_modal.dart';
import 'package:obtainium/custom_errors.dart';
import 'package:obtainium/pages/add_app.dart';
import 'package:obtainium/pages/apps.dart';
@@ -76,14 +77,39 @@ class _HomePageState extends State {
try {
if (action == 'add') {
await goToAddApp(data);
- } else if (action == 'app') {
- await context
- .read()
- .import('{ "apps": [${Uri.decodeComponent(data)}] }');
- } else if (action == 'apps') {
- await context
- .read()
- .import('{ "apps": ${Uri.decodeComponent(data)} }');
+ } else if (action == 'app' || action == 'apps') {
+ var dataStr = Uri.decodeComponent(data);
+ if (await showDialog(
+ context: context,
+ builder: (BuildContext ctx) {
+ return GeneratedFormModal(
+ title: tr('importX', args: [
+ action == 'app' ? tr('app') : tr('appsString')
+ ]),
+ items: const [],
+ additionalWidgets: [
+ ExpansionTile(
+ title: const Text('Raw JSON'),
+ children: [
+ Text(
+ dataStr,
+ style: const TextStyle(fontFamily: 'monospace'),
+ )
+ ],
+ )
+ ],
+ );
+ }) !=
+ null) {
+ // ignore: use_build_context_synchronously
+ var result = await context.read().import(
+ action == 'app'
+ ? '{ "apps": [$dataStr] }'
+ : '{ "apps": $dataStr }');
+ // ignore: use_build_context_synchronously
+ showMessage(
+ tr('importedX', args: [plural('apps', result.key)]), context);
+ }
} else {
throw ObtainiumError(tr('unknown'));
}
diff --git a/lib/providers/source_provider.dart b/lib/providers/source_provider.dart
index ef0aa8d..b1c8591 100644
--- a/lib/providers/source_provider.dart
+++ b/lib/providers/source_provider.dart
@@ -5,6 +5,7 @@ import 'dart:convert';
import 'package:device_info_plus/device_info_plus.dart';
import 'package:easy_localization/easy_localization.dart';
+import 'package:flutter/material.dart';
import 'package:html/dom.dart';
import 'package:http/http.dart';
import 'package:obtainium/app_sources/apkmirror.dart';
@@ -444,6 +445,19 @@ abstract class AppSource {
label: tr('trackOnly'),
)
],
+ [
+ GeneratedFormTextField('versionExtractionRegEx',
+ label: tr('versionExtractionRegEx'),
+ required: false,
+ additionalValidators: [(value) => regExValidator(value)]),
+ ],
+ [
+ GeneratedFormTextField('matchGroupToUse',
+ label: tr('matchGroupToUse'),
+ required: false,
+ hint: '\$0',
+ textInputType: const TextInputType.numberWithOptions())
+ ],
[
GeneratedFormDropdown(
'versionDetection',
@@ -580,6 +594,57 @@ bool isTempId(App app) {
return RegExp('^[0-9]+\$').hasMatch(app.id);
}
+replaceMatchGroupsInString(RegExpMatch match, String matchGroupString) {
+ if (RegExp('^\\d+\$').hasMatch(matchGroupString)) {
+ matchGroupString = '\$$matchGroupString';
+ }
+ // Regular expression to match numbers in the input string
+ final numberRegex = RegExp(r'\$\d+');
+ // Extract all numbers from the input string
+ final numbers = numberRegex.allMatches(matchGroupString);
+ if (numbers.isEmpty) {
+ // If no numbers found, return the original string
+ return null;
+ }
+ // Replace numbers with corresponding match groups
+ var outputString = matchGroupString;
+ for (final numberMatch in numbers) {
+ final number = numberMatch.group(0)!;
+ final matchGroup = match.group(int.parse(number.substring(1))) ?? '';
+ // Check if the number is preceded by a single backslash
+ final isEscaped = outputString.contains('\\$number');
+ // Replace the number with the corresponding match group
+ if (!isEscaped) {
+ outputString = outputString.replaceAll(number, matchGroup);
+ } else {
+ outputString = outputString.replaceAll('\\$number', number);
+ }
+ }
+ return outputString;
+}
+
+String? extractVersion(String? versionExtractionRegEx, String? matchGroupString,
+ String stringToCheck) {
+ if (versionExtractionRegEx?.isNotEmpty == true) {
+ String? version = stringToCheck;
+ var match = RegExp(versionExtractionRegEx!).allMatches(version);
+ if (match.isEmpty) {
+ throw NoVersionError();
+ }
+ matchGroupString = matchGroupString?.trim() ?? '';
+ if (matchGroupString.isEmpty) {
+ matchGroupString = "0";
+ }
+ version = replaceMatchGroupsInString(match.last, matchGroupString);
+ if (version?.isNotEmpty != true) {
+ throw NoVersionError();
+ }
+ return version!;
+ } else {
+ return null;
+ }
+}
+
class SourceProvider {
// Add more source classes here so they are available via the service
List get sources => [
@@ -679,6 +744,18 @@ class SourceProvider {
String standardUrl = source.standardizeUrl(url);
APKDetails apk =
await source.getLatestAPKDetails(standardUrl, additionalSettings);
+
+ if (source.runtimeType != HTML().runtimeType) {
+ // HTML does it separately
+ String? extractedVersion = extractVersion(
+ additionalSettings['versionExtractionRegEx'] as String?,
+ additionalSettings['matchGroupToUse'] as String?,
+ apk.version);
+ if (extractedVersion != null) {
+ apk.version = extractedVersion;
+ }
+ }
+
if (additionalSettings['versionDetection'] == 'releaseDateAsVersion' &&
apk.releaseDate != null) {
apk.version = apk.releaseDate!.microsecondsSinceEpoch.toString();
diff --git a/pubspec.lock b/pubspec.lock
index 5a31918..c1f6125 100644
--- a/pubspec.lock
+++ b/pubspec.lock
@@ -46,10 +46,10 @@ packages:
dependency: transitive
description:
name: archive
- sha256: "7b875fd4a20b165a3084bd2d210439b22ebc653f21cea4842729c0c30c82596b"
+ sha256: "22600aa1e926be775fa5fe7e6894e7fb3df9efda8891c73f70fb3262399a432d"
url: "https://pub.dev"
source: hosted
- version: "3.4.9"
+ version: "3.4.10"
args:
dependency: transitive
description:
diff --git a/pubspec.yaml b/pubspec.yaml
index da63a01..1ceb551 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.15.3+239 # When changing this, update the tag in main() accordingly
+version: 0.15.4+240 # When changing this, update the tag in main() accordingly
environment:
sdk: '>=3.0.0 <4.0.0'