mirror of
https://github.com/ImranR98/Obtainium.git
synced 2025-08-19 21:19:28 +02:00
- All Sources now have a "Track-Only" option that will prevent Obtainium from looking for APKs (though the App must still have a release of some kind so that a version string can be grabbed). - These Apps cannot be installed through Obtainium, but update notifications will still be sent. - The user needs to manually mark them as updated when appropriate. - This addresses issue #119. - It also partially addresses #44 by allowing some sources to be configured as "Track-Only"-only. The first such source (APKMirror) will be added later. - Includes various UI changes to accommodate the above change. - Also makes App loading a bit more responsive (sending Obtainium to the background then returning will now cause App re-load to pick up changes in App versioning that may have been made in the meantime, for instance through update checking).
This commit is contained in:
@@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.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/pages/app.dart';
|
||||
import 'package:obtainium/providers/apps_provider.dart';
|
||||
@@ -22,8 +23,10 @@ class _AddAppPageState extends State<AddAppPage> {
|
||||
|
||||
String userInput = '';
|
||||
AppSource? pickedSource;
|
||||
List<String> additionalData = [];
|
||||
bool validAdditionalData = true;
|
||||
List<String> sourceSpecificAdditionalData = [];
|
||||
bool sourceSpecificDataIsValid = true;
|
||||
List<String> otherAdditionalData = [];
|
||||
bool otherAdditionalDataIsValid = true;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@@ -67,23 +70,34 @@ class _AddAppPageState extends State<AddAppPage> {
|
||||
]
|
||||
],
|
||||
onValueChanges: (values, valid, isBuilding) {
|
||||
setState(() {
|
||||
fn() {
|
||||
userInput = values[0];
|
||||
var source = valid
|
||||
? sourceProvider.getSource(userInput)
|
||||
: null;
|
||||
if (pickedSource != source) {
|
||||
pickedSource = source;
|
||||
additionalData = source != null
|
||||
? source.additionalDataDefaults
|
||||
sourceSpecificAdditionalData = source !=
|
||||
null
|
||||
? source
|
||||
.additionalSourceAppSpecificDefaults
|
||||
: [];
|
||||
validAdditionalData = source != null
|
||||
sourceSpecificDataIsValid = source !=
|
||||
null
|
||||
? sourceProvider
|
||||
.ifSourceAppsRequireAdditionalData(
|
||||
source)
|
||||
: true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (isBuilding) {
|
||||
fn();
|
||||
} else {
|
||||
setState(() {
|
||||
fn();
|
||||
});
|
||||
}
|
||||
},
|
||||
defaultValues: const [])),
|
||||
const SizedBox(
|
||||
@@ -94,9 +108,14 @@ class _AddAppPageState extends State<AddAppPage> {
|
||||
: ElevatedButton(
|
||||
onPressed: gettingAppInfo ||
|
||||
pickedSource == null ||
|
||||
(pickedSource!.additionalDataFormItems
|
||||
(pickedSource!
|
||||
.additionalSourceAppSpecificFormItems
|
||||
.isNotEmpty &&
|
||||
!validAdditionalData)
|
||||
!sourceSpecificDataIsValid) ||
|
||||
(pickedSource!
|
||||
.additionalAppSpecificSourceAgnosticDefaults
|
||||
.isNotEmpty &&
|
||||
!otherAdditionalDataIsValid)
|
||||
? null
|
||||
: () async {
|
||||
setState(() {
|
||||
@@ -107,47 +126,87 @@ class _AddAppPageState extends State<AddAppPage> {
|
||||
var settingsProvider =
|
||||
context.read<SettingsProvider>();
|
||||
() async {
|
||||
HapticFeedback.selectionClick();
|
||||
App app =
|
||||
await sourceProvider.getApp(
|
||||
pickedSource!,
|
||||
userInput,
|
||||
additionalData);
|
||||
await settingsProvider
|
||||
.getInstallPermission();
|
||||
// Only download the APK here if you need to for the package ID
|
||||
if (sourceProvider
|
||||
.isTempId(app.id)) {
|
||||
// ignore: use_build_context_synchronously
|
||||
var apkUrl = await appsProvider
|
||||
.confirmApkUrl(app, context);
|
||||
if (apkUrl == null) {
|
||||
throw ObtainiumError(
|
||||
'Cancelled');
|
||||
var userPickedTrackOnly =
|
||||
findGeneratedFormValueByKey(
|
||||
pickedSource!
|
||||
.additionalAppSpecificSourceAgnosticFormItems,
|
||||
otherAdditionalData,
|
||||
'trackOnlyFormItemKey') ==
|
||||
'true';
|
||||
var cont = true;
|
||||
if ((userPickedTrackOnly ||
|
||||
pickedSource!
|
||||
.enforceTrackOnly) &&
|
||||
await showDialog(
|
||||
context: context,
|
||||
builder:
|
||||
(BuildContext ctx) {
|
||||
return GeneratedFormModal(
|
||||
title:
|
||||
'App is Track-Only',
|
||||
items: const [],
|
||||
defaultValues: const [],
|
||||
message:
|
||||
'${pickedSource!.enforceTrackOnly ? 'Apps from this source are \'Track-Only\'.' : 'You have selected the \'Track-Only\' option.'}\n\nThe App will be tracked for updates, but Obtainium will not be able to download or install it.',
|
||||
);
|
||||
}) ==
|
||||
null) {
|
||||
cont = false;
|
||||
}
|
||||
if (cont) {
|
||||
HapticFeedback.selectionClick();
|
||||
App app = await sourceProvider.getApp(
|
||||
pickedSource!,
|
||||
userInput,
|
||||
sourceSpecificAdditionalData,
|
||||
trackOnly: pickedSource!
|
||||
.enforceTrackOnly ||
|
||||
userPickedTrackOnly);
|
||||
await settingsProvider
|
||||
.getInstallPermission();
|
||||
// Only download the APK here if you need to for the package ID
|
||||
if (sourceProvider
|
||||
.isTempId(app.id) &&
|
||||
!app.trackOnly) {
|
||||
// ignore: use_build_context_synchronously
|
||||
var apkUrl = await appsProvider
|
||||
.confirmApkUrl(
|
||||
app, context);
|
||||
if (apkUrl == null) {
|
||||
throw ObtainiumError(
|
||||
'Cancelled');
|
||||
}
|
||||
app.preferredApkIndex =
|
||||
app.apkUrls.indexOf(apkUrl);
|
||||
var downloadedApk =
|
||||
await appsProvider
|
||||
.downloadApp(app);
|
||||
app.id = downloadedApk.appId;
|
||||
}
|
||||
app.preferredApkIndex =
|
||||
app.apkUrls.indexOf(apkUrl);
|
||||
var downloadedApk =
|
||||
await appsProvider
|
||||
.downloadApp(app);
|
||||
app.id = downloadedApk.appId;
|
||||
}
|
||||
if (appsProvider.apps
|
||||
.containsKey(app.id)) {
|
||||
throw ObtainiumError(
|
||||
'App already added');
|
||||
}
|
||||
await appsProvider.saveApps([app]);
|
||||
if (appsProvider.apps
|
||||
.containsKey(app.id)) {
|
||||
throw ObtainiumError(
|
||||
'App already added');
|
||||
}
|
||||
if (app.trackOnly) {
|
||||
app.installedVersion =
|
||||
app.latestVersion;
|
||||
}
|
||||
await appsProvider
|
||||
.saveApps([app]);
|
||||
|
||||
return app;
|
||||
return app;
|
||||
}
|
||||
}()
|
||||
.then((app) {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) =>
|
||||
AppPage(
|
||||
appId: app.id)));
|
||||
if (app != null) {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) =>
|
||||
AppPage(
|
||||
appId: app.id)));
|
||||
}
|
||||
}).catchError((e) {
|
||||
showError(e, context);
|
||||
}).whenComplete(() {
|
||||
@@ -160,7 +219,11 @@ class _AddAppPageState extends State<AddAppPage> {
|
||||
],
|
||||
),
|
||||
if (pickedSource != null &&
|
||||
pickedSource!.additionalDataDefaults.isNotEmpty)
|
||||
(pickedSource!.additionalSourceAppSpecificDefaults
|
||||
.isNotEmpty ||
|
||||
pickedSource!
|
||||
.additionalAppSpecificSourceAgnosticDefaults
|
||||
.isNotEmpty))
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
@@ -176,19 +239,54 @@ class _AddAppPageState extends State<AddAppPage> {
|
||||
height: 16,
|
||||
),
|
||||
if (pickedSource!
|
||||
.additionalDataFormItems.isNotEmpty)
|
||||
.additionalSourceAppSpecificFormItems
|
||||
.isNotEmpty)
|
||||
GeneratedForm(
|
||||
items: pickedSource!.additionalDataFormItems,
|
||||
items: pickedSource!
|
||||
.additionalSourceAppSpecificFormItems,
|
||||
onValueChanges: (values, valid, isBuilding) {
|
||||
setState(() {
|
||||
additionalData = values;
|
||||
validAdditionalData = valid;
|
||||
});
|
||||
if (isBuilding) {
|
||||
sourceSpecificAdditionalData = values;
|
||||
sourceSpecificDataIsValid = valid;
|
||||
} else {
|
||||
setState(() {
|
||||
sourceSpecificAdditionalData = values;
|
||||
sourceSpecificDataIsValid = valid;
|
||||
});
|
||||
}
|
||||
},
|
||||
defaultValues:
|
||||
pickedSource!.additionalDataDefaults),
|
||||
defaultValues: pickedSource!
|
||||
.additionalSourceAppSpecificDefaults),
|
||||
if (pickedSource!
|
||||
.additionalDataFormItems.isNotEmpty)
|
||||
.additionalSourceAppSpecificFormItems
|
||||
.isNotEmpty)
|
||||
const SizedBox(
|
||||
height: 8,
|
||||
),
|
||||
if (pickedSource!
|
||||
.additionalAppSpecificSourceAgnosticFormItems
|
||||
.isNotEmpty)
|
||||
GeneratedForm(
|
||||
items: pickedSource!
|
||||
.additionalAppSpecificSourceAgnosticFormItems
|
||||
.map((e) => [e])
|
||||
.toList(),
|
||||
onValueChanges: (values, valid, isBuilding) {
|
||||
if (isBuilding) {
|
||||
otherAdditionalData = values;
|
||||
otherAdditionalDataIsValid = valid;
|
||||
} else {
|
||||
setState(() {
|
||||
otherAdditionalData = values;
|
||||
otherAdditionalDataIsValid = valid;
|
||||
});
|
||||
}
|
||||
},
|
||||
defaultValues: pickedSource!
|
||||
.additionalAppSpecificSourceAgnosticDefaults),
|
||||
if (pickedSource!
|
||||
.additionalAppSpecificSourceAgnosticDefaults
|
||||
.isNotEmpty)
|
||||
const SizedBox(
|
||||
height: 8,
|
||||
),
|
||||
|
Reference in New Issue
Block a user