mirror of
https://github.com/ImranR98/Obtainium.git
synced 2025-07-15 14:16:43 +02:00
Compare commits
10 Commits
v0.10.3-be
...
v0.10.5-be
Author | SHA1 | Date | |
---|---|---|---|
4252c2711b | |||
52913b0450 | |||
427b0ed8d2 | |||
a85d6d4f08 | |||
05f712603c | |||
fa2a80e34c | |||
f43e5a2ff1 | |||
b72aa8273e | |||
520f186e4a | |||
e1e97672cf |
@ -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>
|
||||||
|
@ -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"
|
||||||
|
@ -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"
|
||||||
|
@ -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"
|
||||||
|
@ -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"
|
||||||
|
@ -211,6 +211,7 @@
|
|||||||
"language": "言語",
|
"language": "言語",
|
||||||
"storagePermissionDenied": "ストレージ権限が拒否されました",
|
"storagePermissionDenied": "ストレージ権限が拒否されました",
|
||||||
"selectedCategorizeWarning": "これにより、選択したアプリの既存のカテゴリ設定がすべて置き換えられます。",
|
"selectedCategorizeWarning": "これにより、選択したアプリの既存のカテゴリ設定がすべて置き換えられます。",
|
||||||
|
"filterAPKsByRegEx": "Filter APKs by Regular Expression",
|
||||||
"tooManyRequestsTryAgainInMinutes": {
|
"tooManyRequestsTryAgainInMinutes": {
|
||||||
"one": "リクエストが多すぎます(レート制限)- {}分後に再試行してください",
|
"one": "リクエストが多すぎます(レート制限)- {}分後に再試行してください",
|
||||||
"other": "リクエストが多すぎます(レート制限)- {}分後に再試行してください"
|
"other": "リクエストが多すぎます(レート制限)- {}分後に再試行してください"
|
||||||
|
@ -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 限制) - 在 {} 分钟后重试"
|
||||||
|
@ -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>;
|
||||||
|
|
||||||
|
@ -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>;
|
||||||
|
|
||||||
|
@ -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 {
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
@ -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) {
|
||||||
|
@ -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(
|
||||||
|
@ -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();
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
|
@ -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';
|
||||||
|
@ -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) {
|
||||||
|
@ -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();
|
||||||
}
|
}
|
||||||
|
398
pubspec.lock
398
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
|
# 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'
|
||||||
|
Reference in New Issue
Block a user