mirror of
https://github.com/ImranR98/Obtainium.git
synced 2025-10-29 20:43:28 +01:00
Switched to package names as app ids (#69)
* Fixes #14 (although detection is disabled in background processes due to the bug described in #60) * Added App icons and basic installed detection * Real Package Names Used as IDs + App Icons (INCONVENIENT FOR PREVIOUS VERSION USERS) * Switch to using extracted names (no custom names) * Fixes #57 * Fixes #67 * Fixes #64 * Fixes #61 * Commented out APKMirror and added code to remove their Apps * Updated README * Switched to Flutter stable (causes some UI elements to switch back to the old material design style, but this will be fixed in later Flutter releases) * BG task silently retries on network errors * Updated screenshots
This commit is contained in:
@@ -22,7 +22,6 @@ class _AddAppPageState extends State<AddAppPage> {
|
||||
String userInput = '';
|
||||
AppSource? pickedSource;
|
||||
List<String> additionalData = [];
|
||||
String customName = '';
|
||||
bool validAdditionalData = true;
|
||||
|
||||
@override
|
||||
@@ -80,9 +79,6 @@ class _AddAppPageState extends State<AddAppPage> {
|
||||
.doesSourceHaveRequiredAdditionalData(
|
||||
source)
|
||||
: true;
|
||||
if (source == null) {
|
||||
customName = '';
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
@@ -90,56 +86,72 @@ class _AddAppPageState extends State<AddAppPage> {
|
||||
const SizedBox(
|
||||
width: 16,
|
||||
),
|
||||
ElevatedButton(
|
||||
onPressed: gettingAppInfo ||
|
||||
pickedSource == null ||
|
||||
(pickedSource!.additionalDataFormItems
|
||||
.isNotEmpty &&
|
||||
!validAdditionalData)
|
||||
? null
|
||||
: () {
|
||||
HapticFeedback.selectionClick();
|
||||
setState(() {
|
||||
gettingAppInfo = true;
|
||||
});
|
||||
sourceProvider
|
||||
.getApp(pickedSource!, userInput,
|
||||
additionalData,
|
||||
customName: customName)
|
||||
.then((app) {
|
||||
var appsProvider =
|
||||
context.read<AppsProvider>();
|
||||
var settingsProvider =
|
||||
context.read<SettingsProvider>();
|
||||
if (appsProvider.apps
|
||||
.containsKey(app.id)) {
|
||||
throw 'App already added';
|
||||
}
|
||||
settingsProvider
|
||||
.getInstallPermission()
|
||||
.then((_) {
|
||||
appsProvider
|
||||
.saveApps([app]).then((_) {
|
||||
gettingAppInfo
|
||||
? const CircularProgressIndicator()
|
||||
: ElevatedButton(
|
||||
onPressed: gettingAppInfo ||
|
||||
pickedSource == null ||
|
||||
(pickedSource!.additionalDataFormItems
|
||||
.isNotEmpty &&
|
||||
!validAdditionalData)
|
||||
? null
|
||||
: () async {
|
||||
setState(() {
|
||||
gettingAppInfo = true;
|
||||
});
|
||||
var appsProvider =
|
||||
context.read<AppsProvider>();
|
||||
var settingsProvider =
|
||||
context.read<SettingsProvider>();
|
||||
() async {
|
||||
HapticFeedback.selectionClick();
|
||||
App app =
|
||||
await sourceProvider.getApp(
|
||||
pickedSource!,
|
||||
userInput,
|
||||
additionalData);
|
||||
await settingsProvider
|
||||
.getInstallPermission();
|
||||
// ignore: use_build_context_synchronously
|
||||
var apkUrl = await appsProvider
|
||||
.selectApkUrl(app, context);
|
||||
if (apkUrl == null) {
|
||||
throw 'Cancelled';
|
||||
}
|
||||
app.preferredApkIndex =
|
||||
app.apkUrls.indexOf(apkUrl);
|
||||
var downloadedApk =
|
||||
await appsProvider
|
||||
.downloadApp(app);
|
||||
app.id = downloadedApk.appId;
|
||||
if (appsProvider.apps
|
||||
.containsKey(app.id)) {
|
||||
throw 'App already added';
|
||||
}
|
||||
await appsProvider.saveApps([app]);
|
||||
|
||||
return app;
|
||||
}()
|
||||
.then((app) {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) =>
|
||||
AppPage(
|
||||
appId: app.id)));
|
||||
}).catchError((e) {
|
||||
ScaffoldMessenger.of(context)
|
||||
.showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(e.toString())),
|
||||
);
|
||||
}).whenComplete(() {
|
||||
setState(() {
|
||||
gettingAppInfo = false;
|
||||
});
|
||||
});
|
||||
});
|
||||
}).catchError((e) {
|
||||
ScaffoldMessenger.of(context)
|
||||
.showSnackBar(
|
||||
SnackBar(content: Text(e.toString())),
|
||||
);
|
||||
}).whenComplete(() {
|
||||
setState(() {
|
||||
gettingAppInfo = false;
|
||||
});
|
||||
});
|
||||
},
|
||||
child: const Text('Add'))
|
||||
},
|
||||
child: const Text('Add'))
|
||||
],
|
||||
),
|
||||
if (pickedSource != null)
|
||||
@@ -174,21 +186,6 @@ class _AddAppPageState extends State<AddAppPage> {
|
||||
const SizedBox(
|
||||
height: 8,
|
||||
),
|
||||
if (pickedSource != null)
|
||||
GeneratedForm(
|
||||
items: [
|
||||
[
|
||||
GeneratedFormItem(
|
||||
label: 'Custom App Name',
|
||||
required: false)
|
||||
]
|
||||
],
|
||||
onValueChanges: (values, valid) {
|
||||
setState(() {
|
||||
customName = values[0];
|
||||
});
|
||||
},
|
||||
defaultValues: [customName])
|
||||
],
|
||||
)
|
||||
else
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:obtainium/components/generated_form.dart';
|
||||
import 'package:obtainium/components/generated_form_modal.dart';
|
||||
import 'package:obtainium/providers/apps_provider.dart';
|
||||
import 'package:obtainium/providers/settings_provider.dart';
|
||||
@@ -46,6 +45,7 @@ class _AppPageState extends State<AppPage> {
|
||||
body: RefreshIndicator(
|
||||
child: settingsProvider.showAppWebpage
|
||||
? WebView(
|
||||
backgroundColor: Theme.of(context).colorScheme.background,
|
||||
initialUrl: app?.app.url,
|
||||
javascriptMode: JavascriptMode.unrestricted,
|
||||
)
|
||||
@@ -56,8 +56,18 @@ class _AppPageState extends State<AppPage> {
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
app?.installedInfo != null
|
||||
? Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Image.memory(
|
||||
app!.installedInfo!.icon!,
|
||||
scale: 1.5,
|
||||
)
|
||||
])
|
||||
: Container(),
|
||||
Text(
|
||||
app?.app.name ?? 'App',
|
||||
app?.installedInfo?.name ?? app?.app.name ?? 'App',
|
||||
textAlign: TextAlign.center,
|
||||
style: Theme.of(context).textTheme.displayLarge,
|
||||
),
|
||||
@@ -126,7 +136,8 @@ class _AppPageState extends State<AppPage> {
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||
children: [
|
||||
if (app?.app.installedVersion != app?.app.latestVersion)
|
||||
if (app?.app.installedVersion != null &&
|
||||
app?.app.installedVersion != app?.app.latestVersion)
|
||||
IconButton(
|
||||
onPressed: app?.downloadProgress != null
|
||||
? null
|
||||
@@ -135,8 +146,8 @@ class _AppPageState extends State<AppPage> {
|
||||
context: context,
|
||||
builder: (BuildContext ctx) {
|
||||
return AlertDialog(
|
||||
title: Text(
|
||||
'App Already ${app?.app.installedVersion == null ? 'Installed' : 'Updated'}?'),
|
||||
title: const Text(
|
||||
'App Already up to Date?'),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
@@ -161,54 +172,13 @@ class _AppPageState extends State<AppPage> {
|
||||
.pop();
|
||||
},
|
||||
child: const Text(
|
||||
'Yes, Mark as Installed'))
|
||||
'Yes, Mark as Updated'))
|
||||
],
|
||||
);
|
||||
});
|
||||
},
|
||||
tooltip: 'Mark as Installed',
|
||||
icon: const Icon(Icons.done))
|
||||
else
|
||||
IconButton(
|
||||
onPressed: app?.downloadProgress != null
|
||||
? null
|
||||
: () {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (BuildContext ctx) {
|
||||
return AlertDialog(
|
||||
title: const Text(
|
||||
'App Not Installed?'),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
Navigator.of(context)
|
||||
.pop();
|
||||
},
|
||||
child: const Text('No')),
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
HapticFeedback
|
||||
.selectionClick();
|
||||
var updatedApp = app?.app;
|
||||
if (updatedApp != null) {
|
||||
updatedApp
|
||||
.installedVersion =
|
||||
null;
|
||||
appsProvider.saveApps(
|
||||
[updatedApp]);
|
||||
}
|
||||
Navigator.of(context)
|
||||
.pop();
|
||||
},
|
||||
child: const Text(
|
||||
'Yes, Mark as Not Installed'))
|
||||
],
|
||||
);
|
||||
});
|
||||
},
|
||||
tooltip: 'Mark as Not Installed',
|
||||
icon: const Icon(Icons.no_cell_outlined)),
|
||||
tooltip: 'Mark as Updated',
|
||||
icon: const Icon(Icons.done)),
|
||||
if (source != null &&
|
||||
source.additionalDataFormItems.isNotEmpty)
|
||||
IconButton(
|
||||
@@ -220,30 +190,15 @@ class _AppPageState extends State<AppPage> {
|
||||
builder: (BuildContext ctx) {
|
||||
return GeneratedFormModal(
|
||||
title: 'Additional Options',
|
||||
items: [
|
||||
...source
|
||||
.additionalDataFormItems,
|
||||
[
|
||||
GeneratedFormItem(
|
||||
label: 'App Name',
|
||||
required: true)
|
||||
]
|
||||
],
|
||||
items: source
|
||||
.additionalDataFormItems,
|
||||
defaultValues: app != null
|
||||
? [
|
||||
...app
|
||||
.app.additionalData,
|
||||
app.app.name
|
||||
]
|
||||
: [
|
||||
...source
|
||||
.additionalDataDefaults
|
||||
]);
|
||||
? app.app.additionalData
|
||||
: source
|
||||
.additionalDataDefaults);
|
||||
}).then((values) {
|
||||
if (app != null && values != null) {
|
||||
var changedApp = app.app;
|
||||
var name = values.removeLast();
|
||||
changedApp.name = name;
|
||||
changedApp.additionalData = values;
|
||||
appsProvider.saveApps(
|
||||
[changedApp]).then((value) {
|
||||
@@ -265,12 +220,18 @@ class _AppPageState extends State<AppPage> {
|
||||
? () {
|
||||
HapticFeedback.heavyImpact();
|
||||
appsProvider
|
||||
.downloadAndInstallLatestApp(
|
||||
.downloadAndInstallLatestApps(
|
||||
[app!.app.id],
|
||||
context).then((res) {
|
||||
if (res.isNotEmpty && mounted) {
|
||||
Navigator.of(context).pop();
|
||||
}
|
||||
}).catchError((e) {
|
||||
ScaffoldMessenger.of(context)
|
||||
.showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(e.toString())),
|
||||
);
|
||||
});
|
||||
}
|
||||
: null,
|
||||
@@ -288,7 +249,7 @@ class _AppPageState extends State<AppPage> {
|
||||
return AlertDialog(
|
||||
title: const Text('Remove App?'),
|
||||
content: Text(
|
||||
'This will remove \'${app?.app.name}\' from Obtainium.${app?.app.installedVersion != null ? '\n\nNote that while Obtainium will no longer track its updates, the App will remain installed.' : ''}'),
|
||||
'This will remove \'${app?.installedInfo?.name ?? app?.app.name}\' from Obtainium.${app?.app.installedVersion != null ? '\n\nNote that while Obtainium will no longer track its updates, the App will remain installed.' : ''}'),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
|
||||
@@ -89,7 +89,8 @@ class AppsPageState extends State<AppsPage> {
|
||||
.toList();
|
||||
|
||||
for (var t in nameTokens) {
|
||||
if (!app.app.name.toLowerCase().contains(t.toLowerCase())) {
|
||||
var name = app.installedInfo?.name ?? app.app.name;
|
||||
if (!name.toLowerCase().contains(t.toLowerCase())) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -103,13 +104,13 @@ class AppsPageState extends State<AppsPage> {
|
||||
}
|
||||
|
||||
sortedApps.sort((a, b) {
|
||||
var nameA = a.installedInfo?.name ?? a.app.name;
|
||||
var nameB = b.installedInfo?.name ?? b.app.name;
|
||||
int result = 0;
|
||||
if (settingsProvider.sortColumn == SortColumnSettings.authorName) {
|
||||
result =
|
||||
(a.app.author + a.app.name).compareTo(b.app.author + b.app.name);
|
||||
result = (a.app.author + nameA).compareTo(b.app.author + nameB);
|
||||
} else if (settingsProvider.sortColumn == SortColumnSettings.nameAuthor) {
|
||||
result =
|
||||
(a.app.name + a.app.author).compareTo(b.app.name + b.app.author);
|
||||
result = (nameA + a.app.author).compareTo(nameB + b.app.author);
|
||||
}
|
||||
return result;
|
||||
});
|
||||
@@ -166,7 +167,11 @@ class AppsPageState extends State<AppsPage> {
|
||||
onLongPress: () {
|
||||
toggleAppSelected(sortedApps[index].app.id);
|
||||
},
|
||||
title: Text(sortedApps[index].app.name),
|
||||
leading: sortedApps[index].installedInfo != null
|
||||
? Image.memory(sortedApps[index].installedInfo!.icon!)
|
||||
: null,
|
||||
title: Text(sortedApps[index].installedInfo?.name ??
|
||||
sortedApps[index].app.name),
|
||||
subtitle: Text('By ${sortedApps[index].app.author}'),
|
||||
trailing: sortedApps[index].downloadProgress != null
|
||||
? Text(
|
||||
@@ -178,7 +183,9 @@ class AppsPageState extends State<AppsPage> {
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
crossAxisAlignment: CrossAxisAlignment.end,
|
||||
children: [
|
||||
const Text('Update Available'),
|
||||
Text(appsProvider.areDownloadsRunning()
|
||||
? 'Please Wait...'
|
||||
: 'Update Available'),
|
||||
SourceProvider()
|
||||
.getSource(sortedApps[index].app.url)
|
||||
.changeLogPageFromStandardUrl(
|
||||
@@ -303,7 +310,7 @@ class AppsPageState extends State<AppsPage> {
|
||||
message:
|
||||
'${existingUpdateIdsAllOrSelected.length} update${existingUpdateIdsAllOrSelected.length == 1 ? '' : 's'} and ${newInstallIdsAllOrSelected.length} new install${newInstallIdsAllOrSelected.length == 1 ? '' : 's'}.',
|
||||
items: formInputs,
|
||||
defaultValues: const ['true', 'true'],
|
||||
defaultValues: const ['true'],
|
||||
initValid: true,
|
||||
);
|
||||
}).then((values) {
|
||||
@@ -311,7 +318,7 @@ class AppsPageState extends State<AppsPage> {
|
||||
bool shouldInstallUpdates =
|
||||
values.length < 2 || values[0] == 'true';
|
||||
bool shouldInstallNew =
|
||||
values.length < 2 || values[1] == 'true';
|
||||
values.length >= 2 && values[1] == 'true';
|
||||
settingsProvider
|
||||
.getInstallPermission()
|
||||
.then((_) {
|
||||
@@ -324,8 +331,14 @@ class AppsPageState extends State<AppsPage> {
|
||||
toInstall
|
||||
.addAll(newInstallIdsAllOrSelected);
|
||||
}
|
||||
appsProvider.downloadAndInstallLatestApp(
|
||||
toInstall, context);
|
||||
appsProvider
|
||||
.downloadAndInstallLatestApps(
|
||||
toInstall, context)
|
||||
.catchError((e) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: Text(e.toString())),
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -349,7 +362,7 @@ class AppsPageState extends State<AppsPage> {
|
||||
padding: const EdgeInsets.only(top: 6),
|
||||
child: Row(
|
||||
mainAxisAlignment:
|
||||
MainAxisAlignment.spaceBetween,
|
||||
MainAxisAlignment.spaceAround,
|
||||
children: [
|
||||
IconButton(
|
||||
onPressed:
|
||||
@@ -364,7 +377,10 @@ class AppsPageState extends State<AppsPage> {
|
||||
ctx) {
|
||||
return AlertDialog(
|
||||
title: Text(
|
||||
'Mark ${selectedIds.length} Selected Apps as Not Installed?'),
|
||||
'Mark ${selectedIds.length} Selected Apps as Updated?'),
|
||||
content:
|
||||
const Text(
|
||||
'Only applies to installed but out of date Apps.'),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed:
|
||||
@@ -383,8 +399,10 @@ class AppsPageState extends State<AppsPage> {
|
||||
.saveApps(selectedIds.map((e) {
|
||||
var a =
|
||||
appsProvider.apps[e]!.app;
|
||||
a.installedVersion =
|
||||
null;
|
||||
if (a.installedVersion !=
|
||||
null) {
|
||||
a.installedVersion = a.latestVersion;
|
||||
}
|
||||
return a;
|
||||
}).toList());
|
||||
|
||||
@@ -398,57 +416,7 @@ class AppsPageState extends State<AppsPage> {
|
||||
});
|
||||
},
|
||||
tooltip:
|
||||
'Mark Selected Apps as Not Installed',
|
||||
icon: const Icon(
|
||||
Icons.no_cell_outlined)),
|
||||
IconButton(
|
||||
onPressed:
|
||||
appsProvider
|
||||
.areDownloadsRunning()
|
||||
? null
|
||||
: () {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder:
|
||||
(BuildContext
|
||||
ctx) {
|
||||
return AlertDialog(
|
||||
title: Text(
|
||||
'Mark ${selectedIds.length} Selected Apps as Installed/Updated?'),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed:
|
||||
() {
|
||||
Navigator.of(context)
|
||||
.pop();
|
||||
},
|
||||
child: const Text(
|
||||
'No')),
|
||||
TextButton(
|
||||
onPressed:
|
||||
() {
|
||||
HapticFeedback
|
||||
.selectionClick();
|
||||
appsProvider
|
||||
.saveApps(selectedIds.map((e) {
|
||||
var a =
|
||||
appsProvider.apps[e]!.app;
|
||||
a.installedVersion =
|
||||
a.latestVersion;
|
||||
return a;
|
||||
}).toList());
|
||||
|
||||
Navigator.of(context)
|
||||
.pop();
|
||||
},
|
||||
child: const Text(
|
||||
'Yes'))
|
||||
],
|
||||
);
|
||||
});
|
||||
},
|
||||
tooltip:
|
||||
'Mark Selected Apps as Installed/Updated',
|
||||
'Mark Selected Apps as Updated',
|
||||
icon: const Icon(Icons.done)),
|
||||
IconButton(
|
||||
onPressed: () {
|
||||
|
||||
Reference in New Issue
Block a user