mirror of
https://github.com/ImranR98/Obtainium.git
synced 2025-07-20 16:19:41 +02:00
Compare commits
5 Commits
v0.1.0-bet
...
v0.1.1-bet
Author | SHA1 | Date | |
---|---|---|---|
|
9dba372244 | ||
|
88b60fe362 | ||
|
0362cdf8ac | ||
|
aeada9635d | ||
|
ffe212ebf2 |
BIN
android/app/src/main/res/drawable/ic_notification.png
Normal file → Executable file
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 |
@@ -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',
|
||||||
|
''));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@@ -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(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@@ -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
|
||||||
|
@@ -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),
|
||||||
|
@@ -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'
|
||||||
|
Reference in New Issue
Block a user