Compare commits

...

5 Commits

Author SHA1 Message Date
Imran Remtulla
9dba372244 Updated version 2022-08-25 14:36:59 -04:00
Imran Remtulla
88b60fe362 Ignore 'www' in URL 2022-08-25 14:35:46 -04:00
Imran Remtulla
0362cdf8ac Added update all button + Obtainium added by default 2022-08-25 14:26:15 -04:00
Imran Remtulla
aeada9635d Made app ids unique 2022-08-25 13:22:21 -04:00
Imran Remtulla
ffe212ebf2 Fixed bg task issue + notification icon 2022-08-25 11:17:47 -04:00
6 changed files with 127 additions and 60 deletions

BIN
android/app/src/main/res/drawable/ic_notification.png Normal file → Executable file

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 4.1 KiB

View File

@@ -8,25 +8,49 @@ import 'package:provider/provider.dart';
import 'package:workmanager/workmanager.dart'; import 'package:workmanager/workmanager.dart';
import 'package:dynamic_color/dynamic_color.dart'; import 'package:dynamic_color/dynamic_color.dart';
@pragma('vm:entry-point')
void backgroundUpdateCheck() { void backgroundUpdateCheck() {
Workmanager().executeTask((task, inputData) async { Workmanager().executeTask((task, inputData) async {
var appsProvider = AppsProvider(bg: true); var appsProvider = AppsProvider(bg: true);
await appsProvider.loadApps(); await appsProvider.notify(
List<App> updates = await appsProvider.getUpdates(); 4,
if (updates.isNotEmpty) { 'Checking for Updates',
String message = updates.length == 1 '',
? '${updates[0].name} has an update.' 'BG_UPDATE_CHECK',
: '${(updates.length == 2 ? '${updates[0].name} and ${updates[1].name}' : '${updates[0].name} and ${updates.length - 1} more apps')} have updates.'; 'Checking for Updates',
await appsProvider.downloaderNotifications.cancel(2); 'Transient notification that appears when checking for updates',
important: false);
try {
await appsProvider.loadApps();
List<App> updates = await appsProvider.checkUpdates();
if (updates.isNotEmpty) {
String message = updates.length == 1
? '${updates[0].name} has an update.'
: '${(updates.length == 2 ? '${updates[0].name} and ${updates[1].name}' : '${updates[0].name} and ${updates.length - 1} more apps')} have updates.';
await appsProvider.downloaderNotifications.cancel(2);
await appsProvider.notify(
2,
'Updates Available',
message,
'UPDATES_AVAILABLE',
'Updates Available',
'Notifies the user that updates are available for one or more Apps tracked by Obtainium');
}
return Future.value(true);
} catch (e) {
await appsProvider.downloaderNotifications.cancel(5);
await appsProvider.notify( await appsProvider.notify(
2, 5,
'Updates Available', 'Error Checking for Updates',
message, e.toString(),
'UPDATES_AVAILABLE', 'BG_UPDATE_CHECK_ERROR',
'Updates Available', 'Error Checking for Updates',
'Notifies the user that updates are available for one or more Apps tracked by Obtainium'); 'A notification that shows when background update checking fails',
important: false);
return Future.value(false);
} finally {
await appsProvider.downloaderNotifications.cancel(4);
} }
return Future.value(true);
}); });
} }
@@ -63,13 +87,14 @@ class MyApp extends StatelessWidget {
Widget build(BuildContext context) { Widget build(BuildContext context) {
return DynamicColorBuilder( return DynamicColorBuilder(
builder: (ColorScheme? lightDynamic, ColorScheme? darkDynamic) { builder: (ColorScheme? lightDynamic, ColorScheme? darkDynamic) {
AppsProvider appsProvider = context.read<AppsProvider>();
appsProvider.deleteSavedAPKs();
// Initialize the settings provider (if needed) and perform first-run actions if needed // Initialize the settings provider (if needed) and perform first-run actions if needed
SettingsProvider settingsProvider = context.watch<SettingsProvider>(); SettingsProvider settingsProvider = context.watch<SettingsProvider>();
if (settingsProvider.prefs == null) { if (settingsProvider.prefs == null) {
settingsProvider.initializeSettings().then((_) { settingsProvider.initializeSettings().then((_) {
bool isFirstRun = settingsProvider.checkAndFlipFirstRun(); bool isFirstRun = settingsProvider.checkAndFlipFirstRun();
if (isFirstRun) { if (isFirstRun) {
AppsProvider appsProvider = context.read<AppsProvider>();
appsProvider appsProvider
.notify( .notify(
3, 3,
@@ -82,6 +107,14 @@ class MyApp extends StatelessWidget {
.whenComplete(() { .whenComplete(() {
appsProvider.downloaderNotifications.cancel(3); appsProvider.downloaderNotifications.cancel(3);
}); });
appsProvider.saveApp(App(
'imranr98_obtainium_github',
'https://github.com/ImranR98/Obtainium',
'ImranR98',
'Obtainium',
'v0.1.1-beta', // KEEP THIS IN SYNC WITH GITHUB RELEASES
'v0.1.1-beta',
''));
} }
}); });
} }

View File

@@ -14,46 +14,62 @@ class _AppsPageState extends State<AppsPage> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
var appsProvider = context.watch<AppsProvider>(); var appsProvider = context.watch<AppsProvider>();
appsProvider.getUpdates(); appsProvider.checkUpdates();
var existingUpdateAppIds = appsProvider.getExistingUpdates();
return Center( return Scaffold(
child: appsProvider.loadingApps floatingActionButton: existingUpdateAppIds.isEmpty
? const CircularProgressIndicator() ? null
: appsProvider.apps.isEmpty : ElevatedButton.icon(
? Text( onPressed: appsProvider.apps.values
'No Apps', .where((element) => element.downloadProgress != null)
style: Theme.of(context).textTheme.headline4, .isNotEmpty
) ? null
: RefreshIndicator( : () {
onRefresh: appsProvider.getUpdates, for (var e in existingUpdateAppIds) {
child: ListView( appsProvider.downloadAndInstallLatestApp(e);
children: appsProvider.apps.values }
.map( },
(e) => ListTile( icon: const Icon(Icons.update),
title: Text('${e.app.author}/${e.app.name}'), label: const Text('Update All')),
subtitle: body: Center(
Text(e.app.installedVersion ?? 'Not Installed'), child: appsProvider.loadingApps
trailing: e.downloadProgress != null ? const CircularProgressIndicator()
? Text( : appsProvider.apps.isEmpty
'Downloading - ${e.downloadProgress!.toInt()}%') ? Text(
: (e.app.installedVersion != null && 'No Apps',
e.app.installedVersion != style: Theme.of(context).textTheme.headline4,
e.app.latestVersion )
? const Text('Update Available') : RefreshIndicator(
: null), onRefresh: appsProvider.checkUpdates,
onTap: () { child: ListView(
Navigator.push( children: appsProvider.apps.values
context, .map(
MaterialPageRoute( (e) => ListTile(
builder: (context) => title: Text('${e.app.author}/${e.app.name}'),
AppPage(appId: e.app.id)), subtitle: Text(
); e.app.installedVersion ?? 'Not Installed'),
}, trailing: e.downloadProgress != null
), ? Text(
) 'Downloading - ${e.downloadProgress!.toInt()}%')
.toList(), : (e.app.installedVersion != null &&
), e.app.installedVersion !=
), e.app.latestVersion
); ? const Text('Update Available')
: null),
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) =>
AppPage(appId: e.app.id)),
);
},
),
)
.toList(),
),
),
));
} }
} }

View File

@@ -132,6 +132,15 @@ class AppsProvider with ChangeNotifier {
return appsDir; return appsDir;
} }
Future<void> deleteSavedAPKs() async {
(await getExternalStorageDirectory())
?.listSync()
.where((element) => element.path.endsWith('.apk'))
.forEach((element) {
element.deleteSync();
});
}
Future<void> loadApps() async { Future<void> loadApps() async {
loadingApps = true; loadingApps = true;
notifyListeners(); notifyListeners();
@@ -186,7 +195,7 @@ class AppsProvider with ChangeNotifier {
return null; return null;
} }
Future<List<App>> getUpdates() async { Future<List<App>> checkUpdates() async {
List<App> updates = []; List<App> updates = [];
if (!gettingUpdates) { if (!gettingUpdates) {
gettingUpdates = true; gettingUpdates = true;
@@ -203,14 +212,16 @@ class AppsProvider with ChangeNotifier {
return updates; return updates;
} }
Future<void> installUpdates() async { List<String> getExistingUpdates() {
List<String> updateAppIds = [];
List<String> appIds = apps.keys.toList(); List<String> appIds = apps.keys.toList();
for (int i = 0; i < appIds.length; i++) { for (int i = 0; i < appIds.length; i++) {
App? app = apps[appIds[i]]!.app; App? app = apps[appIds[i]]!.app;
if (app.installedVersion != app.latestVersion) { if (app.installedVersion != app.latestVersion) {
await downloadAndInstallLatestApp(app.id); updateAppIds.add(app.id);
} }
} }
return updateAppIds;
} }
@override @override

View File

@@ -23,6 +23,7 @@ class APKDetails {
// App Source abstract class (diff. implementations for GitHub, GitLab, etc.) // App Source abstract class (diff. implementations for GitHub, GitLab, etc.)
abstract class AppSource { abstract class AppSource {
late String sourceId;
String standardizeURL(String url); String standardizeURL(String url);
Future<APKDetails> getLatestAPKDetails(String standardUrl); Future<APKDetails> getLatestAPKDetails(String standardUrl);
AppNames getAppNames(String standardUrl); AppNames getAppNames(String standardUrl);
@@ -77,6 +78,9 @@ class App {
// Specific App Source classes // Specific App Source classes
class GitHub implements AppSource { class GitHub implements AppSource {
@override
String sourceId = 'github';
@override @override
String standardizeURL(String url) { String standardizeURL(String url) {
RegExp standardUrlRegEx = RegExp(r'^https?://github.com/[^/]*/[^/]*'); RegExp standardUrlRegEx = RegExp(r'^https?://github.com/[^/]*/[^/]*');
@@ -143,12 +147,15 @@ class SourceService {
url.toLowerCase().indexOf('https://') != 0) { url.toLowerCase().indexOf('https://') != 0) {
url = 'https://$url'; url = 'https://$url';
} }
if (url.toLowerCase().indexOf('https://www.') == 0) {
url = 'https://${url.substring(12)}';
}
AppSource source = getSource(url); AppSource source = getSource(url);
String standardUrl = source.standardizeURL(url); String standardUrl = source.standardizeURL(url);
AppNames names = source.getAppNames(standardUrl); AppNames names = source.getAppNames(standardUrl);
APKDetails apk = await source.getLatestAPKDetails(standardUrl); APKDetails apk = await source.getLatestAPKDetails(standardUrl);
return App( return App(
'${names.author}_${names.name}', '${names.author}_${names.name}_${source.sourceId}',
standardUrl, standardUrl,
names.author[0].toUpperCase() + names.author.substring(1), names.author[0].toUpperCase() + names.author.substring(1),
names.name[0].toUpperCase() + names.name.substring(1), names.name[0].toUpperCase() + names.name.substring(1),

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.1.0+1 version: 0.1.1+2 # When changing this, update the tag in main() accordingly
environment: environment:
sdk: '>=2.19.0-79.0.dev <3.0.0' sdk: '>=2.19.0-79.0.dev <3.0.0'