mirror of
https://github.com/ImranR98/Obtainium.git
synced 2025-08-19 13:09:30 +02:00
Adds Source-specific options + other changes (#26)
* Started work on dynamic forms * dynamic form progress (switch doesn't work) * dynamic forms work * Gen. form improvements, source specific data (untested) * Gen form bugfix * Removed redundant generated modal code * Added custom validators to gen. forms * Progress on source options (incomplete), gen form bugfixes * Tweaks, more * More * Progress * Changed a default * Additional options done!
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:obtainium/components/custom_app_bar.dart';
|
||||
import 'package:obtainium/components/generated_form.dart';
|
||||
import 'package:obtainium/pages/app.dart';
|
||||
import 'package:obtainium/providers/apps_provider.dart';
|
||||
import 'package:obtainium/providers/settings_provider.dart';
|
||||
@@ -16,10 +17,13 @@ class AddAppPage extends StatefulWidget {
|
||||
}
|
||||
|
||||
class _AddAppPageState extends State<AddAppPage> {
|
||||
final _formKey = GlobalKey<FormState>();
|
||||
final urlInputController = TextEditingController();
|
||||
bool gettingAppInfo = false;
|
||||
|
||||
String userInput = "";
|
||||
AppSource? pickedSource;
|
||||
List<String> additionalData = [];
|
||||
bool validAdditionalData = true;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
SourceProvider sourceProvider = SourceProvider();
|
||||
@@ -28,103 +32,147 @@ class _AddAppPageState extends State<AddAppPage> {
|
||||
body: 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: [
|
||||
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.selectionClick();
|
||||
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';
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: GeneratedForm(
|
||||
items: [
|
||||
[
|
||||
GeneratedFormItem(
|
||||
label: "App Source Url",
|
||||
additionalValidators: [
|
||||
(value) {
|
||||
try {
|
||||
sourceProvider
|
||||
.getSource(value ?? "")
|
||||
.standardizeURL(
|
||||
makeUrlHttps(
|
||||
value ?? ""));
|
||||
} catch (e) {
|
||||
return e is String
|
||||
? e
|
||||
: "Error";
|
||||
}
|
||||
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'),
|
||||
),
|
||||
),
|
||||
],
|
||||
return null;
|
||||
}
|
||||
])
|
||||
]
|
||||
],
|
||||
onValueChanges: (values, valid) {
|
||||
setState(() {
|
||||
userInput = values[0];
|
||||
var source = valid
|
||||
? sourceProvider.getSource(userInput)
|
||||
: null;
|
||||
if (pickedSource != source) {
|
||||
pickedSource = source;
|
||||
additionalData = [];
|
||||
validAdditionalData = source != null
|
||||
? sourceProvider
|
||||
.doesSourceHaveRequiredAdditionalData(
|
||||
source)
|
||||
: true;
|
||||
}
|
||||
});
|
||||
},
|
||||
defaultValues: const [])),
|
||||
const SizedBox(
|
||||
width: 16,
|
||||
),
|
||||
),
|
||||
ElevatedButton(
|
||||
onPressed: gettingAppInfo ||
|
||||
pickedSource == null ||
|
||||
(pickedSource!.additionalDataFormItems
|
||||
.isNotEmpty &&
|
||||
!validAdditionalData)
|
||||
? null
|
||||
: () {
|
||||
HapticFeedback.selectionClick();
|
||||
setState(() {
|
||||
gettingAppInfo = true;
|
||||
});
|
||||
sourceProvider
|
||||
.getApp(pickedSource!, userInput,
|
||||
additionalData)
|
||||
.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((_) {
|
||||
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 Divider(
|
||||
height: 64,
|
||||
),
|
||||
if (pickedSource != null &&
|
||||
(pickedSource!.additionalDataFormItems.isNotEmpty))
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
Text(
|
||||
'Additional Options for ${pickedSource?.runtimeType}',
|
||||
style: TextStyle(
|
||||
color:
|
||||
Theme.of(context).colorScheme.primary)),
|
||||
const SizedBox(
|
||||
height: 16,
|
||||
),
|
||||
GeneratedForm(
|
||||
items: pickedSource!.additionalDataFormItems,
|
||||
onValueChanges: (values, valid) {
|
||||
setState(() {
|
||||
additionalData = values;
|
||||
validAdditionalData = valid;
|
||||
});
|
||||
},
|
||||
defaultValues:
|
||||
pickedSource!.additionalDataDefaults)
|
||||
],
|
||||
)
|
||||
else
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
// const SizedBox(
|
||||
// height: 48,
|
||||
// ),
|
||||
const Text(
|
||||
'Supported Sources:',
|
||||
// style: TextStyle(fontWeight: FontWeight.bold),
|
||||
// style: Theme.of(context).textTheme.bodySmall,
|
||||
),
|
||||
const SizedBox(
|
||||
height: 8,
|
||||
@@ -145,14 +193,9 @@ class _AddAppPageState extends State<AddAppPage> {
|
||||
fontStyle: FontStyle.italic),
|
||||
)))
|
||||
.toList()
|
||||
]),
|
||||
if (gettingAppInfo)
|
||||
const LinearProgressIndicator()
|
||||
else
|
||||
Container(),
|
||||
],
|
||||
)),
|
||||
))
|
||||
])),
|
||||
])),
|
||||
)
|
||||
]));
|
||||
}
|
||||
}
|
||||
|
@@ -1,7 +1,10 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:obtainium/components/generated_form.dart';
|
||||
import 'package:obtainium/components/generated_form_modal.dart';
|
||||
import 'package:obtainium/providers/apps_provider.dart';
|
||||
import 'package:obtainium/providers/settings_provider.dart';
|
||||
import 'package:obtainium/providers/source_provider.dart';
|
||||
import 'package:url_launcher/url_launcher_string.dart';
|
||||
import 'package:webview_flutter/webview_flutter.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
@@ -20,7 +23,9 @@ class _AppPageState extends State<AppPage> {
|
||||
Widget build(BuildContext context) {
|
||||
var appsProvider = context.watch<AppsProvider>();
|
||||
var settingsProvider = context.watch<SettingsProvider>();
|
||||
var sourceProvider = SourceProvider();
|
||||
AppInMemory? app = appsProvider.apps[widget.appId];
|
||||
var source = app != null ? sourceProvider.getSource(app.app.url) : null;
|
||||
if (app?.app.installedVersion != null) {
|
||||
appsProvider.getUpdate(app!.app.id);
|
||||
}
|
||||
@@ -159,6 +164,37 @@ class _AppPageState extends State<AppPage> {
|
||||
},
|
||||
tooltip: 'Mark as Not Installed',
|
||||
icon: const Icon(Icons.no_cell_outlined)),
|
||||
if (source != null &&
|
||||
source.additionalDataFormItems.isNotEmpty)
|
||||
IconButton(
|
||||
onPressed: () {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (BuildContext ctx) {
|
||||
return GeneratedFormModal(
|
||||
title: 'Additional Options',
|
||||
items: source.additionalDataFormItems,
|
||||
defaultValues:
|
||||
source.additionalDataDefaults);
|
||||
}).then((values) {
|
||||
if (app != null && values != null) {
|
||||
var changedApp = app.app;
|
||||
changedApp.additionalData = values;
|
||||
sourceProvider
|
||||
.getApp(source, changedApp.url,
|
||||
changedApp.additionalData)
|
||||
.then((finalChangedApp) {
|
||||
appsProvider.saveApp(finalChangedApp);
|
||||
}).catchError((e) {
|
||||
ScaffoldMessenger.of(context)
|
||||
.showSnackBar(
|
||||
SnackBar(content: Text(e.toString())),
|
||||
);
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
icon: const Icon(Icons.settings)),
|
||||
const SizedBox(width: 16.0),
|
||||
Expanded(
|
||||
child: ElevatedButton(
|
||||
|
@@ -5,6 +5,7 @@ import 'package:obtainium/pages/add_app.dart';
|
||||
import 'package:obtainium/pages/apps.dart';
|
||||
import 'package:obtainium/pages/import_export.dart';
|
||||
import 'package:obtainium/pages/settings.dart';
|
||||
import 'package:obtainium/pages/test_page.dart';
|
||||
|
||||
class HomePage extends StatefulWidget {
|
||||
const HomePage({super.key});
|
||||
|
@@ -4,6 +4,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.dart';
|
||||
import 'package:obtainium/components/generated_form_modal.dart';
|
||||
import 'package:obtainium/providers/apps_provider.dart';
|
||||
import 'package:obtainium/providers/settings_provider.dart';
|
||||
@@ -167,9 +168,34 @@ class _ImportExportPageState extends State<ImportExportPage> {
|
||||
return GeneratedFormModal(
|
||||
title: 'Import from URL List',
|
||||
items: [
|
||||
GeneratedFormItem(
|
||||
'App URL List', true, 7)
|
||||
[
|
||||
GeneratedFormItem(
|
||||
label: 'App URL List',
|
||||
max: 7,
|
||||
additionalValidators: [
|
||||
(String? value) {
|
||||
if (value != null &&
|
||||
value.isNotEmpty) {
|
||||
var lines = value
|
||||
.trim()
|
||||
.split('\n');
|
||||
for (int i = 0;
|
||||
i < lines.length;
|
||||
i++) {
|
||||
try {
|
||||
sourceProvider
|
||||
.getSource(
|
||||
lines[i]);
|
||||
} catch (e) {
|
||||
return 'Line ${i + 1}: $e';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
])
|
||||
]
|
||||
],
|
||||
defaultValues: const [],
|
||||
);
|
||||
}).then((values) {
|
||||
if (values != null) {
|
||||
@@ -226,16 +252,17 @@ class _ImportExportPageState extends State<ImportExportPage> {
|
||||
builder:
|
||||
(BuildContext ctx) {
|
||||
return GeneratedFormModal(
|
||||
title:
|
||||
'Import ${source.name}',
|
||||
items: source
|
||||
.requiredArgs
|
||||
.map((e) =>
|
||||
title:
|
||||
'Import ${source.name}',
|
||||
items: source
|
||||
.requiredArgs
|
||||
.map((e) => [
|
||||
GeneratedFormItem(
|
||||
e,
|
||||
true,
|
||||
1))
|
||||
.toList());
|
||||
label: e)
|
||||
])
|
||||
.toList(),
|
||||
defaultValues: const [],
|
||||
);
|
||||
}).then((values) {
|
||||
if (values != null) {
|
||||
source
|
||||
|
53
lib/pages/test_page.dart
Normal file
53
lib/pages/test_page.dart
Normal file
@@ -0,0 +1,53 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:obtainium/components/generated_form.dart';
|
||||
|
||||
class TestPage extends StatefulWidget {
|
||||
const TestPage({super.key});
|
||||
|
||||
@override
|
||||
State<TestPage> createState() => _TestPageState();
|
||||
}
|
||||
|
||||
class _TestPageState extends State<TestPage> {
|
||||
List<String?>? sourceSpecificData;
|
||||
bool valid = false;
|
||||
|
||||
List<List<GeneratedFormItem>> sourceSpecificInputs = [
|
||||
[GeneratedFormItem(label: 'Test Item 1')],
|
||||
[
|
||||
GeneratedFormItem(label: 'Test Item 2', required: false),
|
||||
GeneratedFormItem(label: 'Test Item 3')
|
||||
],
|
||||
[GeneratedFormItem(label: 'Test Item 4', type: FormItemType.bool)]
|
||||
];
|
||||
|
||||
List<String> defaultInputValues = ["ABC"];
|
||||
|
||||
void onSourceSpecificDataChanges(
|
||||
List<String?> valuesFromForm, bool formValid) {
|
||||
setState(() {
|
||||
sourceSpecificData = valuesFromForm;
|
||||
valid = formValid;
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(title: const Text('Test Page')),
|
||||
backgroundColor: Theme.of(context).colorScheme.surface,
|
||||
body: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
child: Column(children: [
|
||||
GeneratedForm(
|
||||
items: sourceSpecificInputs,
|
||||
onValueChanges: onSourceSpecificDataChanges,
|
||||
defaultValues: defaultInputValues,
|
||||
),
|
||||
...(sourceSpecificData != null
|
||||
? (sourceSpecificData as List<String?>)
|
||||
.map((e) => Text(e ?? ""))
|
||||
: [Container()])
|
||||
])));
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user