mirror of
https://github.com/ImranR98/Obtainium.git
synced 2025-07-20 16:19:41 +02:00
Compare commits
14 Commits
v0.11.10-b
...
v0.11.13-b
Author | SHA1 | Date | |
---|---|---|---|
|
3bc5837999 | ||
|
9fbe524818 | ||
|
c21a9d7292 | ||
|
9c6068b270 | ||
|
cd86d6112b | ||
|
1112c79c14 | ||
|
08555bac75 | ||
|
6db31e2b24 | ||
|
48d2532323 | ||
|
f1fc43a3e7 | ||
|
280827d8ec | ||
|
05ee0f9c48 | ||
|
ef06ae289e | ||
|
bd0e322465 |
@@ -19,6 +19,8 @@ Currently supported App sources:
|
|||||||
- Third Party F-Droid Repos
|
- Third Party F-Droid Repos
|
||||||
- Any URLs ending with `/fdroid/<word>`, where `<word>` can be anything - most often `repo`
|
- Any URLs ending with `/fdroid/<word>`, where `<word>` can be anything - most often `repo`
|
||||||
- [Steam](https://store.steampowered.com/mobile)
|
- [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)
|
- [Neutron Code](https://neutroncode.com)
|
||||||
- "HTML" (Fallback)
|
- "HTML" (Fallback)
|
||||||
- Any other URL that returns an HTML page with links to APK files (if multiple, the last file alphabetically is picked)
|
- 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) {
|
if (version == null) {
|
||||||
throw NoVersionError();
|
throw NoVersionError();
|
||||||
}
|
}
|
||||||
|
var changeLog = targetRelease['body'].toString();
|
||||||
return APKDetails(version, targetRelease['apkUrls'] as List<String>,
|
return APKDetails(version, targetRelease['apkUrls'] as List<String>,
|
||||||
getAppNames(standardUrl),
|
getAppNames(standardUrl),
|
||||||
releaseDate: releaseDate);
|
releaseDate: releaseDate,
|
||||||
|
changeLog: changeLog.isEmpty ? null : changeLog);
|
||||||
} else {
|
} else {
|
||||||
throw getObtainiumHttpError(res);
|
throw getObtainiumHttpError(res);
|
||||||
}
|
}
|
||||||
|
@@ -27,9 +27,6 @@ class FDroid extends AppSource {
|
|||||||
return url.substring(0, match.end);
|
return url.substring(0, match.end);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
|
||||||
String? changeLogPageFromStandardUrl(String standardUrl) => null;
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String? tryInferringAppId(String standardUrl,
|
String? tryInferringAppId(String standardUrl,
|
||||||
{Map<String, dynamic> additionalSettings = const {}}) {
|
{Map<String, dynamic> additionalSettings = const {}}) {
|
||||||
|
@@ -160,9 +160,11 @@ class GitHub extends AppSource {
|
|||||||
if (version == null) {
|
if (version == null) {
|
||||||
throw NoVersionError();
|
throw NoVersionError();
|
||||||
}
|
}
|
||||||
|
var changeLog = targetRelease['body'].toString();
|
||||||
return APKDetails(version, targetRelease['apkUrls'] as List<String>,
|
return APKDetails(version, targetRelease['apkUrls'] as List<String>,
|
||||||
getAppNames(standardUrl),
|
getAppNames(standardUrl),
|
||||||
releaseDate: releaseDate);
|
releaseDate: releaseDate,
|
||||||
|
changeLog: changeLog.isEmpty ? null : changeLog);
|
||||||
} else {
|
} else {
|
||||||
rateLimitErrorCheck(res);
|
rateLimitErrorCheck(res);
|
||||||
throw getObtainiumHttpError(res);
|
throw getObtainiumHttpError(res);
|
||||||
|
@@ -10,9 +10,6 @@ class HTML extends AppSource {
|
|||||||
return url;
|
return url;
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
|
||||||
String? changeLogPageFromStandardUrl(String standardUrl) => null;
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<APKDetails> getLatestAPKDetails(
|
Future<APKDetails> getLatestAPKDetails(
|
||||||
String standardUrl,
|
String standardUrl,
|
||||||
|
@@ -18,9 +18,6 @@ class IzzyOnDroid extends AppSource {
|
|||||||
return url.substring(0, match.end);
|
return url.substring(0, match.end);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
|
||||||
String? changeLogPageFromStandardUrl(String standardUrl) => null;
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String? tryInferringAppId(String standardUrl,
|
String? tryInferringAppId(String standardUrl,
|
||||||
{Map<String, dynamic> additionalSettings = const {}}) {
|
{Map<String, dynamic> additionalSettings = const {}}) {
|
||||||
|
@@ -97,10 +97,13 @@ class NeutronCode extends AppSource {
|
|||||||
var dateString = dateStringOriginal != null
|
var dateString = dateStringOriginal != null
|
||||||
? (customDateParse(dateStringOriginal))
|
? (customDateParse(dateStringOriginal))
|
||||||
: null;
|
: null;
|
||||||
|
var changeLogElements = http.querySelectorAll('.pd-fdesc p');
|
||||||
return APKDetails(version, [apkUrl],
|
return APKDetails(version, [apkUrl],
|
||||||
AppNames(runtimeType.toString(), name ?? standardUrl.split('/').last),
|
AppNames(runtimeType.toString(), name ?? standardUrl.split('/').last),
|
||||||
releaseDate: dateString != null ? DateTime.parse(dateString) : null);
|
releaseDate: dateString != null ? DateTime.parse(dateString) : null,
|
||||||
|
changeLog: changeLogElements.isNotEmpty
|
||||||
|
? changeLogElements.last.innerHtml
|
||||||
|
: null);
|
||||||
} else {
|
} else {
|
||||||
throw getObtainiumHttpError(res);
|
throw getObtainiumHttpError(res);
|
||||||
}
|
}
|
||||||
|
@@ -13,9 +13,6 @@ class Signal extends AppSource {
|
|||||||
return 'https://$host';
|
return 'https://$host';
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
|
||||||
String? changeLogPageFromStandardUrl(String standardUrl) => null;
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<APKDetails> getLatestAPKDetails(
|
Future<APKDetails> getLatestAPKDetails(
|
||||||
String standardUrl,
|
String standardUrl,
|
||||||
|
@@ -18,9 +18,6 @@ class SourceForge extends AppSource {
|
|||||||
return url.substring(0, match.end);
|
return url.substring(0, match.end);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
|
||||||
String? changeLogPageFromStandardUrl(String standardUrl) => null;
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<APKDetails> getLatestAPKDetails(
|
Future<APKDetails> getLatestAPKDetails(
|
||||||
String standardUrl,
|
String standardUrl,
|
||||||
|
@@ -24,9 +24,6 @@ class SteamMobile extends AppSource {
|
|||||||
return 'https://$host';
|
return 'https://$host';
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
|
||||||
String? changeLogPageFromStandardUrl(String standardUrl) => null;
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<APKDetails> getLatestAPKDetails(
|
Future<APKDetails> getLatestAPKDetails(
|
||||||
String standardUrl,
|
String standardUrl,
|
||||||
|
@@ -15,9 +15,6 @@ class TelegramApp extends AppSource {
|
|||||||
return 'https://$host';
|
return 'https://$host';
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
|
||||||
String? changeLogPageFromStandardUrl(String standardUrl) => null;
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<APKDetails> getLatestAPKDetails(
|
Future<APKDetails> getLatestAPKDetails(
|
||||||
String standardUrl,
|
String standardUrl,
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -476,6 +476,7 @@ class _GeneratedFormState extends State<GeneratedForm> {
|
|||||||
rowItems.add(Expanded(
|
rowItems.add(Expanded(
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
rowInput.value,
|
rowInput.value,
|
||||||
...widget.items[rowInputs.key][rowInput.key].belowWidgets
|
...widget.items[rowInputs.key][rowInput.key].belowWidgets
|
||||||
|
@@ -21,7 +21,7 @@ import 'package:easy_localization/src/easy_localization_controller.dart';
|
|||||||
// ignore: implementation_imports
|
// ignore: implementation_imports
|
||||||
import 'package:easy_localization/src/localization.dart';
|
import 'package:easy_localization/src/localization.dart';
|
||||||
|
|
||||||
const String currentVersion = '0.11.10';
|
const String currentVersion = '0.11.13';
|
||||||
const String currentReleaseTag =
|
const String currentReleaseTag =
|
||||||
'v$currentVersion-beta'; // KEEP THIS IN SYNC WITH GITHUB RELEASES
|
'v$currentVersion-beta'; // KEEP THIS IN SYNC WITH GITHUB RELEASES
|
||||||
|
|
||||||
|
@@ -1,6 +1,7 @@
|
|||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.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/custom_app_bar.dart';
|
||||||
import 'package:obtainium/components/generated_form.dart';
|
import 'package:obtainium/components/generated_form.dart';
|
||||||
import 'package:obtainium/components/generated_form_modal.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: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';
|
import 'package:url_launcher/url_launcher_string.dart';
|
||||||
|
import 'package:markdown/markdown.dart' as md;
|
||||||
|
|
||||||
class AppsPage extends StatefulWidget {
|
class AppsPage extends StatefulWidget {
|
||||||
const AppsPage({super.key});
|
const AppsPage({super.key});
|
||||||
@@ -229,9 +231,88 @@ class AppsPageState extends State<AppsPage> {
|
|||||||
SliverList(
|
SliverList(
|
||||||
delegate: SliverChildBuilderDelegate(
|
delegate: SliverChildBuilderDelegate(
|
||||||
(BuildContext context, int index) {
|
(BuildContext context, int index) {
|
||||||
String? changesUrl = SourceProvider()
|
AppSource appSource =
|
||||||
.getSource(listedApps[index].app.url)
|
SourceProvider().getSource(listedApps[index].app.url);
|
||||||
|
String? changesUrl = appSource
|
||||||
.changeLogPageFromStandardUrl(listedApps[index].app.url);
|
.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 transparent = const Color.fromARGB(0, 0, 0, 0).value;
|
||||||
var hasUpdate = listedApps[index].app.installedVersion != null &&
|
var hasUpdate = listedApps[index].app.installedVersion != null &&
|
||||||
listedApps[index].app.installedVersion !=
|
listedApps[index].app.installedVersion !=
|
||||||
@@ -366,25 +447,22 @@ class AppsPageState extends State<AppsPage> {
|
|||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
GestureDetector(
|
GestureDetector(
|
||||||
onTap: changesUrl == null
|
onTap: showChanges,
|
||||||
? null
|
|
||||||
: () {
|
|
||||||
launchUrlString(changesUrl,
|
|
||||||
mode: LaunchMode
|
|
||||||
.externalApplication);
|
|
||||||
},
|
|
||||||
child: Text(
|
child: Text(
|
||||||
listedApps[index].app.releaseDate ==
|
listedApps[index].app.releaseDate ==
|
||||||
null
|
null
|
||||||
? tr('changes')
|
? showChanges != null
|
||||||
|
? tr('changes')
|
||||||
|
: ''
|
||||||
: DateFormat('yyyy-MM-dd')
|
: DateFormat('yyyy-MM-dd')
|
||||||
.format(listedApps[index]
|
.format(listedApps[index]
|
||||||
.app
|
.app
|
||||||
.releaseDate!),
|
.releaseDate!),
|
||||||
style: const TextStyle(
|
style: TextStyle(
|
||||||
fontStyle: FontStyle.italic,
|
fontStyle: FontStyle.italic,
|
||||||
decoration:
|
decoration: showChanges != null
|
||||||
TextDecoration.underline),
|
? TextDecoration.underline
|
||||||
|
: TextDecoration.none),
|
||||||
))
|
))
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
@@ -571,7 +571,21 @@ class AppsProvider with ChangeNotifier {
|
|||||||
List<App> newApps = (await getAppsDir())
|
List<App> newApps = (await getAppsDir())
|
||||||
.listSync()
|
.listSync()
|
||||||
.where((item) => item.path.toLowerCase().endsWith('.json'))
|
.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();
|
.toList();
|
||||||
var idsToDelete = apps.values
|
var idsToDelete = apps.values
|
||||||
.map((e) => e.app.id)
|
.map((e) => e.app.id)
|
||||||
|
@@ -20,6 +20,8 @@ import 'package:obtainium/app_sources/signal.dart';
|
|||||||
import 'package:obtainium/app_sources/sourceforge.dart';
|
import 'package:obtainium/app_sources/sourceforge.dart';
|
||||||
import 'package:obtainium/app_sources/steammobile.dart';
|
import 'package:obtainium/app_sources/steammobile.dart';
|
||||||
import 'package:obtainium/app_sources/telegramapp.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/components/generated_form.dart';
|
||||||
import 'package:obtainium/custom_errors.dart';
|
import 'package:obtainium/custom_errors.dart';
|
||||||
import 'package:obtainium/mass_app_sources/githubstars.dart';
|
import 'package:obtainium/mass_app_sources/githubstars.dart';
|
||||||
@@ -36,8 +38,10 @@ class APKDetails {
|
|||||||
late List<String> apkUrls;
|
late List<String> apkUrls;
|
||||||
late AppNames names;
|
late AppNames names;
|
||||||
late DateTime? releaseDate;
|
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 {
|
class App {
|
||||||
@@ -54,6 +58,7 @@ class App {
|
|||||||
bool pinned = false;
|
bool pinned = false;
|
||||||
List<String> categories;
|
List<String> categories;
|
||||||
late DateTime? releaseDate;
|
late DateTime? releaseDate;
|
||||||
|
late String? changeLog;
|
||||||
App(
|
App(
|
||||||
this.id,
|
this.id,
|
||||||
this.url,
|
this.url,
|
||||||
@@ -67,7 +72,8 @@ class App {
|
|||||||
this.lastUpdateCheck,
|
this.lastUpdateCheck,
|
||||||
this.pinned,
|
this.pinned,
|
||||||
{this.categories = const [],
|
{this.categories = const [],
|
||||||
this.releaseDate});
|
this.releaseDate,
|
||||||
|
this.changeLog});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() {
|
String toString() {
|
||||||
@@ -130,34 +136,35 @@ class App {
|
|||||||
preferredApkIndex = 0;
|
preferredApkIndex = 0;
|
||||||
}
|
}
|
||||||
return App(
|
return App(
|
||||||
json['id'] as String,
|
json['id'] as String,
|
||||||
json['url'] as String,
|
json['url'] as String,
|
||||||
json['author'] as String,
|
json['author'] as String,
|
||||||
json['name'] as String,
|
json['name'] as String,
|
||||||
json['installedVersion'] == null
|
json['installedVersion'] == null
|
||||||
? null
|
? null
|
||||||
: json['installedVersion'] as String,
|
: json['installedVersion'] as String,
|
||||||
json['latestVersion'] as String,
|
json['latestVersion'] as String,
|
||||||
json['apkUrls'] == null
|
json['apkUrls'] == null
|
||||||
? []
|
? []
|
||||||
: List<String>.from(jsonDecode(json['apkUrls'])),
|
: List<String>.from(jsonDecode(json['apkUrls'])),
|
||||||
preferredApkIndex,
|
preferredApkIndex,
|
||||||
additionalSettings,
|
additionalSettings,
|
||||||
json['lastUpdateCheck'] == null
|
json['lastUpdateCheck'] == null
|
||||||
? null
|
? null
|
||||||
: DateTime.fromMicrosecondsSinceEpoch(json['lastUpdateCheck']),
|
: DateTime.fromMicrosecondsSinceEpoch(json['lastUpdateCheck']),
|
||||||
json['pinned'] ?? false,
|
json['pinned'] ?? false,
|
||||||
categories: json['categories'] != null
|
categories: json['categories'] != null
|
||||||
? (json['categories'] as List<dynamic>)
|
? (json['categories'] as List<dynamic>)
|
||||||
.map((e) => e.toString())
|
.map((e) => e.toString())
|
||||||
.toList()
|
.toList()
|
||||||
: json['category'] != null
|
: json['category'] != null
|
||||||
? [json['category'] as String]
|
? [json['category'] as String]
|
||||||
: [],
|
: [],
|
||||||
releaseDate: json['releaseDate'] == null
|
releaseDate: json['releaseDate'] == null
|
||||||
? null
|
? null
|
||||||
: DateTime.fromMicrosecondsSinceEpoch(json['releaseDate']),
|
: DateTime.fromMicrosecondsSinceEpoch(json['releaseDate']),
|
||||||
);
|
changeLog:
|
||||||
|
json['changeLog'] == null ? null : json['changeLog'] as String);
|
||||||
}
|
}
|
||||||
|
|
||||||
Map<String, dynamic> toJson() => {
|
Map<String, dynamic> toJson() => {
|
||||||
@@ -173,7 +180,8 @@ class App {
|
|||||||
'lastUpdateCheck': lastUpdateCheck?.microsecondsSinceEpoch,
|
'lastUpdateCheck': lastUpdateCheck?.microsecondsSinceEpoch,
|
||||||
'pinned': pinned,
|
'pinned': pinned,
|
||||||
'categories': categories,
|
'categories': categories,
|
||||||
'releaseDate': releaseDate?.microsecondsSinceEpoch
|
'releaseDate': releaseDate?.microsecondsSinceEpoch,
|
||||||
|
'changeLog': changeLog
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -222,6 +230,7 @@ class AppSource {
|
|||||||
String? host;
|
String? host;
|
||||||
late String name;
|
late String name;
|
||||||
bool enforceTrackOnly = false;
|
bool enforceTrackOnly = false;
|
||||||
|
bool changeLogIfAnyIsMarkDown = true;
|
||||||
|
|
||||||
AppSource() {
|
AppSource() {
|
||||||
name = runtimeType.toString();
|
name = runtimeType.toString();
|
||||||
@@ -334,13 +343,15 @@ class SourceProvider {
|
|||||||
Codeberg(),
|
Codeberg(),
|
||||||
FDroid(),
|
FDroid(),
|
||||||
IzzyOnDroid(),
|
IzzyOnDroid(),
|
||||||
Mullvad(),
|
FDroidRepo(),
|
||||||
Signal(),
|
|
||||||
SourceForge(),
|
SourceForge(),
|
||||||
APKMirror(),
|
APKMirror(),
|
||||||
FDroidRepo(),
|
Mullvad(),
|
||||||
SteamMobile(),
|
Signal(),
|
||||||
|
VLC(),
|
||||||
|
// WhatsApp(), // As of 2023-03-20 this is unusable as the version on the webpage is months out of date
|
||||||
TelegramApp(),
|
TelegramApp(),
|
||||||
|
SteamMobile(),
|
||||||
NeutronCode(),
|
NeutronCode(),
|
||||||
HTML() // This should ALWAYS be the last option as they are tried in order
|
HTML() // This should ALWAYS be the last option as they are tried in order
|
||||||
];
|
];
|
||||||
@@ -437,7 +448,8 @@ class SourceProvider {
|
|||||||
DateTime.now(),
|
DateTime.now(),
|
||||||
currentApp?.pinned ?? false,
|
currentApp?.pinned ?? false,
|
||||||
categories: currentApp?.categories ?? const [],
|
categories: currentApp?.categories ?? const [],
|
||||||
releaseDate: apk.releaseDate);
|
releaseDate: apk.releaseDate,
|
||||||
|
changeLog: apk.changeLog);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns errors in [results, errors] instead of throwing them
|
// Returns errors in [results, errors] instead of throwing them
|
||||||
|
24
pubspec.lock
24
pubspec.lock
@@ -235,6 +235,14 @@ packages:
|
|||||||
description: flutter
|
description: flutter
|
||||||
source: sdk
|
source: sdk
|
||||||
version: "0.0.0"
|
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:
|
flutter_plugin_android_lifecycle:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -325,6 +333,14 @@ packages:
|
|||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.0.1"
|
version: "2.0.1"
|
||||||
|
markdown:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: markdown
|
||||||
|
sha256: b3c60dee8c2af50ad0e6e90cceba98e47718a6ee0a7a6772c77846a0cc21f78b
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "7.0.1"
|
||||||
matcher:
|
matcher:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -702,10 +718,10 @@ packages:
|
|||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: url_launcher_ios
|
name: url_launcher_ios
|
||||||
sha256: "7ab1e5b646623d6a2537aa59d5d039f90eebef75a7c25e105f6f75de1f7750c3"
|
sha256: "3dedc66ca3c0bef9e6a93c0999aee102556a450afcc1b7bcfeace7a424927d92"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "6.1.2"
|
version: "6.1.3"
|
||||||
url_launcher_linux:
|
url_launcher_linux:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@@ -766,10 +782,10 @@ packages:
|
|||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: webview_flutter
|
name: webview_flutter
|
||||||
sha256: b6cd42db3ced5411f3d01599906156885b18e4188f7065a8a351eb84bee347e0
|
sha256: "47663d51a9061451aa3880a214ee9a65dcbb933b77bc44388e194279ab3ccaf6"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "4.0.6"
|
version: "4.0.7"
|
||||||
webview_flutter_android:
|
webview_flutter_android:
|
||||||
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.11.10+131 # When changing this, update the tag in main() accordingly
|
version: 0.11.13+134 # When changing this, update the tag in main() accordingly
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: '>=2.18.2 <3.0.0'
|
sdk: '>=2.18.2 <3.0.0'
|
||||||
@@ -59,6 +59,7 @@ dependencies:
|
|||||||
sqflite: ^2.2.0+3
|
sqflite: ^2.2.0+3
|
||||||
easy_localization: ^3.0.1
|
easy_localization: ^3.0.1
|
||||||
android_intent_plus: ^3.1.5
|
android_intent_plus: ^3.1.5
|
||||||
|
flutter_markdown: ^0.6.14
|
||||||
|
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
|
Reference in New Issue
Block a user