mirror of
https://github.com/ImranR98/Obtainium.git
synced 2025-07-13 13:26:43 +02:00
Compare commits
13 Commits
v0.5.2-bet
...
v0.5.7-bet
Author | SHA1 | Date | |
---|---|---|---|
07f6d4ad2c | |||
dfbb4e19a5 | |||
f5fda2ca90 | |||
661dc1626c | |||
dde3fc20fb | |||
017b867d8d | |||
1cb1c124eb | |||
fdeb852c7b | |||
67f50ba776 | |||
a0968caa5c | |||
e3e945d13b | |||
61f7f171b1 | |||
de07583161 |
@ -13,6 +13,7 @@ Currently supported App sources:
|
|||||||
- [IzzyOnDroid](https://android.izzysoft.de/)
|
- [IzzyOnDroid](https://android.izzysoft.de/)
|
||||||
- [Mullvad](https://mullvad.net/en/)
|
- [Mullvad](https://mullvad.net/en/)
|
||||||
- [Signal](https://signal.org/)
|
- [Signal](https://signal.org/)
|
||||||
|
- [APKMirror](https://apkmirror.com/)
|
||||||
|
|
||||||
## Limitations
|
## Limitations
|
||||||
- App installs are assumed to have succeeded; failures and cancelled installs cannot be detected.
|
- App installs are assumed to have succeeded; failures and cancelled installs cannot be detected.
|
||||||
|
@ -30,16 +30,6 @@
|
|||||||
<meta-data
|
<meta-data
|
||||||
android:name="flutterEmbedding"
|
android:name="flutterEmbedding"
|
||||||
android:value="2" />
|
android:value="2" />
|
||||||
<provider
|
|
||||||
android:name="androidx.core.content.FileProvider"
|
|
||||||
android:authorities="${applicationId}.fileProvider"
|
|
||||||
android:exported="false"
|
|
||||||
android:grantUriPermissions="true">
|
|
||||||
<meta-data
|
|
||||||
android:name="android.support.FILE_PROVIDER_PATHS"
|
|
||||||
android:resource="@xml/file_paths" />
|
|
||||||
</provider>
|
|
||||||
|
|
||||||
</application>
|
</application>
|
||||||
<uses-permission android:name="android.permission.INTERNET" />
|
<uses-permission android:name="android.permission.INTERNET" />
|
||||||
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
|
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
|
||||||
|
112
lib/app_sources/apkmirror.dart
Normal file
112
lib/app_sources/apkmirror.dart
Normal file
@ -0,0 +1,112 @@
|
|||||||
|
import 'package:html/parser.dart';
|
||||||
|
import 'package:http/http.dart';
|
||||||
|
import 'package:obtainium/components/generated_form.dart';
|
||||||
|
import 'package:obtainium/providers/source_provider.dart';
|
||||||
|
|
||||||
|
class APKMirror implements AppSource {
|
||||||
|
@override
|
||||||
|
late String host = 'apkmirror.com';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String standardizeURL(String url) {
|
||||||
|
RegExp standardUrlRegEx = RegExp('^https?://$host/apk/[^/]+/[^/]+');
|
||||||
|
RegExpMatch? match = standardUrlRegEx.firstMatch(url.toLowerCase());
|
||||||
|
if (match == null) {
|
||||||
|
throw notValidURL(runtimeType.toString());
|
||||||
|
}
|
||||||
|
return url.substring(0, match.end);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String? changeLogPageFromStandardUrl(String standardUrl) =>
|
||||||
|
'$standardUrl#whatsnew';
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<String> apkUrlPrefetchModifier(String apkUrl) async {
|
||||||
|
var originalUri = Uri.parse(apkUrl);
|
||||||
|
var res = await get(originalUri);
|
||||||
|
if (res.statusCode != 200) {
|
||||||
|
throw false;
|
||||||
|
}
|
||||||
|
var href =
|
||||||
|
parse(res.body).querySelector('.downloadButton')?.attributes['href'];
|
||||||
|
if (href == null) {
|
||||||
|
throw false;
|
||||||
|
}
|
||||||
|
var res2 = await get(Uri.parse('${originalUri.origin}$href'), headers: {
|
||||||
|
'User-Agent':
|
||||||
|
'Mozilla/5.0 (X11; Linux x86_64; rv:105.0) Gecko/20100101 Firefox/105.0'
|
||||||
|
});
|
||||||
|
if (res2.statusCode != 200) {
|
||||||
|
throw false;
|
||||||
|
}
|
||||||
|
var links = parse(res2.body)
|
||||||
|
.querySelectorAll('a')
|
||||||
|
.where((element) => element.innerHtml == 'here')
|
||||||
|
.map((e) => e.attributes['href'])
|
||||||
|
.where((element) => element != null)
|
||||||
|
.toList();
|
||||||
|
if (links.isEmpty) {
|
||||||
|
throw false;
|
||||||
|
}
|
||||||
|
return '${originalUri.origin}${links[0]}';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<APKDetails> getLatestAPKDetails(
|
||||||
|
String standardUrl, List<String> additionalData) async {
|
||||||
|
Response res = await get(Uri.parse('$standardUrl/feed'));
|
||||||
|
if (res.statusCode != 200) {
|
||||||
|
throw couldNotFindReleases;
|
||||||
|
}
|
||||||
|
var nextUrl = parse(res.body)
|
||||||
|
.querySelector('item')
|
||||||
|
?.querySelector('link')
|
||||||
|
?.nextElementSibling
|
||||||
|
?.innerHtml;
|
||||||
|
if (nextUrl == null) {
|
||||||
|
throw couldNotFindReleases;
|
||||||
|
}
|
||||||
|
Response res2 = await get(Uri.parse(nextUrl), headers: {
|
||||||
|
'User-Agent':
|
||||||
|
'Mozilla/5.0 (X11; Linux x86_64; rv:105.0) Gecko/20100101 Firefox/105.0'
|
||||||
|
});
|
||||||
|
if (res2.statusCode != 200) {
|
||||||
|
throw couldNotFindReleases;
|
||||||
|
}
|
||||||
|
var html2 = parse(res2.body);
|
||||||
|
var origin = Uri.parse(standardUrl).origin;
|
||||||
|
List<String> apkUrls = html2
|
||||||
|
.querySelectorAll('.apkm-badge')
|
||||||
|
.map((e) => e.innerHtml != 'APK'
|
||||||
|
? ''
|
||||||
|
: e.previousElementSibling?.attributes['href'] ?? '')
|
||||||
|
.where((element) => element.isNotEmpty)
|
||||||
|
.map((e) => '$origin$e')
|
||||||
|
.toList();
|
||||||
|
if (apkUrls.isEmpty) {
|
||||||
|
throw noAPKFound;
|
||||||
|
}
|
||||||
|
var version = html2.querySelector('span.active.accent_color')?.innerHtml;
|
||||||
|
if (version == null) {
|
||||||
|
throw couldNotFindLatestVersion;
|
||||||
|
}
|
||||||
|
return APKDetails(version, apkUrls);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
AppNames getAppNames(String standardUrl) {
|
||||||
|
String temp = standardUrl.substring(standardUrl.indexOf('://') + 3);
|
||||||
|
List<String> names = temp.substring(temp.indexOf('/') + 1).split('/');
|
||||||
|
return AppNames(names[1], names[2]);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<List<GeneratedFormItem>> additionalDataFormItems = [];
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<String> additionalDataDefaults = [];
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<GeneratedFormItem> moreSourceSettingsFormItems = [];
|
||||||
|
}
|
@ -23,6 +23,12 @@ class FDroid implements AppSource {
|
|||||||
return url.substring(0, match.end);
|
return url.substring(0, match.end);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String? changeLogPageFromStandardUrl(String standardUrl) => null;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<String> apkUrlPrefetchModifier(String apkUrl) async => apkUrl;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<APKDetails> getLatestAPKDetails(
|
Future<APKDetails> getLatestAPKDetails(
|
||||||
String standardUrl, List<String> additionalData) async {
|
String standardUrl, List<String> additionalData) async {
|
||||||
|
@ -29,6 +29,13 @@ class GitHub implements AppSource {
|
|||||||
return creds != null && creds.isNotEmpty ? '$creds@' : '';
|
return creds != null && creds.isNotEmpty ? '$creds@' : '';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String? changeLogPageFromStandardUrl(String standardUrl) =>
|
||||||
|
'$standardUrl/releases';
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<String> apkUrlPrefetchModifier(String apkUrl) async => apkUrl;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<APKDetails> getLatestAPKDetails(
|
Future<APKDetails> getLatestAPKDetails(
|
||||||
String standardUrl, List<String> additionalData) async {
|
String standardUrl, List<String> additionalData) async {
|
||||||
|
@ -18,6 +18,13 @@ class GitLab implements AppSource {
|
|||||||
return url.substring(0, match.end);
|
return url.substring(0, match.end);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String? changeLogPageFromStandardUrl(String standardUrl) =>
|
||||||
|
'$standardUrl/-/releases';
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<String> apkUrlPrefetchModifier(String apkUrl) async => apkUrl;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<APKDetails> getLatestAPKDetails(
|
Future<APKDetails> getLatestAPKDetails(
|
||||||
String standardUrl, List<String> additionalData) async {
|
String standardUrl, List<String> additionalData) async {
|
||||||
|
@ -17,6 +17,12 @@ class IzzyOnDroid implements AppSource {
|
|||||||
return url.substring(0, match.end);
|
return url.substring(0, match.end);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String? changeLogPageFromStandardUrl(String standardUrl) => null;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<String> apkUrlPrefetchModifier(String apkUrl) async => apkUrl;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<APKDetails> getLatestAPKDetails(
|
Future<APKDetails> getLatestAPKDetails(
|
||||||
String standardUrl, List<String> additionalData) async {
|
String standardUrl, List<String> additionalData) async {
|
||||||
|
@ -17,6 +17,13 @@ class Mullvad implements AppSource {
|
|||||||
return url.substring(0, match.end);
|
return url.substring(0, match.end);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String? changeLogPageFromStandardUrl(String standardUrl) =>
|
||||||
|
'https://github.com/mullvad/mullvadvpn-app/blob/master/CHANGELOG.md';
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<String> apkUrlPrefetchModifier(String apkUrl) async => apkUrl;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<APKDetails> getLatestAPKDetails(
|
Future<APKDetails> getLatestAPKDetails(
|
||||||
String standardUrl, List<String> additionalData) async {
|
String standardUrl, List<String> additionalData) async {
|
||||||
|
@ -12,6 +12,12 @@ class Signal implements AppSource {
|
|||||||
return 'https://$host';
|
return 'https://$host';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String? changeLogPageFromStandardUrl(String standardUrl) => null;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<String> apkUrlPrefetchModifier(String apkUrl) async => apkUrl;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<APKDetails> getLatestAPKDetails(
|
Future<APKDetails> getLatestAPKDetails(
|
||||||
String standardUrl, List<String> additionalData) async {
|
String standardUrl, List<String> additionalData) async {
|
||||||
|
@ -17,6 +17,12 @@ class SourceForge implements AppSource {
|
|||||||
return url.substring(0, match.end);
|
return url.substring(0, match.end);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String? changeLogPageFromStandardUrl(String standardUrl) => null;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<String> apkUrlPrefetchModifier(String apkUrl) async => apkUrl;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<APKDetails> getLatestAPKDetails(
|
Future<APKDetails> getLatestAPKDetails(
|
||||||
String standardUrl, List<String> additionalData) async {
|
String standardUrl, List<String> additionalData) async {
|
||||||
|
@ -14,7 +14,7 @@ import 'package:dynamic_color/dynamic_color.dart';
|
|||||||
import 'package:device_info_plus/device_info_plus.dart';
|
import 'package:device_info_plus/device_info_plus.dart';
|
||||||
|
|
||||||
const String currentReleaseTag =
|
const String currentReleaseTag =
|
||||||
'v0.5.2-beta'; // KEEP THIS IN SYNC WITH GITHUB RELEASES
|
'v0.5.7-beta'; // KEEP THIS IN SYNC WITH GITHUB RELEASES
|
||||||
|
|
||||||
const String bgUpdateCheckTaskName = 'bg-update-check';
|
const String bgUpdateCheckTaskName = 'bg-update-check';
|
||||||
|
|
||||||
@ -28,8 +28,6 @@ bgUpdateCheck(int? ignoreAfterMicroseconds) async {
|
|||||||
var appsProvider = AppsProvider();
|
var appsProvider = AppsProvider();
|
||||||
await notificationsProvider.cancel(ErrorCheckingUpdatesNotification('').id);
|
await notificationsProvider.cancel(ErrorCheckingUpdatesNotification('').id);
|
||||||
await appsProvider.loadApps();
|
await appsProvider.loadApps();
|
||||||
// List<String> existingUpdateIds = // TODO: Uncomment this and below when it works
|
|
||||||
// appsProvider.getExistingUpdates(installedOnly: true);
|
|
||||||
List<String> existingUpdateIds =
|
List<String> existingUpdateIds =
|
||||||
appsProvider.getExistingUpdates(installedOnly: true);
|
appsProvider.getExistingUpdates(installedOnly: true);
|
||||||
DateTime nextIgnoreAfter = DateTime.now();
|
DateTime nextIgnoreAfter = DateTime.now();
|
||||||
@ -52,11 +50,13 @@ bgUpdateCheck(int? ignoreAfterMicroseconds) async {
|
|||||||
.where((id) => !existingUpdateIds.contains(id))
|
.where((id) => !existingUpdateIds.contains(id))
|
||||||
.map((e) => appsProvider.apps[e]!.app)
|
.map((e) => appsProvider.apps[e]!.app)
|
||||||
.toList();
|
.toList();
|
||||||
|
|
||||||
|
// TODO: This silent update code doesn't work yet
|
||||||
// List<String> silentlyUpdated = await appsProvider
|
// List<String> silentlyUpdated = await appsProvider
|
||||||
// .downloadAndInstallLatestApp(
|
// .downloadAndInstallLatestApp(
|
||||||
// [...newUpdates.map((e) => e.id), ...existingUpdateIds], null);
|
// [...newUpdates.map((e) => e.id), ...existingUpdateIds], null);
|
||||||
// if (silentlyUpdated.isNotEmpty) {
|
// if (silentlyUpdated.isNotEmpty) {
|
||||||
// newUpdates
|
// newUpdates = newUpdates
|
||||||
// .where((element) => !silentlyUpdated.contains(element.id))
|
// .where((element) => !silentlyUpdated.contains(element.id))
|
||||||
// .toList();
|
// .toList();
|
||||||
// notificationsProvider.notify(
|
// notificationsProvider.notify(
|
||||||
@ -64,6 +64,7 @@ bgUpdateCheck(int? ignoreAfterMicroseconds) async {
|
|||||||
// silentlyUpdated.map((e) => appsProvider.apps[e]!.app).toList()),
|
// silentlyUpdated.map((e) => appsProvider.apps[e]!.app).toList()),
|
||||||
// cancelExisting: true);
|
// cancelExisting: true);
|
||||||
// }
|
// }
|
||||||
|
|
||||||
if (newUpdates.isNotEmpty) {
|
if (newUpdates.isNotEmpty) {
|
||||||
notificationsProvider.notify(UpdateNotification(newUpdates),
|
notificationsProvider.notify(UpdateNotification(newUpdates),
|
||||||
cancelExisting: true);
|
cancelExisting: true);
|
||||||
@ -107,14 +108,21 @@ void main() async {
|
|||||||
ChangeNotifierProvider(create: (context) => SettingsProvider()),
|
ChangeNotifierProvider(create: (context) => SettingsProvider()),
|
||||||
Provider(create: (context) => NotificationsProvider())
|
Provider(create: (context) => NotificationsProvider())
|
||||||
],
|
],
|
||||||
child: const MyApp(),
|
child: const Obtainium(),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
var defaultThemeColour = Colors.deepPurple;
|
var defaultThemeColour = Colors.deepPurple;
|
||||||
|
|
||||||
class MyApp extends StatelessWidget {
|
class Obtainium extends StatefulWidget {
|
||||||
const MyApp({super.key});
|
const Obtainium({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<Obtainium> createState() => _ObtainiumState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _ObtainiumState extends State<Obtainium> {
|
||||||
|
var existingUpdateInterval = -1;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
@ -143,18 +151,21 @@ class MyApp extends StatelessWidget {
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
// Register the background update task according to the user's setting
|
// Register the background update task according to the user's setting
|
||||||
if (settingsProvider.updateInterval == 0) {
|
if (existingUpdateInterval != settingsProvider.updateInterval) {
|
||||||
Workmanager().cancelByUniqueName(bgUpdateCheckTaskName);
|
existingUpdateInterval = settingsProvider.updateInterval;
|
||||||
} else {
|
if (existingUpdateInterval == 0) {
|
||||||
Workmanager().registerPeriodicTask(
|
Workmanager().cancelByUniqueName(bgUpdateCheckTaskName);
|
||||||
bgUpdateCheckTaskName, bgUpdateCheckTaskName,
|
} else {
|
||||||
frequency: Duration(minutes: settingsProvider.updateInterval),
|
Workmanager().registerPeriodicTask(
|
||||||
initialDelay: Duration(minutes: settingsProvider.updateInterval),
|
bgUpdateCheckTaskName, bgUpdateCheckTaskName,
|
||||||
constraints: Constraints(networkType: NetworkType.connected),
|
frequency: Duration(minutes: existingUpdateInterval),
|
||||||
existingWorkPolicy: ExistingWorkPolicy.keep,
|
initialDelay: Duration(minutes: existingUpdateInterval),
|
||||||
backoffPolicy: BackoffPolicy.linear,
|
constraints: Constraints(networkType: NetworkType.connected),
|
||||||
backoffPolicyDelay:
|
existingWorkPolicy: ExistingWorkPolicy.replace,
|
||||||
const Duration(minutes: minUpdateIntervalMinutes));
|
backoffPolicy: BackoffPolicy.linear,
|
||||||
|
backoffPolicyDelay:
|
||||||
|
const Duration(minutes: minUpdateIntervalMinutes));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -19,20 +19,27 @@ class AppPage extends StatefulWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class _AppPageState extends State<AppPage> {
|
class _AppPageState extends State<AppPage> {
|
||||||
|
AppInMemory? prevApp;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
var appsProvider = context.watch<AppsProvider>();
|
var appsProvider = context.watch<AppsProvider>();
|
||||||
var settingsProvider = context.watch<SettingsProvider>();
|
var settingsProvider = context.watch<SettingsProvider>();
|
||||||
var sourceProvider = SourceProvider();
|
getUpdate(String id) {
|
||||||
AppInMemory? app = appsProvider.apps[widget.appId];
|
appsProvider.getUpdate(id).catchError((e) {
|
||||||
var source = app != null ? sourceProvider.getSource(app.app.url) : null;
|
|
||||||
if (!appsProvider.areDownloadsRunning() && app != null) {
|
|
||||||
appsProvider.getUpdate(app.app.id).catchError((e) {
|
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
SnackBar(content: Text(e.toString())),
|
SnackBar(content: Text(e.toString())),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var sourceProvider = SourceProvider();
|
||||||
|
AppInMemory? app = appsProvider.apps[widget.appId];
|
||||||
|
var source = app != null ? sourceProvider.getSource(app.app.url) : null;
|
||||||
|
if (!appsProvider.areDownloadsRunning() && prevApp == null && app != null) {
|
||||||
|
prevApp = app;
|
||||||
|
getUpdate(app.app.id);
|
||||||
|
}
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: settingsProvider.showAppWebpage ? AppBar() : null,
|
appBar: settingsProvider.showAppWebpage ? AppBar() : null,
|
||||||
backgroundColor: Theme.of(context).colorScheme.surface,
|
backgroundColor: Theme.of(context).colorScheme.surface,
|
||||||
@ -105,13 +112,7 @@ class _AppPageState extends State<AppPage> {
|
|||||||
),
|
),
|
||||||
onRefresh: () async {
|
onRefresh: () async {
|
||||||
if (app != null) {
|
if (app != null) {
|
||||||
try {
|
getUpdate(app.app.id);
|
||||||
await appsProvider.getUpdate(app.app.id);
|
|
||||||
} catch (e) {
|
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
|
||||||
SnackBar(content: Text(e.toString())),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
bottomSheet: Padding(
|
bottomSheet: Padding(
|
||||||
@ -244,7 +245,10 @@ class _AppPageState extends State<AppPage> {
|
|||||||
var name = values.removeLast();
|
var name = values.removeLast();
|
||||||
changedApp.name = name;
|
changedApp.name = name;
|
||||||
changedApp.additionalData = values;
|
changedApp.additionalData = values;
|
||||||
appsProvider.saveApps([changedApp]);
|
appsProvider.saveApps(
|
||||||
|
[changedApp]).then((value) {
|
||||||
|
getUpdate(changedApp.id);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
@ -6,8 +6,10 @@ import 'package:obtainium/components/generated_form_modal.dart';
|
|||||||
import 'package:obtainium/pages/app.dart';
|
import 'package:obtainium/pages/app.dart';
|
||||||
import 'package:obtainium/providers/apps_provider.dart';
|
import 'package:obtainium/providers/apps_provider.dart';
|
||||||
import 'package:obtainium/providers/settings_provider.dart';
|
import 'package:obtainium/providers/settings_provider.dart';
|
||||||
|
import 'package:obtainium/providers/source_provider.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
import 'package:share_plus/share_plus.dart';
|
import 'package:share_plus/share_plus.dart';
|
||||||
|
import 'package:url_launcher/url_launcher_string.dart';
|
||||||
|
|
||||||
class AppsPage extends StatefulWidget {
|
class AppsPage extends StatefulWidget {
|
||||||
const AppsPage({super.key});
|
const AppsPage({super.key});
|
||||||
@ -172,7 +174,37 @@ class AppsPageState extends State<AppsPage> {
|
|||||||
: (sortedApps[index].app.installedVersion != null &&
|
: (sortedApps[index].app.installedVersion != null &&
|
||||||
sortedApps[index].app.installedVersion !=
|
sortedApps[index].app.installedVersion !=
|
||||||
sortedApps[index].app.latestVersion
|
sortedApps[index].app.latestVersion
|
||||||
? const Text('Update Available')
|
? Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.end,
|
||||||
|
children: [
|
||||||
|
const Text('Update Available'),
|
||||||
|
SourceProvider()
|
||||||
|
.getSource(sortedApps[index].app.url)
|
||||||
|
.changeLogPageFromStandardUrl(
|
||||||
|
sortedApps[index].app.url) ==
|
||||||
|
null
|
||||||
|
? const SizedBox()
|
||||||
|
: GestureDetector(
|
||||||
|
onTap: () {
|
||||||
|
launchUrlString(
|
||||||
|
SourceProvider()
|
||||||
|
.getSource(
|
||||||
|
sortedApps[index].app.url)
|
||||||
|
.changeLogPageFromStandardUrl(
|
||||||
|
sortedApps[index].app.url)!,
|
||||||
|
mode:
|
||||||
|
LaunchMode.externalApplication);
|
||||||
|
},
|
||||||
|
child: const Text(
|
||||||
|
'See Changes',
|
||||||
|
style: TextStyle(
|
||||||
|
fontStyle: FontStyle.italic,
|
||||||
|
decoration:
|
||||||
|
TextDecoration.underline),
|
||||||
|
)),
|
||||||
|
],
|
||||||
|
)
|
||||||
: Text(sortedApps[index].app.installedVersion ??
|
: Text(sortedApps[index].app.installedVersion ??
|
||||||
'Not Installed')),
|
'Not Installed')),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
@ -308,16 +340,139 @@ class AppsPageState extends State<AppsPage> {
|
|||||||
: IconButton(
|
: IconButton(
|
||||||
visualDensity: VisualDensity.compact,
|
visualDensity: VisualDensity.compact,
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
String urls = '';
|
showDialog(
|
||||||
for (var id in selectedIds) {
|
context: context,
|
||||||
urls += '${appsProvider.apps[id]!.app.url}\n';
|
builder: (BuildContext ctx) {
|
||||||
}
|
return AlertDialog(
|
||||||
urls = urls.substring(0, urls.length - 1);
|
scrollable: true,
|
||||||
Share.share(urls,
|
content: Padding(
|
||||||
subject: 'Selected App URLs from Obtainium');
|
padding: const EdgeInsets.only(top: 6),
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment:
|
||||||
|
MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
IconButton(
|
||||||
|
onPressed:
|
||||||
|
appsProvider
|
||||||
|
.areDownloadsRunning()
|
||||||
|
? null
|
||||||
|
: () {
|
||||||
|
showDialog(
|
||||||
|
context: context,
|
||||||
|
builder:
|
||||||
|
(BuildContext
|
||||||
|
ctx) {
|
||||||
|
return AlertDialog(
|
||||||
|
title: Text(
|
||||||
|
'Mark ${selectedIds.length} Selected Apps as Not Installed?'),
|
||||||
|
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 =
|
||||||
|
null;
|
||||||
|
return a;
|
||||||
|
}).toList());
|
||||||
|
|
||||||
|
Navigator.of(context)
|
||||||
|
.pop();
|
||||||
|
},
|
||||||
|
child: const Text(
|
||||||
|
'Yes'))
|
||||||
|
],
|
||||||
|
);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
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',
|
||||||
|
icon: const Icon(Icons.done)),
|
||||||
|
IconButton(
|
||||||
|
onPressed: () {
|
||||||
|
String urls = '';
|
||||||
|
for (var id in selectedIds) {
|
||||||
|
urls +=
|
||||||
|
'${appsProvider.apps[id]!.app.url}\n';
|
||||||
|
}
|
||||||
|
urls = urls.substring(
|
||||||
|
0, urls.length - 1);
|
||||||
|
Share.share(urls,
|
||||||
|
subject:
|
||||||
|
'${selectedIds.length} Selected App URLs from Obtainium');
|
||||||
|
},
|
||||||
|
tooltip: 'Share Selected App URLs',
|
||||||
|
icon: const Icon(Icons.share),
|
||||||
|
),
|
||||||
|
]),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
});
|
||||||
},
|
},
|
||||||
tooltip: 'Share Selected App URLs',
|
tooltip: 'More',
|
||||||
icon: const Icon(Icons.share),
|
icon: const Icon(Icons.more_horiz),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
)),
|
)),
|
||||||
|
@ -8,13 +8,14 @@ import 'dart:io';
|
|||||||
import 'package:device_info_plus/device_info_plus.dart';
|
import 'package:device_info_plus/device_info_plus.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
|
import 'package:install_plugin_v2/install_plugin_v2.dart';
|
||||||
|
import 'package:obtainium/app_sources/github.dart';
|
||||||
import 'package:obtainium/providers/notifications_provider.dart';
|
import 'package:obtainium/providers/notifications_provider.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
import 'package:path_provider/path_provider.dart';
|
import 'package:path_provider/path_provider.dart';
|
||||||
import 'package:flutter_fgbg/flutter_fgbg.dart';
|
import 'package:flutter_fgbg/flutter_fgbg.dart';
|
||||||
import 'package:obtainium/providers/source_provider.dart';
|
import 'package:obtainium/providers/source_provider.dart';
|
||||||
import 'package:http/http.dart';
|
import 'package:http/http.dart';
|
||||||
import 'package:flutter_install_app/flutter_install_app.dart';
|
|
||||||
|
|
||||||
class AppInMemory {
|
class AppInMemory {
|
||||||
late App app;
|
late App app;
|
||||||
@ -63,6 +64,9 @@ class AppsProvider with ChangeNotifier {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<ApkFile> downloadApp(String apkUrl, String appId) async {
|
Future<ApkFile> downloadApp(String apkUrl, String appId) async {
|
||||||
|
apkUrl = await SourceProvider()
|
||||||
|
.getSource(apps[appId]!.app.url)
|
||||||
|
.apkUrlPrefetchModifier(apkUrl);
|
||||||
StreamedResponse response =
|
StreamedResponse response =
|
||||||
await Client().send(Request('GET', Uri.parse(apkUrl)));
|
await Client().send(Request('GET', Uri.parse(apkUrl)));
|
||||||
File downloadFile =
|
File downloadFile =
|
||||||
@ -124,7 +128,7 @@ class AppsProvider with ChangeNotifier {
|
|||||||
// If appropriate criteria are met, the update (never a fresh install) happens silently in the background
|
// If appropriate criteria are met, the update (never a fresh install) happens silently in the background
|
||||||
// But even then, we don't know if it actually succeeded
|
// But even then, we don't know if it actually succeeded
|
||||||
Future<void> installApk(ApkFile file) async {
|
Future<void> installApk(ApkFile file) async {
|
||||||
await AppInstaller.installApk(file.file.path, actionRequired: false);
|
await InstallPlugin.installApk(file.file.path, 'dev.imranr.obtainium');
|
||||||
apps[file.appId]!.app.installedVersion =
|
apps[file.appId]!.app.installedVersion =
|
||||||
apps[file.appId]!.app.latestVersion;
|
apps[file.appId]!.app.latestVersion;
|
||||||
await saveApps([apps[file.appId]!.app]);
|
await saveApps([apps[file.appId]!.app]);
|
||||||
@ -194,14 +198,38 @@ class AppsProvider with ChangeNotifier {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (var u in silentUpdates) {
|
// If Obtainium is being installed, it should be the last one
|
||||||
await installApk(u);
|
List<ApkFile> moveObtainiumToEnd(List<ApkFile> items) {
|
||||||
|
String obtainiumId = 'imranr98_obtainium_${GitHub().host}';
|
||||||
|
ApkFile? temp;
|
||||||
|
items.removeWhere((element) {
|
||||||
|
bool res = element.appId == obtainiumId;
|
||||||
|
if (res) {
|
||||||
|
temp = element;
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
});
|
||||||
|
if (temp != null) {
|
||||||
|
items.add(temp!);
|
||||||
|
}
|
||||||
|
return items;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: Remove below line if silentupdates are ever figured out
|
||||||
|
regularInstalls.addAll(silentUpdates);
|
||||||
|
|
||||||
|
silentUpdates = moveObtainiumToEnd(silentUpdates);
|
||||||
|
regularInstalls = moveObtainiumToEnd(regularInstalls);
|
||||||
|
|
||||||
|
// TODO: Uncomment below if silentupdates are ever figured out
|
||||||
|
// for (var u in silentUpdates) {
|
||||||
|
// await installApk(u, silent: true); // Would need to add silent option
|
||||||
|
// }
|
||||||
|
|
||||||
if (context != null) {
|
if (context != null) {
|
||||||
if (regularInstalls.isNotEmpty) {
|
if (regularInstalls.isNotEmpty) {
|
||||||
// ignore: use_build_context_synchronously
|
// ignore: use_build_context_synchronously
|
||||||
await askUserToReturnToForeground(context);
|
await askUserToReturnToForeground(context, waitForFG: true);
|
||||||
}
|
}
|
||||||
for (var i in regularInstalls) {
|
for (var i in regularInstalls) {
|
||||||
await installApk(i);
|
await installApk(i);
|
||||||
@ -285,21 +313,14 @@ class AppsProvider with ChangeNotifier {
|
|||||||
App newApp = await sourceProvider.getApp(
|
App newApp = await sourceProvider.getApp(
|
||||||
sourceProvider.getSource(currentApp.url),
|
sourceProvider.getSource(currentApp.url),
|
||||||
currentApp.url,
|
currentApp.url,
|
||||||
currentApp.additionalData);
|
currentApp.additionalData,
|
||||||
if (newApp.latestVersion != currentApp.latestVersion) {
|
customName: currentApp.name);
|
||||||
newApp.installedVersion = currentApp.installedVersion;
|
newApp.installedVersion = currentApp.installedVersion;
|
||||||
if (currentApp.preferredApkIndex < newApp.apkUrls.length) {
|
if (currentApp.preferredApkIndex < newApp.apkUrls.length) {
|
||||||
newApp.preferredApkIndex = currentApp.preferredApkIndex;
|
newApp.preferredApkIndex = currentApp.preferredApkIndex;
|
||||||
}
|
|
||||||
await saveApps([newApp]);
|
|
||||||
return newApp;
|
|
||||||
} else if ((newApp.lastUpdateCheck?.microsecondsSinceEpoch ?? 0) -
|
|
||||||
(currentApp.lastUpdateCheck?.microsecondsSinceEpoch ?? 0) >
|
|
||||||
5000000) {
|
|
||||||
currentApp.lastUpdateCheck = newApp.lastUpdateCheck;
|
|
||||||
await saveApps([currentApp]);
|
|
||||||
}
|
}
|
||||||
return null;
|
await saveApps([newApp]);
|
||||||
|
return newApp.latestVersion != currentApp.latestVersion ? newApp : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<List<App>> checkUpdates({DateTime? ignoreAfter}) async {
|
Future<List<App>> checkUpdates({DateTime? ignoreAfter}) async {
|
||||||
@ -407,7 +428,10 @@ class _APKPickerState extends State<APKPicker> {
|
|||||||
Text('${widget.app.name} has more than one package:'),
|
Text('${widget.app.name} has more than one package:'),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
...widget.app.apkUrls.map((u) => RadioListTile<String>(
|
...widget.app.apkUrls.map((u) => RadioListTile<String>(
|
||||||
title: Text(Uri.parse(u).pathSegments.last),
|
title: Text(Uri.parse(u)
|
||||||
|
.pathSegments
|
||||||
|
.where((element) => element.isNotEmpty)
|
||||||
|
.last),
|
||||||
value: u,
|
value: u,
|
||||||
groupValue: apkUrl,
|
groupValue: apkUrl,
|
||||||
onChanged: (String? val) {
|
onChanged: (String? val) {
|
||||||
|
@ -129,5 +129,6 @@ class SettingsProvider with ChangeNotifier {
|
|||||||
|
|
||||||
void setSettingString(String settingId, String value) {
|
void setSettingString(String settingId, String value) {
|
||||||
prefs?.setString(settingId, value);
|
prefs?.setString(settingId, value);
|
||||||
|
notifyListeners();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,6 +4,7 @@
|
|||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
|
|
||||||
import 'package:html/dom.dart';
|
import 'package:html/dom.dart';
|
||||||
|
import 'package:obtainium/app_sources/apkmirror.dart';
|
||||||
import 'package:obtainium/app_sources/fdroid.dart';
|
import 'package:obtainium/app_sources/fdroid.dart';
|
||||||
import 'package:obtainium/app_sources/github.dart';
|
import 'package:obtainium/app_sources/github.dart';
|
||||||
import 'package:obtainium/app_sources/gitlab.dart';
|
import 'package:obtainium/app_sources/gitlab.dart';
|
||||||
@ -136,6 +137,8 @@ abstract class AppSource {
|
|||||||
late List<List<GeneratedFormItem>> additionalDataFormItems;
|
late List<List<GeneratedFormItem>> additionalDataFormItems;
|
||||||
late List<String> additionalDataDefaults;
|
late List<String> additionalDataDefaults;
|
||||||
late List<GeneratedFormItem> moreSourceSettingsFormItems;
|
late List<GeneratedFormItem> moreSourceSettingsFormItems;
|
||||||
|
String? changeLogPageFromStandardUrl(String standardUrl);
|
||||||
|
Future<String> apkUrlPrefetchModifier(String apkUrl);
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract class MassAppSource {
|
abstract class MassAppSource {
|
||||||
@ -153,7 +156,8 @@ class SourceProvider {
|
|||||||
IzzyOnDroid(),
|
IzzyOnDroid(),
|
||||||
Mullvad(),
|
Mullvad(),
|
||||||
Signal(),
|
Signal(),
|
||||||
SourceForge()
|
SourceForge(),
|
||||||
|
APKMirror()
|
||||||
];
|
];
|
||||||
|
|
||||||
// Add more mass source classes here so they are available via the service
|
// Add more mass source classes here so they are available via the service
|
||||||
|
55
pubspec.lock
55
pubspec.lock
@ -7,7 +7,7 @@ packages:
|
|||||||
name: animations
|
name: animations
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.0.5"
|
version: "2.0.7"
|
||||||
archive:
|
archive:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -71,6 +71,13 @@ packages:
|
|||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.16.0"
|
version: "1.16.0"
|
||||||
|
cross_file:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: cross_file
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "0.3.3+2"
|
||||||
crypto:
|
crypto:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -105,42 +112,42 @@ packages:
|
|||||||
name: device_info_plus
|
name: device_info_plus
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "4.1.2"
|
version: "5.0.5"
|
||||||
device_info_plus_linux:
|
device_info_plus_linux:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: device_info_plus_linux
|
name: device_info_plus_linux
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.0.0"
|
version: "4.0.2"
|
||||||
device_info_plus_macos:
|
device_info_plus_macos:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: device_info_plus_macos
|
name: device_info_plus_macos
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.0.0"
|
version: "4.0.2"
|
||||||
device_info_plus_platform_interface:
|
device_info_plus_platform_interface:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: device_info_plus_platform_interface
|
name: device_info_plus_platform_interface
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.0.0"
|
version: "4.0.1"
|
||||||
device_info_plus_web:
|
device_info_plus_web:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: device_info_plus_web
|
name: device_info_plus_web
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.0.0"
|
version: "4.0.2"
|
||||||
device_info_plus_windows:
|
device_info_plus_windows:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: device_info_plus_windows
|
name: device_info_plus_windows
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "4.1.0"
|
version: "5.0.2"
|
||||||
dynamic_color:
|
dynamic_color:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@ -188,13 +195,6 @@ packages:
|
|||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.2.0"
|
version: "0.2.0"
|
||||||
flutter_install_app:
|
|
||||||
dependency: "direct main"
|
|
||||||
description:
|
|
||||||
name: flutter_install_app
|
|
||||||
url: "https://pub.dartlang.org"
|
|
||||||
source: hosted
|
|
||||||
version: "1.3.0"
|
|
||||||
flutter_launcher_icons:
|
flutter_launcher_icons:
|
||||||
dependency: "direct dev"
|
dependency: "direct dev"
|
||||||
description:
|
description:
|
||||||
@ -215,7 +215,7 @@ packages:
|
|||||||
name: flutter_local_notifications
|
name: flutter_local_notifications
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "11.0.1"
|
version: "12.0.0"
|
||||||
flutter_local_notifications_linux:
|
flutter_local_notifications_linux:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -282,6 +282,13 @@ packages:
|
|||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.2.0"
|
version: "3.2.0"
|
||||||
|
install_plugin_v2:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: install_plugin_v2
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "1.0.0"
|
||||||
js:
|
js:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -400,35 +407,35 @@ packages:
|
|||||||
name: permission_handler
|
name: permission_handler
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "10.0.2"
|
version: "10.1.0"
|
||||||
permission_handler_android:
|
permission_handler_android:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: permission_handler_android
|
name: permission_handler_android
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "10.0.0"
|
version: "10.1.0"
|
||||||
permission_handler_apple:
|
permission_handler_apple:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: permission_handler_apple
|
name: permission_handler_apple
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "9.0.4"
|
version: "9.0.6"
|
||||||
permission_handler_platform_interface:
|
permission_handler_platform_interface:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: permission_handler_platform_interface
|
name: permission_handler_platform_interface
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.8.0"
|
version: "3.9.0"
|
||||||
permission_handler_windows:
|
permission_handler_windows:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: permission_handler_windows
|
name: permission_handler_windows
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.1.0"
|
version: "0.1.1"
|
||||||
petitparser:
|
petitparser:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -470,7 +477,7 @@ packages:
|
|||||||
name: share_plus
|
name: share_plus
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "4.4.0"
|
version: "4.5.3"
|
||||||
share_plus_linux:
|
share_plus_linux:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -491,14 +498,14 @@ packages:
|
|||||||
name: share_plus_platform_interface
|
name: share_plus_platform_interface
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.0.3"
|
version: "3.1.1"
|
||||||
share_plus_web:
|
share_plus_web:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: share_plus_web
|
name: share_plus_web
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.0.1"
|
version: "3.1.0"
|
||||||
share_plus_windows:
|
share_plus_windows:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -699,7 +706,7 @@ packages:
|
|||||||
name: webview_flutter_android
|
name: webview_flutter_android
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.10.3"
|
version: "2.10.4"
|
||||||
webview_flutter_platform_interface:
|
webview_flutter_platform_interface:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -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.5.2+23 # When changing this, update the tag in main() accordingly
|
version: 0.5.7+28 # 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'
|
||||||
@ -38,7 +38,7 @@ dependencies:
|
|||||||
cupertino_icons: ^1.0.5
|
cupertino_icons: ^1.0.5
|
||||||
path_provider: ^2.0.11
|
path_provider: ^2.0.11
|
||||||
flutter_fgbg: ^0.2.0 # Try removing reliance on this
|
flutter_fgbg: ^0.2.0 # Try removing reliance on this
|
||||||
flutter_local_notifications: ^11.0.1
|
flutter_local_notifications: ^12.0.0
|
||||||
provider: ^6.0.3
|
provider: ^6.0.3
|
||||||
http: ^0.13.5
|
http: ^0.13.5
|
||||||
webview_flutter: ^3.0.4
|
webview_flutter: ^3.0.4
|
||||||
@ -49,10 +49,10 @@ dependencies:
|
|||||||
url_launcher: ^6.1.5
|
url_launcher: ^6.1.5
|
||||||
permission_handler: ^10.0.0
|
permission_handler: ^10.0.0
|
||||||
fluttertoast: ^8.0.9
|
fluttertoast: ^8.0.9
|
||||||
device_info_plus: ^4.1.2
|
device_info_plus: ^5.0.5
|
||||||
file_picker: ^5.1.0
|
file_picker: ^5.1.0
|
||||||
animations: ^2.0.4
|
animations: ^2.0.4
|
||||||
flutter_install_app: ^1.3.0
|
install_plugin_v2: ^1.0.0
|
||||||
share_plus: ^4.4.0
|
share_plus: ^4.4.0
|
||||||
|
|
||||||
|
|
||||||
|
@ -13,7 +13,7 @@ import 'package:obtainium/main.dart';
|
|||||||
void main() {
|
void main() {
|
||||||
testWidgets('Counter increments smoke test', (WidgetTester tester) async {
|
testWidgets('Counter increments smoke test', (WidgetTester tester) async {
|
||||||
// Build our app and trigger a frame.
|
// Build our app and trigger a frame.
|
||||||
await tester.pumpWidget(const MyApp());
|
await tester.pumpWidget(const Obtainium());
|
||||||
|
|
||||||
// Verify that our counter starts at 0.
|
// Verify that our counter starts at 0.
|
||||||
expect(find.text('0'), findsOneWidget);
|
expect(find.text('0'), findsOneWidget);
|
||||||
|
Reference in New Issue
Block a user