From 0e36e53b469c8d8a484b598cbd6be2913d3bf602 Mon Sep 17 00:00:00 2001 From: Imran Remtulla Date: Thu, 18 Aug 2022 15:25:30 -0400 Subject: [PATCH] More progress on UI - basics almost done --- lib/main.dart | 61 ---------------------- lib/pages/add_app.dart | 89 +++++++++++++++++++++------------ lib/pages/app.dart | 69 ++++++++++++++++++++++--- lib/pages/apps.dart | 44 ++++++++++------ lib/services/apps_provider.dart | 39 +++++++++++---- 5 files changed, 176 insertions(+), 126 deletions(-) diff --git a/lib/main.dart b/lib/main.dart index 222d20b..0142de8 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -22,67 +22,6 @@ class MyApp extends StatelessWidget { theme: ThemeData( primarySwatch: Colors.blue, ), - // home: const MyHomePage(title: 'Obtainium'), home: const AppsPage()); } } - -// class MyHomePage extends StatefulWidget { -// const MyHomePage({super.key, required this.title}); - -// final String title; - -// @override -// State createState() => _MyHomePageState(); -// } - -// class _MyHomePageState extends State { -// int ind = 0; -// List urls = [ -// '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 -// Widget build(BuildContext context) { -// ToastContext().init(context); -// return Scaffold( -// appBar: AppBar( -// title: Text(widget.title), -// ), -// body: Center( -// child: Column( -// mainAxisAlignment: MainAxisAlignment.center, -// children: [ -// Text( -// urls[ind], -// style: Theme.of(context).textTheme.headline4, -// ), -// ], -// ), -// ), -// floatingActionButton: FloatingActionButton( -// onPressed: () { -// context.read().installApp(urls[ind]).then((_) { -// setState(() { -// ind = ind == (urls.length - 1) ? 0 : ind + 1; -// }); -// }).catchError((err) { -// if (err is! String) { -// err = "Unknown Error"; -// } -// Toast.show(err); -// }); -// }, -// tooltip: 'Increment', -// child: const Icon(Icons.add), -// ), -// ); -// } - -// @override -// void dispose() { -// super.dispose(); -// } -// } diff --git a/lib/pages/add_app.dart b/lib/pages/add_app.dart index daeed1d..92bea22 100644 --- a/lib/pages/add_app.dart +++ b/lib/pages/add_app.dart @@ -14,6 +14,7 @@ class AddAppPage extends StatefulWidget { class _AddAppPageState extends State { final _formKey = GlobalKey(); final urlInputController = TextEditingController(); + bool gettingAppInfo = false; @override Widget build(BuildContext context) { @@ -25,45 +26,67 @@ class _AddAppPageState extends State { child: Form( key: _formKey, child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.stretch, children: [ - TextFormField( - controller: urlInputController, - validator: (value) { - if (value == null || - value.isEmpty || - Uri.tryParse(value) == null) { - return 'Please enter a supported source URL'; - } - return null; - }, - ), + const Spacer(), Padding( - padding: const EdgeInsets.symmetric(vertical: 16.0), + padding: const EdgeInsets.symmetric(horizontal: 16.0), + child: TextFormField( + decoration: const InputDecoration( + border: OutlineInputBorder(), + hintText: 'https://github.com/Author/Project', + helperText: 'Enter the App source URL'), + controller: urlInputController, + validator: (value) { + if (value == null || + value.isEmpty || + Uri.tryParse(value) == null) { + return 'Please enter a supported source URL'; + } + return null; + }, + )), + Padding( + padding: + const EdgeInsets.symmetric(vertical: 8.0, horizontal: 16.0), child: ElevatedButton( - onPressed: () { - if (_formKey.currentState!.validate()) { - SourceService() - .getApp(urlInputController.value.text) - .then((app) { - var appsProvider = context.read(); - appsProvider.saveApp(app).then((_) { - Navigator.push( - context, - MaterialPageRoute( - builder: (context) => AppPage(appId: app.id))); - }); - }).catchError((e) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text(e.toString())), - ); - }); - } - }, + onPressed: gettingAppInfo + ? null + : () { + if (_formKey.currentState!.validate()) { + setState(() { + gettingAppInfo = true; + }); + SourceService() + .getApp(urlInputController.value.text) + .then((app) { + var appsProvider = context.read(); + if (appsProvider.apps.containsKey(app.id)) { + throw 'App already added'; + } + appsProvider.saveApp(app).then((_) { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => + AppPage(appId: app.id))); + }); + }).catchError((e) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text(e.toString())), + ); + }).whenComplete(() { + setState(() { + gettingAppInfo = false; + }); + }); + } + }, child: const Text('Add'), ), ), + const Spacer(), + if (gettingAppInfo) const LinearProgressIndicator(), ], ), )), diff --git a/lib/pages/app.dart b/lib/pages/app.dart index 5898934..95b9552 100644 --- a/lib/pages/app.dart +++ b/lib/pages/app.dart @@ -18,15 +18,68 @@ class _AppPageState extends State { Widget build(BuildContext context) { var appsProvider = context.watch(); App? app = appsProvider.apps[widget.appId]; - if (app == null) { - Navigator.pop(context); + if (app?.installedVersion != null) { + appsProvider.getUpdate(app!.id); } return Scaffold( - appBar: AppBar( - title: Text('App - ${app?.name} - ${app?.author}'), - ), - body: WebView( - initialUrl: app?.url, - )); + appBar: AppBar( + title: Text('${app?.author}/${app?.name}'), + ), + body: WebView( + initialUrl: app?.url, + ), + bottomSheet: Padding( + padding: const EdgeInsets.symmetric(vertical: 8.0, horizontal: 16.0), + child: + Row(mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ + Expanded( + child: OutlinedButton( + onPressed: (app?.installedVersion == null || + appsProvider.checkAppObjectForUpdate(app!)) && + app?.currentDownloadId == null + ? () { + appsProvider.backgroundDownloadAndInstallApp(app!); + } + : null, + child: Text( + app?.installedVersion == null ? 'Install' : 'Update'))), + const SizedBox(width: 16.0), + OutlinedButton( + onPressed: app?.currentDownloadId != null + ? null + : () { + showDialog( + context: context, + builder: (BuildContext ctx) { + return AlertDialog( + title: const Text('Remove App?'), + content: Text( + 'This will remove \'${app?.name}\' from Obtainium.${app?.installedVersion != null ? '\n\nNote that while Obtainium will no longer track its updates, the App will remain installed.' : ''}'), + actions: [ + TextButton( + onPressed: () { + appsProvider.removeApp(app!.id).then((_) { + int count = 0; + Navigator.of(context) + .popUntil((_) => count++ >= 2); + }); + }, + child: const Text('Remove')), + TextButton( + onPressed: () { + Navigator.of(context).pop(); + }, + child: const Text('Cancel')) + ], + ); + }); + }, + style: TextButton.styleFrom( + foregroundColor: Theme.of(context).errorColor), + child: const Text('Remove'), + ) + // TODO: Add progress bar when app?.currentDownloadId != null + ])), + ); } } diff --git a/lib/pages/apps.dart b/lib/pages/apps.dart index f65643e..2621303 100644 --- a/lib/pages/apps.dart +++ b/lib/pages/apps.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:obtainium/pages/add_app.dart'; +import 'package:obtainium/pages/app.dart'; import 'package:obtainium/services/apps_provider.dart'; import 'package:provider/provider.dart'; @@ -13,24 +14,39 @@ class AppsPage extends StatefulWidget { class _AppsPageState extends State { @override Widget build(BuildContext context) { + var appsProvider = context.watch(); + return Scaffold( appBar: AppBar( - title: const Text('Obtainium - Apps'), + title: const Text('Obtainium'), ), body: Center( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: () { - var appsProvider = context.watch(); - if (appsProvider.loadingApps) { - return [const Text('Loading Apps...')]; - } else if (appsProvider.apps.isEmpty) { - return [const Text('No Apps Yet.')]; - } else { - return appsProvider.apps.values.map((e) => Text(e.id)).toList(); - } - }(), - ), + child: appsProvider.loadingApps + ? const CircularProgressIndicator() + : appsProvider.apps.isEmpty + ? Text( + 'No Apps', + style: Theme.of(context).textTheme.headline4, + ) + : ListView( + children: appsProvider.apps.values + .map( + (e) => ListTile( + title: Text(e.name), + subtitle: Text(e.author), + trailing: + Text(e.installedVersion ?? 'Not Installed'), + onTap: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => AppPage(appId: e.id)), + ); + }, + ), + ) + .toList(), + ), ), floatingActionButton: FloatingActionButton( onPressed: () { diff --git a/lib/services/apps_provider.dart b/lib/services/apps_provider.dart index 614d813..6d21410 100644 --- a/lib/services/apps_provider.dart +++ b/lib/services/apps_provider.dart @@ -92,9 +92,9 @@ class AppsProvider with ChangeNotifier { } // Change App status to no longer downloading App? foundApp; - apps.forEach((id, app) { + apps.forEach((appId, app) { if (app.currentDownloadId == id) { - foundApp = apps[app.id]; + foundApp = apps[appId]; } }); foundApp!.currentDownloadId = null; @@ -112,7 +112,7 @@ class AppsProvider with ChangeNotifier { if (apkDir.existsSync()) apkDir.deleteSync(recursive: true); apkDir.createSync(recursive: true); String? downloadId = await FlutterDownloader.enqueue( - url: app.url, + url: app.apkUrl, savedDir: apkDir.path, showNotification: true, openFileFromNotification: false, @@ -158,23 +158,42 @@ class AppsProvider with ChangeNotifier { notifyListeners(); } - bool checkUpdate(App app) { + Future removeApp(String appId) async { + File file = File('${(await getAppsDir()).path}/$appId.json'); + if (file.existsSync()) { + file.deleteSync(); + } + if (apps.containsKey(appId)) { + apps.remove(appId); + } + notifyListeners(); + } + + bool checkAppObjectForUpdate(App app) { if (!apps.containsKey(app.id)) { throw 'App not found'; } return app.latestVersion != apps[app.id]?.installedVersion; } - Future> checkUpdates() async { + Future getUpdate(String appId) async { + App? currentApp = apps[appId]; + App newApp = await SourceService().getApp(currentApp!.url); + if (newApp.latestVersion != currentApp.latestVersion) { + newApp.installedVersion = currentApp.installedVersion; + await saveApp(newApp); + return newApp; + } + return null; + } + + Future> getUpdates() async { List updates = []; List appIds = apps.keys.toList(); for (int i = 0; i < appIds.length; i++) { - App? currentApp = apps[appIds[i]]; - App newApp = await SourceService().getApp(currentApp!.url); - if (newApp.latestVersion != currentApp.latestVersion) { - newApp.installedVersion = currentApp.installedVersion; + App? newApp = await getUpdate(appIds[i]); + if (newApp != null) { updates.add(newApp); - await saveApp(newApp); } } return updates;