Compare commits

...

10 Commits

Author SHA1 Message Date
4252c2711b Merge pull request #240 from ImranR98/dev
APK RegEx Filter, Increased GitHub/Codeberg Release Range, UI Tweaks
Addresses #237, #238.
2023-01-28 00:17:10 -05:00
52913b0450 Slight UI tweak 2023-01-28 00:15:52 -05:00
427b0ed8d2 Changed a string 2023-01-28 00:13:03 -05:00
a85d6d4f08 Increment version, remove comment 2023-01-28 00:11:40 -05:00
05f712603c GitHub & Codeberg - get first 100 releases (not 30) 2023-01-28 00:08:17 -05:00
fa2a80e34c APK RegEx Filter + Updated Packages 2023-01-28 00:04:57 -05:00
f43e5a2ff1 Merge pull request #235 from ImranR98/dev
Increment version, update packages
2023-01-22 19:55:35 -05:00
b72aa8273e Increment version, update packages 2023-01-22 19:55:14 -05:00
520f186e4a Merge pull request #234 from p1gp1g/themed-icon
Add themed icon for Android 13
2023-01-22 19:53:43 -05:00
sim
e1e97672cf Add themed icon for Android 13 2023-01-23 01:09:14 +01:00
19 changed files with 849 additions and 697 deletions

View File

@ -2,4 +2,5 @@
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android"> <adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/ic_launcher_background"/> <background android:drawable="@color/ic_launcher_background"/>
<foreground android:drawable="@drawable/ic_launcher_foreground"/> <foreground android:drawable="@drawable/ic_launcher_foreground"/>
<monochrome android:drawable="@drawable/ic_launcher_foreground"/>
</adaptive-icon> </adaptive-icon>

View File

