diff --git a/lib/main.dart b/lib/main.dart index 86f6355..6b53cb4 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -11,7 +11,7 @@ import 'package:workmanager/workmanager.dart'; import 'package:dynamic_color/dynamic_color.dart'; const String currentReleaseTag = - 'v0.1.4-beta'; // KEEP THIS IN SYNC WITH GITHUB RELEASES + 'v0.1.5-beta'; // KEEP THIS IN SYNC WITH GITHUB RELEASES @pragma('vm:entry-point') void bgTaskCallback() { diff --git a/lib/pages/add_app.dart b/lib/pages/add_app.dart index 9cd16d2..fab23d4 100644 --- a/lib/pages/add_app.dart +++ b/lib/pages/add_app.dart @@ -1,9 +1,11 @@ import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; import 'package:obtainium/pages/app.dart'; import 'package:obtainium/providers/apps_provider.dart'; import 'package:obtainium/providers/settings_provider.dart'; import 'package:obtainium/providers/source_provider.dart'; import 'package:provider/provider.dart'; +import 'package:url_launcher/url_launcher_string.dart'; class AddAppPage extends StatefulWidget { const AddAppPage({super.key}); @@ -19,77 +21,114 @@ class _AddAppPageState extends State { @override Widget build(BuildContext context) { + SourceProvider sourceProvider = SourceProvider(); return Center( - child: Form( - key: _formKey, - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - const Spacer(), - Padding( - padding: const EdgeInsets.symmetric(horizontal: 16.0), - child: 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, horizontal: 16.0), - child: ElevatedButton( - onPressed: gettingAppInfo - ? null - : () { - if (_formKey.currentState!.validate()) { - setState(() { - gettingAppInfo = true; - }); - sourceProvider() - .getApp(urlInputController.value.text) - .then((app) { - var appsProvider = context.read(); - var settingsProvider = - context.read(); - 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'), - ), - ), - const Spacer(), - if (gettingAppInfo) const LinearProgressIndicator(), - ], - ), - )); + child: Form( + key: _formKey, + child: Column( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Container(), + Padding( + 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(); + var settingsProvider = + context.read(); + 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(), + ], + )), + ); } } diff --git a/lib/pages/app.dart b/lib/pages/app.dart index 1852680..203c0b2 100644 --- a/lib/pages/app.dart +++ b/lib/pages/app.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; import 'package:obtainium/providers/apps_provider.dart'; import 'package:webview_flutter/webview_flutter.dart'; import 'package:provider/provider.dart'; @@ -47,6 +48,7 @@ class _AppPageState extends State { app!.app)) && app?.downloadProgress == null ? () { + HapticFeedback.heavyImpact(); appsProvider .downloadAndInstallLatestApp( [app!.app.id], @@ -65,6 +67,7 @@ class _AppPageState extends State { onPressed: app?.downloadProgress != null ? null : () { + HapticFeedback.lightImpact(); showDialog( context: context, builder: (BuildContext ctx) { @@ -75,6 +78,7 @@ class _AppPageState extends State { actions: [ TextButton( onPressed: () { + HapticFeedback.heavyImpact(); appsProvider .removeApp(app!.app.id) .then((_) { @@ -87,6 +91,7 @@ class _AppPageState extends State { child: const Text('Remove')), TextButton( onPressed: () { + HapticFeedback.lightImpact(); Navigator.of(context).pop(); }, child: const Text('Cancel')) diff --git a/lib/pages/apps.dart b/lib/pages/apps.dart index a2bbc13..14bbca8 100644 --- a/lib/pages/apps.dart +++ b/lib/pages/apps.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; import 'package:obtainium/pages/app.dart'; import 'package:obtainium/providers/apps_provider.dart'; import 'package:obtainium/providers/settings_provider.dart'; @@ -26,6 +27,7 @@ class _AppsPageState extends State { .isNotEmpty ? null : () { + HapticFeedback.heavyImpact(); context .read() .getInstallPermission() @@ -45,7 +47,10 @@ class _AppsPageState extends State { style: Theme.of(context).textTheme.headline4, ) : RefreshIndicator( - onRefresh: appsProvider.checkUpdates, + onRefresh: () { + HapticFeedback.lightImpact(); + return appsProvider.checkUpdates(); + }, child: ListView( children: appsProvider.apps.values .map( diff --git a/lib/pages/settings.dart b/lib/pages/settings.dart index be7df5d..4bfbd72 100644 --- a/lib/pages/settings.dart +++ b/lib/pages/settings.dart @@ -1,6 +1,7 @@ import 'dart:convert'; import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; import 'package:obtainium/providers/apps_provider.dart'; import 'package:obtainium/providers/settings_provider.dart'; import 'package:provider/provider.dart'; @@ -118,6 +119,7 @@ class _SettingsPageState extends State { onPressed: appsProvider.apps.isEmpty ? null : () { + HapticFeedback.lightImpact(); appsProvider.exportApps().then((String path) { ScaffoldMessenger.of(context).showSnackBar( SnackBar( @@ -128,6 +130,7 @@ class _SettingsPageState extends State { child: const Text('Export Apps')), ElevatedButton( onPressed: () { + HapticFeedback.lightImpact(); showDialog( context: context, builder: (BuildContext ctx) { @@ -172,11 +175,13 @@ class _SettingsPageState extends State { actions: [ TextButton( onPressed: () { + HapticFeedback.lightImpact(); Navigator.of(context).pop(); }, child: const Text('Cancel')), TextButton( onPressed: () { + HapticFeedback.heavyImpact(); if (formKey.currentState! .validate()) { appsProvider @@ -223,6 +228,7 @@ class _SettingsPageState extends State { }), ), onPressed: () { + HapticFeedback.lightImpact(); launchUrlString(settingsProvider.sourceUrl, mode: LaunchMode.externalApplication); }, diff --git a/lib/providers/apps_provider.dart b/lib/providers/apps_provider.dart index 10ac212..d02ddc3 100644 --- a/lib/providers/apps_provider.dart +++ b/lib/providers/apps_provider.dart @@ -6,6 +6,7 @@ import 'dart:convert'; import 'dart:io'; import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; import 'package:obtainium/providers/notifications_provider.dart'; import 'package:provider/provider.dart'; import 'package:path_provider/path_provider.dart'; @@ -191,7 +192,7 @@ class AppsProvider with ChangeNotifier { Future getUpdate(String appId) async { App? currentApp = apps[appId]!.app; - App newApp = await sourceProvider().getApp(currentApp.url); + App newApp = await SourceProvider().getApp(currentApp.url); if (newApp.latestVersion != currentApp.latestVersion) { newApp.installedVersion = currentApp.installedVersion; await saveApp(newApp); @@ -299,11 +300,13 @@ class _APKPickerState extends State { actions: [ TextButton( onPressed: () { + HapticFeedback.lightImpact(); Navigator.of(context).pop(null); }, child: const Text('Cancel')), TextButton( onPressed: () { + HapticFeedback.mediumImpact(); Navigator.of(context).pop(apkUrl); }, child: const Text('Continue')) diff --git a/lib/providers/source_provider.dart b/lib/providers/source_provider.dart index ef59727..4b52d49 100644 --- a/lib/providers/source_provider.dart +++ b/lib/providers/source_provider.dart @@ -195,7 +195,7 @@ class GitLab implements AppSource { } } -class sourceProvider { +class SourceProvider { // Add more source classes here so they are available via the service AppSource getSource(String url) { if (url.toLowerCase().contains('://github.com')) { @@ -227,4 +227,6 @@ class sourceProvider { apk.version, apk.apkUrls); } + + List getSourceHosts() => ['github.com', 'gitlab.com']; } diff --git a/pubspec.yaml b/pubspec.yaml index f0ac49b..c54c88d 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -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.1.4+5 # When changing this, update the tag in main() accordingly +version: 0.1.5+6 # When changing this, update the tag in main() accordingly environment: sdk: '>=2.19.0-79.0.dev <3.0.0'