mirror of
				https://github.com/ImranR98/Obtainium.git
				synced 2025-10-30 04:53:28 +01: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 | ||||
|   - 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 {}}) { | ||||
|   | ||||
| @@ -97,10 +97,13 @@ class NeutronCode extends AppSource { | ||||
|       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); | ||||
|           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, | ||||
|   | ||||
| @@ -15,9 +15,6 @@ class TelegramApp extends AppSource { | ||||
|     return 'https://$host'; | ||||
|   } | ||||
|  | ||||
|   @override | ||||
|   String? changeLogPageFromStandardUrl(String standardUrl) => null; | ||||
|  | ||||
|   @override | ||||
|   Future<APKDetails> getLatestAPKDetails( | ||||
|     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( | ||||
|             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.10'; | ||||
| 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), | ||||
|                                           )) | ||||
|                                     ], | ||||
|                                   ), | ||||
|   | ||||
| @@ -571,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) | ||||
|   | ||||
| @@ -20,6 +20,8 @@ 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'; | ||||
| @@ -36,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 { | ||||
| @@ -54,6 +58,7 @@ class App { | ||||
|   bool pinned = false; | ||||
|   List<String> categories; | ||||
|   late DateTime? releaseDate; | ||||
|   late String? changeLog; | ||||
|   App( | ||||
|       this.id, | ||||
|       this.url, | ||||
| @@ -67,7 +72,8 @@ class App { | ||||
|       this.lastUpdateCheck, | ||||
|       this.pinned, | ||||
|       {this.categories = const [], | ||||
|       this.releaseDate}); | ||||
|       this.releaseDate, | ||||
|       this.changeLog}); | ||||
|  | ||||
|   @override | ||||
|   String toString() { | ||||
| @@ -130,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() => { | ||||
| @@ -173,7 +180,8 @@ class App { | ||||
|         'lastUpdateCheck': lastUpdateCheck?.microsecondsSinceEpoch, | ||||
|         'pinned': pinned, | ||||
|         'categories': categories, | ||||
|         'releaseDate': releaseDate?.microsecondsSinceEpoch | ||||
|         'releaseDate': releaseDate?.microsecondsSinceEpoch, | ||||
|         'changeLog': changeLog | ||||
|       }; | ||||
| } | ||||
|  | ||||
| @@ -222,6 +230,7 @@ class AppSource { | ||||
|   String? host; | ||||
|   late String name; | ||||
|   bool enforceTrackOnly = false; | ||||
|   bool changeLogIfAnyIsMarkDown = true; | ||||
|  | ||||
|   AppSource() { | ||||
|     name = runtimeType.toString(); | ||||
| @@ -334,13 +343,15 @@ class SourceProvider { | ||||
|     Codeberg(), | ||||
|     FDroid(), | ||||
|     IzzyOnDroid(), | ||||
|     Mullvad(), | ||||
|     Signal(), | ||||
|     FDroidRepo(), | ||||
|     SourceForge(), | ||||
|     APKMirror(), | ||||
|     FDroidRepo(), | ||||
|     SteamMobile(), | ||||
|     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 | ||||
|   ]; | ||||
| @@ -437,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 | ||||
|   | ||||
							
								
								
									
										24
									
								
								pubspec.lock
									
									
									
									
									
								
							
							
						
						
									
										24
									
								
								pubspec.lock
									
									
									
									
									
								
							| @@ -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: | ||||
| @@ -702,10 +718,10 @@ packages: | ||||
|     dependency: transitive | ||||
|     description: | ||||
|       name: url_launcher_ios | ||||
|       sha256: "7ab1e5b646623d6a2537aa59d5d039f90eebef75a7c25e105f6f75de1f7750c3" | ||||
|       sha256: "3dedc66ca3c0bef9e6a93c0999aee102556a450afcc1b7bcfeace7a424927d92" | ||||
|       url: "https://pub.dev" | ||||
|     source: hosted | ||||
|     version: "6.1.2" | ||||
|     version: "6.1.3" | ||||
|   url_launcher_linux: | ||||
|     dependency: transitive | ||||
|     description: | ||||
| @@ -766,10 +782,10 @@ 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: | ||||
|   | ||||
| @@ -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.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: | ||||
|   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