@ -211,6 +211,7 @@
"language": "Sprache", "language": "Sprache",
"storagePermissionDenied": "Storage permission denied", "storagePermissionDenied": "Storage permission denied",
"selectedCategorizeWarning": "This will replace any existing category settings for the selected Apps.", "selectedCategorizeWarning": "This will replace any existing category settings for the selected Apps.",
"filterAPKsByRegEx": "Filter APKs by Regular Expression",
"tooManyRequestsTryAgainInMinutes": { "tooManyRequestsTryAgainInMinutes": {
"one": "Zu viele Anfragen (Rate begrenzt) - versuchen Sie es in {} Minute erneut", "one": "Zu viele Anfragen (Rate begrenzt) - versuchen Sie es in {} Minute erneut",
"other": "Zu viele Anfragen (Rate begrenzt) - versuchen Sie es in {} Minuten erneut" "other": "Zu viele Anfragen (Rate begrenzt) - versuchen Sie es in {} Minuten erneut"

View File

@ -211,6 +211,7 @@
"language": "Language", "language": "Language",
"storagePermissionDenied": "Storage permission denied", "storagePermissionDenied": "Storage permission denied",
"selectedCategorizeWarning": "This will replace any existing category settings for the selected Apps.", "selectedCategorizeWarning": "This will replace any existing category settings for the selected Apps.",
"filterAPKsByRegEx": "Filter APKs by Regular Expression",
"tooManyRequestsTryAgainInMinutes": { "tooManyRequestsTryAgainInMinutes": {
"one": "Too many requests (rate limited) - try again in {} minute", "one": "Too many requests (rate limited) - try again in {} minute",
"other": "Too many requests (rate limited) - try again in {} minutes" "other": "Too many requests (rate limited) - try again in {} minutes"

View File

@ -210,6 +210,7 @@
"language": "Nyelv", "language": "Nyelv",
"storagePermissionDenied": "Tárhely engedély megtagadva", "storagePermissionDenied": "Tárhely engedély megtagadva",
"selectedCategorizeWarning": "Ez felváltja a kiválasztott alkalmazások meglévő kategória-beállításait.", "selectedCategorizeWarning": "Ez felváltja a kiválasztott alkalmazások meglévő kategória-beállításait.",
"filterAPKsByRegEx": "Filter APKs by Regular Expression",
"tooManyRequestsTryAgainInMinutes": { "tooManyRequestsTryAgainInMinutes": {
"one": "Túl sok kérés (korlátozott arány) próbálja újra {} perc múlva", "one": "Túl sok kérés (korlátozott arány) próbálja újra {} perc múlva",
"other": "Túl sok kérés (korlátozott arány) próbálja újra {} perc múlva" "other": "Túl sok kérés (korlátozott arány) próbálja újra {} perc múlva"

View File

@ -211,6 +211,7 @@
"language": "Lingua", "language": "Lingua",
"storagePermissionDenied": "Storage permission denied", "storagePermissionDenied": "Storage permission denied",
"selectedCategorizeWarning": "This will replace any existing category settings for the selected Apps.", "selectedCategorizeWarning": "This will replace any existing category settings for the selected Apps.",
"filterAPKsByRegEx": "Filter APKs by Regular Expression",
"tooManyRequestsTryAgainInMinutes": { "tooManyRequestsTryAgainInMinutes": {
"one": "Troppe richieste (traffico limitato) - riprova tra {} minuto", "one": "Troppe richieste (traffico limitato) - riprova tra {} minuto",
"other": "Troppe richieste (traffico limitato) - riprova tra {} minuti" "other": "Troppe richieste (traffico limitato) - riprova tra {} minuti"

View File

@ -211,6 +211,7 @@
"language": "言語", "language": "言語",
"storagePermissionDenied": "ストレージ権限が拒否されました", "storagePermissionDenied": "ストレージ権限が拒否されました",
"selectedCategorizeWarning": "これにより、選択したアプリの既存のカテゴリ設定がすべて置き換えられます。", "selectedCategorizeWarning": "これにより、選択したアプリの既存のカテゴリ設定がすべて置き換えられます。",
"filterAPKsByRegEx": "Filter APKs by Regular Expression",
"tooManyRequestsTryAgainInMinutes": { "tooManyRequestsTryAgainInMinutes": {
"one": "リクエストが多すぎます(レート制限)- {}分後に再試行してください", "one": "リクエストが多すぎます(レート制限)- {}分後に再試行してください",
"other": "リクエストが多すぎます(レート制限)- {}分後に再試行してください" "other": "リクエストが多すぎます(レート制限)- {}分後に再試行してください"

View File

@ -211,6 +211,7 @@
"language": "语言", "language": "语言",
"storagePermissionDenied": "存储权限已被拒绝", "storagePermissionDenied": "存储权限已被拒绝",
"selectedCategorizeWarning": "这将取代所选应用程序的任何现有类别", "selectedCategorizeWarning": "这将取代所选应用程序的任何现有类别",
"filterAPKsByRegEx": "Filter APKs by Regular Expression",
"tooManyRequestsTryAgainInMinutes": { "tooManyRequestsTryAgainInMinutes": {
"one": "请求过多 (API 限制) - 在 {} 分钟后重试", "one": "请求过多 (API 限制) - 在 {} 分钟后重试",
"other": "请求过多 (API 限制) - 在 {} 分钟后重试" "other": "请求过多 (API 限制) - 在 {} 分钟后重试"

View File

@ -26,15 +26,7 @@ class Codeberg extends AppSource {
required: false, required: false,
additionalValidators: [ additionalValidators: [
(value) { (value) {
if (value == null || value.isEmpty) { return regExValidator(value);
return null;
}
try {
RegExp(value);
} catch (e) {
return tr('invalidRegEx');
}
return null;
} }
]) ])
] ]
@ -72,7 +64,7 @@ class Codeberg extends AppSource {
? additionalSettings['filterReleaseTitlesByRegEx'] ? additionalSettings['filterReleaseTitlesByRegEx']
: null; : null;
Response res = await get(Uri.parse( Response res = await get(Uri.parse(
'https://$host/api/v1/repos${standardUrl.substring('https://$host'.length)}/releases')); 'https://$host/api/v1/repos${standardUrl.substring('https://$host'.length)}/releases?per_page=100'));
if (res.statusCode == 200) { if (res.statusCode == 200) {
var releases = jsonDecode(res.body) as List<dynamic>; var releases = jsonDecode(res.body) as List<dynamic>;

View File

@ -65,15 +65,7 @@ class GitHub extends AppSource {
required: false, required: false,
additionalValidators: [ additionalValidators: [
(value) { (value) {
if (value == null || value.isEmpty) { return regExValidator(value);
return null;
}
try {
RegExp(value);
} catch (e) {
return tr('invalidRegEx');
}
return null;
} }
]) ])
] ]
@ -119,7 +111,7 @@ class GitHub extends AppSource {
? additionalSettings['filterReleaseTitlesByRegEx'] ? additionalSettings['filterReleaseTitlesByRegEx']
: null; : null;
Response res = await get(Uri.parse( Response res = await get(Uri.parse(
'https://${await getCredentialPrefixIfAny()}api.$host/repos${standardUrl.substring('https://$host'.length)}/releases')); 'https://${await getCredentialPrefixIfAny()}api.$host/repos${standardUrl.substring('https://$host'.length)}/releases?per_page=100'));
if (res.statusCode == 200) { if (res.statusCode == 200) {
var releases = jsonDecode(res.body) as List<dynamic>; var releases = jsonDecode(res.body) as List<dynamic>;

View File

@ -29,7 +29,7 @@ class NoReleasesError extends ObtainiumError {
} }
class NoAPKError extends ObtainiumError { class NoAPKError extends ObtainiumError {
NoAPKError() : super(tr('noReleaseFound')); NoAPKError() : super(tr('noAPKFound'));
} }
class NoVersionError extends ObtainiumError { class NoVersionError extends ObtainiumError {

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.10.3'; const String currentVersion = '0.10.5';
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

@ -70,6 +70,7 @@ class _AddAppPageState extends State<AddAppPage> {
additionalSettings['noVersionDetection'] == true; additionalSettings['noVersionDetection'] == true;
var cont = true; var cont = true;
if ((userPickedTrackOnly || pickedSource!.enforceTrackOnly) && if ((userPickedTrackOnly || pickedSource!.enforceTrackOnly) &&
// ignore: use_build_context_synchronously
await showDialog( await showDialog(
context: context, context: context,
builder: (BuildContext ctx) { builder: (BuildContext ctx) {
@ -88,6 +89,7 @@ class _AddAppPageState extends State<AddAppPage> {
cont = false; cont = false;
} }
if (userPickedNoVersionDetection && if (userPickedNoVersionDetection &&
// ignore: use_build_context_synchronously
await showDialog( await showDialog(
context: context, context: context,
builder: (BuildContext ctx) { builder: (BuildContext ctx) {

View File

@ -317,7 +317,7 @@ class _AppPageState extends State<AppPage> {
tooltip: tr('more')), tooltip: tr('more')),
const SizedBox(width: 16.0), const SizedBox(width: 16.0),
Expanded( Expanded(
child: ElevatedButton( child: TextButton(
onPressed: (app?.app.installedVersion == null || onPressed: (app?.app.installedVersion == null ||
app?.app.installedVersion != app?.app.installedVersion !=
app?.app.latestVersion) && app?.app.latestVersion) &&
@ -356,7 +356,8 @@ class _AppPageState extends State<AppPage> {
? tr('update') ? tr('update')
: tr('markUpdated')))), : tr('markUpdated')))),
const SizedBox(width: 16.0), const SizedBox(width: 16.0),
ElevatedButton( Expanded(
child: TextButton(
onPressed: app?.downloadProgress != null onPressed: app?.downloadProgress != null
? null ? null
: () { : () {
@ -401,7 +402,7 @@ class _AppPageState extends State<AppPage> {
surfaceTintColor: surfaceTintColor:
Theme.of(context).colorScheme.error), Theme.of(context).colorScheme.error),
child: Text(tr('remove')), child: Text(tr('remove')),
), )),
])), ])),
if (app?.downloadProgress != null) if (app?.downloadProgress != null)
Padding( Padding(

View File

@ -344,13 +344,15 @@ class AppsPageState extends State<AppsPage> {
)); ));
}, childCount: sortedApps.length)) }, childCount: sortedApps.length))
])), ])),
persistentFooterButtons: [ persistentFooterButtons: appsProvider.apps.isEmpty
? null
: [
Row( Row(
children: [ children: [
selectedApps.isEmpty selectedApps.isEmpty
? TextButton.icon( ? TextButton.icon(
style: style: const ButtonStyle(
const ButtonStyle(visualDensity: VisualDensity.compact), visualDensity: VisualDensity.compact),
onPressed: () { onPressed: () {
selectThese(sortedApps.map((e) => e.app).toList()); selectThese(sortedApps.map((e) => e.app).toList());
}, },
@ -360,11 +362,12 @@ class AppsPageState extends State<AppsPage> {
), ),
label: Text(sortedApps.length.toString())) label: Text(sortedApps.length.toString()))
: TextButton.icon( : TextButton.icon(
style: style: const ButtonStyle(
const ButtonStyle(visualDensity: VisualDensity.compact), visualDensity: VisualDensity.compact),
onPressed: () { onPressed: () {
selectedApps.isEmpty selectedApps.isEmpty
? selectThese(sortedApps.map((e) => e.app).toList()) ? selectThese(
sortedApps.map((e) => e.app).toList())
: clearSelected(); : clearSelected();
}, },
icon: Icon( icon: Icon(
@ -390,15 +393,15 @@ class AppsPageState extends State<AppsPage> {
context: context, context: context,
builder: (BuildContext ctx) { builder: (BuildContext ctx) {
return GeneratedFormModal( return GeneratedFormModal(
title: title: tr(
tr('removeSelectedAppsQuestion'), 'removeSelectedAppsQuestion'),
items: const [], items: const [],
initValid: true, initValid: true,
message: tr( message: tr(
'xWillBeRemovedButRemainInstalled', 'xWillBeRemovedButRemainInstalled',
args: [ args: [
plural( plural('apps',
'apps', selectedApps.length) selectedApps.length)
]), ]),
); );
}).then((values) { }).then((values) {
@ -414,14 +417,19 @@ class AppsPageState extends State<AppsPage> {
), ),
IconButton( IconButton(
visualDensity: VisualDensity.compact, visualDensity: VisualDensity.compact,
onPressed: appsProvider.areDownloadsRunning() || onPressed: appsProvider
(existingUpdateIdsAllOrSelected.isEmpty && .areDownloadsRunning() ||
newInstallIdsAllOrSelected.isEmpty && (existingUpdateIdsAllOrSelected
trackOnlyUpdateIdsAllOrSelected.isEmpty) .isEmpty &&
newInstallIdsAllOrSelected
.isEmpty &&
trackOnlyUpdateIdsAllOrSelected
.isEmpty)
? null ? null
: () { : () {
HapticFeedback.heavyImpact(); HapticFeedback.heavyImpact();
List<GeneratedFormItem> formItems = []; List<GeneratedFormItem> formItems =
[];
if (existingUpdateIdsAllOrSelected if (existingUpdateIdsAllOrSelected
.isNotEmpty) { .isNotEmpty) {
formItems.add(GeneratedFormSwitch( formItems.add(GeneratedFormSwitch(
@ -434,7 +442,8 @@ class AppsPageState extends State<AppsPage> {
]), ]),
defaultValue: true)); defaultValue: true));
} }
if (newInstallIdsAllOrSelected.isNotEmpty) { if (newInstallIdsAllOrSelected
.isNotEmpty) {
formItems.add(GeneratedFormSwitch( formItems.add(GeneratedFormSwitch(
'installs', 'installs',
label: tr('installX', args: [ label: tr('installX', args: [
@ -451,7 +460,8 @@ class AppsPageState extends State<AppsPage> {
.isNotEmpty) { .isNotEmpty) {
formItems.add(GeneratedFormSwitch( formItems.add(GeneratedFormSwitch(
'trackonlies', 'trackonlies',
label: tr('markXTrackOnlyAsUpdated', label: tr(
'markXTrackOnlyAsUpdated',
args: [ args: [
plural( plural(
'apps', 'apps',
@ -467,8 +477,8 @@ class AppsPageState extends State<AppsPage> {
showDialog<Map<String, dynamic>?>( showDialog<Map<String, dynamic>?>(
context: context, context: context,
builder: (BuildContext ctx) { builder: (BuildContext ctx) {
var totalApps = var totalApps = existingUpdateIdsAllOrSelected
existingUpdateIdsAllOrSelected.length + .length +
newInstallIdsAllOrSelected newInstallIdsAllOrSelected
.length + .length +
trackOnlyUpdateIdsAllOrSelected trackOnlyUpdateIdsAllOrSelected
@ -560,7 +570,8 @@ class AppsPageState extends State<AppsPage> {
cont = await showDialog< cont = await showDialog<
Map<String, dynamic>?>( Map<String, dynamic>?>(
context: context, context: context,
builder: (BuildContext ctx) { builder:
(BuildContext ctx) {
return GeneratedFormModal( return GeneratedFormModal(
title: tr('categorize'), title: tr('categorize'),
items: const [], items: const [],
@ -572,7 +583,9 @@ class AppsPageState extends State<AppsPage> {
null; null;
} }
if (cont) { if (cont) {
await showDialog<Map<String, dynamic>?>( // ignore: use_build_context_synchronously
await showDialog<
Map<String, dynamic>?>(
context: context, context: context,
builder: (BuildContext ctx) { builder: (BuildContext ctx) {
return GeneratedFormModal( return GeneratedFormModal(
@ -586,11 +599,15 @@ class AppsPageState extends State<AppsPage> {
preselected: !showPrompt preselected: !showPrompt
? preselected ?? {} ? preselected ?? {}
: {}, : {},
showLabelWhenNotEmpty: false, showLabelWhenNotEmpty:
onSelected: (categories) { false,
onSelected:
(categories) {
appsProvider.saveApps( appsProvider.saveApps(
selectedApps.map((e) { selectedApps
e.categories = categories; .map((e) {
e.categories =
categories;
return e; return e;
}).toList()); }).toList());
}, },
@ -618,7 +635,8 @@ class AppsPageState extends State<AppsPage> {
scrollable: true, scrollable: true,
content: Padding( content: Padding(
padding: padding:
const EdgeInsets.only(top: 6), const EdgeInsets.only(
top: 6),
child: Row( child: Row(
mainAxisAlignment: mainAxisAlignment:
MainAxisAlignment MainAxisAlignment
@ -636,30 +654,23 @@ class AppsPageState extends State<AppsPage> {
(BuildContext (BuildContext
ctx) { ctx) {
return AlertDialog( return AlertDialog(
title: Text(tr( title:
'markXSelectedAppsAsUpdated', Text(tr('markXSelectedAppsAsUpdated', args: [
args: [
selectedApps.length.toString() selectedApps.length.toString()
])), ])),
content: content:
Text( Text(
tr('onlyWorksWithNonEVDApps'), tr('onlyWorksWithNonEVDApps'),
style: const TextStyle( style: const TextStyle(fontWeight: FontWeight.bold, fontStyle: FontStyle.italic),
fontWeight:
FontWeight.bold,
fontStyle: FontStyle.italic),
), ),
actions: [ actions: [
TextButton( TextButton(
onPressed: onPressed: () {
() {
Navigator.of(context).pop(); Navigator.of(context).pop();
}, },
child: child: Text(tr('no'))),
Text(tr('no'))),
TextButton( TextButton(
onPressed: onPressed: () {
() {
HapticFeedback.selectionClick(); HapticFeedback.selectionClick();
appsProvider.saveApps(selectedApps.map((a) { appsProvider.saveApps(selectedApps.map((a) {
if (a.installedVersion != null) { if (a.installedVersion != null) {
@ -670,8 +681,7 @@ class AppsPageState extends State<AppsPage> {
Navigator.of(context).pop(); Navigator.of(context).pop();
}, },
child: child: Text(tr('yes')))
Text(tr('yes')))
], ],
); );
}).whenComplete(() { }).whenComplete(() {
@ -686,29 +696,36 @@ class AppsPageState extends State<AppsPage> {
Icons.done)), Icons.done)),
IconButton( IconButton(
onPressed: () { onPressed: () {
var pinStatus = var pinStatus = selectedApps
selectedApps
.where((element) => .where((element) =>
element element
.pinned) .pinned)
.isEmpty; .isEmpty;
appsProvider.saveApps( appsProvider
selectedApps.map((e) { .saveApps(
e.pinned = pinStatus; selectedApps
.map(
(e) {
e.pinned =
pinStatus;
return e; return e;
}).toList()); }).toList());
Navigator.of(context) Navigator.of(
context)
.pop(); .pop();
}, },
tooltip: selectedApps tooltip: selectedApps
.where((element) => .where((element) =>
element.pinned) element
.pinned)
.isEmpty .isEmpty
? tr('pinToTop') ? tr('pinToTop')
: tr('unpinFromTop'), : tr(
'unpinFromTop'),
icon: Icon(selectedApps icon: Icon(selectedApps
.where((element) => .where((element) =>
element.pinned) element
.pinned)
.isEmpty .isEmpty
? Icons ? Icons
.bookmark_outline_rounded .bookmark_outline_rounded
@ -720,53 +737,63 @@ class AppsPageState extends State<AppsPage> {
String urls = ''; String urls = '';
for (var a for (var a
in selectedApps) { in selectedApps) {
urls += '${a.url}\n'; urls +=
'${a.url}\n';
} }
urls = urls.substring( urls =
0, urls.length - 1); urls.substring(
0,
urls.length -
1);
Share.share(urls, Share.share(urls,
subject: tr( subject: tr(
'selectedAppURLsFromObtainium')); 'selectedAppURLsFromObtainium'));
Navigator.of(context) Navigator.of(
context)
.pop(); .pop();
}, },
tooltip: tr( tooltip: tr(
'shareSelectedAppURLs'), 'shareSelectedAppURLs'),
icon: icon: const Icon(
const Icon(Icons.share), Icons.share),
), ),
IconButton( IconButton(
onPressed: () { onPressed: () {
showDialog( showDialog(
context: context, context:
builder: (BuildContext context,
builder:
(BuildContext
ctx) { ctx) {
return GeneratedFormModal( return GeneratedFormModal(
title: tr( title: tr(
'resetInstallStatusForSelectedAppsQuestion'), 'resetInstallStatusForSelectedAppsQuestion'),
items: const [], items: const [],
initValid: true, initValid:
true,
message: tr( message: tr(
'installStatusOfXWillBeResetExplanation', 'installStatusOfXWillBeResetExplanation',
args: [ args: [
plural( plural(
'app', 'app',
selectedApps selectedApps.length)
.length)
]), ]),
); );
}).then((values) { }).then((values) {
if (values != null) { if (values !=
null) {
appsProvider.saveApps( appsProvider.saveApps(
selectedApps selectedApps
.map((e) { .map(
(e) {
e.installedVersion = e.installedVersion =
null; null;
return e; return e;
}).toList()); }).toList());
} }
}).whenComplete(() { }).whenComplete(() {
Navigator.of(context) Navigator.of(
context)
.pop(); .pop();
}); });
}, },
@ -807,11 +834,9 @@ class AppsPageState extends State<AppsPage> {
color: Theme.of(context).colorScheme.primary, color: Theme.of(context).colorScheme.primary,
), ),
), ),
appsProvider.apps.isEmpty TextButton.icon(
? const SizedBox() style: const ButtonStyle(
: TextButton.icon( visualDensity: VisualDensity.compact),
style:
const ButtonStyle(visualDensity: VisualDensity.compact),
label: Text( label: Text(
filter.isIdenticalTo(neutralFilter, settingsProvider) filter.isIdenticalTo(neutralFilter, settingsProvider)
? tr('filter') ? tr('filter')
@ -859,7 +884,8 @@ class AppsPageState extends State<AppsPage> {
CategoryEditorSelector( CategoryEditorSelector(
preselected: filter.categoryFilter, preselected: filter.categoryFilter,
onSelected: (categories) { onSelected: (categories) {
filter.categoryFilter = categories.toSet(); filter.categoryFilter =
categories.toSet();
}, },
) )
], ],

View File

@ -4,10 +4,8 @@ import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:obtainium/components/custom_app_bar.dart'; import 'package:obtainium/components/custom_app_bar.dart';
import 'package:obtainium/components/generated_form.dart'; import 'package:obtainium/components/generated_form.dart';
import 'package:obtainium/components/generated_form_modal.dart';
import 'package:obtainium/custom_errors.dart'; import 'package:obtainium/custom_errors.dart';
import 'package:obtainium/main.dart'; import 'package:obtainium/main.dart';
import 'package:obtainium/providers/apps_provider.dart';
import 'package:obtainium/providers/logs_provider.dart'; import 'package:obtainium/providers/logs_provider.dart';
import 'package:obtainium/providers/settings_provider.dart'; import 'package:obtainium/providers/settings_provider.dart';
import 'package:obtainium/providers/source_provider.dart'; import 'package:obtainium/providers/source_provider.dart';

View File

@ -262,6 +262,7 @@ class AppsProvider with ChangeNotifier {
List<String> archs = (await DeviceInfoPlugin().androidInfo).supportedAbis; List<String> archs = (await DeviceInfoPlugin().androidInfo).supportedAbis;
if (app.apkUrls.length > 1 && context != null) { if (app.apkUrls.length > 1 && context != null) {
// ignore: use_build_context_synchronously
apkUrl = await showDialog( apkUrl = await showDialog(
context: context, context: context,
builder: (BuildContext ctx) { builder: (BuildContext ctx) {
@ -281,6 +282,7 @@ class AppsProvider with ChangeNotifier {
if (apkUrl != null && if (apkUrl != null &&
getHost(apkUrl) != getHost(app.url) && getHost(apkUrl) != getHost(app.url) &&
context != null) { context != null) {
// ignore: use_build_context_synchronously
if (await showDialog( if (await showDialog(
context: context, context: context,
builder: (BuildContext ctx) { builder: (BuildContext ctx) {

View File

@ -225,7 +225,19 @@ class AppSource {
label: tr('trackOnly'), label: tr('trackOnly'),
) )
], ],
[GeneratedFormSwitch('noVersionDetection', label: tr('noVersionDetection'))] [
GeneratedFormSwitch('noVersionDetection', label: tr('noVersionDetection'))
],
[
GeneratedFormTextField('apkFilterRegEx',
label: tr('filterAPKsByRegEx'),
required: false,
additionalValidators: [
(value) {
return regExValidator(value);
}
])
]
]; ];
// Previous 2 variables combined into one at runtime for convenient usage // Previous 2 variables combined into one at runtime for convenient usage
@ -269,6 +281,18 @@ abstract class MassAppUrlSource {
Future<Map<String, String>> getUrlsWithDescriptions(List<String> args); Future<Map<String, String>> getUrlsWithDescriptions(List<String> args);
} }
regExValidator(String? value) {
if (value == null || value.isEmpty) {
return null;
}
try {
RegExp(value);
} catch (e) {
return tr('invalidRegEx');
}
return null;
}
class SourceProvider { class SourceProvider {
// Add more source classes here so they are available via the service // Add more source classes here so they are available via the service
List<AppSource> sources = [ List<AppSource> sources = [
@ -344,10 +368,13 @@ class SourceProvider {
} }
Future<App> getApp( Future<App> getApp(
AppSource source, String url, Map<String, dynamic> additionalSettings, AppSource source,
{App? currentApp, String url,
Map<String, dynamic> additionalSettings, {
App? currentApp,
bool trackOnlyOverride = false, bool trackOnlyOverride = false,
noVersionDetectionOverride = false}) async { noVersionDetectionOverride = false,
}) async {
if (trackOnlyOverride || source.enforceTrackOnly) { if (trackOnlyOverride || source.enforceTrackOnly) {
additionalSettings['trackOnly'] = true; additionalSettings['trackOnly'] = true;
} }
@ -358,6 +385,11 @@ class SourceProvider {
String standardUrl = source.standardizeURL(preStandardizeUrl(url)); String standardUrl = source.standardizeURL(preStandardizeUrl(url));
APKDetails apk = APKDetails apk =
await source.getLatestAPKDetails(standardUrl, additionalSettings); await source.getLatestAPKDetails(standardUrl, additionalSettings);
if (additionalSettings['apkFilterRegEx'] != null) {
var reg = RegExp(additionalSettings['apkFilterRegEx']);
apk.apkUrls =
apk.apkUrls.where((element) => reg.hasMatch(element)).toList();
}
if (apk.apkUrls.isEmpty && !trackOnly) { if (apk.apkUrls.isEmpty && !trackOnly) {
throw NoAPKError(); throw NoAPKError();
} }

File diff suppressed because it is too large Load Diff

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.10.3+109 # When changing this, update the tag in main() accordingly version: 0.10.5+111 # 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'