mirror of
https://github.com/ImranR98/Obtainium.git
synced 2025-07-13 13:26:43 +02:00
Compare commits
24 Commits
v0.11.8-be
...
v0.11.13-b
Author | SHA1 | Date | |
---|---|---|---|
3bc5837999 | |||
9fbe524818 | |||
c21a9d7292 | |||
9c6068b270 | |||
cd86d6112b | |||
1112c79c14 | |||
08555bac75 | |||
6db31e2b24 | |||
48d2532323 | |||
f1fc43a3e7 | |||
280827d8ec | |||
05ee0f9c48 | |||
ef06ae289e | |||
bd0e322465 | |||
a93a2411fa | |||
26e6eef72e | |||
e49a6e311b | |||
53d3397651 | |||
fe540f5e61 | |||
234374224b | |||
83390f648a | |||
1143b6a546 | |||
0f3e029312 | |||
c0120f4e40 |
@ -19,6 +19,9 @@ Currently supported App sources:
|
||||
- Third Party F-Droid Repos
|
||||
- Any URLs ending with `/fdroid/<word>`, where `<word>` can be anything - most often `repo`
|
||||
- [Steam](https://store.steampowered.com/mobile)
|
||||
- [Telegram App](https://telegram.org)
|
||||
- [VLC](https://www.videolan.org/vlc/download-android.html)
|
||||
- [Neutron Code](https://neutroncode.com)
|
||||
- "HTML" (Fallback)
|
||||
- Any other URL that returns an HTML page with links to APK files (if multiple, the last file alphabetically is picked)
|
||||
|
||||
|
@ -118,9 +118,11 @@ class Codeberg extends AppSource {
|
||||
if (version == null) {
|
||||
throw NoVersionError();
|
||||
}
|
||||
var changeLog = targetRelease['body'].toString();
|
||||
return APKDetails(version, targetRelease['apkUrls'] as List<String>,
|
||||
getAppNames(standardUrl),
|
||||
releaseDate: releaseDate);
|
||||
releaseDate: releaseDate,
|
||||
changeLog: changeLog.isEmpty ? null : changeLog);
|
||||
} else {
|
||||
throw getObtainiumHttpError(res);
|
||||
}
|
||||
|
@ -27,9 +27,6 @@ class FDroid extends AppSource {
|
||||
return url.substring(0, match.end);
|
||||
}
|
||||
|
||||
@override
|
||||
String? changeLogPageFromStandardUrl(String standardUrl) => null;
|
||||
|
||||
@override
|
||||
String? tryInferringAppId(String standardUrl,
|
||||
{Map<String, dynamic> additionalSettings = const {}}) {
|
||||
|
@ -160,9 +160,11 @@ class GitHub extends AppSource {
|
||||
if (version == null) {
|
||||
throw NoVersionError();
|
||||
}
|
||||
var changeLog = targetRelease['body'].toString();
|
||||
return APKDetails(version, targetRelease['apkUrls'] as List<String>,
|
||||
getAppNames(standardUrl),
|
||||
releaseDate: releaseDate);
|
||||
releaseDate: releaseDate,
|
||||
changeLog: changeLog.isEmpty ? null : changeLog);
|
||||
} else {
|
||||
rateLimitErrorCheck(res);
|
||||
throw getObtainiumHttpError(res);
|
||||
|
@ -10,9 +10,6 @@ class HTML extends AppSource {
|
||||
return url;
|
||||
}
|
||||
|
||||
@override
|
||||
String? changeLogPageFromStandardUrl(String standardUrl) => null;
|
||||
|
||||
@override
|
||||
Future<APKDetails> getLatestAPKDetails(
|
||||
String standardUrl,
|
||||
|
@ -18,9 +18,6 @@ class IzzyOnDroid extends AppSource {
|
||||
return url.substring(0, match.end);
|
||||
}
|
||||
|
||||
@override
|
||||
String? changeLogPageFromStandardUrl(String standardUrl) => null;
|
||||
|
||||
@override
|
||||
String? tryInferringAppId(String standardUrl,
|
||||
{Map<String, dynamic> additionalSettings = const {}}) {
|
||||
|
111
lib/app_sources/neutroncode.dart
Normal file
111
lib/app_sources/neutroncode.dart
Normal file
@ -0,0 +1,111 @@
|
||||
import 'package:html/parser.dart';
|
||||
import 'package:http/http.dart';
|
||||
import 'package:obtainium/custom_errors.dart';
|
||||
import 'package:obtainium/providers/source_provider.dart';
|
||||
|
||||
class NeutronCode extends AppSource {
|
||||
NeutronCode() {
|
||||
host = 'neutroncode.com';
|
||||
}
|
||||
|
||||
@override
|
||||
String standardizeURL(String url) {
|
||||
RegExp standardUrlRegEx = RegExp('^https?://$host/downloads/file/[^/]+');
|
||||
RegExpMatch? match = standardUrlRegEx.firstMatch(url.toLowerCase());
|
||||
if (match == null) {
|
||||
throw InvalidURLError(name);
|
||||
}
|
||||
return url.substring(0, match.end);
|
||||
}
|
||||
|
||||
@override
|
||||
String? changeLogPageFromStandardUrl(String standardUrl) => standardUrl;
|
||||
|
||||
String monthNameToNumberString(String s) {
|
||||
switch (s.toLowerCase()) {
|
||||
case 'january':
|
||||
return '01';
|
||||
case 'february':
|
||||
return '02';
|
||||
case 'march':
|
||||
return '03';
|
||||
case 'april':
|
||||
return '04';
|
||||
case 'may':
|
||||
return '05';
|
||||
case 'june':
|
||||
return '06';
|
||||
case 'july':
|
||||
return '07';
|
||||
case 'august':
|
||||
return '08';
|
||||
case 'september':
|
||||
return '09';
|
||||
case 'october':
|
||||
return '10';
|
||||
case 'november':
|
||||
return '11';
|
||||
case 'december':
|
||||
return '12';
|
||||
default:
|
||||
throw ArgumentError('Invalid month name: $s');
|
||||
}
|
||||
}
|
||||
|
||||
customDateParse(String dateString) {
|
||||
List<String> parts = dateString.split(' ');
|
||||
if (parts.length != 3) {
|
||||
return null;
|
||||
}
|
||||
String result = '';
|
||||
for (var s in parts.reversed) {
|
||||
try {
|
||||
try {
|
||||
int.parse(s);
|
||||
result += '$s-';
|
||||
} catch (e) {
|
||||
result += '${monthNameToNumberString(s)}-';
|
||||
}
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return result.substring(0, result.length - 1);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<APKDetails> getLatestAPKDetails(
|
||||
String standardUrl,
|
||||
Map<String, dynamic> additionalSettings,
|
||||
) async {
|
||||
Response res = await get(Uri.parse(standardUrl));
|
||||
if (res.statusCode == 200) {
|
||||
var http = parse(res.body);
|
||||
var name = http.querySelector('.pd-title')?.innerHtml;
|
||||
var filename = http.querySelector('.pd-filename .pd-float')?.innerHtml;
|
||||
if (filename == null) {
|
||||
throw NoReleasesError();
|
||||
}
|
||||
var version =
|
||||
http.querySelector('.pd-version-txt')?.nextElementSibling?.innerHtml;
|
||||
if (version == null) {
|
||||
throw NoVersionError();
|
||||
}
|
||||
String? apkUrl = 'https://$host/download/$filename';
|
||||
var dateStringOriginal =
|
||||
http.querySelector('.pd-date-txt')?.nextElementSibling?.innerHtml;
|
||||
var dateString = dateStringOriginal != null
|
||||
? (customDateParse(dateStringOriginal))
|
||||
: null;
|
||||
var changeLogElements = http.querySelectorAll('.pd-fdesc p');
|
||||
return APKDetails(version, [apkUrl],
|
||||
AppNames(runtimeType.toString(), name ?? standardUrl.split('/').last),
|
||||
releaseDate: dateString != null ? DateTime.parse(dateString) : null,
|
||||
changeLog: changeLogElements.isNotEmpty
|
||||
? changeLogElements.last.innerHtml
|
||||
: null);
|
||||
} else {
|
||||
throw getObtainiumHttpError(res);
|
||||
}
|
||||
}
|
||||
}
|
@ -13,9 +13,6 @@ class Signal extends AppSource {
|
||||
return 'https://$host';
|
||||
}
|
||||
|
||||
@override
|
||||
String? changeLogPageFromStandardUrl(String standardUrl) => null;
|
||||
|
||||
@override
|
||||
Future<APKDetails> getLatestAPKDetails(
|
||||
String standardUrl,
|
||||
|
@ -18,9 +18,6 @@ class SourceForge extends AppSource {
|
||||
return url.substring(0, match.end);
|
||||
}
|
||||
|
||||
@override
|
||||
String? changeLogPageFromStandardUrl(String standardUrl) => null;
|
||||
|
||||
@override
|
||||
Future<APKDetails> getLatestAPKDetails(
|
||||
String standardUrl,
|
||||
|
@ -24,9 +24,6 @@ class SteamMobile extends AppSource {
|
||||
return 'https://$host';
|
||||
}
|
||||
|
||||
@override
|
||||
String? changeLogPageFromStandardUrl(String standardUrl) => null;
|
||||
|
||||
@override
|
||||
Future<APKDetails> getLatestAPKDetails(
|
||||
String standardUrl,
|
||||
|
40
lib/app_sources/telegramapp.dart
Normal file
40
lib/app_sources/telegramapp.dart
Normal file
@ -0,0 +1,40 @@
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:html/parser.dart';
|
||||
import 'package:http/http.dart';
|
||||
import 'package:obtainium/custom_errors.dart';
|
||||
import 'package:obtainium/providers/source_provider.dart';
|
||||
|
||||
class TelegramApp extends AppSource {
|
||||
TelegramApp() {
|
||||
host = 'telegram.org';
|
||||
name = 'Telegram ${tr('app')}';
|
||||
}
|
||||
|
||||
@override
|
||||
String standardizeURL(String url) {
|
||||
return 'https://$host';
|
||||
}
|
||||
|
||||
@override
|
||||
Future<APKDetails> getLatestAPKDetails(
|
||||
String standardUrl,
|
||||
Map<String, dynamic> additionalSettings,
|
||||
) async {
|
||||
Response res = await get(Uri.parse('https://t.me/s/TAndroidAPK'));
|
||||
if (res.statusCode == 200) {
|
||||
var http = parse(res.body);
|
||||
var messages =
|
||||
http.querySelectorAll('.tgme_widget_message_text.js-message_text');
|
||||
var version = messages.isNotEmpty
|
||||
? messages.last.innerHtml.split('\n').first.trim().split(' ').first
|
||||
: null;
|
||||
if (version == null) {
|
||||
throw NoVersionError();
|
||||
}
|
||||
String? apkUrl = 'https://telegram.org/dl/android/apk';
|
||||
return APKDetails(version, [apkUrl], AppNames('Telegram', 'Telegram'));
|
||||
} else {
|
||||
throw getObtainiumHttpError(res);
|
||||
}
|
||||
}
|
||||
}
|
62
lib/app_sources/vlc.dart
Normal file
62
lib/app_sources/vlc.dart
Normal file
@ -0,0 +1,62 @@
|
||||
import 'package:html/parser.dart';
|
||||
import 'package:http/http.dart';
|
||||
import 'package:obtainium/app_sources/html.dart';
|
||||
import 'package:obtainium/custom_errors.dart';
|
||||
import 'package:obtainium/providers/source_provider.dart';
|
||||
|
||||
class VLC extends AppSource {
|
||||
VLC() {
|
||||
host = 'videolan.org';
|
||||
}
|
||||
|
||||
@override
|
||||
String standardizeURL(String url) {
|
||||
return 'https://$host';
|
||||
}
|
||||
|
||||
@override
|
||||
Future<APKDetails> getLatestAPKDetails(
|
||||
String standardUrl,
|
||||
Map<String, dynamic> additionalSettings,
|
||||
) async {
|
||||
Response res = await get(
|
||||
Uri.parse('https://www.videolan.org/vlc/download-android.html'));
|
||||
if (res.statusCode == 200) {
|
||||
var dwUrlBase = 'get.videolan.org/vlc-android';
|
||||
var dwLinks = parse(res.body)
|
||||
.querySelectorAll('a')
|
||||
.where((element) =>
|
||||
element.attributes['href']?.contains(dwUrlBase) ?? false)
|
||||
.toList();
|
||||
String? version = dwLinks.isNotEmpty
|
||||
? dwLinks.first.attributes['href']
|
||||
?.split('/')
|
||||
.where((s) => s.isNotEmpty)
|
||||
.last
|
||||
: null;
|
||||
if (version == null) {
|
||||
throw NoVersionError();
|
||||
}
|
||||
String? targetUrl = 'https://$dwUrlBase/$version/';
|
||||
Response res2 = await get(Uri.parse(targetUrl));
|
||||
String mirrorDwBase =
|
||||
'https://plug-mirror.rcac.purdue.edu/vlc/vlc-android/$version/';
|
||||
List<String> apkUrls = [];
|
||||
if (res2.statusCode == 200) {
|
||||
apkUrls = parse(res2.body)
|
||||
.querySelectorAll('a')
|
||||
.map((e) => e.attributes['href'])
|
||||
.where((h) =>
|
||||
h != null && h.isNotEmpty && h.toLowerCase().endsWith('.apk'))
|
||||
.map((e) => mirrorDwBase + e!)
|
||||
.toList();
|
||||
} else {
|
||||
throw getObtainiumHttpError(res2);
|
||||
}
|
||||
|
||||
return APKDetails(version, apkUrls, AppNames('VideoLAN', 'VLC'));
|
||||
} else {
|
||||
throw getObtainiumHttpError(res);
|
||||
}
|
||||
}
|
||||
}
|
75
lib/app_sources/whatsapp.dart
Normal file
75
lib/app_sources/whatsapp.dart
Normal file
@ -0,0 +1,75 @@
|
||||
import 'package:html/parser.dart';
|
||||
import 'package:http/http.dart';
|
||||
import 'package:obtainium/custom_errors.dart';
|
||||
import 'package:obtainium/providers/source_provider.dart';
|
||||
|
||||
class WhatsApp extends AppSource {
|
||||
WhatsApp() {
|
||||
host = 'whatsapp.com';
|
||||
}
|
||||
|
||||
@override
|
||||
String standardizeURL(String url) {
|
||||
return 'https://$host';
|
||||
}
|
||||
|
||||
@override
|
||||
Future<String> apkUrlPrefetchModifier(String apkUrl) async {
|
||||
Response res = await get(Uri.parse('https://www.whatsapp.com/android'));
|
||||
if (res.statusCode == 200) {
|
||||
var targetLinks = parse(res.body)
|
||||
.querySelectorAll('a')
|
||||
.map((e) => e.attributes['href'])
|
||||
.where((e) => e != null)
|
||||
.where((e) =>
|
||||
e!.contains('scontent.whatsapp.net') &&
|
||||
e.contains('WhatsApp.apk'))
|
||||
.toList();
|
||||
if (targetLinks.isEmpty) {
|
||||
throw NoAPKError();
|
||||
}
|
||||
return targetLinks[0]!;
|
||||
} else {
|
||||
throw getObtainiumHttpError(res);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<APKDetails> getLatestAPKDetails(
|
||||
String standardUrl,
|
||||
Map<String, dynamic> additionalSettings,
|
||||
) async {
|
||||
Response res = await get(Uri.parse('https://www.whatsapp.com/android'));
|
||||
if (res.statusCode == 200) {
|
||||
var targetElements = parse(res.body)
|
||||
.querySelectorAll('p')
|
||||
.where((element) => element.innerHtml.contains('Version '))
|
||||
.toList();
|
||||
if (targetElements.isEmpty) {
|
||||
throw NoVersionError();
|
||||
}
|
||||
var vLines = targetElements[0]
|
||||
.innerHtml
|
||||
.split('\n')
|
||||
.where((element) => element.contains('Version '))
|
||||
.toList();
|
||||
if (vLines.isEmpty) {
|
||||
throw NoVersionError();
|
||||
}
|
||||
var versionMatch = RegExp('[0-9]+(\\.[0-9]+)+').firstMatch(vLines[0]);
|
||||
if (versionMatch == null) {
|
||||
throw NoVersionError();
|
||||
}
|
||||
String version =
|
||||
vLines[0].substring(versionMatch.start, versionMatch.end);
|
||||
return APKDetails(
|
||||
version,
|
||||
[
|
||||
'https://www.whatsapp.com/android?v=$version&=thisIsaPlaceholder&a=realURLPrefetchedAtDownloadTime'
|
||||
],
|
||||
AppNames('Meta', 'WhatsApp'));
|
||||
} else {
|
||||
throw getObtainiumHttpError(res);
|
||||
}
|
||||
}
|
||||
}
|
@ -460,10 +460,9 @@ class _GeneratedFormState extends State<GeneratedForm> {
|
||||
if (rowInputs.key > 0) {
|
||||
rows.add([
|
||||
SizedBox(
|
||||
height: widget.items[rowInputs.key][0] is GeneratedFormSwitch &&
|
||||
widget.items[rowInputs.key - 1][0] is! GeneratedFormSwitch
|
||||
? 25
|
||||
: 8,
|
||||
height: widget.items[rowInputs.key - 1][0] is GeneratedFormSwitch
|
||||
? 8
|
||||
: 25,
|
||||
)
|
||||
]);
|
||||
}
|
||||
@ -477,6 +476,7 @@ class _GeneratedFormState extends State<GeneratedForm> {
|
||||
rowItems.add(Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
rowInput.value,
|
||||
...widget.items[rowInputs.key][rowInput.key].belowWidgets
|
||||
|
@ -21,7 +21,7 @@ import 'package:easy_localization/src/easy_localization_controller.dart';
|
||||
// ignore: implementation_imports
|
||||
import 'package:easy_localization/src/localization.dart';
|
||||
|
||||
const String currentVersion = '0.11.8';
|
||||
const String currentVersion = '0.11.13';
|
||||
const String currentReleaseTag =
|
||||
'v$currentVersion-beta'; // KEEP THIS IN SYNC WITH GITHUB RELEASES
|
||||
|
||||
|
@ -1,6 +1,7 @@
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_markdown/flutter_markdown.dart';
|
||||
import 'package:obtainium/components/custom_app_bar.dart';
|
||||
import 'package:obtainium/components/generated_form.dart';
|
||||
import 'package:obtainium/components/generated_form_modal.dart';
|
||||
@ -14,6 +15,7 @@ import 'package:obtainium/providers/source_provider.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:share_plus/share_plus.dart';
|
||||
import 'package:url_launcher/url_launcher_string.dart';
|
||||
import 'package:markdown/markdown.dart' as md;
|
||||
|
||||
class AppsPage extends StatefulWidget {
|
||||
const AppsPage({super.key});
|
||||
@ -229,9 +231,88 @@ class AppsPageState extends State<AppsPage> {
|
||||
SliverList(
|
||||
delegate: SliverChildBuilderDelegate(
|
||||
(BuildContext context, int index) {
|
||||
String? changesUrl = SourceProvider()
|
||||
.getSource(listedApps[index].app.url)
|
||||
AppSource appSource =
|
||||
SourceProvider().getSource(listedApps[index].app.url);
|
||||
String? changesUrl = appSource
|
||||
.changeLogPageFromStandardUrl(listedApps[index].app.url);
|
||||
String? changeLog = listedApps[index].app.changeLog;
|
||||
var showChanges = (changeLog == null && changesUrl == null)
|
||||
? null
|
||||
: () {
|
||||
if (changeLog != null) {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return GeneratedFormModal(
|
||||
title: tr('changes'),
|
||||
items: const [],
|
||||
additionalWidgets: [
|
||||
changesUrl != null
|
||||
? GestureDetector(
|
||||
child: Text(
|
||||
changesUrl,
|
||||
style: const TextStyle(
|
||||
decoration:
|
||||
TextDecoration.underline,
|
||||
fontStyle: FontStyle.italic),
|
||||
),
|
||||
onTap: () {
|
||||
launchUrlString(changesUrl,
|
||||
mode: LaunchMode
|
||||
.externalApplication);
|
||||
},
|
||||
)
|
||||
: const SizedBox.shrink(),
|
||||
changesUrl != null
|
||||
? const SizedBox(
|
||||
height: 16,
|
||||
)
|
||||
: const SizedBox.shrink(),
|
||||
appSource.changeLogIfAnyIsMarkDown
|
||||
? SizedBox(
|
||||
width:
|
||||
MediaQuery.of(context).size.width,
|
||||
height: MediaQuery.of(context)
|
||||
.size
|
||||
.height -
|
||||
350,
|
||||
child: Markdown(
|
||||
data: changeLog,
|
||||
onTapLink: (text, href, title) {
|
||||
if (href != null) {
|
||||
launchUrlString(
|
||||
href.startsWith(
|
||||
'http://') ||
|
||||
href.startsWith(
|
||||
'https://')
|
||||
? href
|
||||
: '${Uri.parse(listedApps[index].app.url).origin}/$href',
|
||||
mode: LaunchMode
|
||||
.externalApplication);
|
||||
}
|
||||
},
|
||||
extensionSet: md.ExtensionSet(
|
||||
md.ExtensionSet.gitHubFlavored
|
||||
.blockSyntaxes,
|
||||
[
|
||||
md.EmojiSyntax(),
|
||||
...md
|
||||
.ExtensionSet
|
||||
.gitHubFlavored
|
||||
.inlineSyntaxes
|
||||
],
|
||||
),
|
||||
))
|
||||
: Text(changeLog),
|
||||
],
|
||||
singleNullReturnButton: tr('ok'),
|
||||
);
|
||||
});
|
||||
} else {
|
||||
launchUrlString(changesUrl!,
|
||||
mode: LaunchMode.externalApplication);
|
||||
}
|
||||
};
|
||||
var transparent = const Color.fromARGB(0, 0, 0, 0).value;
|
||||
var hasUpdate = listedApps[index].app.installedVersion != null &&
|
||||
listedApps[index].app.installedVersion !=
|
||||
@ -366,25 +447,22 @@ class AppsPageState extends State<AppsPage> {
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
GestureDetector(
|
||||
onTap: changesUrl == null
|
||||
? null
|
||||
: () {
|
||||
launchUrlString(changesUrl,
|
||||
mode: LaunchMode
|
||||
.externalApplication);
|
||||
},
|
||||
onTap: showChanges,
|
||||
child: Text(
|
||||
listedApps[index].app.releaseDate ==
|
||||
null
|
||||
? tr('changes')
|
||||
? showChanges != null
|
||||
? tr('changes')
|
||||
: ''
|
||||
: DateFormat('yyyy-MM-dd')
|
||||
.format(listedApps[index]
|
||||
.app
|
||||
.releaseDate!),
|
||||
style: const TextStyle(
|
||||
style: TextStyle(
|
||||
fontStyle: FontStyle.italic,
|
||||
decoration:
|
||||
TextDecoration.underline),
|
||||
decoration: showChanges != null
|
||||
? TextDecoration.underline
|
||||
: TextDecoration.none),
|
||||
))
|
||||
],
|
||||
),
|
||||
|
@ -145,56 +145,68 @@ class AppsProvider with ChangeNotifier {
|
||||
}
|
||||
|
||||
Future<DownloadedApk> downloadApp(App app, BuildContext? context) async {
|
||||
var fileName =
|
||||
'${app.id}-${app.latestVersion}-${app.preferredApkIndex}.apk';
|
||||
String downloadUrl = await SourceProvider()
|
||||
.getSource(app.url)
|
||||
.apkUrlPrefetchModifier(app.apkUrls[app.preferredApkIndex]);
|
||||
NotificationsProvider? notificationsProvider =
|
||||
context?.read<NotificationsProvider>();
|
||||
var notif = DownloadNotification(app.name, 100);
|
||||
notificationsProvider?.cancel(notif.id);
|
||||
int? prevProg;
|
||||
File downloadedFile =
|
||||
await downloadFile(downloadUrl, fileName, (double? progress) {
|
||||
int? prog = progress?.ceil();
|
||||
var notifId = DownloadNotification(app.name, 0).id;
|
||||
if (apps[app.id] != null) {
|
||||
apps[app.id]!.downloadProgress = 0;
|
||||
notifyListeners();
|
||||
}
|
||||
try {
|
||||
var fileName =
|
||||
'${app.id}-${app.latestVersion}-${app.preferredApkIndex}.apk';
|
||||
String downloadUrl = await SourceProvider()
|
||||
.getSource(app.url)
|
||||
.apkUrlPrefetchModifier(app.apkUrls[app.preferredApkIndex]);
|
||||
var notif = DownloadNotification(app.name, 100);
|
||||
notificationsProvider?.cancel(notif.id);
|
||||
int? prevProg;
|
||||
File downloadedFile =
|
||||
await downloadFile(downloadUrl, fileName, (double? progress) {
|
||||
int? prog = progress?.ceil();
|
||||
if (apps[app.id] != null) {
|
||||
apps[app.id]!.downloadProgress = progress;
|
||||
notifyListeners();
|
||||
}
|
||||
notif = DownloadNotification(app.name, prog ?? 100);
|
||||
if (prog != null && prevProg != prog) {
|
||||
notificationsProvider?.notify(notif);
|
||||
}
|
||||
prevProg = prog;
|
||||
});
|
||||
// Delete older versions of the APK if any
|
||||
for (var file in downloadedFile.parent.listSync()) {
|
||||
var fn = file.path.split('/').last;
|
||||
if (fn.startsWith('${app.id}-') &&
|
||||
fn.endsWith('.apk') &&
|
||||
fn != fileName) {
|
||||
file.delete();
|
||||
}
|
||||
}
|
||||
// If the APK package ID is different from the App ID, it is either new (using a placeholder ID) or the ID has changed
|
||||
// The former case should be handled (give the App its real ID), the latter is a security issue
|
||||
var newInfo = await PackageArchiveInfo.fromPath(downloadedFile.path);
|
||||
if (app.id != newInfo.packageName) {
|
||||
if (apps[app.id] != null && !SourceProvider().isTempId(app)) {
|
||||
throw IDChangedError();
|
||||
}
|
||||
var originalAppId = app.id;
|
||||
app.id = newInfo.packageName;
|
||||
downloadedFile = downloadedFile.renameSync(
|
||||
'${downloadedFile.parent.path}/${app.id}-${app.latestVersion}-${app.preferredApkIndex}.apk');
|
||||
if (apps[originalAppId] != null) {
|
||||
await removeApps([originalAppId]);
|
||||
await saveApps([app]);
|
||||
}
|
||||
}
|
||||
return DownloadedApk(app.id, downloadedFile);
|
||||
} finally {
|
||||
notificationsProvider?.cancel(notifId);
|
||||
if (apps[app.id] != null) {
|
||||
apps[app.id]!.downloadProgress = progress;
|
||||
apps[app.id]!.downloadProgress = null;
|
||||
notifyListeners();
|
||||
}
|
||||
notif = DownloadNotification(app.name, prog ?? 100);
|
||||
if (prog != null && prevProg != prog) {
|
||||
notificationsProvider?.notify(notif);
|
||||
}
|
||||
prevProg = prog;
|
||||
});
|
||||
notificationsProvider?.cancel(notif.id);
|
||||
// Delete older versions of the APK if any
|
||||
for (var file in downloadedFile.parent.listSync()) {
|
||||
var fn = file.path.split('/').last;
|
||||
if (fn.startsWith('${app.id}-') &&
|
||||
fn.endsWith('.apk') &&
|
||||
fn != fileName) {
|
||||
file.delete();
|
||||
}
|
||||
}
|
||||
// If the APK package ID is different from the App ID, it is either new (using a placeholder ID) or the ID has changed
|
||||
// The former case should be handled (give the App its real ID), the latter is a security issue
|
||||
var newInfo = await PackageArchiveInfo.fromPath(downloadedFile.path);
|
||||
if (app.id != newInfo.packageName) {
|
||||
if (apps[app.id] != null && !SourceProvider().isTempId(app)) {
|
||||
throw IDChangedError();
|
||||
}
|
||||
var originalAppId = app.id;
|
||||
app.id = newInfo.packageName;
|
||||
downloadedFile = downloadedFile.renameSync(
|
||||
'${downloadedFile.parent.path}/${app.id}-${app.latestVersion}-${app.preferredApkIndex}.apk');
|
||||
if (apps[originalAppId] != null) {
|
||||
await removeApps([originalAppId]);
|
||||
await saveApps([app]);
|
||||
}
|
||||
}
|
||||
return DownloadedApk(app.id, downloadedFile);
|
||||
}
|
||||
|
||||
bool areDownloadsRunning() => apps.values
|
||||
@ -559,7 +571,21 @@ class AppsProvider with ChangeNotifier {
|
||||
List<App> newApps = (await getAppsDir())
|
||||
.listSync()
|
||||
.where((item) => item.path.toLowerCase().endsWith('.json'))
|
||||
.map((e) => App.fromJson(jsonDecode(File(e.path).readAsStringSync())))
|
||||
.map((e) {
|
||||
try {
|
||||
return App.fromJson(jsonDecode(File(e.path).readAsStringSync()));
|
||||
} catch (err) {
|
||||
if (err is FormatException) {
|
||||
logs.add('Corrupt JSON when loading App (will be ignored): $e');
|
||||
e.renameSync('${e.path}.corrupt');
|
||||
return App(
|
||||
'', '', '', '', '', '', [], 0, {}, DateTime.now(), false);
|
||||
} else {
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
})
|
||||
.where((element) => element.id.isNotEmpty)
|
||||
.toList();
|
||||
var idsToDelete = apps.values
|
||||
.map((e) => e.app.id)
|
||||
|
@ -15,9 +15,13 @@ import 'package:obtainium/app_sources/gitlab.dart';
|
||||
import 'package:obtainium/app_sources/izzyondroid.dart';
|
||||
import 'package:obtainium/app_sources/html.dart';
|
||||
import 'package:obtainium/app_sources/mullvad.dart';
|
||||
import 'package:obtainium/app_sources/neutroncode.dart';
|
||||
import 'package:obtainium/app_sources/signal.dart';
|
||||
import 'package:obtainium/app_sources/sourceforge.dart';
|
||||
import 'package:obtainium/app_sources/steammobile.dart';
|
||||
import 'package:obtainium/app_sources/telegramapp.dart';
|
||||
import 'package:obtainium/app_sources/vlc.dart';
|
||||
import 'package:obtainium/app_sources/whatsapp.dart';
|
||||
import 'package:obtainium/components/generated_form.dart';
|
||||
import 'package:obtainium/custom_errors.dart';
|
||||
import 'package:obtainium/mass_app_sources/githubstars.dart';
|
||||
@ -34,8 +38,10 @@ class APKDetails {
|
||||
late List<String> apkUrls;
|
||||
late AppNames names;
|
||||
late DateTime? releaseDate;
|
||||
late String? changeLog;
|
||||
|
||||
APKDetails(this.version, this.apkUrls, this.names, {this.releaseDate});
|
||||
APKDetails(this.version, this.apkUrls, this.names,
|
||||
{this.releaseDate, this.changeLog});
|
||||
}
|
||||
|
||||
class App {
|
||||
@ -52,6 +58,7 @@ class App {
|
||||
bool pinned = false;
|
||||
List<String> categories;
|
||||
late DateTime? releaseDate;
|
||||
late String? changeLog;
|
||||
App(
|
||||
this.id,
|
||||
this.url,
|
||||
@ -65,7 +72,8 @@ class App {
|
||||
this.lastUpdateCheck,
|
||||
this.pinned,
|
||||
{this.categories = const [],
|
||||
this.releaseDate});
|
||||
this.releaseDate,
|
||||
this.changeLog});
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
@ -128,34 +136,35 @@ class App {
|
||||
preferredApkIndex = 0;
|
||||
}
|
||||
return App(
|
||||
json['id'] as String,
|
||||
json['url'] as String,
|
||||
json['author'] as String,
|
||||
json['name'] as String,
|
||||
json['installedVersion'] == null
|
||||
? null
|
||||
: json['installedVersion'] as String,
|
||||
json['latestVersion'] as String,
|
||||
json['apkUrls'] == null
|
||||
? []
|
||||
: List<String>.from(jsonDecode(json['apkUrls'])),
|
||||
preferredApkIndex,
|
||||
additionalSettings,
|
||||
json['lastUpdateCheck'] == null
|
||||
? null
|
||||
: DateTime.fromMicrosecondsSinceEpoch(json['lastUpdateCheck']),
|
||||
json['pinned'] ?? false,
|
||||
categories: json['categories'] != null
|
||||
? (json['categories'] as List<dynamic>)
|
||||
.map((e) => e.toString())
|
||||
.toList()
|
||||
: json['category'] != null
|
||||
? [json['category'] as String]
|
||||
: [],
|
||||
releaseDate: json['releaseDate'] == null
|
||||
? null
|
||||
: DateTime.fromMicrosecondsSinceEpoch(json['releaseDate']),
|
||||
);
|
||||
json['id'] as String,
|
||||
json['url'] as String,
|
||||
json['author'] as String,
|
||||
json['name'] as String,
|
||||
json['installedVersion'] == null
|
||||
? null
|
||||
: json['installedVersion'] as String,
|
||||
json['latestVersion'] as String,
|
||||
json['apkUrls'] == null
|
||||
? []
|
||||
: List<String>.from(jsonDecode(json['apkUrls'])),
|
||||
preferredApkIndex,
|
||||
additionalSettings,
|
||||
json['lastUpdateCheck'] == null
|
||||
? null
|
||||
: DateTime.fromMicrosecondsSinceEpoch(json['lastUpdateCheck']),
|
||||
json['pinned'] ?? false,
|
||||
categories: json['categories'] != null
|
||||
? (json['categories'] as List<dynamic>)
|
||||
.map((e) => e.toString())
|
||||
.toList()
|
||||
: json['category'] != null
|
||||
? [json['category'] as String]
|
||||
: [],
|
||||
releaseDate: json['releaseDate'] == null
|
||||
? null
|
||||
: DateTime.fromMicrosecondsSinceEpoch(json['releaseDate']),
|
||||
changeLog:
|
||||
json['changeLog'] == null ? null : json['changeLog'] as String);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
@ -171,7 +180,8 @@ class App {
|
||||
'lastUpdateCheck': lastUpdateCheck?.microsecondsSinceEpoch,
|
||||
'pinned': pinned,
|
||||
'categories': categories,
|
||||
'releaseDate': releaseDate?.microsecondsSinceEpoch
|
||||
'releaseDate': releaseDate?.microsecondsSinceEpoch,
|
||||
'changeLog': changeLog
|
||||
};
|
||||
}
|
||||
|
||||
@ -220,6 +230,7 @@ class AppSource {
|
||||
String? host;
|
||||
late String name;
|
||||
bool enforceTrackOnly = false;
|
||||
bool changeLogIfAnyIsMarkDown = true;
|
||||
|
||||
AppSource() {
|
||||
name = runtimeType.toString();
|
||||
@ -332,12 +343,16 @@ class SourceProvider {
|
||||
Codeberg(),
|
||||
FDroid(),
|
||||
IzzyOnDroid(),
|
||||
Mullvad(),
|
||||
Signal(),
|
||||
FDroidRepo(),
|
||||
SourceForge(),
|
||||
APKMirror(),
|
||||
FDroidRepo(),
|
||||
Mullvad(),
|
||||
Signal(),
|
||||
VLC(),
|
||||
// WhatsApp(), // As of 2023-03-20 this is unusable as the version on the webpage is months out of date
|
||||
TelegramApp(),
|
||||
SteamMobile(),
|
||||
NeutronCode(),
|
||||
HTML() // This should ALWAYS be the last option as they are tried in order
|
||||
];
|
||||
|
||||
@ -433,7 +448,8 @@ class SourceProvider {
|
||||
DateTime.now(),
|
||||
currentApp?.pinned ?? false,
|
||||
categories: currentApp?.categories ?? const [],
|
||||
releaseDate: apk.releaseDate);
|
||||
releaseDate: apk.releaseDate,
|
||||
changeLog: apk.changeLog);
|
||||
}
|
||||
|
||||
// Returns errors in [results, errors] instead of throwing them
|
||||
|
104
pubspec.lock
104
pubspec.lock
@ -181,10 +181,10 @@ packages:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: file_picker
|
||||
sha256: d090ae03df98b0247b82e5928f44d1b959867049d18d73635e2e0bc3f49542b9
|
||||
sha256: d8e9ca7e5d1983365c277f12c21b4362df6cf659c99af146ad4d04eb33033013
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "5.2.5"
|
||||
version: "5.2.6"
|
||||
flutter:
|
||||
dependency: "direct main"
|
||||
description: flutter
|
||||
@ -235,6 +235,14 @@ packages:
|
||||
description: flutter
|
||||
source: sdk
|
||||
version: "0.0.0"
|
||||
flutter_markdown:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: flutter_markdown
|
||||
sha256: "7b25c10de1fea883f3c4f9b8389506b54053cd00807beab69fd65c8653a2711f"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.6.14"
|
||||
flutter_plugin_android_lifecycle:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -325,6 +333,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.1"
|
||||
markdown:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: markdown
|
||||
sha256: b3c60dee8c2af50ad0e6e90cceba98e47718a6ee0a7a6772c77846a0cc21f78b
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "7.0.1"
|
||||
matcher:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -401,26 +417,26 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: path_provider_android
|
||||
sha256: "7623b7d4be0f0f7d9a8b5ee6879fc13e4522d4c875ab86801dee4af32b54b83e"
|
||||
sha256: "019f18c9c10ae370b08dce1f3e3b73bc9f58e7f087bb5e921f06529438ac0ae7"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.23"
|
||||
version: "2.0.24"
|
||||
path_provider_foundation:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: path_provider_foundation
|
||||
sha256: eec003594f19fe2456ea965ae36b3fc967bc5005f508890aafe31fa75e41d972
|
||||
sha256: "12eee51abdf4d34c590f043f45073adbb45514a108bd9db4491547a2fd891059"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.2"
|
||||
version: "2.2.0"
|
||||
path_provider_linux:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: path_provider_linux
|
||||
sha256: "525ad5e07622d19447ad740b1ed5070031f7a5437f44355ae915ff56e986429a"
|
||||
sha256: "2ae08f2216225427e64ad224a24354221c2c7907e448e6e0e8b57b1eb9f10ad1"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.9"
|
||||
version: "2.1.10"
|
||||
path_provider_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -433,10 +449,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: path_provider_windows
|
||||
sha256: "642ddf65fde5404f83267e8459ddb4556316d3ee6d511ed193357e25caa3632d"
|
||||
sha256: f53720498d5a543f9607db4b0e997c4b5438884de25b0f73098cc2671a51b130
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.4"
|
||||
version: "2.1.5"
|
||||
permission_handler:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@ -545,26 +561,26 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shared_preferences_android
|
||||
sha256: a51a4f9375097f94df1c6e0a49c0374440d31ab026b59d58a7e7660675879db4
|
||||
sha256: ad423a80fe7b4e48b50d6111b3ea1027af0e959e49d485712e134863d9c1c521
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.16"
|
||||
version: "2.0.17"
|
||||
shared_preferences_foundation:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shared_preferences_foundation
|
||||
sha256: "6b84fdf06b32bb336f972d373cd38b63734f3461ba56ac2ba01b56d052796259"
|
||||
sha256: "1e755f8583229f185cfca61b1d80fb2344c9d660e1c69ede5450d8f478fa5310"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.4"
|
||||
version: "2.1.5"
|
||||
shared_preferences_linux:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shared_preferences_linux
|
||||
sha256: d7fb71e6e20cd3dfffcc823a28da3539b392e53ed5fc5c2b90b55fdaa8a7e8fa
|
||||
sha256: "3a59ed10890a8409ad0faad7bb2957dab4b92b8fbe553257b05d30ed8af2c707"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.4"
|
||||
version: "2.1.5"
|
||||
shared_preferences_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -577,18 +593,18 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shared_preferences_web
|
||||
sha256: "6737b757e49ba93de2a233df229d0b6a87728cea1684da828cbc718b65dcf9d7"
|
||||
sha256: "0dc2633f215a3d4aa3184c9b2c5766f4711e4e5a6b256e62aafee41f89f1bfb8"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.5"
|
||||
version: "2.0.6"
|
||||
shared_preferences_windows:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shared_preferences_windows
|
||||
sha256: bd014168e8484837c39ef21065b78f305810ceabc1d4f90be6e3b392ce81b46d
|
||||
sha256: "71bcd669bb9cdb6b39f22c4a7728b6d49e934f6cba73157ffa5a54f1eed67436"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.4"
|
||||
version: "2.1.5"
|
||||
sky_engine:
|
||||
dependency: transitive
|
||||
description: flutter
|
||||
@ -606,18 +622,18 @@ packages:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: sqflite
|
||||
sha256: "851d5040552cf911f4cabda08d003eca76b27da3ed0002978272e27c8fbf8ecc"
|
||||
sha256: "500d6fec583d2c021f2d25a056d96654f910662c64f836cd2063167b8f1fa758"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.2.5"
|
||||
version: "2.2.6"
|
||||
sqflite_common:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: sqflite_common
|
||||
sha256: bfd6973aaeeb93475bc0d875ac9aefddf7965ef22ce09790eb963992ffc5183f
|
||||
sha256: "963dad8c4aa2f814ce7d2d5b1da2f36f31bd1a439d8f27e3dc189bb9d26bc684"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.4.2+2"
|
||||
version: "2.4.3"
|
||||
stack_trace:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -694,34 +710,34 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: url_launcher_android
|
||||
sha256: "1f4d9ebe86f333c15d318f81dcdc08b01d45da44af74552608455ebdc08d9732"
|
||||
sha256: "845530e5e05db5500c1a4c1446785d60cbd8f9bd45e21e7dd643a3273bb4bbd1"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.0.24"
|
||||
version: "6.0.25"
|
||||
url_launcher_ios:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: url_launcher_ios
|
||||
sha256: c9cd648d2f7ab56968e049d4e9116f96a85517f1dd806b96a86ea1018a3a82e5
|
||||
sha256: "3dedc66ca3c0bef9e6a93c0999aee102556a450afcc1b7bcfeace7a424927d92"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.1.1"
|
||||
version: "6.1.3"
|
||||
url_launcher_linux:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: url_launcher_linux
|
||||
sha256: e29039160ab3730e42f3d811dc2a6d5f2864b90a70fb765ea60144b03307f682
|
||||
sha256: "206fb8334a700ef7754d6a9ed119e7349bc830448098f21a69bf1b4ed038cabc"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.3"
|
||||
version: "3.0.4"
|
||||
url_launcher_macos:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: url_launcher_macos
|
||||
sha256: "2dddb3291a57b074dade66b5e07e64401dd2487caefd4e9e2f467138d8c7eb06"
|
||||
sha256: "0ef2b4f97942a16523e51256b799e9aa1843da6c60c55eefbfa9dbc2dcb8331a"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.3"
|
||||
version: "3.0.4"
|
||||
url_launcher_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -734,18 +750,18 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: url_launcher_web
|
||||
sha256: "574cfbe2390666003c3a1d129bdc4574aaa6728f0c00a4829a81c316de69dd9b"
|
||||
sha256: "81fe91b6c4f84f222d186a9d23c73157dc4c8e1c71489c4d08be1ad3b228f1aa"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.15"
|
||||
version: "2.0.16"
|
||||
url_launcher_windows:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: url_launcher_windows
|
||||
sha256: "97c9067950a0d09cbd93e2e3f0383d1403989362b97102fbf446473a48079a4b"
|
||||
sha256: a83ba3607a507758669cfafb03f9de09bf6e6280c14d9b9cb18f013e406dcacd
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.4"
|
||||
version: "3.0.5"
|
||||
uuid:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -766,34 +782,34 @@ packages:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: webview_flutter
|
||||
sha256: b6cd42db3ced5411f3d01599906156885b18e4188f7065a8a351eb84bee347e0
|
||||
sha256: "47663d51a9061451aa3880a214ee9a65dcbb933b77bc44388e194279ab3ccaf6"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.0.6"
|
||||
version: "4.0.7"
|
||||
webview_flutter_android:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: webview_flutter_android
|
||||
sha256: "5dd3f32b5c2d8f4bf9d05a349e4a65fa718eb137f396f336c3893d558a58fe84"
|
||||
sha256: "34f83c2f0f64c75ad75c77a2ccfc8d2e531afbe8ad41af1fd787d6d33336aa90"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.3.2"
|
||||
version: "3.4.3"
|
||||
webview_flutter_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: webview_flutter_platform_interface
|
||||
sha256: df6472164b3f4eaf3280422227f361dc8424b106726b7f21d79a8656ba53f71f
|
||||
sha256: "1939c39e2150fb4d30fd3cc59a891a49fed9935db53007df633ed83581b6117b"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.2"
|
||||
version: "2.1.0"
|
||||
webview_flutter_wkwebview:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: webview_flutter_wkwebview
|
||||
sha256: "87b6353b40e04f04d5f895a484ad6d92d682d9cce4d2d5b32d2d8aca2448d46e"
|
||||
sha256: ab12479f7a0cf112b9420c36aaf206a1ca47cd60cd42de74a4be2e97a697587b
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.2.0"
|
||||
version: "3.2.1"
|
||||
win32:
|
||||
dependency: transitive
|
||||
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
|
||||
# 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.
|
||||
version: 0.11.8+129 # When changing this, update the tag in main() accordingly
|
||||
version: 0.11.13+134 # When changing this, update the tag in main() accordingly
|
||||
|
||||
environment:
|
||||
sdk: '>=2.18.2 <3.0.0'
|
||||
@ -59,6 +59,7 @@ dependencies:
|
||||
sqflite: ^2.2.0+3
|
||||
easy_localization: ^3.0.1
|
||||
android_intent_plus: ^3.1.5
|
||||
flutter_markdown: ^0.6.14
|
||||
|
||||
|
||||
dev_dependencies:
|
||||
|
Reference in New Issue
Block a user