Implement connection selector

- Ability to Add/Modify/Delete connections
This commit is contained in:
2024-02-29 20:06:13 +01:00
parent 959796210d
commit 8e82ad36cb
3 changed files with 224 additions and 1 deletions

View File

@ -0,0 +1,84 @@
import 'dart:math';
import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart';
class ConnectionEditor extends StatefulWidget {
final SharedPreferences prefs;
final Function updateData;
final String connectionName;
final dynamic connection;
const ConnectionEditor(
{super.key,
required this.prefs,
required this.updateData,
required this.connectionName,
required this.connection});
@override
State<ConnectionEditor> createState() => _ConnectionEditorState();
}
class _ConnectionEditorState extends State<ConnectionEditor> {
final wsRegex = RegExp(r'^(ws|wss):\/\/.*');
final _formKey = GlobalKey<FormState>();
String _connectionName = "";
dynamic _connection;
bool _isValid = false;
@override
void initState() {
super.initState();
_connectionName = widget.connectionName;
_connection = widget.connection;
_isValid =
_connectionName != "" && wsRegex.hasMatch(_connection["url"] ?? "");
widget.updateData(_connectionName, _connection, _isValid);
}
@override
Widget build(BuildContext context) {
return SizedBox(
width: min(MediaQuery.of(context).size.width, 400),
child: Form(
key: _formKey,
autovalidateMode: AutovalidateMode.always,
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
TextFormField(
initialValue: widget.connectionName,
onChanged: (value) {
_connectionName = value;
_isValid = _formKey.currentState!.validate();
widget.updateData(_connectionName, _connection, _isValid);
},
validator: (value) {
return value == "" ? "Name cannot be empty" : null;
},
decoration: const InputDecoration(
labelText: "Name", border: OutlineInputBorder()),
),
const SizedBox(height: 15),
TextFormField(
initialValue: widget.connection["url"],
onChanged: (value) {
_connection["url"] = value;
_isValid = _formKey.currentState!.validate();
widget.updateData(_connectionName, _connection, _isValid);
},
validator: (value) {
return wsRegex.hasMatch(value ?? "")
? null
: "Must be: ws(s)://[host](:[port])/[path]";
},
decoration: const InputDecoration(
labelText: "Connection URL", border: OutlineInputBorder()),
)
],
),
),
);
}
}

View File

@ -0,0 +1,133 @@
import 'dart:convert';
import 'dart:math';
import 'package:flutter/material.dart';
import 'package:gomix_flutter/connection_editor.dart';
import 'package:shared_preferences/shared_preferences.dart';
class ConnectionSelector extends StatefulWidget {
final SharedPreferences prefs;
final Function initWs;
final Map<String, dynamic> connections;
const ConnectionSelector(
{super.key,
required this.prefs,
required this.initWs,
required this.connections});
@override
State<ConnectionSelector> createState() => _ConnectionSelectorState();
}
class _ConnectionSelectorState extends State<ConnectionSelector> {
String _updateConnectionName = "";
dynamic _updateConnection;
bool _updateValid = false;
void updateData(String connectionName, dynamic connection, bool isValid) {
_updateConnectionName = connectionName;
_updateConnection = connection;
_updateValid = isValid;
}
void openEditDialog(bool showDelete, void Function()? onDelete,
void Function()? onSubmit, String submitButtonText, String headerText) {
showDialog<String>(
context: context,
builder: (BuildContext context) => AlertDialog(
title: Text(headerText),
content: ConnectionEditor(
connectionName: _updateConnectionName,
connection: _updateConnection,
prefs: widget.prefs,
updateData: updateData,
),
actions: <Widget>[
if (showDelete)
TextButton(
onPressed: onDelete,
child: const Text('Delete', style: TextStyle(color: Colors.red)),
),
TextButton(
onPressed: () => Navigator.pop(context, 'Cancel'),
child: const Text('Cancel'),
),
TextButton(
onPressed: onSubmit,
child: Text(submitButtonText),
),
],
),
);
}
@override
Widget build(BuildContext context) {
return SizedBox(
width: min(MediaQuery.of(context).size.width, 400),
child: ListView(
shrinkWrap: true,
children: [
...widget.connections.keys.map(
(key) => ListTile(
contentPadding: const EdgeInsets.only(left: 5, right: 5),
title: Text(key),
trailing: IconButton(
icon: const Icon(Icons.edit_outlined),
onPressed: () {
_updateValid = false;
_updateConnectionName = key;
_updateConnection = widget.connections[key];
openEditDialog(true, () {
setState(() {
widget.connections.removeWhere((k, _) => k == key);
widget.prefs.setString(
"connections", jsonEncode(widget.connections));
});
Navigator.pop(context, 'Cancel');
}, () {
if (!_updateValid) return;
setState(() {
widget.connections.removeWhere((k, _) => k == key);
widget.connections[_updateConnectionName] =
_updateConnection;
widget.prefs.setString(
"connections", jsonEncode(widget.connections));
});
Navigator.pop(context, 'Save');
}, "Save", "Edit Instance");
},
),
onTap: () {
widget.prefs.setString("selectedConnection", key);
Navigator.pop(context, 'Select');
widget.initWs();
},
),
),
const SizedBox(height: 5),
TextButton.icon(
onPressed: () {
_updateValid = false;
_updateConnection = {"url": ""};
_updateConnectionName =
"Connection #${DateTime.now().millisecondsSinceEpoch.remainder(100000)}";
openEditDialog(false, () {}, () {
if (!_updateValid) return;
setState(() {
widget.connections[_updateConnectionName] =
_updateConnection;
widget.prefs.setString(
"connections", jsonEncode(widget.connections));
});
Navigator.pop(context, 'Save');
}, "Create", "Create Connection");
},
icon: const Icon(Icons.add),
label: const Text("New connection"))
],
),
);
}
}

View File

@ -2,6 +2,7 @@ import 'dart:convert';
import 'dart:math';
import 'package:flutter/material.dart';
import 'package:gomix_flutter/connection_selector.dart';
import 'package:gomix_flutter/mixer_state.dart' as mixer;
import 'package:gomix_flutter/mixing_tab.dart';
import 'package:gomix_flutter/ports_tab.dart';
@ -176,7 +177,12 @@ class _GoMixHomeState extends State<GoMixHome> {
context: context,
builder: (BuildContext context) => AlertDialog(
title: const Text('Select Instance'),
content: const Text("WIP"),
content: ConnectionSelector(
prefs: prefs,
initWs: initWs,
connections: (jsonDecode(
prefs.getString("connections") ?? "{}"))
as Map<String, dynamic>),
actions: <Widget>[
TextButton(
onPressed: () => Navigator.pop(context, 'Cancel'),