mirror of
				https://github.com/ImranR98/Obtainium.git
				synced 2025-10-30 21:13:28 +01:00 
			
		
		
		
	
							
								
								
									
										29
									
								
								lib/components/custom_app_bar.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								lib/components/custom_app_bar.dart
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,29 @@ | ||||
| import 'package:flutter/material.dart'; | ||||
|  | ||||
| class CustomAppBar extends StatefulWidget { | ||||
|   const CustomAppBar({super.key, required this.title}); | ||||
|  | ||||
|   final String title; | ||||
|  | ||||
|   @override | ||||
|   State<CustomAppBar> createState() => _CustomAppBarState(); | ||||
| } | ||||
|  | ||||
| class _CustomAppBarState extends State<CustomAppBar> { | ||||
|   @override | ||||
|   Widget build(BuildContext context) { | ||||
|     return SliverAppBar( | ||||
|       pinned: true, | ||||
|       automaticallyImplyLeading: false, | ||||
|       expandedHeight: 100, | ||||
|       flexibleSpace: FlexibleSpaceBar( | ||||
|         titlePadding: const EdgeInsets.symmetric(horizontal: 20, vertical: 16), | ||||
|         title: Text( | ||||
|           widget.title, | ||||
|           style: | ||||
|               TextStyle(color: Theme.of(context).textTheme.bodyMedium!.color), | ||||
|         ), | ||||
|       ), | ||||
|     ); | ||||
|   } | ||||
| } | ||||
| @@ -59,14 +59,13 @@ class _GeneratedFormModalState extends State<GeneratedFormModal> { | ||||
|       actions: [ | ||||
|         TextButton( | ||||
|             onPressed: () { | ||||
|               HapticFeedback.lightImpact(); | ||||
|               Navigator.of(context).pop(null); | ||||
|             }, | ||||
|             child: const Text('Cancel')), | ||||
|         TextButton( | ||||
|             onPressed: () { | ||||
|               if (_formKey.currentState?.validate() == true) { | ||||
|                 HapticFeedback.heavyImpact(); | ||||
|                 HapticFeedback.selectionClick(); | ||||
|                 Navigator.of(context).pop(formInputs | ||||
|                     .map((e) => (e[0] as TextEditingController).value.text) | ||||
|                     .toList()); | ||||
|   | ||||
| @@ -12,7 +12,7 @@ import 'package:dynamic_color/dynamic_color.dart'; | ||||
| import 'package:device_info_plus/device_info_plus.dart'; | ||||
|  | ||||
| const String currentReleaseTag = | ||||
|     'v0.2.1-beta'; // KEEP THIS IN SYNC WITH GITHUB RELEASES | ||||
|     'v0.2.2-beta'; // KEEP THIS IN SYNC WITH GITHUB RELEASES | ||||
|  | ||||
| @pragma('vm:entry-point') | ||||
| void bgTaskCallback() { | ||||
|   | ||||
| @@ -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,7 +23,11 @@ class _AddAppPageState extends State<AddAppPage> { | ||||
|   @override | ||||
|   Widget build(BuildContext context) { | ||||
|     SourceProvider sourceProvider = SourceProvider(); | ||||
|     return Center( | ||||
|     return CustomScrollView(slivers: <Widget>[ | ||||
|       const CustomAppBar(title: 'Add App'), | ||||
|       SliverFillRemaining( | ||||
|           hasScrollBody: false, | ||||
|           child: Center( | ||||
|             child: Form( | ||||
|                 key: _formKey, | ||||
|                 child: Column( | ||||
| @@ -55,19 +60,21 @@ class _AddAppPageState extends State<AddAppPage> { | ||||
|                               onPressed: gettingAppInfo | ||||
|                                   ? null | ||||
|                                   : () { | ||||
|                                 HapticFeedback.mediumImpact(); | ||||
|                                       HapticFeedback.selectionClick(); | ||||
|                                       if (_formKey.currentState!.validate()) { | ||||
|                                         setState(() { | ||||
|                                           gettingAppInfo = true; | ||||
|                                         }); | ||||
|                                         sourceProvider | ||||
|                                       .getApp(urlInputController.value.text) | ||||
|                                             .getApp( | ||||
|                                                 urlInputController.value.text) | ||||
|                                             .then((app) { | ||||
|                                           var appsProvider = | ||||
|                                               context.read<AppsProvider>(); | ||||
|                                           var settingsProvider = | ||||
|                                               context.read<SettingsProvider>(); | ||||
|                                     if (appsProvider.apps.containsKey(app.id)) { | ||||
|                                           if (appsProvider.apps | ||||
|                                               .containsKey(app.id)) { | ||||
|                                             throw 'App already added'; | ||||
|                                           } | ||||
|                                           settingsProvider | ||||
| @@ -79,12 +86,15 @@ class _AddAppPageState extends State<AddAppPage> { | ||||
|                                                   context, | ||||
|                                                   MaterialPageRoute( | ||||
|                                                       builder: (context) => | ||||
|                                                     AppPage(appId: app.id))); | ||||
|                                                           AppPage( | ||||
|                                                               appId: app.id))); | ||||
|                                             }); | ||||
|                                           }); | ||||
|                                         }).catchError((e) { | ||||
|                                     ScaffoldMessenger.of(context).showSnackBar( | ||||
|                                       SnackBar(content: Text(e.toString())), | ||||
|                                           ScaffoldMessenger.of(context) | ||||
|                                               .showSnackBar( | ||||
|                                             SnackBar( | ||||
|                                                 content: Text(e.toString())), | ||||
|                                           ); | ||||
|                                         }).whenComplete(() { | ||||
|                                           setState(() { | ||||
| @@ -99,7 +109,9 @@ class _AddAppPageState extends State<AddAppPage> { | ||||
|                         ], | ||||
|                       ), | ||||
|                     ), | ||||
|               Column(crossAxisAlignment: CrossAxisAlignment.center, children: [ | ||||
|                     Column( | ||||
|                         crossAxisAlignment: CrossAxisAlignment.center, | ||||
|                         children: [ | ||||
|                           const Text( | ||||
|                             'Supported Sources:', | ||||
|                             // style: TextStyle(fontWeight: FontWeight.bold), | ||||
| @@ -129,6 +141,7 @@ class _AddAppPageState extends State<AddAppPage> { | ||||
|                       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,10 +26,11 @@ 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 | ||||
|       backgroundColor: Theme.of(context).colorScheme.surface, | ||||
|       body: CustomScrollView(slivers: <Widget>[ | ||||
|         CustomAppBar(title: '${app?.app.name}'), | ||||
|         SliverFillRemaining( | ||||
|           child: settingsProvider.showAppWebpage | ||||
|               ? WebView( | ||||
|                   initialUrl: app?.app.url, | ||||
|                   javascriptMode: JavascriptMode.unrestricted, | ||||
| @@ -80,6 +82,8 @@ class _AppPageState extends State<AppPage> { | ||||
|                     ), | ||||
|                   ], | ||||
|                 ), | ||||
|         ), | ||||
|       ]), | ||||
|       bottomSheet: Padding( | ||||
|           padding: EdgeInsets.fromLTRB( | ||||
|               0, 0, 0, MediaQuery.of(context).padding.bottom), | ||||
| @@ -91,15 +95,15 @@ class _AppPageState extends State<AppPage> { | ||||
|                   child: Row( | ||||
|                       mainAxisAlignment: MainAxisAlignment.spaceEvenly, | ||||
|                       children: [ | ||||
|                         if (app?.app.installedVersion == null) | ||||
|                         if (app?.app.installedVersion != app?.app.latestVersion) | ||||
|                           IconButton( | ||||
|                               onPressed: () { | ||||
|                                 showDialog( | ||||
|                                     context: context, | ||||
|                                     builder: (BuildContext ctx) { | ||||
|                                       return AlertDialog( | ||||
|                                         title: const Text( | ||||
|                                             'App Already Installed?'), | ||||
|                                         title: Text( | ||||
|                                             'App Already ${app?.app.installedVersion == null ? 'Installed' : 'Updated'}?'), | ||||
|                                         actions: [ | ||||
|                                           TextButton( | ||||
|                                               onPressed: () { | ||||
| @@ -108,6 +112,7 @@ class _AppPageState extends State<AppPage> { | ||||
|                                               child: const Text('No')), | ||||
|                                           TextButton( | ||||
|                                               onPressed: () { | ||||
|                                                 HapticFeedback.selectionClick(); | ||||
|                                                 var updatedApp = app?.app; | ||||
|                                                 if (updatedApp != null) { | ||||
|                                                   updatedApp.installedVersion = | ||||
| @@ -124,8 +129,41 @@ class _AppPageState extends State<AppPage> { | ||||
|                                     }); | ||||
|                               }, | ||||
|                               tooltip: 'Mark as Installed', | ||||
|                               icon: const Icon(Icons.done)), | ||||
|                         if (app?.app.installedVersion == null) | ||||
|                               icon: const Icon(Icons.done)) | ||||
|                         else | ||||
|                           IconButton( | ||||
|                               onPressed: () { | ||||
|                                 showDialog( | ||||
|                                     context: context, | ||||
|                                     builder: (BuildContext ctx) { | ||||
|                                       return AlertDialog( | ||||
|                                         title: const Text('App Not Installed?'), | ||||
|                                         actions: [ | ||||
|                                           TextButton( | ||||
|                                               onPressed: () { | ||||
|                                                 Navigator.of(context).pop(); | ||||
|                                               }, | ||||
|                                               child: const Text('No')), | ||||
|                                           TextButton( | ||||
|                                               onPressed: () { | ||||
|                                                 HapticFeedback.selectionClick(); | ||||
|                                                 var updatedApp = app?.app; | ||||
|                                                 if (updatedApp != null) { | ||||
|                                                   updatedApp.installedVersion = | ||||
|                                                       null; | ||||
|                                                   appsProvider | ||||
|                                                       .saveApp(updatedApp); | ||||
|                                                 } | ||||
|                                                 Navigator.of(context).pop(); | ||||
|                                               }, | ||||
|                                               child: const Text( | ||||
|                                                   'Yes, Mark as Not Installed')) | ||||
|                                         ], | ||||
|                                       ); | ||||
|                                     }); | ||||
|                               }, | ||||
|                               tooltip: 'Mark as Not Installed', | ||||
|                               icon: const Icon(Icons.no_cell_outlined)), | ||||
|                         const SizedBox(width: 16.0), | ||||
|                         Expanded( | ||||
|                             child: ElevatedButton( | ||||
| @@ -154,7 +192,6 @@ class _AppPageState extends State<AppPage> { | ||||
|                           onPressed: app?.downloadProgress != null | ||||
|                               ? null | ||||
|                               : () { | ||||
|                                   HapticFeedback.lightImpact(); | ||||
|                                   showDialog( | ||||
|                                       context: context, | ||||
|                                       builder: (BuildContext ctx) { | ||||
| @@ -165,7 +202,8 @@ class _AppPageState extends State<AppPage> { | ||||
|                                           actions: [ | ||||
|                                             TextButton( | ||||
|                                                 onPressed: () { | ||||
|                                                   HapticFeedback.heavyImpact(); | ||||
|                                                   HapticFeedback | ||||
|                                                       .selectionClick(); | ||||
|                                                   appsProvider | ||||
|                                                       .removeApp(app!.app.id) | ||||
|                                                       .then((_) { | ||||
| @@ -178,7 +216,6 @@ class _AppPageState extends State<AppPage> { | ||||
|                                                 child: const Text('Remove')), | ||||
|                                             TextButton( | ||||
|                                                 onPressed: () { | ||||
|                                                   HapticFeedback.lightImpact(); | ||||
|                                                   Navigator.of(context).pop(); | ||||
|                                                 }, | ||||
|                                                 child: const Text('Cancel')) | ||||
|   | ||||
| @@ -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'; | ||||
| @@ -35,6 +36,7 @@ class _AppsPageState extends State<AppsPage> { | ||||
|     } | ||||
|  | ||||
|     return Scaffold( | ||||
|         backgroundColor: Theme.of(context).colorScheme.surface, | ||||
|         floatingActionButton: existingUpdateAppIds.isEmpty | ||||
|             ? null | ||||
|             : ElevatedButton.icon( | ||||
| @@ -47,34 +49,39 @@ class _AppsPageState extends State<AppsPage> { | ||||
|                               existingUpdateAppIds, context); | ||||
|                         }); | ||||
|                       }, | ||||
|                 icon: const Icon(Icons.update), | ||||
|                 label: const Text('Update All')), | ||||
|         body: Center( | ||||
|           child: appsProvider.loadingApps | ||||
|               ? const CircularProgressIndicator() | ||||
|               : appsProvider.apps.isEmpty | ||||
|                   ? Text( | ||||
|                       'No Apps', | ||||
|                       style: Theme.of(context).textTheme.headlineMedium, | ||||
|                     ) | ||||
|                   : RefreshIndicator( | ||||
|                 icon: const Icon(Icons.install_mobile_outlined), | ||||
|                 label: const Text('Install All')), | ||||
|         body: RefreshIndicator( | ||||
|             onRefresh: () { | ||||
|               HapticFeedback.lightImpact(); | ||||
|               return appsProvider.checkUpdates(); | ||||
|             }, | ||||
|                       child: ListView( | ||||
|                         children: sortedApps | ||||
|                             .map( | ||||
|                               (e) => ListTile( | ||||
|                                 title: Text('${e.app.author}/${e.app.name}'), | ||||
|                                 subtitle: Text( | ||||
|                                     e.app.installedVersion ?? 'Not Installed'), | ||||
|                                 trailing: e.downloadProgress != null | ||||
|             child: CustomScrollView(slivers: <Widget>[ | ||||
|               const CustomAppBar(title: 'Apps'), | ||||
|               if (appsProvider.loadingApps || appsProvider.apps.isEmpty) | ||||
|                 SliverFillRemaining( | ||||
|                     child: Center( | ||||
|                         child: appsProvider.loadingApps | ||||
|                             ? const CircularProgressIndicator() | ||||
|                             : Text( | ||||
|                                 'No Apps', | ||||
|                                 style: | ||||
|                                     Theme.of(context).textTheme.headlineMedium, | ||||
|                               ))), | ||||
|               SliverList( | ||||
|                   delegate: SliverChildBuilderDelegate( | ||||
|                       (BuildContext context, int index) { | ||||
|                 return ListTile( | ||||
|                   title: Text( | ||||
|                       '${sortedApps[index].app.author}/${sortedApps[index].app.name}'), | ||||
|                   subtitle: Text(sortedApps[index].app.installedVersion ?? | ||||
|                       'Not Installed'), | ||||
|                   trailing: sortedApps[index].downloadProgress != null | ||||
|                       ? Text( | ||||
|                                         'Downloading - ${e.downloadProgress?.toInt()}%') | ||||
|                                     : (e.app.installedVersion != null && | ||||
|                                             e.app.installedVersion != | ||||
|                                                 e.app.latestVersion | ||||
|                           'Downloading - ${sortedApps[index].downloadProgress?.toInt()}%') | ||||
|                       : (sortedApps[index].app.installedVersion != null && | ||||
|                               sortedApps[index].app.installedVersion != | ||||
|                                   sortedApps[index].app.latestVersion | ||||
|                           ? const Text('Update Available') | ||||
|                           : null), | ||||
|                   onTap: () { | ||||
| @@ -82,14 +89,11 @@ class _AppsPageState extends State<AppsPage> { | ||||
|                       context, | ||||
|                       MaterialPageRoute( | ||||
|                           builder: (context) => | ||||
|                                             AppPage(appId: e.app.id)), | ||||
|                               AppPage(appId: sortedApps[index].app.id)), | ||||
|                     ); | ||||
|                   }, | ||||
|                               ), | ||||
|                             ) | ||||
|                             .toList(), | ||||
|                       ), | ||||
|                     ), | ||||
|         )); | ||||
|                 ); | ||||
|               }, childCount: sortedApps.length)) | ||||
|             ]))); | ||||
|   } | ||||
| } | ||||
|   | ||||
| @@ -1,3 +1,4 @@ | ||||
| import 'package:animations/animations.dart'; | ||||
| import 'package:flutter/material.dart'; | ||||
| import 'package:flutter/services.dart'; | ||||
| import 'package:obtainium/pages/add_app.dart'; | ||||
| @@ -12,33 +13,56 @@ 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), | ||||
|           backgroundColor: Theme.of(context).colorScheme.surface, | ||||
|           body: PageTransitionSwitcher( | ||||
|             transitionBuilder: ( | ||||
|               Widget child, | ||||
|               Animation<double> animation, | ||||
|               Animation<double> secondaryAnimation, | ||||
|             ) { | ||||
|               return SharedAxisTransition( | ||||
|                 animation: animation, | ||||
|                 secondaryAnimation: secondaryAnimation, | ||||
|                 transitionType: SharedAxisTransitionType.horizontal, | ||||
|                 child: child, | ||||
|               ); | ||||
|             }, | ||||
|             child: 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(); | ||||
|               HapticFeedback.selectionClick(); | ||||
|               setState(() { | ||||
|                 if (index == 0) { | ||||
|                   selectedIndexHistory.clear(); | ||||
|   | ||||
| @@ -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'; | ||||
| @@ -25,6 +26,16 @@ class _ImportExportPageState extends State<ImportExportPage> { | ||||
|     SourceProvider sourceProvider = SourceProvider(); | ||||
|     var settingsProvider = context.read<SettingsProvider>(); | ||||
|     var appsProvider = context.read<AppsProvider>(); | ||||
|     var outlineButtonStyle = ButtonStyle( | ||||
|       shape: MaterialStateProperty.all( | ||||
|         StadiumBorder( | ||||
|           side: BorderSide( | ||||
|             width: 1, | ||||
|             color: Theme.of(context).colorScheme.primary, | ||||
|           ), | ||||
|         ), | ||||
|       ), | ||||
|     ); | ||||
|  | ||||
|     Future<List<List<String>>> addApps(List<String> urls) async { | ||||
|       await settingsProvider.getInstallPermission(); | ||||
| @@ -43,45 +54,67 @@ class _ImportExportPageState extends State<ImportExportPage> { | ||||
|       return errors; | ||||
|     } | ||||
|  | ||||
|     return Padding( | ||||
|     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 | ||||
|                   Row( | ||||
|                     children: [ | ||||
|                       Expanded( | ||||
|                           child: TextButton( | ||||
|                               style: outlineButtonStyle, | ||||
|                               onPressed: appsProvider.apps.isEmpty || | ||||
|                                       importInProgress | ||||
|                                   ? null | ||||
|                                   : () { | ||||
|                         HapticFeedback.lightImpact(); | ||||
|                         appsProvider.exportApps().then((String path) { | ||||
|                           ScaffoldMessenger.of(context).showSnackBar( | ||||
|                             SnackBar(content: Text('Exported to $path')), | ||||
|                                       HapticFeedback.selectionClick(); | ||||
|                                       appsProvider | ||||
|                                           .exportApps() | ||||
|                                           .then((String path) { | ||||
|                                         ScaffoldMessenger.of(context) | ||||
|                                             .showSnackBar( | ||||
|                                           SnackBar( | ||||
|                                               content: | ||||
|                                                   Text('Exported to $path')), | ||||
|                                         ); | ||||
|                                       }); | ||||
|                                     }, | ||||
|                 child: const Text('Obtainium Export')), | ||||
|                               child: const Text('Obtainium Export'))), | ||||
|                       const SizedBox( | ||||
|               height: 8, | ||||
|                         width: 16, | ||||
|                       ), | ||||
|             ElevatedButton( | ||||
|                       Expanded( | ||||
|                           child: TextButton( | ||||
|                               style: outlineButtonStyle, | ||||
|                               onPressed: importInProgress | ||||
|                                   ? null | ||||
|                                   : () { | ||||
|                         HapticFeedback.lightImpact(); | ||||
|                         FilePicker.platform.pickFiles().then((result) { | ||||
|                                       HapticFeedback.selectionClick(); | ||||
|                                       FilePicker.platform | ||||
|                                           .pickFiles() | ||||
|                                           .then((result) { | ||||
|                                         setState(() { | ||||
|                                           importInProgress = true; | ||||
|                                         }); | ||||
|                                         if (result != null) { | ||||
|                             String data = File(result.files.single.path!) | ||||
|                                           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( | ||||
|                                           appsProvider | ||||
|                                               .importApps(data) | ||||
|                                               .then((value) { | ||||
|                                             ScaffoldMessenger.of(context) | ||||
|                                                 .showSnackBar( | ||||
|                                               SnackBar( | ||||
|                                                   content: Text( | ||||
|                                                       '$value App${value == 1 ? '' : 's'} Imported')), | ||||
| @@ -91,7 +124,8 @@ class _ImportExportPageState extends State<ImportExportPage> { | ||||
|                                           // User canceled the picker | ||||
|                                         } | ||||
|                                       }).catchError((e) { | ||||
|                           ScaffoldMessenger.of(context).showSnackBar( | ||||
|                                         ScaffoldMessenger.of(context) | ||||
|                                             .showSnackBar( | ||||
|                                           SnackBar(content: Text(e.toString())), | ||||
|                                         ); | ||||
|                                       }).whenComplete(() { | ||||
| @@ -100,7 +134,9 @@ class _ImportExportPageState extends State<ImportExportPage> { | ||||
|                                         }); | ||||
|                                       }); | ||||
|                                     }, | ||||
|                 child: const Text('Obtainium Import')), | ||||
|                               child: const Text('Obtainium Import'))) | ||||
|                     ], | ||||
|                   ), | ||||
|                   if (importInProgress) | ||||
|                     Column( | ||||
|                       children: const [ | ||||
| @@ -127,7 +163,8 @@ class _ImportExportPageState extends State<ImportExportPage> { | ||||
|                                     return GeneratedFormModal( | ||||
|                                       title: 'Import from URL List', | ||||
|                                       items: [ | ||||
|                                   GeneratedFormItem('App URL List', true, 7) | ||||
|                                         GeneratedFormItem( | ||||
|                                             'App URL List', true, 7) | ||||
|                                       ], | ||||
|                                     ); | ||||
|                                   }).then((values) { | ||||
| @@ -138,10 +175,11 @@ class _ImportExportPageState extends State<ImportExportPage> { | ||||
|                                   }); | ||||
|                                   addApps(urls).then((errors) { | ||||
|                                     if (errors.isEmpty) { | ||||
|                                 ScaffoldMessenger.of(context).showSnackBar( | ||||
|                                       ScaffoldMessenger.of(context) | ||||
|                                           .showSnackBar( | ||||
|                                         SnackBar( | ||||
|                                       content: | ||||
|                                           Text('Imported ${urls.length} Apps')), | ||||
|                                             content: Text( | ||||
|                                                 'Imported ${urls.length} Apps')), | ||||
|                                       ); | ||||
|                                     } else { | ||||
|                                       showDialog( | ||||
| @@ -164,7 +202,9 @@ class _ImportExportPageState extends State<ImportExportPage> { | ||||
|                                 } | ||||
|                               }); | ||||
|                             }, | ||||
|                 child: const Text('Import from URL List')), | ||||
|                       child: const Text( | ||||
|                         'Import from URL List', | ||||
|                       )), | ||||
|                   ...sourceProvider.massSources | ||||
|                       .map((source) => Column( | ||||
|                               crossAxisAlignment: CrossAxisAlignment.stretch, | ||||
| @@ -178,7 +218,8 @@ class _ImportExportPageState extends State<ImportExportPage> { | ||||
|                                                 context: context, | ||||
|                                                 builder: (BuildContext ctx) { | ||||
|                                                   return GeneratedFormModal( | ||||
|                                                 title: 'Import ${source.name}', | ||||
|                                                       title: | ||||
|                                                           'Import ${source.name}', | ||||
|                                                       items: source.requiredArgs | ||||
|                                                           .map((e) => | ||||
|                                                               GeneratedFormItem( | ||||
| @@ -186,13 +227,16 @@ class _ImportExportPageState extends State<ImportExportPage> { | ||||
|                                                           .toList()); | ||||
|                                                 }).then((values) { | ||||
|                                               if (values != null) { | ||||
|                                           source.getUrls(values).then((urls) { | ||||
|                                                 source | ||||
|                                                     .getUrls(values) | ||||
|                                                     .then((urls) { | ||||
|                                                   setState(() { | ||||
|                                                     importInProgress = true; | ||||
|                                                   }); | ||||
|                                                   addApps(urls).then((errors) { | ||||
|                                                     if (errors.isEmpty) { | ||||
|                                                 ScaffoldMessenger.of(context) | ||||
|                                                       ScaffoldMessenger.of( | ||||
|                                                               context) | ||||
|                                                           .showSnackBar( | ||||
|                                                         SnackBar( | ||||
|                                                             content: Text( | ||||
| @@ -201,8 +245,8 @@ class _ImportExportPageState extends State<ImportExportPage> { | ||||
|                                                     } else { | ||||
|                                                       showDialog( | ||||
|                                                           context: context, | ||||
|                                                     builder: | ||||
|                                                         (BuildContext ctx) { | ||||
|                                                           builder: (BuildContext | ||||
|                                                               ctx) { | ||||
|                                                             return ImportErrorDialog( | ||||
|                                                                 urlsLength: | ||||
|                                                                     urls.length, | ||||
| @@ -218,7 +262,8 @@ class _ImportExportPageState extends State<ImportExportPage> { | ||||
|                                                   ScaffoldMessenger.of(context) | ||||
|                                                       .showSnackBar( | ||||
|                                                     SnackBar( | ||||
|                                                   content: Text(e.toString())), | ||||
|                                                         content: | ||||
|                                                             Text(e.toString())), | ||||
|                                                   ); | ||||
|                                                 }); | ||||
|                                               } | ||||
| @@ -228,7 +273,8 @@ class _ImportExportPageState extends State<ImportExportPage> { | ||||
|                               ])) | ||||
|                       .toList() | ||||
|                 ], | ||||
|         )); | ||||
|               ))) | ||||
|     ]); | ||||
|   } | ||||
| } | ||||
|  | ||||
| @@ -278,7 +324,6 @@ class _ImportErrorDialogState extends State<ImportErrorDialog> { | ||||
|       actions: [ | ||||
|         TextButton( | ||||
|             onPressed: () { | ||||
|               HapticFeedback.lightImpact(); | ||||
|               Navigator.of(context).pop(null); | ||||
|             }, | ||||
|             child: const Text('Okay')) | ||||
|   | ||||
| @@ -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,14 +19,25 @@ class _SettingsPageState extends State<SettingsPage> { | ||||
|     if (settingsProvider.prefs == null) { | ||||
|       settingsProvider.initializeSettings(); | ||||
|     } | ||||
|     return Padding( | ||||
|     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( | ||||
|                       crossAxisAlignment: CrossAxisAlignment.start, | ||||
|                       children: [ | ||||
|                         Text( | ||||
|                           'Appearance', | ||||
|                           style: TextStyle( | ||||
|                               color: Theme.of(context).colorScheme.primary), | ||||
|                         ), | ||||
|                         DropdownButtonFormField( | ||||
|                       decoration: const InputDecoration(labelText: 'Theme'), | ||||
|                             decoration: | ||||
|                                 const InputDecoration(labelText: 'Theme'), | ||||
|                             value: settingsProvider.theme, | ||||
|                             items: const [ | ||||
|                               DropdownMenuItem( | ||||
| @@ -50,7 +62,8 @@ class _SettingsPageState extends State<SettingsPage> { | ||||
|                           height: 16, | ||||
|                         ), | ||||
|                         DropdownButtonFormField( | ||||
|                       decoration: const InputDecoration(labelText: 'Colour'), | ||||
|                             decoration: | ||||
|                                 const InputDecoration(labelText: 'Colour'), | ||||
|                             value: settingsProvider.colour, | ||||
|                             items: const [ | ||||
|                               DropdownMenuItem( | ||||
| @@ -70,9 +83,88 @@ class _SettingsPageState extends State<SettingsPage> { | ||||
|                         const SizedBox( | ||||
|                           height: 16, | ||||
|                         ), | ||||
|                         Row( | ||||
|                           mainAxisAlignment: MainAxisAlignment.start, | ||||
|                           crossAxisAlignment: CrossAxisAlignment.start, | ||||
|                           children: [ | ||||
|                             Expanded( | ||||
|                                 child: 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( | ||||
|                               width: 16, | ||||
|                             ), | ||||
|                             Expanded( | ||||
|                                 child: 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 Divider( | ||||
|                           height: 16, | ||||
|                         ), | ||||
|                         const SizedBox( | ||||
|                           height: 16, | ||||
|                         ), | ||||
|                         Text( | ||||
|                           'More', | ||||
|                           style: TextStyle( | ||||
|                               color: Theme.of(context).colorScheme.primary), | ||||
|                         ), | ||||
|                         DropdownButtonFormField( | ||||
|                             decoration: const InputDecoration( | ||||
|                           labelText: 'Background Update Checking Interval'), | ||||
|                                 labelText: | ||||
|                                     'Background Update Checking Interval'), | ||||
|                             value: settingsProvider.updateInterval, | ||||
|                             items: const [ | ||||
|                               DropdownMenuItem( | ||||
| @@ -109,68 +201,6 @@ class _SettingsPageState extends State<SettingsPage> { | ||||
|                                 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, | ||||
| @@ -184,7 +214,6 @@ class _SettingsPageState extends State<SettingsPage> { | ||||
|                                 }), | ||||
|                               ), | ||||
|                               onPressed: () { | ||||
|                           HapticFeedback.lightImpact(); | ||||
|                                 launchUrlString(settingsProvider.sourceUrl, | ||||
|                                     mode: LaunchMode.externalApplication); | ||||
|                               }, | ||||
| @@ -197,6 +226,7 @@ class _SettingsPageState extends State<SettingsPage> { | ||||
|                           ], | ||||
|                         ), | ||||
|                       ], | ||||
|               )); | ||||
|                     ))) | ||||
|     ]); | ||||
|   } | ||||
| } | ||||
|   | ||||
| @@ -339,13 +339,12 @@ class _APKPickerState extends State<APKPicker> { | ||||
|       actions: [ | ||||
|         TextButton( | ||||
|             onPressed: () { | ||||
|               HapticFeedback.lightImpact(); | ||||
|               Navigator.of(context).pop(null); | ||||
|             }, | ||||
|             child: const Text('Cancel')), | ||||
|         TextButton( | ||||
|             onPressed: () { | ||||
|               HapticFeedback.heavyImpact(); | ||||
|               HapticFeedback.selectionClick(); | ||||
|               Navigator.of(context).pop(apkUrl); | ||||
|             }, | ||||
|             child: const Text('Continue')) | ||||
| @@ -376,13 +375,12 @@ class _APKOriginWarningDialogState extends State<APKOriginWarningDialog> { | ||||
|       actions: [ | ||||
|         TextButton( | ||||
|             onPressed: () { | ||||
|               HapticFeedback.lightImpact(); | ||||
|               Navigator.of(context).pop(null); | ||||
|             }, | ||||
|             child: const Text('Cancel')), | ||||
|         TextButton( | ||||
|             onPressed: () { | ||||
|               HapticFeedback.heavyImpact(); | ||||
|               HapticFeedback.selectionClick(); | ||||
|               Navigator.of(context).pop(true); | ||||
|             }, | ||||
|             child: const Text('Continue')) | ||||
|   | ||||
| @@ -399,9 +399,9 @@ class SourceProvider { | ||||
|     GitHub(), | ||||
|     GitLab(), | ||||
|     FDroid(), | ||||
|     IzzyOnDroid(), | ||||
|     Mullvad(), | ||||
|     Signal(), | ||||
|     IzzyOnDroid() | ||||
|     Signal() | ||||
|   ]; | ||||
|  | ||||
|   List<MassAppSource> massSources = [GitHubStars()]; | ||||
|   | ||||
							
								
								
									
										13
									
								
								pubspec.lock
									
									
									
									
									
								
							
							
						
						
									
										13
									
								
								pubspec.lock
									
									
									
									
									
								
							| @@ -1,6 +1,13 @@ | ||||
| # Generated by pub | ||||
| # See https://dart.dev/tools/pub/glossary#lockfile | ||||
| packages: | ||||
|   animations: | ||||
|     dependency: "direct main" | ||||
|     description: | ||||
|       name: animations | ||||
|       url: "https://pub.dartlang.org" | ||||
|     source: hosted | ||||
|     version: "2.0.4" | ||||
|   archive: | ||||
|     dependency: transitive | ||||
|     description: | ||||
| @@ -201,21 +208,21 @@ packages: | ||||
|       name: flutter_local_notifications | ||||
|       url: "https://pub.dartlang.org" | ||||
|     source: hosted | ||||
|     version: "9.9.1" | ||||
|     version: "10.0.0" | ||||
|   flutter_local_notifications_linux: | ||||
|     dependency: transitive | ||||
|     description: | ||||
|       name: flutter_local_notifications_linux | ||||
|       url: "https://pub.dartlang.org" | ||||
|     source: hosted | ||||
|     version: "0.5.1" | ||||
|     version: "1.0.0" | ||||
|   flutter_local_notifications_platform_interface: | ||||
|     dependency: transitive | ||||
|     description: | ||||
|       name: flutter_local_notifications_platform_interface | ||||
|       url: "https://pub.dartlang.org" | ||||
|     source: hosted | ||||
|     version: "5.0.0" | ||||
|     version: "6.0.0" | ||||
|   flutter_plugin_android_lifecycle: | ||||
|     dependency: transitive | ||||
|     description: | ||||
|   | ||||
| @@ -17,7 +17,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev | ||||
| # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html | ||||
| # In Windows, build-name is used as the major, minor, and patch parts | ||||
| # of the product and file versions while build-number is used as the build suffix. | ||||
| version: 0.2.1+12 # When changing this, update the tag in main() accordingly | ||||
| version: 0.2.2+13 # When changing this, update the tag in main() accordingly | ||||
|  | ||||
| environment: | ||||
|   sdk: '>=2.19.0-79.0.dev <3.0.0' | ||||
| @@ -38,7 +38,7 @@ dependencies: | ||||
|   cupertino_icons: ^1.0.5 | ||||
|   path_provider: ^2.0.11 | ||||
|   flutter_fgbg: ^0.2.0 # Try removing reliance on this | ||||
|   flutter_local_notifications: ^9.9.1 | ||||
|   flutter_local_notifications: ^10.0.0 | ||||
|   provider: ^6.0.3 | ||||
|   http: ^0.13.5 | ||||
|   webview_flutter: ^3.0.4 | ||||
| @@ -52,6 +52,7 @@ dependencies: | ||||
|   fluttertoast: ^8.0.9 | ||||
|   device_info_plus: ^4.1.2 | ||||
|   file_picker: ^5.1.0 | ||||
|   animations: ^2.0.4 | ||||
|  | ||||
|  | ||||
| dev_dependencies: | ||||
|   | ||||
		Reference in New Issue
	
	Block a user