mirror of
				https://github.com/ImranR98/Obtainium.git
				synced 2025-10-31 13:33:28 +01:00 
			
		
		
		
	Progress. Bit less confusion, 0% tested.
This commit is contained in:
		| @@ -1,10 +1,10 @@ | ||||
| import 'package:flutter/material.dart'; | ||||
| import 'package:obtainium/services/apk_service.dart'; | ||||
| import 'package:obtainium/services/app_service.dart'; | ||||
| import 'package:obtainium/services/source_service.dart'; | ||||
| import 'package:provider/provider.dart'; | ||||
|  | ||||
| void main() async { | ||||
|   ; | ||||
|   WidgetsFlutterBinding.ensureInitialized(); | ||||
|   runApp(MultiProvider( | ||||
|     providers: [ | ||||
| @@ -12,7 +12,9 @@ void main() async { | ||||
|         create: (context) => APKService(), | ||||
|         dispose: (context, apkInstallService) => apkInstallService.dispose(), | ||||
|       ), | ||||
|       Provider(create: (context) => SourceService()) | ||||
|       Provider(create: (context) => SourceService()), | ||||
|       Provider( | ||||
|           create: (context) => AppService(Provider.of<SourceService>(context))) | ||||
|     ], | ||||
|     child: const MyApp(), | ||||
|   )); | ||||
| @@ -59,9 +61,9 @@ class MyHomePage extends StatefulWidget { | ||||
| class _MyHomePageState extends State<MyHomePage> { | ||||
|   int ind = 0; | ||||
|   var urls = [ | ||||
|     "https://github.com/Ashinch/ReadYou/releases/download/0.8.0/ReadYou-0.8.0-eec397e.apk", | ||||
|     "https://github.com/Ashinch/ReadYou/releases/download/0.8.1/ReadYou-0.8.1-c741f19.apk", | ||||
|     "https://github.com/Ashinch/ReadYou/releases/download/0.8.3/ReadYou-0.8.3-7a47329.apk" | ||||
|     "https://github.com/Ashinch/ReadYou/releases/download", // Should work | ||||
|     "http://github.com/syncthing/syncthing-android/releases/tag/1.20.4", // Should work | ||||
|     "https://github.com/videolan/vlc" // Should not | ||||
|   ]; | ||||
|  | ||||
|   @override | ||||
| @@ -83,17 +85,15 @@ class _MyHomePageState extends State<MyHomePage> { | ||||
|       ), | ||||
|       floatingActionButton: FloatingActionButton( | ||||
|         onPressed: () { | ||||
|           var source = Provider.of<SourceService>(context).getSource(urls[ind]); | ||||
|  | ||||
|           var standardURL = Provider.of<SourceService>(context) | ||||
|               .standardizeURL(urls[ind], source.standardURLRegEx); | ||||
|           var names = | ||||
|               Provider.of<SourceService>(context).getAppNames(standardURL); | ||||
|           Provider.of<APKService>(context, listen: false).downloadAndInstallAPK( | ||||
|               urls[ind], "${names.author}_${names.name}"); | ||||
|           Provider.of<AppService>(context).getApp(urls[ind]).then((app) { | ||||
|             Provider.of<APKService>(context, listen: false) | ||||
|                 .downloadAndInstallAPK(app.apkUrl, app.id); | ||||
|             setState(() { | ||||
|               ind = ind == (urls.length - 1) ? 0 : ind + 1; | ||||
|             }); | ||||
|           }).catchError((err) { | ||||
|             print(err); | ||||
|           }); | ||||
|         }, | ||||
|         tooltip: 'Increment', | ||||
|         child: const Icon(Icons.add), | ||||
|   | ||||
| @@ -1,10 +0,0 @@ | ||||
| class App { | ||||
|   late String id; | ||||
|   late String url; | ||||
|   String? installedVersion; | ||||
|   late String latestVersion; | ||||
|   String? readmeHTML; | ||||
|   String? base64Icon; | ||||
|   App(this.id, this.url, this.installedVersion, this.latestVersion, | ||||
|       this.readmeHTML, this.base64Icon); | ||||
| } | ||||
							
								
								
									
										27
									
								
								lib/services/app_service.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								lib/services/app_service.dart
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,27 @@ | ||||
| import 'package:obtainium/services/source_service.dart'; | ||||
|  | ||||
| class App { | ||||
|   late String id; | ||||
|   late String url; | ||||
|   String? installedVersion; | ||||
|   late String latestVersion; | ||||
|   late String apkUrl; | ||||
|   App(this.id, this.url, this.installedVersion, this.latestVersion, | ||||
|       this.apkUrl); | ||||
| } | ||||
|  | ||||
| class AppService { | ||||
|   late SourceService sourceService; | ||||
|   AppService(this.sourceService); | ||||
|  | ||||
|   Future<App> getApp(String url) async { | ||||
|     AppSource source = sourceService.getSource(url); | ||||
|     String standardUrl = source.standardizeURL(url); | ||||
|     AppNames names = source.getAppNames(standardUrl); | ||||
|     APKDetails apk = await source.getLatestAPKUrl(standardUrl); | ||||
|     return App("${names.author}_${names.name}", standardUrl, null, apk.version, | ||||
|         apk.downloadUrl); | ||||
|   } | ||||
|  | ||||
|   // Load Apps, Save App | ||||
| } | ||||
| @@ -1,9 +1,7 @@ | ||||
| import 'dart:convert'; | ||||
| import 'package:http/http.dart'; | ||||
| import 'package:markdown/markdown.dart'; | ||||
| import 'package:html/parser.dart'; | ||||
|  | ||||
| // Sub-classes of App Source | ||||
| // Sub-classes used in App Source | ||||
|  | ||||
| class AppNames { | ||||
|   late String author; | ||||
| @@ -19,33 +17,38 @@ class APKDetails { | ||||
|   APKDetails(this.version, this.downloadUrl); | ||||
| } | ||||
|  | ||||
| // App Source abstract class (GitHub, GitLab, etc.) | ||||
| // App Source abstract class (diff. implementations for GitHub, GitLab, etc.) | ||||
|  | ||||
| abstract class AppSource { | ||||
|   late RegExp standardURLRegEx; | ||||
|   Future<APKDetails?> getLatestAPKUrl(String url); | ||||
|   Future<String?> getReadMeHTML(String url); | ||||
|   Future<String?> getBase64IconURLFromHTML(String url, String html); | ||||
|  | ||||
|   AppSource(this.standardURLRegEx); | ||||
|   String standardizeURL(String url); | ||||
|   Future<APKDetails> getLatestAPKUrl(String standardUrl); | ||||
|   AppNames getAppNames(String standardUrl); | ||||
| } | ||||
|  | ||||
| // Specific App Source definitions | ||||
| // Specific App Source classes | ||||
|  | ||||
| class GitHub extends AppSource { | ||||
|   GitHub() : super(RegExp(r"^https?://github.com/[^/]*/[^/]*")); | ||||
| class GitHub implements AppSource { | ||||
|   @override | ||||
|   String standardizeURL(String url) { | ||||
|     RegExp standardUrlRegEx = RegExp(r"^https?://github.com/[^/]*/[^/]*"); | ||||
|     var match = standardUrlRegEx.firstMatch(url.toLowerCase()); | ||||
|     if (match == null) { | ||||
|       throw "Not a valid URL"; | ||||
|     } | ||||
|     return url.substring(0, match.end); | ||||
|   } | ||||
|  | ||||
|   String getRawContentURL(String url) { | ||||
|   String convertURLToRawContentURL(String url) { | ||||
|     int tempInd1 = url.indexOf('://') + 3; | ||||
|     int tempInd2 = url.indexOf('://') + 13; | ||||
|     int tempInd2 = url.substring(tempInd1).indexOf('/') + tempInd1; | ||||
|     return "${url.substring(0, tempInd1)}raw.githubusercontent.com${url.substring(tempInd2)}"; | ||||
|   } | ||||
|  | ||||
|   @override | ||||
|   Future<APKDetails?> getLatestAPKUrl(String url) async { | ||||
|     int tempInd = url.indexOf('://') + 3; | ||||
|   Future<APKDetails> getLatestAPKUrl(String standardUrl) async { | ||||
|     int tempInd = standardUrl.indexOf('://') + 3; | ||||
|     Response res = await get(Uri.parse( | ||||
|         "${url.substring(0, tempInd)}api.${url.substring(tempInd)}/releases/latest")); | ||||
|         "${standardUrl.substring(0, tempInd)}api.${standardUrl.substring(tempInd)}/releases/latest")); | ||||
|     if (res.statusCode == 200) { | ||||
|       var release = jsonDecode(res.body); | ||||
|       for (var i = 0; i < release['assets'].length; i++) { | ||||
| @@ -65,51 +68,14 @@ class GitHub extends AppSource { | ||||
|   } | ||||
|  | ||||
|   @override | ||||
|   Future<String?> getReadMeHTML(String url) async { | ||||
|     String uri = getRawContentURL(url); | ||||
|     List<String> possibleSuffixes = ["main/README.md", "master/README.md"]; | ||||
|     for (var i = 0; i < possibleSuffixes.length; i++) { | ||||
|       Response res = await get(Uri.parse("$uri/${possibleSuffixes[i]}")); | ||||
|       if (res.statusCode == 200) { | ||||
|         return markdownToHtml(res.body); | ||||
|       } | ||||
|     } | ||||
|     return null; | ||||
|   } | ||||
|  | ||||
|   @override | ||||
|   Future<String?> getBase64IconURLFromHTML(String url, String html) async { | ||||
|     var icon = parse(html).getElementsByClassName("img")?[0]; | ||||
|     if (icon != null) { | ||||
|       String uri = getRawContentURL(url); | ||||
|       List<String> possibleBranches = ["main", "master"]; | ||||
|       for (var i = 0; i < possibleBranches.length; i++) { | ||||
|         var imgUrl = "$uri/${possibleBranches[i]}/${icon.attributes['src']}"; | ||||
|         Response res = await get(Uri.parse(imgUrl)); | ||||
|         if (res.statusCode == 200) { | ||||
|           return imgUrl; | ||||
|         } | ||||
|       } | ||||
|     } | ||||
|     return null; | ||||
|   AppNames getAppNames(String standardUrl) { | ||||
|     String temp = standardUrl.substring(standardUrl.indexOf('://') + 3); | ||||
|     List<String> names = temp.substring(temp.indexOf('/')).split('/'); | ||||
|     return AppNames(names[0], names[1]); | ||||
|   } | ||||
| } | ||||
|  | ||||
| class SourceService { | ||||
|   String standardizeURL(String url, RegExp standardURLRegEx) { | ||||
|     var match = standardURLRegEx.firstMatch(url.toLowerCase()); | ||||
|     if (match == null) { | ||||
|       throw "Not a valid URL"; | ||||
|     } | ||||
|     return url.substring(0, match.end); | ||||
|   } | ||||
|  | ||||
|   AppNames getAppNames(String standardURL) { | ||||
|     String temp = standardURL.substring(standardURL.indexOf('://') + 3); | ||||
|     List<String> names = temp.substring(temp.indexOf('/')).split('/'); | ||||
|     return AppNames(names[0], names[1]); | ||||
|   } | ||||
|  | ||||
|   // Add more source classes here so they are available via the service | ||||
|   var github = GitHub(); | ||||
|   AppSource getSource(String url) { | ||||
| @@ -119,19 +85,3 @@ class SourceService { | ||||
|     throw "URL does not match a known source"; | ||||
|   } | ||||
| } | ||||
|  | ||||
| /* | ||||
| - Make a function that validates and standardizes github URLs, do the same for gitlab (fail = error) | ||||
| - Make a function that gets the App title and Author name from a github URL, do the same for gitlab (can't fail) | ||||
| - Make a function that takes a github URL and finds the latest APK release if any (with version), do the same for gitlab (fail = error) | ||||
| - Make a function that takes a github URL and returns a README HTML if any, do the same for gitlab (fail = "no description") | ||||
| - Make a function that looks for the first image in a README HTML and returns its url (fail = no icon) | ||||
|  | ||||
| - Make a function that integrates all above and returns an App object for a given github URL, do the same for gitlab | ||||
|  | ||||
| - Make a function that detects the URL (Github or Gitlab) and runs the right function above | ||||
|  | ||||
| - Make a function that can save/load an App object to/from persistent storage (JSON file with unique App ID as file name) | ||||
|  | ||||
| - Make a function (using the above fn) that loads an array of all Apps | ||||
| */ | ||||
							
								
								
									
										21
									
								
								pubspec.lock
									
									
									
									
									
								
							
							
						
						
									
										21
									
								
								pubspec.lock
									
									
									
									
									
								
							| @@ -43,13 +43,6 @@ packages: | ||||
|       url: "https://pub.dartlang.org" | ||||
|     source: hosted | ||||
|     version: "1.16.0" | ||||
|   csslib: | ||||
|     dependency: transitive | ||||
|     description: | ||||
|       name: csslib | ||||
|       url: "https://pub.dartlang.org" | ||||
|     source: hosted | ||||
|     version: "0.17.2" | ||||
|   cupertino_icons: | ||||
|     dependency: "direct main" | ||||
|     description: | ||||
| @@ -137,13 +130,6 @@ packages: | ||||
|     description: flutter | ||||
|     source: sdk | ||||
|     version: "0.0.0" | ||||
|   html: | ||||
|     dependency: "direct main" | ||||
|     description: | ||||
|       name: html | ||||
|       url: "https://pub.dartlang.org" | ||||
|     source: hosted | ||||
|     version: "0.15.0" | ||||
|   http: | ||||
|     dependency: "direct main" | ||||
|     description: | ||||
| @@ -165,13 +151,6 @@ packages: | ||||
|       url: "https://pub.dartlang.org" | ||||
|     source: hosted | ||||
|     version: "2.0.0" | ||||
|   markdown: | ||||
|     dependency: "direct main" | ||||
|     description: | ||||
|       name: markdown | ||||
|       url: "https://pub.dartlang.org" | ||||
|     source: hosted | ||||
|     version: "6.0.0" | ||||
|   matcher: | ||||
|     dependency: transitive | ||||
|     description: | ||||
|   | ||||
| @@ -42,8 +42,6 @@ dependencies: | ||||
|   flutter_local_notifications: ^9.7.0 | ||||
|   provider: ^6.0.3 | ||||
|   http: ^0.13.5 | ||||
|   markdown: ^6.0.0 | ||||
|   html: ^0.15.0 | ||||
|  | ||||
| dev_dependencies: | ||||
|   flutter_test: | ||||
|   | ||||
		Reference in New Issue
	
	Block a user