Skip to content

Commit

Permalink
Added Signal.org, fixed bugs, UX tweaks, readme update
Browse files Browse the repository at this point in the history
  • Loading branch information
Imran Remtulla committed Aug 27, 2022
1 parent 5bdab1b commit 7e5affe
Show file tree
Hide file tree
Showing 5 changed files with 49 additions and 10 deletions.
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# ![](./android/app/src/main/res/drawable/ic_notification.png) Obtainium
# ![Obtainium Icon](./android/app/src/main/res/drawable/ic_notification.png) Obtainium

Get Android App Updates Directly From the Source.

Expand All @@ -13,6 +13,7 @@ Motivation: [Side Of Burritos - You should use this instead of F-Droid | How to
## Limitations
- App installs are assumed to have succeeded; failures and cancelled installs cannot be detected.
- Auto (unattended) updates are unsupported due to a lack of any capable Flutter plugin.
- For GitHub, data is gathered using Web scraping and can easily break due to changes in website design. More reliable methods are either insufficient (GitHub RSS) or subject to rate limits (GitHub API). This may also apply to new sources added in the future.

## Screenshots

Expand Down
4 changes: 2 additions & 2 deletions lib/pages/app.dart
Original file line number Diff line number Diff line change
Expand Up @@ -46,14 +46,14 @@ class _AppPageState extends State<AppPage> {
appsProvider
.checkAppObjectForUpdate(
app!.app)) &&
app?.downloadProgress == null
!appsProvider.areDownloadsRunning()
? () {
HapticFeedback.heavyImpact();
appsProvider
.downloadAndInstallLatestApp(
[app!.app.id],
context).then((res) {
if (res) {
if (res && mounted) {
Navigator.of(context).pop();
}
});
Expand Down
6 changes: 2 additions & 4 deletions lib/pages/apps.dart
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,7 @@ class _AppsPageState extends State<AppsPage> {
floatingActionButton: existingUpdateAppIds.isEmpty
? null
: ElevatedButton.icon(
onPressed: appsProvider.apps.values
.where((element) => element.downloadProgress != null)
.isNotEmpty
onPressed: appsProvider.areDownloadsRunning()
? null
: () {
HapticFeedback.heavyImpact();
Expand Down Expand Up @@ -60,7 +58,7 @@ class _AppsPageState extends State<AppsPage> {
e.app.installedVersion ?? 'Not Installed'),
trailing: e.downloadProgress != null
? Text(
'Downloading - ${e.downloadProgress!.toInt()}%')
'Downloading - ${e.downloadProgress?.toInt()}%')
: (e.app.installedVersion != null &&
e.app.installedVersion !=
e.app.latestVersion
Expand Down
5 changes: 5 additions & 0 deletions lib/providers/apps_provider.dart
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,10 @@ class AppsProvider with ChangeNotifier {
return ApkFile(appId, downloadFile);
}

bool areDownloadsRunning() => apps.values
.where((element) => element.downloadProgress != null)
.isNotEmpty;

// Given an AppId, uses stored info about the app to download an APK (with user input if needed) and install it
// Installs can only be done in the foreground, so a notification is sent to get the user's attention if needed
// Returns upon successful download, regardless of installation result
Expand Down Expand Up @@ -112,6 +116,7 @@ class AppsProvider with ChangeNotifier {
await notificationsProvider.notify(completeInstallationNotification,
cancelExisting: true);
await FGBGEvents.stream.first == FGBGType.foreground;
await notificationsProvider.cancel(completeInstallationNotification.id);
// We need to wait for the App to come to the foreground to install it
// Can't try to call install plugin in a background isolate (may not have worked anyways) because of:
// https://github.com/flutter/flutter/issues/13937
Expand Down
41 changes: 38 additions & 3 deletions lib/providers/source_provider.dart
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,7 @@ class GitLab implements AppSource {
var parsedHtml = parse(res.body);
var entry = parsedHtml.querySelector('entry');
var entryContent =
parse(parseFragment(entry!.querySelector('content')!.innerHtml).text);
parse(parseFragment(entry?.querySelector('content')!.innerHtml).text);
var apkUrlList = getLinksFromParsedHTML(
entryContent,
RegExp(
Expand All @@ -176,7 +176,7 @@ class GitLab implements AppSource {
throw 'No APK found';
}

var entryId = entry.querySelector('id')?.innerHtml;
var entryId = entry?.querySelector('id')?.innerHtml;
var version =
entryId == null ? null : Uri.parse(entryId).pathSegments.last;
if (version == null) {
Expand All @@ -195,13 +195,48 @@ class GitLab implements AppSource {
}
}

class Signal implements AppSource {
@override
String sourceId = 'signal';

@override
String standardizeURL(String url) {
return 'https://signal.org';
}

@override
Future<APKDetails> getLatestAPKDetails(String standardUrl) async {
Response res =
await get(Uri.parse('https://updates.signal.org/android/latest.json'));
if (res.statusCode == 200) {
var json = jsonDecode(res.body);
String? apkUrl = json['url'];
if (apkUrl == null) {
throw 'No APK found';
}
String? version = json['versionName'];
if (version == null) {
throw 'Could not determine latest release version';
}
return APKDetails(version, [apkUrl]);
} else {
throw 'Unable to fetch release info';
}
}

@override
AppNames getAppNames(String standardUrl) => AppNames('signal', 'signal');
}

class SourceProvider {
// Add more source classes here so they are available via the service
AppSource getSource(String url) {
if (url.toLowerCase().contains('://github.com')) {
return GitHub();
} else if (url.toLowerCase().contains('://gitlab.com')) {
return GitLab();
} else if (url.toLowerCase().contains('://signal.org')) {
return Signal();
}
throw 'URL does not match a known source';
}
Expand All @@ -228,5 +263,5 @@ class SourceProvider {
apk.apkUrls);
}

List<String> getSourceHosts() => ['github.com', 'gitlab.com'];
List<String> getSourceHosts() => ['github.com', 'gitlab.com', 'signal.org'];
}

0 comments on commit 7e5affe

Please sign in to comment.