mirror of
				https://github.com/ImranR98/Obtainium.git
				synced 2025-10-31 05:23:28 +01:00 
			
		
		
		
	Added strechy appbars to all pages
This commit is contained in:
		| @@ -1,5 +1,6 @@ | ||||
| import 'package:flutter/material.dart'; | ||||
| import 'package:flutter/services.dart'; | ||||
| import 'package:obtainium/components/custom_app_bar.dart'; | ||||
| import 'package:obtainium/pages/app.dart'; | ||||
| import 'package:obtainium/providers/apps_provider.dart'; | ||||
| import 'package:obtainium/providers/settings_provider.dart'; | ||||
| @@ -22,113 +23,125 @@ class _AddAppPageState extends State<AddAppPage> { | ||||
|   @override | ||||
|   Widget build(BuildContext context) { | ||||
|     SourceProvider sourceProvider = SourceProvider(); | ||||
|     return Center( | ||||
|       child: Form( | ||||
|           key: _formKey, | ||||
|           child: Column( | ||||
|             mainAxisAlignment: MainAxisAlignment.spaceBetween, | ||||
|             crossAxisAlignment: CrossAxisAlignment.stretch, | ||||
|             children: [ | ||||
|               Container(), | ||||
|               Padding( | ||||
|                 padding: const EdgeInsets.all(16), | ||||
|     return CustomScrollView(slivers: <Widget>[ | ||||
|       const CustomAppBar(title: 'Add App'), | ||||
|       SliverFillRemaining( | ||||
|           hasScrollBody: false, | ||||
|           child: Center( | ||||
|             child: Form( | ||||
|                 key: _formKey, | ||||
|                 child: Column( | ||||
|                   mainAxisAlignment: MainAxisAlignment.spaceBetween, | ||||
|                   crossAxisAlignment: CrossAxisAlignment.stretch, | ||||
|                   children: [ | ||||
|                     TextFormField( | ||||
|                       decoration: const InputDecoration( | ||||
|                           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; | ||||
|                       }, | ||||
|                     ), | ||||
|                     Container(), | ||||
|                     Padding( | ||||
|                       padding: const EdgeInsets.symmetric(vertical: 16.0), | ||||
|                       child: ElevatedButton( | ||||
|                         onPressed: gettingAppInfo | ||||
|                             ? null | ||||
|                             : () { | ||||
|                                 HapticFeedback.mediumImpact(); | ||||
|                                 if (_formKey.currentState!.validate()) { | ||||
|                                   setState(() { | ||||
|                                     gettingAppInfo = true; | ||||
|                                   }); | ||||
|                                   sourceProvider | ||||
|                                       .getApp(urlInputController.value.text) | ||||
|                                       .then((app) { | ||||
|                                     var appsProvider = | ||||
|                                         context.read<AppsProvider>(); | ||||
|                                     var settingsProvider = | ||||
|                                         context.read<SettingsProvider>(); | ||||
|                                     if (appsProvider.apps.containsKey(app.id)) { | ||||
|                                       throw 'App already added'; | ||||
|                                     } | ||||
|                                     settingsProvider | ||||
|                                         .getInstallPermission() | ||||
|                                         .then((_) { | ||||
|                                       appsProvider.saveApp(app).then((_) { | ||||
|                                         urlInputController.clear(); | ||||
|                                         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'), | ||||
|                       padding: const EdgeInsets.all(16), | ||||
|                       child: Column( | ||||
|                         crossAxisAlignment: CrossAxisAlignment.stretch, | ||||
|                         children: [ | ||||
|                           TextFormField( | ||||
|                             decoration: const InputDecoration( | ||||
|                                 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: 16.0), | ||||
|                             child: ElevatedButton( | ||||
|                               onPressed: gettingAppInfo | ||||
|                                   ? null | ||||
|                                   : () { | ||||
|                                       HapticFeedback.mediumImpact(); | ||||
|                                       if (_formKey.currentState!.validate()) { | ||||
|                                         setState(() { | ||||
|                                           gettingAppInfo = true; | ||||
|                                         }); | ||||
|                                         sourceProvider | ||||
|                                             .getApp( | ||||
|                                                 urlInputController.value.text) | ||||
|                                             .then((app) { | ||||
|                                           var appsProvider = | ||||
|                                               context.read<AppsProvider>(); | ||||
|                                           var settingsProvider = | ||||
|                                               context.read<SettingsProvider>(); | ||||
|                                           if (appsProvider.apps | ||||
|                                               .containsKey(app.id)) { | ||||
|                                             throw 'App already added'; | ||||
|                                           } | ||||
|                                           settingsProvider | ||||
|                                               .getInstallPermission() | ||||
|                                               .then((_) { | ||||
|                                             appsProvider.saveApp(app).then((_) { | ||||
|                                               urlInputController.clear(); | ||||
|                                               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'), | ||||
|                             ), | ||||
|                           ), | ||||
|                         ], | ||||
|                       ), | ||||
|                     ), | ||||
|                     Column( | ||||
|                         crossAxisAlignment: CrossAxisAlignment.center, | ||||
|                         children: [ | ||||
|                           const Text( | ||||
|                             'Supported Sources:', | ||||
|                             // style: TextStyle(fontWeight: FontWeight.bold), | ||||
|                             // style: Theme.of(context).textTheme.bodySmall, | ||||
|                           ), | ||||
|                           const SizedBox( | ||||
|                             height: 8, | ||||
|                           ), | ||||
|                           ...sourceProvider | ||||
|                               .getSourceHosts() | ||||
|                               .map((e) => GestureDetector( | ||||
|                                   onTap: () { | ||||
|                                     launchUrlString('https://$e', | ||||
|                                         mode: LaunchMode.externalApplication); | ||||
|                                   }, | ||||
|                                   child: Text( | ||||
|                                     e, | ||||
|                                     style: const TextStyle( | ||||
|                                         decoration: TextDecoration.underline, | ||||
|                                         fontStyle: FontStyle.italic), | ||||
|                                   ))) | ||||
|                               .toList() | ||||
|                         ]), | ||||
|                     if (gettingAppInfo) | ||||
|                       const LinearProgressIndicator() | ||||
|                     else | ||||
|                       Container(), | ||||
|                   ], | ||||
|                 ), | ||||
|               ), | ||||
|               Column(crossAxisAlignment: CrossAxisAlignment.center, children: [ | ||||
|                 const Text( | ||||
|                   'Supported Sources:', | ||||
|                   // style: TextStyle(fontWeight: FontWeight.bold), | ||||
|                   // style: Theme.of(context).textTheme.bodySmall, | ||||
|                 ), | ||||
|                 const SizedBox( | ||||
|                   height: 8, | ||||
|                 ), | ||||
|                 ...sourceProvider | ||||
|                     .getSourceHosts() | ||||
|                     .map((e) => GestureDetector( | ||||
|                         onTap: () { | ||||
|                           launchUrlString('https://$e', | ||||
|                               mode: LaunchMode.externalApplication); | ||||
|                         }, | ||||
|                         child: Text( | ||||
|                           e, | ||||
|                           style: const TextStyle( | ||||
|                               decoration: TextDecoration.underline, | ||||
|                               fontStyle: FontStyle.italic), | ||||
|                         ))) | ||||
|                     .toList() | ||||
|               ]), | ||||
|               if (gettingAppInfo) | ||||
|                 const LinearProgressIndicator() | ||||
|               else | ||||
|                 Container(), | ||||
|             ], | ||||
|           )), | ||||
|     ); | ||||
|                 )), | ||||
|           )) | ||||
|     ]); | ||||
|   } | ||||
| } | ||||
|   | ||||
| @@ -1,5 +1,6 @@ | ||||
| import 'package:flutter/material.dart'; | ||||
| import 'package:flutter/services.dart'; | ||||
| import 'package:obtainium/components/custom_app_bar.dart'; | ||||
| import 'package:obtainium/providers/apps_provider.dart'; | ||||
| import 'package:obtainium/providers/settings_provider.dart'; | ||||
| import 'package:url_launcher/url_launcher_string.dart'; | ||||
| @@ -25,61 +26,63 @@ class _AppPageState extends State<AppPage> { | ||||
|       appsProvider.getUpdate(app!.app.id); | ||||
|     } | ||||
|     return Scaffold( | ||||
|       appBar: AppBar( | ||||
|         title: Text('${app?.app.author}/${app?.app.name}'), | ||||
|       ), | ||||
|       body: settingsProvider.showAppWebpage | ||||
|           ? WebView( | ||||
|               initialUrl: app?.app.url, | ||||
|               javascriptMode: JavascriptMode.unrestricted, | ||||
|             ) | ||||
|           : Column( | ||||
|               mainAxisAlignment: MainAxisAlignment.center, | ||||
|               crossAxisAlignment: CrossAxisAlignment.stretch, | ||||
|               children: [ | ||||
|                 Text( | ||||
|                   app?.app.name ?? 'App', | ||||
|                   textAlign: TextAlign.center, | ||||
|                   style: Theme.of(context).textTheme.displayLarge, | ||||
|                 ), | ||||
|                 Text( | ||||
|                   'By ${app?.app.author ?? 'Unknown'}', | ||||
|                   textAlign: TextAlign.center, | ||||
|                   style: Theme.of(context).textTheme.headlineMedium, | ||||
|                 ), | ||||
|                 const SizedBox( | ||||
|                   height: 32, | ||||
|                 ), | ||||
|                 GestureDetector( | ||||
|                     onTap: () { | ||||
|                       if (app?.app.url != null) { | ||||
|                         launchUrlString(app?.app.url ?? '', | ||||
|                             mode: LaunchMode.externalApplication); | ||||
|                       } | ||||
|                     }, | ||||
|                     child: Text( | ||||
|                       app?.app.url ?? '', | ||||
|       body: CustomScrollView(slivers: <Widget>[ | ||||
|         CustomAppBar(title: '${app?.app.name}'), | ||||
|         SliverFillRemaining( | ||||
|           child: settingsProvider.showAppWebpage | ||||
|               ? WebView( | ||||
|                   initialUrl: app?.app.url, | ||||
|                   javascriptMode: JavascriptMode.unrestricted, | ||||
|                 ) | ||||
|               : Column( | ||||
|                   mainAxisAlignment: MainAxisAlignment.center, | ||||
|                   crossAxisAlignment: CrossAxisAlignment.stretch, | ||||
|                   children: [ | ||||
|                     Text( | ||||
|                       app?.app.name ?? 'App', | ||||
|                       textAlign: TextAlign.center, | ||||
|                       style: const TextStyle( | ||||
|                           decoration: TextDecoration.underline, | ||||
|                           fontStyle: FontStyle.italic, | ||||
|                           fontSize: 12), | ||||
|                     )), | ||||
|                 const SizedBox( | ||||
|                   height: 32, | ||||
|                       style: Theme.of(context).textTheme.displayLarge, | ||||
|                     ), | ||||
|                     Text( | ||||
|                       'By ${app?.app.author ?? 'Unknown'}', | ||||
|                       textAlign: TextAlign.center, | ||||
|                       style: Theme.of(context).textTheme.headlineMedium, | ||||
|                     ), | ||||
|                     const SizedBox( | ||||
|                       height: 32, | ||||
|                     ), | ||||
|                     GestureDetector( | ||||
|                         onTap: () { | ||||
|                           if (app?.app.url != null) { | ||||
|                             launchUrlString(app?.app.url ?? '', | ||||
|                                 mode: LaunchMode.externalApplication); | ||||
|                           } | ||||
|                         }, | ||||
|                         child: Text( | ||||
|                           app?.app.url ?? '', | ||||
|                           textAlign: TextAlign.center, | ||||
|                           style: const TextStyle( | ||||
|                               decoration: TextDecoration.underline, | ||||
|                               fontStyle: FontStyle.italic, | ||||
|                               fontSize: 12), | ||||
|                         )), | ||||
|                     const SizedBox( | ||||
|                       height: 32, | ||||
|                     ), | ||||
|                     Text( | ||||
|                       'Latest Version: ${app?.app.latestVersion ?? 'Unknown'}', | ||||
|                       textAlign: TextAlign.center, | ||||
|                       style: Theme.of(context).textTheme.bodyLarge, | ||||
|                     ), | ||||
|                     Text( | ||||
|                       'Installed Version: ${app?.app.installedVersion ?? 'None'}', | ||||
|                       textAlign: TextAlign.center, | ||||
|                       style: Theme.of(context).textTheme.bodyLarge, | ||||
|                     ), | ||||
|                   ], | ||||
|                 ), | ||||
|                 Text( | ||||
|                   'Latest Version: ${app?.app.latestVersion ?? 'Unknown'}', | ||||
|                   textAlign: TextAlign.center, | ||||
|                   style: Theme.of(context).textTheme.bodyLarge, | ||||
|                 ), | ||||
|                 Text( | ||||
|                   'Installed Version: ${app?.app.installedVersion ?? 'None'}', | ||||
|                   textAlign: TextAlign.center, | ||||
|                   style: Theme.of(context).textTheme.bodyLarge, | ||||
|                 ), | ||||
|               ], | ||||
|             ), | ||||
|         ), | ||||
|       ]), | ||||
|       bottomSheet: Padding( | ||||
|           padding: EdgeInsets.fromLTRB( | ||||
|               0, 0, 0, MediaQuery.of(context).padding.bottom), | ||||
|   | ||||
| @@ -1,5 +1,6 @@ | ||||
| import 'package:flutter/material.dart'; | ||||
| import 'package:flutter/services.dart'; | ||||
| import 'package:obtainium/components/custom_app_bar.dart'; | ||||
| import 'package:obtainium/pages/app.dart'; | ||||
| import 'package:obtainium/providers/apps_provider.dart'; | ||||
| import 'package:obtainium/providers/settings_provider.dart'; | ||||
| @@ -47,34 +48,17 @@ class _AppsPageState extends State<AppsPage> { | ||||
|                               existingUpdateAppIds, context); | ||||
|                         }); | ||||
|                       }, | ||||
|                 icon: const Icon(Icons.update), | ||||
|                 label: const Text('Update All')), | ||||
|                 icon: const Icon(Icons.install_mobile_outlined), | ||||
|                 label: const Text('Install All')), | ||||
|         body: RefreshIndicator( | ||||
|             onRefresh: () { | ||||
|               HapticFeedback.lightImpact(); | ||||
|               return appsProvider.checkUpdates(); | ||||
|             }, | ||||
|             child: CustomScrollView(slivers: <Widget>[ | ||||
|               SliverAppBar( | ||||
|                 pinned: true, | ||||
|                 snap: false, | ||||
|                 floating: false, | ||||
|                 expandedHeight: 100, | ||||
|                 backgroundColor: MaterialStateColor.resolveWith( | ||||
|                   (states) => states.contains(MaterialState.scrolledUnder) | ||||
|                       ? Theme.of(context).colorScheme.surface | ||||
|                       : Theme.of(context).canvasColor, | ||||
|                 ), | ||||
|                 flexibleSpace: const FlexibleSpaceBar( | ||||
|                   titlePadding: const EdgeInsets.only(bottom: 16.0, left: 20.0), | ||||
|                   title: Text( | ||||
|                     'Apps', | ||||
|                     style: TextStyle(color: Colors.black), | ||||
|                   ), | ||||
|                 ), | ||||
|               ), | ||||
|               const CustomAppBar(title: 'Apps'), | ||||
|               if (appsProvider.loadingApps || appsProvider.apps.isEmpty) | ||||
|                 SliverToBoxAdapter( | ||||
|                 SliverFillRemaining( | ||||
|                     child: appsProvider.loadingApps | ||||
|                         ? const CircularProgressIndicator() | ||||
|                         : Text( | ||||
|   | ||||
| @@ -12,31 +12,38 @@ class HomePage extends StatefulWidget { | ||||
|   State<HomePage> createState() => _HomePageState(); | ||||
| } | ||||
|  | ||||
| class NavigationPageItem { | ||||
|   late String title; | ||||
|   late IconData icon; | ||||
|   late Widget widget; | ||||
|  | ||||
|   NavigationPageItem(this.title, this.icon, this.widget); | ||||
| } | ||||
|  | ||||
| class _HomePageState extends State<HomePage> { | ||||
|   List<int> selectedIndexHistory = []; | ||||
|   List<Widget> pages = [ | ||||
|     const AppsPage(), | ||||
|     const AddAppPage(), | ||||
|     const ImportExportPage(), | ||||
|     const SettingsPage() | ||||
|  | ||||
|   List<NavigationPageItem> pages = [ | ||||
|     NavigationPageItem('Apps', Icons.apps, const AppsPage()), | ||||
|     NavigationPageItem('Add App', Icons.add, const AddAppPage()), | ||||
|     NavigationPageItem( | ||||
|         'Import/Export', Icons.import_export, const ImportExportPage()), | ||||
|     NavigationPageItem('Settings', Icons.settings, const SettingsPage()) | ||||
|   ]; | ||||
|  | ||||
|   @override | ||||
|   Widget build(BuildContext context) { | ||||
|     return WillPopScope( | ||||
|         child: Scaffold( | ||||
|           appBar: AppBar(title: const Text('Obtainium')), | ||||
|           body: pages.elementAt( | ||||
|               selectedIndexHistory.isEmpty ? 0 : selectedIndexHistory.last), | ||||
|           body: pages | ||||
|               .elementAt( | ||||
|                   selectedIndexHistory.isEmpty ? 0 : selectedIndexHistory.last) | ||||
|               .widget, | ||||
|           bottomNavigationBar: NavigationBar( | ||||
|             destinations: const [ | ||||
|               NavigationDestination(icon: Icon(Icons.apps), label: 'Apps'), | ||||
|               NavigationDestination(icon: Icon(Icons.add), label: 'Add App'), | ||||
|               NavigationDestination( | ||||
|                   icon: Icon(Icons.import_export), label: 'Import/Export'), | ||||
|               NavigationDestination( | ||||
|                   icon: Icon(Icons.settings), label: 'Settings'), | ||||
|             ], | ||||
|             destinations: pages | ||||
|                 .map((e) => | ||||
|                     NavigationDestination(icon: Icon(e.icon), label: e.title)) | ||||
|                 .toList(), | ||||
|             onDestinationSelected: (int index) { | ||||
|               HapticFeedback.lightImpact(); | ||||
|               setState(() { | ||||
|   | ||||
| @@ -3,6 +3,7 @@ import 'dart:io'; | ||||
|  | ||||
| import 'package:flutter/material.dart'; | ||||
| import 'package:flutter/services.dart'; | ||||
| import 'package:obtainium/components/custom_app_bar.dart'; | ||||
| import 'package:obtainium/components/generated_form_modal.dart'; | ||||
| import 'package:obtainium/providers/apps_provider.dart'; | ||||
| import 'package:obtainium/providers/settings_provider.dart'; | ||||
| @@ -43,192 +44,204 @@ class _ImportExportPageState extends State<ImportExportPage> { | ||||
|       return errors; | ||||
|     } | ||||
|  | ||||
|     return Padding( | ||||
|         padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 16), | ||||
|         child: Column( | ||||
|           crossAxisAlignment: CrossAxisAlignment.stretch, | ||||
|           children: [ | ||||
|             ElevatedButton( | ||||
|                 onPressed: appsProvider.apps.isEmpty || importInProgress | ||||
|                     ? null | ||||
|                     : () { | ||||
|                         HapticFeedback.lightImpact(); | ||||
|                         appsProvider.exportApps().then((String path) { | ||||
|                           ScaffoldMessenger.of(context).showSnackBar( | ||||
|                             SnackBar(content: Text('Exported to $path')), | ||||
|                           ); | ||||
|                         }); | ||||
|                       }, | ||||
|                 child: const Text('Obtainium Export')), | ||||
|             const SizedBox( | ||||
|               height: 8, | ||||
|             ), | ||||
|             ElevatedButton( | ||||
|                 onPressed: importInProgress | ||||
|                     ? null | ||||
|                     : () { | ||||
|                         HapticFeedback.lightImpact(); | ||||
|                         FilePicker.platform.pickFiles().then((result) { | ||||
|                           setState(() { | ||||
|                             importInProgress = true; | ||||
|                           }); | ||||
|                           if (result != null) { | ||||
|                             String data = File(result.files.single.path!) | ||||
|                                 .readAsStringSync(); | ||||
|                             try { | ||||
|                               jsonDecode(data); | ||||
|                             } catch (e) { | ||||
|                               throw 'Invalid input'; | ||||
|                             } | ||||
|                             appsProvider.importApps(data).then((value) { | ||||
|                               ScaffoldMessenger.of(context).showSnackBar( | ||||
|                                 SnackBar( | ||||
|                                     content: Text( | ||||
|                                         '$value App${value == 1 ? '' : 's'} Imported')), | ||||
|                               ); | ||||
|                             }); | ||||
|                           } else { | ||||
|                             // User canceled the picker | ||||
|                           } | ||||
|                         }).catchError((e) { | ||||
|                           ScaffoldMessenger.of(context).showSnackBar( | ||||
|                             SnackBar(content: Text(e.toString())), | ||||
|                           ); | ||||
|                         }).whenComplete(() { | ||||
|                           setState(() { | ||||
|                             importInProgress = false; | ||||
|                           }); | ||||
|                         }); | ||||
|                       }, | ||||
|                 child: const Text('Obtainium Import')), | ||||
|             if (importInProgress) | ||||
|               Column( | ||||
|                 children: const [ | ||||
|                   SizedBox( | ||||
|                     height: 14, | ||||
|                   ), | ||||
|                   LinearProgressIndicator(), | ||||
|                   SizedBox( | ||||
|                     height: 14, | ||||
|                   ), | ||||
|                 ], | ||||
|               ) | ||||
|             else | ||||
|               const Divider( | ||||
|                 height: 32, | ||||
|               ), | ||||
|             TextButton( | ||||
|                 onPressed: importInProgress | ||||
|                     ? null | ||||
|                     : () { | ||||
|                         showDialog( | ||||
|                             context: context, | ||||
|                             builder: (BuildContext ctx) { | ||||
|                               return GeneratedFormModal( | ||||
|                                 title: 'Import from URL List', | ||||
|                                 items: [ | ||||
|                                   GeneratedFormItem('App URL List', true, 7) | ||||
|                                 ], | ||||
|                               ); | ||||
|                             }).then((values) { | ||||
|                           if (values != null) { | ||||
|                             var urls = (values[0] as String).split('\n'); | ||||
|                             setState(() { | ||||
|                               importInProgress = true; | ||||
|                             }); | ||||
|                             addApps(urls).then((errors) { | ||||
|                               if (errors.isEmpty) { | ||||
|     return CustomScrollView(slivers: <Widget>[ | ||||
|       const CustomAppBar(title: 'Import/Export'), | ||||
|       SliverFillRemaining( | ||||
|           hasScrollBody: false, | ||||
|           child: Padding( | ||||
|               padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 16), | ||||
|               child: Column( | ||||
|                 crossAxisAlignment: CrossAxisAlignment.stretch, | ||||
|                 children: [ | ||||
|                   ElevatedButton( | ||||
|                       onPressed: appsProvider.apps.isEmpty || importInProgress | ||||
|                           ? null | ||||
|                           : () { | ||||
|                               HapticFeedback.lightImpact(); | ||||
|                               appsProvider.exportApps().then((String path) { | ||||
|                                 ScaffoldMessenger.of(context).showSnackBar( | ||||
|                                   SnackBar( | ||||
|                                       content: | ||||
|                                           Text('Imported ${urls.length} Apps')), | ||||
|                                   SnackBar(content: Text('Exported to $path')), | ||||
|                                 ); | ||||
|                               } else { | ||||
|                                 showDialog( | ||||
|                                     context: context, | ||||
|                                     builder: (BuildContext ctx) { | ||||
|                                       return ImportErrorDialog( | ||||
|                                           urlsLength: urls.length, | ||||
|                                           errors: errors); | ||||
|                                     }); | ||||
|                               } | ||||
|                             }).catchError((e) { | ||||
|                               ScaffoldMessenger.of(context).showSnackBar( | ||||
|                                 SnackBar(content: Text(e.toString())), | ||||
|                               ); | ||||
|                             }).whenComplete(() { | ||||
|                               setState(() { | ||||
|                                 importInProgress = false; | ||||
|                               }); | ||||
|                             }); | ||||
|                           } | ||||
|                         }); | ||||
|                       }, | ||||
|                 child: const Text('Import from URL List')), | ||||
|             ...sourceProvider.massSources | ||||
|                 .map((source) => Column( | ||||
|                         crossAxisAlignment: CrossAxisAlignment.stretch, | ||||
|                         children: [ | ||||
|                           const SizedBox(height: 8), | ||||
|                           TextButton( | ||||
|                               onPressed: importInProgress | ||||
|                                   ? null | ||||
|                                   : () { | ||||
|                             }, | ||||
|                       child: const Text('Obtainium Export')), | ||||
|                   const SizedBox( | ||||
|                     height: 8, | ||||
|                   ), | ||||
|                   ElevatedButton( | ||||
|                       onPressed: importInProgress | ||||
|                           ? null | ||||
|                           : () { | ||||
|                               HapticFeedback.lightImpact(); | ||||
|                               FilePicker.platform.pickFiles().then((result) { | ||||
|                                 setState(() { | ||||
|                                   importInProgress = true; | ||||
|                                 }); | ||||
|                                 if (result != null) { | ||||
|                                   String data = File(result.files.single.path!) | ||||
|                                       .readAsStringSync(); | ||||
|                                   try { | ||||
|                                     jsonDecode(data); | ||||
|                                   } catch (e) { | ||||
|                                     throw 'Invalid input'; | ||||
|                                   } | ||||
|                                   appsProvider.importApps(data).then((value) { | ||||
|                                     ScaffoldMessenger.of(context).showSnackBar( | ||||
|                                       SnackBar( | ||||
|                                           content: Text( | ||||
|                                               '$value App${value == 1 ? '' : 's'} Imported')), | ||||
|                                     ); | ||||
|                                   }); | ||||
|                                 } else { | ||||
|                                   // User canceled the picker | ||||
|                                 } | ||||
|                               }).catchError((e) { | ||||
|                                 ScaffoldMessenger.of(context).showSnackBar( | ||||
|                                   SnackBar(content: Text(e.toString())), | ||||
|                                 ); | ||||
|                               }).whenComplete(() { | ||||
|                                 setState(() { | ||||
|                                   importInProgress = false; | ||||
|                                 }); | ||||
|                               }); | ||||
|                             }, | ||||
|                       child: const Text('Obtainium Import')), | ||||
|                   if (importInProgress) | ||||
|                     Column( | ||||
|                       children: const [ | ||||
|                         SizedBox( | ||||
|                           height: 14, | ||||
|                         ), | ||||
|                         LinearProgressIndicator(), | ||||
|                         SizedBox( | ||||
|                           height: 14, | ||||
|                         ), | ||||
|                       ], | ||||
|                     ) | ||||
|                   else | ||||
|                     const Divider( | ||||
|                       height: 32, | ||||
|                     ), | ||||
|                   TextButton( | ||||
|                       onPressed: importInProgress | ||||
|                           ? null | ||||
|                           : () { | ||||
|                               showDialog( | ||||
|                                   context: context, | ||||
|                                   builder: (BuildContext ctx) { | ||||
|                                     return GeneratedFormModal( | ||||
|                                       title: 'Import from URL List', | ||||
|                                       items: [ | ||||
|                                         GeneratedFormItem( | ||||
|                                             'App URL List', true, 7) | ||||
|                                       ], | ||||
|                                     ); | ||||
|                                   }).then((values) { | ||||
|                                 if (values != null) { | ||||
|                                   var urls = (values[0] as String).split('\n'); | ||||
|                                   setState(() { | ||||
|                                     importInProgress = true; | ||||
|                                   }); | ||||
|                                   addApps(urls).then((errors) { | ||||
|                                     if (errors.isEmpty) { | ||||
|                                       ScaffoldMessenger.of(context) | ||||
|                                           .showSnackBar( | ||||
|                                         SnackBar( | ||||
|                                             content: Text( | ||||
|                                                 'Imported ${urls.length} Apps')), | ||||
|                                       ); | ||||
|                                     } else { | ||||
|                                       showDialog( | ||||
|                                           context: context, | ||||
|                                           builder: (BuildContext ctx) { | ||||
|                                             return GeneratedFormModal( | ||||
|                                                 title: 'Import ${source.name}', | ||||
|                                                 items: source.requiredArgs | ||||
|                                                     .map((e) => | ||||
|                                                         GeneratedFormItem( | ||||
|                                                             e, true, 1)) | ||||
|                                                     .toList()); | ||||
|                                           }).then((values) { | ||||
|                                         if (values != null) { | ||||
|                                           source.getUrls(values).then((urls) { | ||||
|                                             setState(() { | ||||
|                                               importInProgress = true; | ||||
|                                             }); | ||||
|                                             addApps(urls).then((errors) { | ||||
|                                               if (errors.isEmpty) { | ||||
|                                                 ScaffoldMessenger.of(context) | ||||
|                                                     .showSnackBar( | ||||
|                                                   SnackBar( | ||||
|                                                       content: Text( | ||||
|                                                           'Imported ${urls.length} Apps')), | ||||
|                                                 ); | ||||
|                                               } else { | ||||
|                                                 showDialog( | ||||
|                                                     context: context, | ||||
|                                                     builder: | ||||
|                                                         (BuildContext ctx) { | ||||
|                                                       return ImportErrorDialog( | ||||
|                                                           urlsLength: | ||||
|                                                               urls.length, | ||||
|                                                           errors: errors); | ||||
|                                                     }); | ||||
|                                               } | ||||
|                                             }).whenComplete(() { | ||||
|                                               setState(() { | ||||
|                                                 importInProgress = false; | ||||
|                                               }); | ||||
|                                             }); | ||||
|                                           }).catchError((e) { | ||||
|                                             ScaffoldMessenger.of(context) | ||||
|                                                 .showSnackBar( | ||||
|                                               SnackBar( | ||||
|                                                   content: Text(e.toString())), | ||||
|                                             ); | ||||
|                                             return ImportErrorDialog( | ||||
|                                                 urlsLength: urls.length, | ||||
|                                                 errors: errors); | ||||
|                                           }); | ||||
|                                         } | ||||
|                                       }); | ||||
|                                     }, | ||||
|                               child: Text('Import ${source.name}')) | ||||
|                         ])) | ||||
|                 .toList() | ||||
|           ], | ||||
|         )); | ||||
|                                     } | ||||
|                                   }).catchError((e) { | ||||
|                                     ScaffoldMessenger.of(context).showSnackBar( | ||||
|                                       SnackBar(content: Text(e.toString())), | ||||
|                                     ); | ||||
|                                   }).whenComplete(() { | ||||
|                                     setState(() { | ||||
|                                       importInProgress = false; | ||||
|                                     }); | ||||
|                                   }); | ||||
|                                 } | ||||
|                               }); | ||||
|                             }, | ||||
|                       child: const Text('Import from URL List')), | ||||
|                   ...sourceProvider.massSources | ||||
|                       .map((source) => Column( | ||||
|                               crossAxisAlignment: CrossAxisAlignment.stretch, | ||||
|                               children: [ | ||||
|                                 const SizedBox(height: 8), | ||||
|                                 TextButton( | ||||
|                                     onPressed: importInProgress | ||||
|                                         ? null | ||||
|                                         : () { | ||||
|                                             showDialog( | ||||
|                                                 context: context, | ||||
|                                                 builder: (BuildContext ctx) { | ||||
|                                                   return GeneratedFormModal( | ||||
|                                                       title: | ||||
|                                                           'Import ${source.name}', | ||||
|                                                       items: source.requiredArgs | ||||
|                                                           .map((e) => | ||||
|                                                               GeneratedFormItem( | ||||
|                                                                   e, true, 1)) | ||||
|                                                           .toList()); | ||||
|                                                 }).then((values) { | ||||
|                                               if (values != null) { | ||||
|                                                 source | ||||
|                                                     .getUrls(values) | ||||
|                                                     .then((urls) { | ||||
|                                                   setState(() { | ||||
|                                                     importInProgress = true; | ||||
|                                                   }); | ||||
|                                                   addApps(urls).then((errors) { | ||||
|                                                     if (errors.isEmpty) { | ||||
|                                                       ScaffoldMessenger.of( | ||||
|                                                               context) | ||||
|                                                           .showSnackBar( | ||||
|                                                         SnackBar( | ||||
|                                                             content: Text( | ||||
|                                                                 'Imported ${urls.length} Apps')), | ||||
|                                                       ); | ||||
|                                                     } else { | ||||
|                                                       showDialog( | ||||
|                                                           context: context, | ||||
|                                                           builder: (BuildContext | ||||
|                                                               ctx) { | ||||
|                                                             return ImportErrorDialog( | ||||
|                                                                 urlsLength: | ||||
|                                                                     urls.length, | ||||
|                                                                 errors: errors); | ||||
|                                                           }); | ||||
|                                                     } | ||||
|                                                   }).whenComplete(() { | ||||
|                                                     setState(() { | ||||
|                                                       importInProgress = false; | ||||
|                                                     }); | ||||
|                                                   }); | ||||
|                                                 }).catchError((e) { | ||||
|                                                   ScaffoldMessenger.of(context) | ||||
|                                                       .showSnackBar( | ||||
|                                                     SnackBar( | ||||
|                                                         content: | ||||
|                                                             Text(e.toString())), | ||||
|                                                   ); | ||||
|                                                 }); | ||||
|                                               } | ||||
|                                             }); | ||||
|                                           }, | ||||
|                                     child: Text('Import ${source.name}')) | ||||
|                               ])) | ||||
|                       .toList() | ||||
|                 ], | ||||
|               ))) | ||||
|     ]); | ||||
|   } | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -1,5 +1,6 @@ | ||||
| import 'package:flutter/material.dart'; | ||||
| import 'package:flutter/services.dart'; | ||||
| import 'package:obtainium/components/custom_app_bar.dart'; | ||||
| import 'package:obtainium/providers/settings_provider.dart'; | ||||
| import 'package:provider/provider.dart'; | ||||
| import 'package:url_launcher/url_launcher_string.dart'; | ||||
| @@ -18,185 +19,193 @@ class _SettingsPageState extends State<SettingsPage> { | ||||
|     if (settingsProvider.prefs == null) { | ||||
|       settingsProvider.initializeSettings(); | ||||
|     } | ||||
|     return Padding( | ||||
|         padding: const EdgeInsets.all(16), | ||||
|         child: settingsProvider.prefs == null | ||||
|             ? Container() | ||||
|             : Column( | ||||
|                 children: [ | ||||
|                   DropdownButtonFormField( | ||||
|                       decoration: const InputDecoration(labelText: 'Theme'), | ||||
|                       value: settingsProvider.theme, | ||||
|                       items: const [ | ||||
|                         DropdownMenuItem( | ||||
|                           value: ThemeSettings.dark, | ||||
|                           child: Text('Dark'), | ||||
|     return CustomScrollView(slivers: <Widget>[ | ||||
|       const CustomAppBar(title: 'Add App'), | ||||
|       SliverFillRemaining( | ||||
|           hasScrollBody: true, | ||||
|           child: Padding( | ||||
|               padding: const EdgeInsets.all(16), | ||||
|               child: settingsProvider.prefs == null | ||||
|                   ? Container() | ||||
|                   : Column( | ||||
|                       children: [ | ||||
|                         DropdownButtonFormField( | ||||
|                             decoration: | ||||
|                                 const InputDecoration(labelText: 'Theme'), | ||||
|                             value: settingsProvider.theme, | ||||
|                             items: const [ | ||||
|                               DropdownMenuItem( | ||||
|                                 value: ThemeSettings.dark, | ||||
|                                 child: Text('Dark'), | ||||
|                               ), | ||||
|                               DropdownMenuItem( | ||||
|                                 value: ThemeSettings.light, | ||||
|                                 child: Text('Light'), | ||||
|                               ), | ||||
|                               DropdownMenuItem( | ||||
|                                 value: ThemeSettings.system, | ||||
|                                 child: Text('Follow System'), | ||||
|                               ) | ||||
|                             ], | ||||
|                             onChanged: (value) { | ||||
|                               if (value != null) { | ||||
|                                 settingsProvider.theme = value; | ||||
|                               } | ||||
|                             }), | ||||
|                         const SizedBox( | ||||
|                           height: 16, | ||||
|                         ), | ||||
|                         DropdownMenuItem( | ||||
|                           value: ThemeSettings.light, | ||||
|                           child: Text('Light'), | ||||
|                         DropdownButtonFormField( | ||||
|                             decoration: | ||||
|                                 const InputDecoration(labelText: 'Colour'), | ||||
|                             value: settingsProvider.colour, | ||||
|                             items: const [ | ||||
|                               DropdownMenuItem( | ||||
|                                 value: ColourSettings.basic, | ||||
|                                 child: Text('Obtainium'), | ||||
|                               ), | ||||
|                               DropdownMenuItem( | ||||
|                                 value: ColourSettings.materialYou, | ||||
|                                 child: Text('Material You'), | ||||
|                               ) | ||||
|                             ], | ||||
|                             onChanged: (value) { | ||||
|                               if (value != null) { | ||||
|                                 settingsProvider.colour = value; | ||||
|                               } | ||||
|                             }), | ||||
|                         const SizedBox( | ||||
|                           height: 16, | ||||
|                         ), | ||||
|                         DropdownMenuItem( | ||||
|                           value: ThemeSettings.system, | ||||
|                           child: Text('Follow System'), | ||||
|                         ) | ||||
|                       ], | ||||
|                       onChanged: (value) { | ||||
|                         if (value != null) { | ||||
|                           settingsProvider.theme = value; | ||||
|                         } | ||||
|                       }), | ||||
|                   const SizedBox( | ||||
|                     height: 16, | ||||
|                   ), | ||||
|                   DropdownButtonFormField( | ||||
|                       decoration: const InputDecoration(labelText: 'Colour'), | ||||
|                       value: settingsProvider.colour, | ||||
|                       items: const [ | ||||
|                         DropdownMenuItem( | ||||
|                           value: ColourSettings.basic, | ||||
|                           child: Text('Obtainium'), | ||||
|                         DropdownButtonFormField( | ||||
|                             decoration: const InputDecoration( | ||||
|                                 labelText: | ||||
|                                     'Background Update Checking Interval'), | ||||
|                             value: settingsProvider.updateInterval, | ||||
|                             items: const [ | ||||
|                               DropdownMenuItem( | ||||
|                                 value: 15, | ||||
|                                 child: Text('15 Minutes'), | ||||
|                               ), | ||||
|                               DropdownMenuItem( | ||||
|                                 value: 30, | ||||
|                                 child: Text('30 Minutes'), | ||||
|                               ), | ||||
|                               DropdownMenuItem( | ||||
|                                 value: 60, | ||||
|                                 child: Text('1 Hour'), | ||||
|                               ), | ||||
|                               DropdownMenuItem( | ||||
|                                 value: 360, | ||||
|                                 child: Text('6 Hours'), | ||||
|                               ), | ||||
|                               DropdownMenuItem( | ||||
|                                 value: 720, | ||||
|                                 child: Text('12 Hours'), | ||||
|                               ), | ||||
|                               DropdownMenuItem( | ||||
|                                 value: 1440, | ||||
|                                 child: Text('1 Day'), | ||||
|                               ), | ||||
|                               DropdownMenuItem( | ||||
|                                 value: 0, | ||||
|                                 child: Text('Never - Manual Only'), | ||||
|                               ), | ||||
|                             ], | ||||
|                             onChanged: (value) { | ||||
|                               if (value != null) { | ||||
|                                 settingsProvider.updateInterval = value; | ||||
|                               } | ||||
|                             }), | ||||
|                         const SizedBox( | ||||
|                           height: 16, | ||||
|                         ), | ||||
|                         DropdownMenuItem( | ||||
|                           value: ColourSettings.materialYou, | ||||
|                           child: Text('Material You'), | ||||
|                         ) | ||||
|                       ], | ||||
|                       onChanged: (value) { | ||||
|                         if (value != null) { | ||||
|                           settingsProvider.colour = value; | ||||
|                         } | ||||
|                       }), | ||||
|                   const SizedBox( | ||||
|                     height: 16, | ||||
|                   ), | ||||
|                   DropdownButtonFormField( | ||||
|                       decoration: const InputDecoration( | ||||
|                           labelText: 'Background Update Checking Interval'), | ||||
|                       value: settingsProvider.updateInterval, | ||||
|                       items: const [ | ||||
|                         DropdownMenuItem( | ||||
|                           value: 15, | ||||
|                           child: Text('15 Minutes'), | ||||
|                         DropdownButtonFormField( | ||||
|                             decoration: | ||||
|                                 const InputDecoration(labelText: 'App Sort By'), | ||||
|                             value: settingsProvider.sortColumn, | ||||
|                             items: const [ | ||||
|                               DropdownMenuItem( | ||||
|                                 value: SortColumnSettings.authorName, | ||||
|                                 child: Text('Author/Name'), | ||||
|                               ), | ||||
|                               DropdownMenuItem( | ||||
|                                 value: SortColumnSettings.nameAuthor, | ||||
|                                 child: Text('Name/Author'), | ||||
|                               ), | ||||
|                               DropdownMenuItem( | ||||
|                                 value: SortColumnSettings.added, | ||||
|                                 child: Text('As Added'), | ||||
|                               ) | ||||
|                             ], | ||||
|                             onChanged: (value) { | ||||
|                               if (value != null) { | ||||
|                                 settingsProvider.sortColumn = value; | ||||
|                               } | ||||
|                             }), | ||||
|                         const SizedBox( | ||||
|                           height: 16, | ||||
|                         ), | ||||
|                         DropdownMenuItem( | ||||
|                           value: 30, | ||||
|                           child: Text('30 Minutes'), | ||||
|                         DropdownButtonFormField( | ||||
|                             decoration: const InputDecoration( | ||||
|                                 labelText: 'App Sort Order'), | ||||
|                             value: settingsProvider.sortOrder, | ||||
|                             items: const [ | ||||
|                               DropdownMenuItem( | ||||
|                                 value: SortOrderSettings.ascending, | ||||
|                                 child: Text('Ascending'), | ||||
|                               ), | ||||
|                               DropdownMenuItem( | ||||
|                                 value: SortOrderSettings.descending, | ||||
|                                 child: Text('Descending'), | ||||
|                               ), | ||||
|                             ], | ||||
|                             onChanged: (value) { | ||||
|                               if (value != null) { | ||||
|                                 settingsProvider.sortOrder = value; | ||||
|                               } | ||||
|                             }), | ||||
|                         const SizedBox( | ||||
|                           height: 16, | ||||
|                         ), | ||||
|                         DropdownMenuItem( | ||||
|                           value: 60, | ||||
|                           child: Text('1 Hour'), | ||||
|                         Row( | ||||
|                           mainAxisAlignment: MainAxisAlignment.spaceBetween, | ||||
|                           children: [ | ||||
|                             const Text('Show Source Webpage in App View'), | ||||
|                             Switch( | ||||
|                                 value: settingsProvider.showAppWebpage, | ||||
|                                 onChanged: (value) { | ||||
|                                   settingsProvider.showAppWebpage = value; | ||||
|                                 }) | ||||
|                           ], | ||||
|                         ), | ||||
|                         DropdownMenuItem( | ||||
|                           value: 360, | ||||
|                           child: Text('6 Hours'), | ||||
|                         ), | ||||
|                         DropdownMenuItem( | ||||
|                           value: 720, | ||||
|                           child: Text('12 Hours'), | ||||
|                         ), | ||||
|                         DropdownMenuItem( | ||||
|                           value: 1440, | ||||
|                           child: Text('1 Day'), | ||||
|                         ), | ||||
|                         DropdownMenuItem( | ||||
|                           value: 0, | ||||
|                           child: Text('Never - Manual Only'), | ||||
|                         const Spacer(), | ||||
|                         Row( | ||||
|                           mainAxisAlignment: MainAxisAlignment.center, | ||||
|                           children: [ | ||||
|                             TextButton.icon( | ||||
|                               style: ButtonStyle( | ||||
|                                 foregroundColor: | ||||
|                                     MaterialStateProperty.resolveWith<Color>( | ||||
|                                         (Set<MaterialState> states) { | ||||
|                                   return Colors.grey; | ||||
|                                 }), | ||||
|                               ), | ||||
|                               onPressed: () { | ||||
|                                 HapticFeedback.lightImpact(); | ||||
|                                 launchUrlString(settingsProvider.sourceUrl, | ||||
|                                     mode: LaunchMode.externalApplication); | ||||
|                               }, | ||||
|                               icon: const Icon(Icons.code), | ||||
|                               label: Text( | ||||
|                                 'Source', | ||||
|                                 style: Theme.of(context).textTheme.bodySmall, | ||||
|                               ), | ||||
|                             ) | ||||
|                           ], | ||||
|                         ), | ||||
|                       ], | ||||
|                       onChanged: (value) { | ||||
|                         if (value != null) { | ||||
|                           settingsProvider.updateInterval = value; | ||||
|                         } | ||||
|                       }), | ||||
|                   const SizedBox( | ||||
|                     height: 16, | ||||
|                   ), | ||||
|                   DropdownButtonFormField( | ||||
|                       decoration: | ||||
|                           const InputDecoration(labelText: 'App Sort By'), | ||||
|                       value: settingsProvider.sortColumn, | ||||
|                       items: const [ | ||||
|                         DropdownMenuItem( | ||||
|                           value: SortColumnSettings.authorName, | ||||
|                           child: Text('Author/Name'), | ||||
|                         ), | ||||
|                         DropdownMenuItem( | ||||
|                           value: SortColumnSettings.nameAuthor, | ||||
|                           child: Text('Name/Author'), | ||||
|                         ), | ||||
|                         DropdownMenuItem( | ||||
|                           value: SortColumnSettings.added, | ||||
|                           child: Text('As Added'), | ||||
|                         ) | ||||
|                       ], | ||||
|                       onChanged: (value) { | ||||
|                         if (value != null) { | ||||
|                           settingsProvider.sortColumn = value; | ||||
|                         } | ||||
|                       }), | ||||
|                   const SizedBox( | ||||
|                     height: 16, | ||||
|                   ), | ||||
|                   DropdownButtonFormField( | ||||
|                       decoration: | ||||
|                           const InputDecoration(labelText: 'App Sort Order'), | ||||
|                       value: settingsProvider.sortOrder, | ||||
|                       items: const [ | ||||
|                         DropdownMenuItem( | ||||
|                           value: SortOrderSettings.ascending, | ||||
|                           child: Text('Ascending'), | ||||
|                         ), | ||||
|                         DropdownMenuItem( | ||||
|                           value: SortOrderSettings.descending, | ||||
|                           child: Text('Descending'), | ||||
|                         ), | ||||
|                       ], | ||||
|                       onChanged: (value) { | ||||
|                         if (value != null) { | ||||
|                           settingsProvider.sortOrder = value; | ||||
|                         } | ||||
|                       }), | ||||
|                   const SizedBox( | ||||
|                     height: 16, | ||||
|                   ), | ||||
|                   Row( | ||||
|                     mainAxisAlignment: MainAxisAlignment.spaceBetween, | ||||
|                     children: [ | ||||
|                       const Text('Show Source Webpage in App View'), | ||||
|                       Switch( | ||||
|                           value: settingsProvider.showAppWebpage, | ||||
|                           onChanged: (value) { | ||||
|                             settingsProvider.showAppWebpage = value; | ||||
|                           }) | ||||
|                     ], | ||||
|                   ), | ||||
|                   const Spacer(), | ||||
|                   Row( | ||||
|                     mainAxisAlignment: MainAxisAlignment.center, | ||||
|                     children: [ | ||||
|                       TextButton.icon( | ||||
|                         style: ButtonStyle( | ||||
|                           foregroundColor: | ||||
|                               MaterialStateProperty.resolveWith<Color>( | ||||
|                                   (Set<MaterialState> states) { | ||||
|                             return Colors.grey; | ||||
|                           }), | ||||
|                         ), | ||||
|                         onPressed: () { | ||||
|                           HapticFeedback.lightImpact(); | ||||
|                           launchUrlString(settingsProvider.sourceUrl, | ||||
|                               mode: LaunchMode.externalApplication); | ||||
|                         }, | ||||
|                         icon: const Icon(Icons.code), | ||||
|                         label: Text( | ||||
|                           'Source', | ||||
|                           style: Theme.of(context).textTheme.bodySmall, | ||||
|                         ), | ||||
|                       ) | ||||
|                     ], | ||||
|                   ), | ||||
|                 ], | ||||
|               )); | ||||
|                     ))) | ||||
|     ]); | ||||
|   } | ||||
| } | ||||
|   | ||||
		Reference in New Issue
	
	Block a user