mirror of
https://github.com/ImranR98/Obtainium.git
synced 2025-07-24 01:49:41 +02:00
Compare commits
12 Commits
v0.10.2-be
...
v0.10.5-be
Author | SHA1 | Date | |
---|---|---|---|
|
4252c2711b | ||
|
52913b0450 | ||
|
427b0ed8d2 | ||
|
a85d6d4f08 | ||
|
05f712603c | ||
|
fa2a80e34c | ||
|
f43e5a2ff1 | ||
|
b72aa8273e | ||
|
520f186e4a | ||
|
e1e97672cf | ||
|
1494bcd013 | ||
|
3457a0a12f |
@@ -2,4 +2,5 @@
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@color/ic_launcher_background"/>
|
||||
<foreground android:drawable="@drawable/ic_launcher_foreground"/>
|
||||
<monochrome android:drawable="@drawable/ic_launcher_foreground"/>
|
||||
</adaptive-icon>
|
||||
|
@@ -211,6 +211,7 @@
|
||||
"language": "Sprache",
|
||||
"storagePermissionDenied": "Storage permission denied",
|
||||
"selectedCategorizeWarning": "This will replace any existing category settings for the selected Apps.",
|
||||
"filterAPKsByRegEx": "Filter APKs by Regular Expression",
|
||||
"tooManyRequestsTryAgainInMinutes": {
|
||||
"one": "Zu viele Anfragen (Rate begrenzt) - versuchen Sie es in {} Minute erneut",
|
||||
"other": "Zu viele Anfragen (Rate begrenzt) - versuchen Sie es in {} Minuten erneut"
|
||||
|
@@ -211,6 +211,7 @@
|
||||
"language": "Language",
|
||||
"storagePermissionDenied": "Storage permission denied",
|
||||
"selectedCategorizeWarning": "This will replace any existing category settings for the selected Apps.",
|
||||
"filterAPKsByRegEx": "Filter APKs by Regular Expression",
|
||||
"tooManyRequestsTryAgainInMinutes": {
|
||||
"one": "Too many requests (rate limited) - try again in {} minute",
|
||||
"other": "Too many requests (rate limited) - try again in {} minutes"
|
||||
|
@@ -210,6 +210,7 @@
|
||||
"language": "Nyelv",
|
||||
"storagePermissionDenied": "Tárhely engedély megtagadva",
|
||||
"selectedCategorizeWarning": "Ez felváltja a kiválasztott alkalmazások meglévő kategória-beállításait.",
|
||||
"filterAPKsByRegEx": "Filter APKs by Regular Expression",
|
||||
"tooManyRequestsTryAgainInMinutes": {
|
||||
"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"
|
||||
|
@@ -211,6 +211,7 @@
|
||||
"language": "Lingua",
|
||||
"storagePermissionDenied": "Storage permission denied",
|
||||
"selectedCategorizeWarning": "This will replace any existing category settings for the selected Apps.",
|
||||
"filterAPKsByRegEx": "Filter APKs by Regular Expression",
|
||||
"tooManyRequestsTryAgainInMinutes": {
|
||||
"one": "Troppe richieste (traffico limitato) - riprova tra {} minuto",
|
||||
"other": "Troppe richieste (traffico limitato) - riprova tra {} minuti"
|
||||
|
@@ -211,6 +211,7 @@
|
||||
"language": "言語",
|
||||
"storagePermissionDenied": "ストレージ権限が拒否されました",
|
||||
"selectedCategorizeWarning": "これにより、選択したアプリの既存のカテゴリ設定がすべて置き換えられます。",
|
||||
"filterAPKsByRegEx": "Filter APKs by Regular Expression",
|
||||
"tooManyRequestsTryAgainInMinutes": {
|
||||
"one": "リクエストが多すぎます(レート制限)- {}分後に再試行してください",
|
||||
"other": "リクエストが多すぎます(レート制限)- {}分後に再試行してください"
|
||||
|
@@ -211,6 +211,7 @@
|
||||
"language": "语言",
|
||||
"storagePermissionDenied": "存储权限已被拒绝",
|
||||
"selectedCategorizeWarning": "这将取代所选应用程序的任何现有类别",
|
||||
"filterAPKsByRegEx": "Filter APKs by Regular Expression",
|
||||
"tooManyRequestsTryAgainInMinutes": {
|
||||
"one": "请求过多 (API 限制) - 在 {} 分钟后重试",
|
||||
"other": "请求过多 (API 限制) - 在 {} 分钟后重试"
|
||||
|
@@ -26,15 +26,7 @@ class Codeberg extends AppSource {
|
||||
required: false,
|
||||
additionalValidators: [
|
||||
(value) {
|
||||
if (value == null || value.isEmpty) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
RegExp(value);
|
||||
} catch (e) {
|
||||
return tr('invalidRegEx');
|
||||
}
|
||||
return null;
|
||||
return regExValidator(value);
|
||||
}
|
||||
])
|
||||
]
|
||||
@@ -72,7 +64,7 @@ class Codeberg extends AppSource {
|
||||
? additionalSettings['filterReleaseTitlesByRegEx']
|
||||
: null;
|
||||
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) {
|
||||
var releases = jsonDecode(res.body) as List<dynamic>;
|
||||
|
||||
@@ -99,8 +91,8 @@ class Codeberg extends AppSource {
|
||||
if (releases[i]['draft'] == true) {
|
||||
// Draft releases not supported
|
||||
}
|
||||
var nameToFilter = releases[i]['name'] as String;
|
||||
if (nameToFilter.trim().isEmpty) {
|
||||
var nameToFilter = releases[i]['name'] as String?;
|
||||
if (nameToFilter == null || nameToFilter.trim().isEmpty) {
|
||||
// Some leave titles empty so tag is used
|
||||
nameToFilter = releases[i]['tag_name'] as String;
|
||||
}
|
||||
|
@@ -65,15 +65,7 @@ class GitHub extends AppSource {
|
||||
required: false,
|
||||
additionalValidators: [
|
||||
(value) {
|
||||
if (value == null || value.isEmpty) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
RegExp(value);
|
||||
} catch (e) {
|
||||
return tr('invalidRegEx');
|
||||
}
|
||||
return null;
|
||||
return regExValidator(value);
|
||||
}
|
||||
])
|
||||
]
|
||||
@@ -119,7 +111,7 @@ class GitHub extends AppSource {
|
||||
? additionalSettings['filterReleaseTitlesByRegEx']
|
||||
: null;
|
||||
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) {
|
||||
var releases = jsonDecode(res.body) as List<dynamic>;
|
||||
|
||||
@@ -141,8 +133,8 @@ class GitHub extends AppSource {
|
||||
if (!includePrereleases && releases[i]['prerelease'] == true) {
|
||||
continue;
|
||||
}
|
||||
var nameToFilter = releases[i]['name'] as String;
|
||||
if (nameToFilter.trim().isEmpty) {
|
||||
var nameToFilter = releases[i]['name'] as String?;
|
||||
if (nameToFilter == null || nameToFilter.trim().isEmpty) {
|
||||
// Some leave titles empty so tag is used
|
||||
nameToFilter = releases[i]['tag_name'] as String;
|
||||
}
|
||||
|
@@ -29,7 +29,7 @@ class NoReleasesError extends ObtainiumError {
|
||||
}
|
||||
|
||||
class NoAPKError extends ObtainiumError {
|
||||
NoAPKError() : super(tr('noReleaseFound'));
|
||||
NoAPKError() : super(tr('noAPKFound'));
|
||||
}
|
||||
|
||||
class NoVersionError extends ObtainiumError {
|
||||
|
@@ -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.10.2';
|
||||
const String currentVersion = '0.10.5';
|
||||
const String currentReleaseTag =
|
||||
'v$currentVersion-beta'; // KEEP THIS IN SYNC WITH GITHUB RELEASES
|
||||
|
||||
|
@@ -70,6 +70,7 @@ class _AddAppPageState extends State<AddAppPage> {
|
||||
additionalSettings['noVersionDetection'] == true;
|
||||
var cont = true;
|
||||
if ((userPickedTrackOnly || pickedSource!.enforceTrackOnly) &&
|
||||
// ignore: use_build_context_synchronously
|
||||
await showDialog(
|
||||
context: context,
|
||||
builder: (BuildContext ctx) {
|
||||
@@ -88,6 +89,7 @@ class _AddAppPageState extends State<AddAppPage> {
|
||||
cont = false;
|
||||
}
|
||||
if (userPickedNoVersionDetection &&
|
||||
// ignore: use_build_context_synchronously
|
||||
await showDialog(
|
||||
context: context,
|
||||
builder: (BuildContext ctx) {
|
||||
|
@@ -317,7 +317,7 @@ class _AppPageState extends State<AppPage> {
|
||||
tooltip: tr('more')),
|
||||
const SizedBox(width: 16.0),
|
||||
Expanded(
|
||||
child: ElevatedButton(
|
||||
child: TextButton(
|
||||
onPressed: (app?.app.installedVersion == null ||
|
||||
app?.app.installedVersion !=
|
||||
app?.app.latestVersion) &&
|
||||
@@ -356,7 +356,8 @@ class _AppPageState extends State<AppPage> {
|
||||
? tr('update')
|
||||
: tr('markUpdated')))),
|
||||
const SizedBox(width: 16.0),
|
||||
ElevatedButton(
|
||||
Expanded(
|
||||
child: TextButton(
|
||||
onPressed: app?.downloadProgress != null
|
||||
? null
|
||||
: () {
|
||||
@@ -401,7 +402,7 @@ class _AppPageState extends State<AppPage> {
|
||||
surfaceTintColor:
|
||||
Theme.of(context).colorScheme.error),
|
||||
child: Text(tr('remove')),
|
||||
),
|
||||
)),
|
||||
])),
|
||||
if (app?.downloadProgress != null)
|
||||
Padding(
|
||||
|
@@ -344,13 +344,15 @@ class AppsPageState extends State<AppsPage> {
|
||||
));
|
||||
}, childCount: sortedApps.length))
|
||||
])),
|
||||
persistentFooterButtons: [
|
||||
persistentFooterButtons: appsProvider.apps.isEmpty
|
||||
? null
|
||||
: [
|
||||
Row(
|
||||
children: [
|
||||
selectedApps.isEmpty
|
||||
? TextButton.icon(
|
||||
style:
|
||||
const ButtonStyle(visualDensity: VisualDensity.compact),
|
||||
style: const ButtonStyle(
|
||||
visualDensity: VisualDensity.compact),
|
||||
onPressed: () {
|
||||
selectThese(sortedApps.map((e) => e.app).toList());
|
||||
},
|
||||
@@ -360,11 +362,12 @@ class AppsPageState extends State<AppsPage> {
|
||||
),
|
||||
label: Text(sortedApps.length.toString()))
|
||||
: TextButton.icon(
|
||||
style:
|
||||
const ButtonStyle(visualDensity: VisualDensity.compact),
|
||||
style: const ButtonStyle(
|
||||
visualDensity: VisualDensity.compact),
|
||||
onPressed: () {
|
||||
selectedApps.isEmpty
|
||||
? selectThese(sortedApps.map((e) => e.app).toList())
|
||||
? selectThese(
|
||||
sortedApps.map((e) => e.app).toList())
|
||||
: clearSelected();
|
||||
},
|
||||
icon: Icon(
|
||||
@@ -390,15 +393,15 @@ class AppsPageState extends State<AppsPage> {
|
||||
context: context,
|
||||
builder: (BuildContext ctx) {
|
||||
return GeneratedFormModal(
|
||||
title:
|
||||
tr('removeSelectedAppsQuestion'),
|
||||
title: tr(
|
||||
'removeSelectedAppsQuestion'),
|
||||
items: const [],
|
||||
initValid: true,
|
||||
message: tr(
|
||||
'xWillBeRemovedButRemainInstalled',
|
||||
args: [
|
||||
plural(
|
||||
'apps', selectedApps.length)
|
||||
plural('apps',
|
||||
selectedApps.length)
|
||||
]),
|
||||
);
|
||||
}).then((values) {
|
||||
@@ -414,14 +417,19 @@ class AppsPageState extends State<AppsPage> {
|
||||
),
|
||||
IconButton(
|
||||
visualDensity: VisualDensity.compact,
|
||||
onPressed: appsProvider.areDownloadsRunning() ||
|
||||
(existingUpdateIdsAllOrSelected.isEmpty &&
|
||||
newInstallIdsAllOrSelected.isEmpty &&
|
||||
trackOnlyUpdateIdsAllOrSelected.isEmpty)
|
||||
onPressed: appsProvider
|
||||
.areDownloadsRunning() ||
|
||||
(existingUpdateIdsAllOrSelected
|
||||
.isEmpty &&
|
||||
newInstallIdsAllOrSelected
|
||||
.isEmpty &&
|
||||
trackOnlyUpdateIdsAllOrSelected
|
||||
.isEmpty)
|
||||
? null
|
||||
: () {
|
||||
HapticFeedback.heavyImpact();
|
||||
List<GeneratedFormItem> formItems = [];
|
||||
List<GeneratedFormItem> formItems =
|
||||
[];
|
||||
if (existingUpdateIdsAllOrSelected
|
||||
.isNotEmpty) {
|
||||
formItems.add(GeneratedFormSwitch(
|
||||
@@ -434,7 +442,8 @@ class AppsPageState extends State<AppsPage> {
|
||||
]),
|
||||
defaultValue: true));
|
||||
}
|
||||
if (newInstallIdsAllOrSelected.isNotEmpty) {
|
||||
if (newInstallIdsAllOrSelected
|
||||
.isNotEmpty) {
|
||||
formItems.add(GeneratedFormSwitch(
|
||||
'installs',
|
||||
label: tr('installX', args: [
|
||||
@@ -451,7 +460,8 @@ class AppsPageState extends State<AppsPage> {
|
||||
.isNotEmpty) {
|
||||
formItems.add(GeneratedFormSwitch(
|
||||
'trackonlies',
|
||||
label: tr('markXTrackOnlyAsUpdated',
|
||||
label: tr(
|
||||
'markXTrackOnlyAsUpdated',
|
||||
args: [
|
||||
plural(
|
||||
'apps',
|
||||
@@ -467,8 +477,8 @@ class AppsPageState extends State<AppsPage> {
|
||||
showDialog<Map<String, dynamic>?>(
|
||||
context: context,
|
||||
builder: (BuildContext ctx) {
|
||||
var totalApps =
|
||||
existingUpdateIdsAllOrSelected.length +
|
||||
var totalApps = existingUpdateIdsAllOrSelected
|
||||
.length +
|
||||
newInstallIdsAllOrSelected
|
||||
.length +
|
||||
trackOnlyUpdateIdsAllOrSelected
|
||||
@@ -560,7 +570,8 @@ class AppsPageState extends State<AppsPage> {
|
||||
cont = await showDialog<
|
||||
Map<String, dynamic>?>(
|
||||
context: context,
|
||||
builder: (BuildContext ctx) {
|
||||
builder:
|
||||
(BuildContext ctx) {
|
||||
return GeneratedFormModal(
|
||||
title: tr('categorize'),
|
||||
items: const [],
|
||||
@@ -572,7 +583,9 @@ class AppsPageState extends State<AppsPage> {
|
||||
null;
|
||||
}
|
||||
if (cont) {
|
||||
await showDialog<Map<String, dynamic>?>(
|
||||
// ignore: use_build_context_synchronously
|
||||
await showDialog<
|
||||
Map<String, dynamic>?>(
|
||||
context: context,
|
||||
builder: (BuildContext ctx) {
|
||||
return GeneratedFormModal(
|
||||
@@ -586,11 +599,15 @@ class AppsPageState extends State<AppsPage> {
|
||||
preselected: !showPrompt
|
||||
? preselected ?? {}
|
||||
: {},
|
||||
showLabelWhenNotEmpty: false,
|
||||
onSelected: (categories) {
|
||||
showLabelWhenNotEmpty:
|
||||
false,
|
||||
onSelected:
|
||||
(categories) {
|
||||
appsProvider.saveApps(
|
||||
selectedApps.map((e) {
|
||||
e.categories = categories;
|
||||
selectedApps
|
||||
.map((e) {
|
||||
e.categories =
|
||||
categories;
|
||||
return e;
|
||||
}).toList());
|
||||
},
|
||||
@@ -618,7 +635,8 @@ class AppsPageState extends State<AppsPage> {
|
||||
scrollable: true,
|
||||
content: Padding(
|
||||
padding:
|
||||
const EdgeInsets.only(top: 6),
|
||||
const EdgeInsets.only(
|
||||
top: 6),
|
||||
child: Row(
|
||||
mainAxisAlignment:
|
||||
MainAxisAlignment
|
||||
@@ -636,30 +654,23 @@ class AppsPageState extends State<AppsPage> {
|
||||
(BuildContext
|
||||
ctx) {
|
||||
return AlertDialog(
|
||||
title: Text(tr(
|
||||
'markXSelectedAppsAsUpdated',
|
||||
args: [
|
||||
title:
|
||||
Text(tr('markXSelectedAppsAsUpdated', args: [
|
||||
selectedApps.length.toString()
|
||||
])),
|
||||
content:
|
||||
Text(
|
||||
tr('onlyWorksWithNonEVDApps'),
|
||||
style: const TextStyle(
|
||||
fontWeight:
|
||||
FontWeight.bold,
|
||||
fontStyle: FontStyle.italic),
|
||||
style: const TextStyle(fontWeight: FontWeight.bold, fontStyle: FontStyle.italic),
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed:
|
||||
() {
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
child:
|
||||
Text(tr('no'))),
|
||||
child: Text(tr('no'))),
|
||||
TextButton(
|
||||
onPressed:
|
||||
() {
|
||||
onPressed: () {
|
||||
HapticFeedback.selectionClick();
|
||||
appsProvider.saveApps(selectedApps.map((a) {
|
||||
if (a.installedVersion != null) {
|
||||
@@ -670,8 +681,7 @@ class AppsPageState extends State<AppsPage> {
|
||||
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
child:
|
||||
Text(tr('yes')))
|
||||
child: Text(tr('yes')))
|
||||
],
|
||||
);
|
||||
}).whenComplete(() {
|
||||
@@ -686,29 +696,36 @@ class AppsPageState extends State<AppsPage> {
|
||||
Icons.done)),
|
||||
IconButton(
|
||||
onPressed: () {
|
||||
var pinStatus =
|
||||
selectedApps
|
||||
var pinStatus = selectedApps
|
||||
.where((element) =>
|
||||
element
|
||||
.pinned)
|
||||
.isEmpty;
|
||||
appsProvider.saveApps(
|
||||
selectedApps.map((e) {
|
||||
e.pinned = pinStatus;
|
||||
appsProvider
|
||||
.saveApps(
|
||||
selectedApps
|
||||
.map(
|
||||
(e) {
|
||||
e.pinned =
|
||||
pinStatus;
|
||||
return e;
|
||||
}).toList());
|
||||
Navigator.of(context)
|
||||
Navigator.of(
|
||||
context)
|
||||
.pop();
|
||||
},
|
||||
tooltip: selectedApps
|
||||
.where((element) =>
|
||||
element.pinned)
|
||||
element
|
||||
.pinned)
|
||||
.isEmpty
|
||||
? tr('pinToTop')
|
||||
: tr('unpinFromTop'),
|
||||
: tr(
|
||||
'unpinFromTop'),
|
||||
icon: Icon(selectedApps
|
||||
.where((element) =>
|
||||
element.pinned)
|
||||
element
|
||||
.pinned)
|
||||
.isEmpty
|
||||
? Icons
|
||||
.bookmark_outline_rounded
|
||||
@@ -720,53 +737,63 @@ class AppsPageState extends State<AppsPage> {
|
||||
String urls = '';
|
||||
for (var a
|
||||
in selectedApps) {
|
||||
urls += '${a.url}\n';
|
||||
urls +=
|
||||
'${a.url}\n';
|
||||
}
|
||||
urls = urls.substring(
|
||||
0, urls.length - 1);
|
||||
urls =
|
||||
urls.substring(
|
||||
0,
|
||||
urls.length -
|
||||
1);
|
||||
Share.share(urls,
|
||||
subject: tr(
|
||||
'selectedAppURLsFromObtainium'));
|
||||
Navigator.of(context)
|
||||
Navigator.of(
|
||||
context)
|
||||
.pop();
|
||||
},
|
||||
tooltip: tr(
|
||||
'shareSelectedAppURLs'),
|
||||
icon:
|
||||
const Icon(Icons.share),
|
||||
icon: const Icon(
|
||||
Icons.share),
|
||||
),
|
||||
IconButton(
|
||||
onPressed: () {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (BuildContext
|
||||
context:
|
||||
context,
|
||||
builder:
|
||||
(BuildContext
|
||||
ctx) {
|
||||
return GeneratedFormModal(
|
||||
title: tr(
|
||||
'resetInstallStatusForSelectedAppsQuestion'),
|
||||
items: const [],
|
||||
initValid: true,
|
||||
initValid:
|
||||
true,
|
||||
message: tr(
|
||||
'installStatusOfXWillBeResetExplanation',
|
||||
args: [
|
||||
plural(
|
||||
'app',
|
||||
selectedApps
|
||||
.length)
|
||||
selectedApps.length)
|
||||
]),
|
||||
);
|
||||
}).then((values) {
|
||||
if (values != null) {
|
||||
if (values !=
|
||||
null) {
|
||||
appsProvider.saveApps(
|
||||
selectedApps
|
||||
.map((e) {
|
||||
.map(
|
||||
(e) {
|
||||
e.installedVersion =
|
||||
null;
|
||||
return e;
|
||||
}).toList());
|
||||
}
|
||||
}).whenComplete(() {
|
||||
Navigator.of(context)
|
||||
Navigator.of(
|
||||
context)
|
||||
.pop();
|
||||
});
|
||||
},
|
||||
@@ -807,11 +834,9 @@ class AppsPageState extends State<AppsPage> {
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
),
|
||||
),
|
||||
appsProvider.apps.isEmpty
|
||||
? const SizedBox()
|
||||
: TextButton.icon(
|
||||
style:
|
||||
const ButtonStyle(visualDensity: VisualDensity.compact),
|
||||
TextButton.icon(
|
||||
style: const ButtonStyle(
|
||||
visualDensity: VisualDensity.compact),
|
||||
label: Text(
|
||||
filter.isIdenticalTo(neutralFilter, settingsProvider)
|
||||
? tr('filter')
|
||||
@@ -859,7 +884,8 @@ class AppsPageState extends State<AppsPage> {
|
||||
CategoryEditorSelector(
|
||||
preselected: filter.categoryFilter,
|
||||
onSelected: (categories) {
|
||||
filter.categoryFilter = categories.toSet();
|
||||
filter.categoryFilter =
|
||||
categories.toSet();
|
||||
},
|
||||
)
|
||||
],
|
||||
|
@@ -4,10 +4,8 @@ import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:obtainium/components/custom_app_bar.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/main.dart';
|
||||
import 'package:obtainium/providers/apps_provider.dart';
|
||||
import 'package:obtainium/providers/logs_provider.dart';
|
||||
import 'package:obtainium/providers/settings_provider.dart';
|
||||
import 'package:obtainium/providers/source_provider.dart';
|
||||
|
@@ -262,6 +262,7 @@ class AppsProvider with ChangeNotifier {
|
||||
List<String> archs = (await DeviceInfoPlugin().androidInfo).supportedAbis;
|
||||
|
||||
if (app.apkUrls.length > 1 && context != null) {
|
||||
// ignore: use_build_context_synchronously
|
||||
apkUrl = await showDialog(
|
||||
context: context,
|
||||
builder: (BuildContext ctx) {
|
||||
@@ -281,6 +282,7 @@ class AppsProvider with ChangeNotifier {
|
||||
if (apkUrl != null &&
|
||||
getHost(apkUrl) != getHost(app.url) &&
|
||||
context != null) {
|
||||
// ignore: use_build_context_synchronously
|
||||
if (await showDialog(
|
||||
context: context,
|
||||
builder: (BuildContext ctx) {
|
||||
|
@@ -225,7 +225,19 @@ class AppSource {
|
||||
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
|
||||
@@ -269,6 +281,18 @@ abstract class MassAppUrlSource {
|
||||
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 {
|
||||
// Add more source classes here so they are available via the service
|
||||
List<AppSource> sources = [
|
||||
@@ -344,10 +368,13 @@ class SourceProvider {
|
||||
}
|
||||
|
||||
Future<App> getApp(
|
||||
AppSource source, String url, Map<String, dynamic> additionalSettings,
|
||||
{App? currentApp,
|
||||
AppSource source,
|
||||
String url,
|
||||
Map<String, dynamic> additionalSettings, {
|
||||
App? currentApp,
|
||||
bool trackOnlyOverride = false,
|
||||
noVersionDetectionOverride = false}) async {
|
||||
noVersionDetectionOverride = false,
|
||||
}) async {
|
||||
if (trackOnlyOverride || source.enforceTrackOnly) {
|
||||
additionalSettings['trackOnly'] = true;
|
||||
}
|
||||
@@ -358,6 +385,11 @@ class SourceProvider {
|
||||
String standardUrl = source.standardizeURL(preStandardizeUrl(url));
|
||||
APKDetails apk =
|
||||
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) {
|
||||
throw NoAPKError();
|
||||
}
|
||||
|
402
pubspec.lock
402
pubspec.lock
File diff suppressed because it is too large
Load Diff
@@ -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.10.2+108 # When changing this, update the tag in main() accordingly
|
||||
version: 0.10.5+111 # When changing this, update the tag in main() accordingly
|
||||
|
||||
environment:
|
||||
sdk: '>=2.18.2 <3.0.0'
|
||||
|
Reference in New Issue
Block a user