haptic feedback, listed sources

This commit is contained in:
Imran Remtulla
2022-08-27 16:25:45 -04:00
parent 6c076751ab
commit 5e785ae1d5
8 changed files with 136 additions and 76 deletions

View File

@@ -11,7 +11,7 @@ import 'package:workmanager/workmanager.dart';
import 'package:dynamic_color/dynamic_color.dart'; import 'package:dynamic_color/dynamic_color.dart';
const String currentReleaseTag = 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') @pragma('vm:entry-point')
void bgTaskCallback() { void bgTaskCallback() {

View File

@@ -1,9 +1,11 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:obtainium/pages/app.dart'; import 'package:obtainium/pages/app.dart';
import 'package:obtainium/providers/apps_provider.dart'; import 'package:obtainium/providers/apps_provider.dart';
import 'package:obtainium/providers/settings_provider.dart'; import 'package:obtainium/providers/settings_provider.dart';
import 'package:obtainium/providers/source_provider.dart'; import 'package:obtainium/providers/source_provider.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'package:url_launcher/url_launcher_string.dart';
class AddAppPage extends StatefulWidget { class AddAppPage extends StatefulWidget {
const AddAppPage({super.key}); const AddAppPage({super.key});
@@ -19,77 +21,114 @@ class _AddAppPageState extends State<AddAppPage> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
SourceProvider sourceProvider = SourceProvider();
return Center( return Center(
child: Form( child: Form(
key: _formKey, key: _formKey,
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ crossAxisAlignment: CrossAxisAlignment.stretch,
const Spacer(), children: [
Padding( Container(),
padding: const EdgeInsets.symmetric(horizontal: 16.0), Padding(
child: TextFormField( padding: const EdgeInsets.all(16),
decoration: const InputDecoration( child: Column(
hintText: 'https://github.com/Author/Project', crossAxisAlignment: CrossAxisAlignment.stretch,
helperText: 'Enter the App source URL'), children: [
controller: urlInputController, TextFormField(
validator: (value) { decoration: const InputDecoration(
if (value == null || hintText: 'https://github.com/Author/Project',
value.isEmpty || helperText: 'Enter the App source URL'),
Uri.tryParse(value) == null) { controller: urlInputController,
return 'Please enter a supported source URL'; validator: (value) {
} if (value == null ||
return null; value.isEmpty ||
}, Uri.tryParse(value) == null) {
)), return 'Please enter a supported source URL';
Padding( }
padding: return null;
const EdgeInsets.symmetric(vertical: 16.0, horizontal: 16.0), },
child: ElevatedButton( ),
onPressed: gettingAppInfo Padding(
? null padding: const EdgeInsets.symmetric(vertical: 16.0),
: () { child: ElevatedButton(
if (_formKey.currentState!.validate()) { onPressed: gettingAppInfo
setState(() { ? null
gettingAppInfo = true; : () {
}); HapticFeedback.mediumImpact();
sourceProvider() if (_formKey.currentState!.validate()) {
.getApp(urlInputController.value.text) setState(() {
.then((app) { gettingAppInfo = true;
var appsProvider = context.read<AppsProvider>(); });
var settingsProvider = sourceProvider
context.read<SettingsProvider>(); .getApp(urlInputController.value.text)
if (appsProvider.apps.containsKey(app.id)) { .then((app) {
throw 'App already added'; var appsProvider =
} context.read<AppsProvider>();
settingsProvider.getInstallPermission().then((_) { var settingsProvider =
appsProvider.saveApp(app).then((_) { context.read<SettingsProvider>();
urlInputController.clear(); if (appsProvider.apps.containsKey(app.id)) {
Navigator.push( throw 'App already added';
context, }
MaterialPageRoute( settingsProvider
builder: (context) => .getInstallPermission()
AppPage(appId: app.id))); .then((_) {
}); appsProvider.saveApp(app).then((_) {
}); urlInputController.clear();
}).catchError((e) { Navigator.push(
ScaffoldMessenger.of(context).showSnackBar( context,
SnackBar(content: Text(e.toString())), MaterialPageRoute(
); builder: (context) =>
}).whenComplete(() { AppPage(appId: app.id)));
setState(() { });
gettingAppInfo = false; });
}); }).catchError((e) {
}); ScaffoldMessenger.of(context).showSnackBar(
} SnackBar(content: Text(e.toString())),
}, );
child: const Text('Add'), }).whenComplete(() {
), setState(() {
), gettingAppInfo = false;
const Spacer(), });
if (gettingAppInfo) const LinearProgressIndicator(), });
], }
), },
)); 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(),
],
)),
);
} }
} }

View File

@@ -1,4 +1,5 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:obtainium/providers/apps_provider.dart'; import 'package:obtainium/providers/apps_provider.dart';
import 'package:webview_flutter/webview_flutter.dart'; import 'package:webview_flutter/webview_flutter.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
@@ -47,6 +48,7 @@ class _AppPageState extends State<AppPage> {
app!.app)) && app!.app)) &&
app?.downloadProgress == null app?.downloadProgress == null
? () { ? () {
HapticFeedback.heavyImpact();
appsProvider appsProvider
.downloadAndInstallLatestApp( .downloadAndInstallLatestApp(
[app!.app.id], [app!.app.id],
@@ -65,6 +67,7 @@ class _AppPageState extends State<AppPage> {
onPressed: app?.downloadProgress != null onPressed: app?.downloadProgress != null
? null ? null
: () { : () {
HapticFeedback.lightImpact();
showDialog( showDialog(
context: context, context: context,
builder: (BuildContext ctx) { builder: (BuildContext ctx) {
@@ -75,6 +78,7 @@ class _AppPageState extends State<AppPage> {
actions: [ actions: [
TextButton( TextButton(
onPressed: () { onPressed: () {
HapticFeedback.heavyImpact();
appsProvider appsProvider
.removeApp(app!.app.id) .removeApp(app!.app.id)
.then((_) { .then((_) {
@@ -87,6 +91,7 @@ class _AppPageState extends State<AppPage> {
child: const Text('Remove')), child: const Text('Remove')),
TextButton( TextButton(
onPressed: () { onPressed: () {
HapticFeedback.lightImpact();
Navigator.of(context).pop(); Navigator.of(context).pop();
}, },
child: const Text('Cancel')) child: const Text('Cancel'))

View File

@@ -1,4 +1,5 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:obtainium/pages/app.dart'; import 'package:obtainium/pages/app.dart';
import 'package:obtainium/providers/apps_provider.dart'; import 'package:obtainium/providers/apps_provider.dart';
import 'package:obtainium/providers/settings_provider.dart'; import 'package:obtainium/providers/settings_provider.dart';
@@ -26,6 +27,7 @@ class _AppsPageState extends State<AppsPage> {
.isNotEmpty .isNotEmpty
? null ? null
: () { : () {
HapticFeedback.heavyImpact();
context context
.read<SettingsProvider>() .read<SettingsProvider>()
.getInstallPermission() .getInstallPermission()
@@ -45,7 +47,10 @@ class _AppsPageState extends State<AppsPage> {
style: Theme.of(context).textTheme.headline4, style: Theme.of(context).textTheme.headline4,
) )
: RefreshIndicator( : RefreshIndicator(
onRefresh: appsProvider.checkUpdates, onRefresh: () {
HapticFeedback.lightImpact();
return appsProvider.checkUpdates();
},
child: ListView( child: ListView(
children: appsProvider.apps.values children: appsProvider.apps.values
.map( .map(

View File

@@ -1,6 +1,7 @@
import 'dart:convert'; import 'dart:convert';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:obtainium/providers/apps_provider.dart'; import 'package:obtainium/providers/apps_provider.dart';
import 'package:obtainium/providers/settings_provider.dart'; import 'package:obtainium/providers/settings_provider.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
@@ -118,6 +119,7 @@ class _SettingsPageState extends State<SettingsPage> {
onPressed: appsProvider.apps.isEmpty onPressed: appsProvider.apps.isEmpty
? null ? null
: () { : () {
HapticFeedback.lightImpact();
appsProvider.exportApps().then((String path) { appsProvider.exportApps().then((String path) {
ScaffoldMessenger.of(context).showSnackBar( ScaffoldMessenger.of(context).showSnackBar(
SnackBar( SnackBar(
@@ -128,6 +130,7 @@ class _SettingsPageState extends State<SettingsPage> {
child: const Text('Export Apps')), child: const Text('Export Apps')),
ElevatedButton( ElevatedButton(
onPressed: () { onPressed: () {
HapticFeedback.lightImpact();
showDialog( showDialog(
context: context, context: context,
builder: (BuildContext ctx) { builder: (BuildContext ctx) {
@@ -172,11 +175,13 @@ class _SettingsPageState extends State<SettingsPage> {
actions: [ actions: [
TextButton( TextButton(
onPressed: () { onPressed: () {
HapticFeedback.lightImpact();
Navigator.of(context).pop(); Navigator.of(context).pop();
}, },
child: const Text('Cancel')), child: const Text('Cancel')),
TextButton( TextButton(
onPressed: () { onPressed: () {
HapticFeedback.heavyImpact();
if (formKey.currentState! if (formKey.currentState!
.validate()) { .validate()) {
appsProvider appsProvider
@@ -223,6 +228,7 @@ class _SettingsPageState extends State<SettingsPage> {
}), }),
), ),
onPressed: () { onPressed: () {
HapticFeedback.lightImpact();
launchUrlString(settingsProvider.sourceUrl, launchUrlString(settingsProvider.sourceUrl,
mode: LaunchMode.externalApplication); mode: LaunchMode.externalApplication);
}, },

View File

@@ -6,6 +6,7 @@ import 'dart:convert';
import 'dart:io'; import 'dart:io';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:obtainium/providers/notifications_provider.dart'; import 'package:obtainium/providers/notifications_provider.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'package:path_provider/path_provider.dart'; import 'package:path_provider/path_provider.dart';
@@ -191,7 +192,7 @@ class AppsProvider with ChangeNotifier {
Future<App?> getUpdate(String appId) async { Future<App?> getUpdate(String appId) async {
App? currentApp = apps[appId]!.app; App? currentApp = apps[appId]!.app;
App newApp = await sourceProvider().getApp(currentApp.url); App newApp = await SourceProvider().getApp(currentApp.url);
if (newApp.latestVersion != currentApp.latestVersion) { if (newApp.latestVersion != currentApp.latestVersion) {
newApp.installedVersion = currentApp.installedVersion; newApp.installedVersion = currentApp.installedVersion;
await saveApp(newApp); await saveApp(newApp);
@@ -299,11 +300,13 @@ class _APKPickerState extends State<APKPicker> {
actions: [ actions: [
TextButton( TextButton(
onPressed: () { onPressed: () {
HapticFeedback.lightImpact();
Navigator.of(context).pop(null); Navigator.of(context).pop(null);
}, },
child: const Text('Cancel')), child: const Text('Cancel')),
TextButton( TextButton(
onPressed: () { onPressed: () {
HapticFeedback.mediumImpact();
Navigator.of(context).pop(apkUrl); Navigator.of(context).pop(apkUrl);
}, },
child: const Text('Continue')) child: const Text('Continue'))

View File

@@ -195,7 +195,7 @@ class GitLab implements AppSource {
} }
} }
class sourceProvider { class SourceProvider {
// Add more source classes here so they are available via the service // Add more source classes here so they are available via the service
AppSource getSource(String url) { AppSource getSource(String url) {
if (url.toLowerCase().contains('://github.com')) { if (url.toLowerCase().contains('://github.com')) {
@@ -227,4 +227,6 @@ class sourceProvider {
apk.version, apk.version,
apk.apkUrls); apk.apkUrls);
} }
List<String> getSourceHosts() => ['github.com', 'gitlab.com'];
} }

View File

@@ -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 # 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 # 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. # 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: environment:
sdk: '>=2.19.0-79.0.dev <3.0.0' sdk: '>=2.19.0-79.0.dev <3.0.0'