diff --git a/assets/translations/br.json b/assets/translations/br.json index efe9093..ad6a4a5 100644 --- a/assets/translations/br.json +++ b/assets/translations/br.json @@ -251,6 +251,8 @@ "exemptFromBackgroundUpdates": "Isento de atualizações em segundo plano (se ativadas)", "bgUpdatesOnWiFiOnly": "Desative atualizações em segundo plano quando não estiver em WiFi", "autoSelectHighestVersionCode": "Auto-select highest versionCode APK", + "versionExtractionRegEx": "Version Extraction RegEx", + "matchGroupToUse": "Match Group to Use", "removeAppQuestion": { "one": "Remover App?", "other": "Remover Apps?" diff --git a/assets/translations/bs.json b/assets/translations/bs.json index e3a4ef6..b65d8a3 100644 --- a/assets/translations/bs.json +++ b/assets/translations/bs.json @@ -248,6 +248,8 @@ "exemptFromBackgroundUpdates": "Exempt from background updates (if enabled)", "bgUpdatesOnWiFiOnly": "Disable background updates when not on WiFi", "autoSelectHighestVersionCode": "Auto-select highest versionCode APK", + "versionExtractionRegEx": "Version Extraction RegEx", + "matchGroupToUse": "Match Group to Use", "removeAppQuestion": { "one": "Želite li ukloniti aplikaciju?", "other": "Želite li ukloniti aplikacije?" diff --git a/assets/translations/de.json b/assets/translations/de.json index 2e9857e..d5a016b 100644 --- a/assets/translations/de.json +++ b/assets/translations/de.json @@ -248,6 +248,8 @@ "exemptFromBackgroundUpdates": "Ausschluss von Hintergrundaktualisierungen (falls aktiviert)", "bgUpdatesOnWiFiOnly": "Hintergrundaktualisierungen deaktivieren, wenn kein WLAN vorhanden ist", "autoSelectHighestVersionCode": "Auto-select highest versionCode APK", + "versionExtractionRegEx": "Version Extraction RegEx", + "matchGroupToUse": "Match Group to Use", "removeAppQuestion": { "one": "App entfernen?", "other": "Apps entfernen?" diff --git a/assets/translations/en.json b/assets/translations/en.json index bba3367..f5473be 100644 --- a/assets/translations/en.json +++ b/assets/translations/en.json @@ -251,6 +251,8 @@ "exemptFromBackgroundUpdates": "Exempt from background updates (if enabled)", "bgUpdatesOnWiFiOnly": "Disable background updates when not on WiFi", "autoSelectHighestVersionCode": "Auto-select highest versionCode APK", + "versionExtractionRegEx": "Version Extraction RegEx", + "matchGroupToUse": "Match Group to Use", "removeAppQuestion": { "one": "Remove App?", "other": "Remove Apps?" diff --git a/assets/translations/es.json b/assets/translations/es.json index e4ffaa0..62fc6e7 100644 --- a/assets/translations/es.json +++ b/assets/translations/es.json @@ -248,6 +248,8 @@ "exemptFromBackgroundUpdates": "Exempt from background updates (if enabled)", "bgUpdatesOnWiFiOnly": "Disable background updates when not on WiFi", "autoSelectHighestVersionCode": "Auto-select highest versionCode APK", + "versionExtractionRegEx": "Version Extraction RegEx", + "matchGroupToUse": "Match Group to Use", "removeAppQuestion": { "one": "¿Eliminar Aplicación?", "other": "¿Eliminar Aplicaciones?" diff --git a/assets/translations/fa.json b/assets/translations/fa.json index 041ce19..466b976 100644 --- a/assets/translations/fa.json +++ b/assets/translations/fa.json @@ -248,6 +248,8 @@ "exemptFromBackgroundUpdates": "Exempt from background updates (if enabled)", "bgUpdatesOnWiFiOnly": "Disable background updates when not on WiFi", "autoSelectHighestVersionCode": "Auto-select highest versionCode APK", + "versionExtractionRegEx": "Version Extraction RegEx", + "matchGroupToUse": "Match Group to Use", "removeAppQuestion": { "one": "برنامه حذف شود؟", "other": "برنامه ها حذف شوند؟" diff --git a/assets/translations/fr.json b/assets/translations/fr.json index 8b5adfc..a23667b 100644 --- a/assets/translations/fr.json +++ b/assets/translations/fr.json @@ -248,6 +248,8 @@ "exemptFromBackgroundUpdates": "Exempt from background updates (if enabled)", "bgUpdatesOnWiFiOnly": "Disable background updates when not on WiFi", "autoSelectHighestVersionCode": "Auto-select highest versionCode APK", + "versionExtractionRegEx": "Version Extraction RegEx", + "matchGroupToUse": "Match Group to Use", "removeAppQuestion": { "one": "Supprimer l'application ?", "other": "Supprimer les applications ?" diff --git a/assets/translations/hu.json b/assets/translations/hu.json index 7c7d912..dfd1274 100644 --- a/assets/translations/hu.json +++ b/assets/translations/hu.json @@ -247,6 +247,8 @@ "exemptFromBackgroundUpdates": "Mentes a háttérben történő frissítések alól (ha engedélyezett)", "bgUpdatesOnWiFiOnly": "Tiltsa le a háttérben frissítéseket, ha nincs Wi-Fi-n", "autoSelectHighestVersionCode": "Auto-select highest versionCode APK", + "versionExtractionRegEx": "Version Extraction RegEx", + "matchGroupToUse": "Match Group to Use", "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 eadf90b..c46ddbd 100644 --- a/assets/translations/it.json +++ b/assets/translations/it.json @@ -248,6 +248,8 @@ "exemptFromBackgroundUpdates": "Exempt from background updates (if enabled)", "bgUpdatesOnWiFiOnly": "Disable background updates when not on WiFi", "autoSelectHighestVersionCode": "Auto-select highest versionCode APK", + "versionExtractionRegEx": "Version Extraction RegEx", + "matchGroupToUse": "Match Group to Use", "removeAppQuestion": { "one": "Rimuovere l'app?", "other": "Rimuovere le app?" diff --git a/assets/translations/ja.json b/assets/translations/ja.json index 1fa24d6..741597e 100644 --- a/assets/translations/ja.json +++ b/assets/translations/ja.json @@ -249,6 +249,8 @@ "exemptFromBackgroundUpdates": "バックグラウンドアップデートを行わない (有効な場合)", "bgUpdatesOnWiFiOnly": "WiFiを使用していない場合,バックグラウンドアップデートを無効にする", "autoSelectHighestVersionCode": "Auto-select highest versionCode APK", + "versionExtractionRegEx": "Version Extraction RegEx", + "matchGroupToUse": "Match Group to Use", "removeAppQuestion": { "one": "アプリを削除しますか?", "other": "アプリを削除しますか?" diff --git a/assets/translations/pl.json b/assets/translations/pl.json index f56121c..5d419ef 100644 --- a/assets/translations/pl.json +++ b/assets/translations/pl.json @@ -254,6 +254,8 @@ "exemptFromBackgroundUpdates": "Wyklucz z uaktualnień w tle (jeśli są włączone)", "bgUpdatesOnWiFiOnly": "Wyłącz aktualizacje w tle, gdy nie ma połączenia z Wi-Fi", "autoSelectHighestVersionCode": "Auto-select highest versionCode APK", + "versionExtractionRegEx": "Version Extraction RegEx", + "matchGroupToUse": "Match Group to Use", "removeAppQuestion": { "one": "Usunąć aplikację?", "few": "Usunąć aplikacje?", diff --git a/assets/translations/ru.json b/assets/translations/ru.json index dfaeb93..34b48f1 100644 --- a/assets/translations/ru.json +++ b/assets/translations/ru.json @@ -248,6 +248,8 @@ "exemptFromBackgroundUpdates": "Exempt from background updates (if enabled)", "bgUpdatesOnWiFiOnly": "Disable background updates when not on WiFi", "autoSelectHighestVersionCode": "Auto-select highest versionCode APK", + "versionExtractionRegEx": "Version Extraction RegEx", + "matchGroupToUse": "Match Group to Use", "removeAppQuestion": { "one": "Удалить приложение?", "other": "Удалить приложения?" diff --git a/assets/translations/zh.json b/assets/translations/zh.json index 6f4e7a2..902034d 100644 --- a/assets/translations/zh.json +++ b/assets/translations/zh.json @@ -249,6 +249,8 @@ "exemptFromBackgroundUpdates": "Exempt from background updates (if enabled)", "bgUpdatesOnWiFiOnly": "Disable background updates when not on WiFi", "autoSelectHighestVersionCode": "Auto-select highest versionCode APK", + "versionExtractionRegEx": "Version Extraction RegEx", + "matchGroupToUse": "Match Group to Use", "removeAppQuestion": { "one": "是否删除应用?", "other": "是否删除应用?" diff --git a/lib/app_sources/html.dart b/lib/app_sources/html.dart index bce8f34..a2a25cc 100644 --- a/lib/app_sources/html.dart +++ b/lib/app_sources/html.dart @@ -1,4 +1,5 @@ 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'; @@ -109,6 +110,26 @@ class HTML extends AppSource { hint: '([0-9]+\.)*[0-9]+/\$', required: false, additionalValidators: [(value) => regExValidator(value)]) + ], + [ + GeneratedFormTextField('versionExtractionRegEx', + label: tr('versionExtractionRegEx'), + required: false, + additionalValidators: [(value) => regExValidator(value)]), + GeneratedFormTextField('matchGroupToUse', + label: tr('matchGroupToUse'), + required: false, + hint: '1', + textInputType: const TextInputType.numberWithOptions(), + additionalValidators: [ + (value) { + value ??= '1'; + if (int.tryParse(value) == null) { + return tr('invalidInput'); + } + return null; + } + ]) ] ]; overrideVersionDetectionFormDefault('noVersionDetection', @@ -183,10 +204,23 @@ class HTML extends AppSource { throw NoReleasesError(); } var rel = links.last; - var version = rel.hashCode.toString(); + String? version = rel.hashCode.toString(); + var versionExtractionRegEx = + additionalSettings['versionExtractionRegEx'] as String?; + if (versionExtractionRegEx?.isNotEmpty == true) { + var match = RegExp(versionExtractionRegEx!).allMatches(rel); + if (match.isEmpty) { + throw NoVersionError(); + } + version = match.last + .group(int.parse(additionalSettings['matchGroupToUse'] as String)); + if (version?.isEmpty == true) { + throw NoVersionError(); + } + } List apkUrls = [rel].map((e) => ensureAbsoluteUrl(e, uri)).toList(); - return APKDetails(version, apkUrls.map((e) => MapEntry(e, e)).toList(), + return APKDetails(version!, apkUrls.map((e) => MapEntry(e, e)).toList(), AppNames(uri.host, tr('app'))); } else { throw getObtainiumHttpError(res); diff --git a/lib/components/generated_form.dart b/lib/components/generated_form.dart index 300b679..41e742f 100644 --- a/lib/components/generated_form.dart +++ b/lib/components/generated_form.dart @@ -25,6 +25,7 @@ class GeneratedFormTextField extends GeneratedFormItem { late int max; late String? hint; late bool password; + late TextInputType? textInputType; GeneratedFormTextField(String key, {String label = 'Input', @@ -34,7 +35,8 @@ class GeneratedFormTextField extends GeneratedFormItem { this.required = true, this.max = 1, this.hint, - this.password = false}) + this.password = false, + this.textInputType}) : super(key, label: label, belowWidgets: belowWidgets, @@ -144,7 +146,8 @@ Color generateRandomLightColor() { // Map from HPLuv color space to RGB, use constant saturation=100, lightness=70 final List rgbValuesDbl = Hsluv.hpluvToRgb([hue, 100, 70]); // Map RBG values from 0-1 to 0-255: - final List rgbValues = rgbValuesDbl.map((rgb) => (rgb * 255).toInt()).toList(); + final List rgbValues = + rgbValuesDbl.map((rgb) => (rgb * 255).toInt()).toList(); return Color.fromARGB(255, rgbValues[0], rgbValues[1], rgbValues[2]); } @@ -190,6 +193,7 @@ class _GeneratedFormState extends State { if (formItem is GeneratedFormTextField) { final formFieldKey = GlobalKey(); return TextFormField( + keyboardType: formItem.textInputType, obscureText: formItem.password, autocorrect: !formItem.password, enableSuggestions: !formItem.password, @@ -370,34 +374,37 @@ class _GeneratedFormState extends State { }) ?? [const SizedBox.shrink()], (values[widget.items[r][e].key] - as Map>?) - ?.values - .where((e) => e.value) - .length == 1 + as Map>?) + ?.values + .where((e) => e.value) + .length == + 1 ? Padding( - padding: const EdgeInsets.symmetric(horizontal: 4), - child: IconButton( - onPressed: () { - setState(() { - var temp = values[widget.items[r][e].key] - as Map>; - // get selected category str where bool is true - final oldEntry = temp.entries.firstWhere((entry) => entry.value.value); - // generate new color, ensure it is not the same - int newColor = oldEntry.value.key; - while(oldEntry.value.key == newColor) { - newColor = generateRandomLightColor().value; - } - // Update entry with new color, remain selected - temp.update(oldEntry.key, (old) => MapEntry(newColor, old.value)); - values[widget.items[r][e].key] = temp; - someValueChanged(); - }); - }, - icon: const Icon(Icons.format_color_fill_rounded), - visualDensity: VisualDensity.compact, - tooltip: tr('colour'), - )) + padding: const EdgeInsets.symmetric(horizontal: 4), + child: IconButton( + onPressed: () { + setState(() { + var temp = values[widget.items[r][e].key] + as Map>; + // get selected category str where bool is true + final oldEntry = temp.entries + .firstWhere((entry) => entry.value.value); + // generate new color, ensure it is not the same + int newColor = oldEntry.value.key; + while (oldEntry.value.key == newColor) { + newColor = generateRandomLightColor().value; + } + // Update entry with new color, remain selected + temp.update(oldEntry.key, + (old) => MapEntry(newColor, old.value)); + values[widget.items[r][e].key] = temp; + someValueChanged(); + }); + }, + icon: const Icon(Icons.format_color_fill_rounded), + visualDensity: VisualDensity.compact, + tooltip: tr('colour'), + )) : const SizedBox.shrink(), (values[widget.items[r][e].key] as Map>?)