~emersion/goguma-dev

Move parseServerUri to irc and make it public v2 APPLIED

delthas: 2
 Move parseServerUri to irc and make it public
 Add a network add/edit page

 6 files changed, 248 insertions(+), 22 deletions(-)
LGTM. Pushed, thanks!
Export patchset (mbox)
How do I use this?

Copy & paste the following snippet into your terminal to import this patchset into git:

curl -s https://lists.sr.ht/~emersion/goguma-dev/patches/31008/mbox | git am -3
Learn more about email & git

[PATCH v2 1/2] Move parseServerUri to irc and make it public Export this patch

This will be needed by the network_details page.
---
 lib/irc.dart          | 20 ++++++++++++++++++++
 lib/page/connect.dart | 24 ++----------------------
 2 files changed, 22 insertions(+), 22 deletions(-)

diff --git a/lib/irc.dart b/lib/irc.dart
index 9c9e568..c986530 100644
--- a/lib/irc.dart
+++ b/lib/irc.dart
@@ -262,6 +262,26 @@ String _unescapeChar(String ch) {
	}
}

Uri parseServerUri(String rawUri) {
	if (!rawUri.contains('://')) {
		rawUri = 'ircs://' + rawUri;
	}

	var uri = Uri.parse(rawUri);
	if (uri.host == '') {
		throw FormatException('Host is required in URI');
	}
	switch (uri.scheme) {
		case 'ircs':
		case 'irc+insecure':
			break; // supported
		default:
			throw FormatException('Unsupported URI scheme: ' + uri.scheme);
	}

	return uri;
}

class IrcSource {
	final String name;
	final String? user;
diff --git a/lib/page/connect.dart b/lib/page/connect.dart
index e7bd142..6e33ffa 100644
--- a/lib/page/connect.dart
+++ b/lib/page/connect.dart
@@ -18,26 +18,6 @@ class ConnectPage extends StatefulWidget {
	ConnectPageState createState() => ConnectPageState();
}

Uri _parseServerUri(String rawUri) {
	if (!rawUri.contains('://')) {
		rawUri = 'ircs://' + rawUri;
	}

	var uri = Uri.parse(rawUri);
	if (uri.host == '') {
		throw FormatException('Host is required in URI');
	}
	switch (uri.scheme) {
	case 'ircs':
	case 'irc+insecure':
		break; // supported
	default:
		throw FormatException('Unsupported URI scheme: ' + uri.scheme);
	}

	return uri;
}

class ConnectPageState extends State<ConnectPage> {
	bool _loading = false;
	Exception? _error;
@@ -53,7 +33,7 @@ class ConnectPageState extends State<ConnectPage> {
			return;
		}

		Uri uri = _parseServerUri(serverController.text);
		Uri uri = parseServerUri(serverController.text);
		var serverEntry = ServerEntry(
			host: uri.host,
			port: uri.hasPort ? uri.port : null,
@@ -175,7 +155,7 @@ class ConnectPageState extends State<ConnectPage> {
								return 'Required';
							}
							try {
								_parseServerUri(value);
								parseServerUri(value);
							} on FormatException catch(e) {
								return e.message;
							}

base-commit: 6c75fafa5a11cd6c37a64c68f74a75208d365072
-- 
2.17.1

[PATCH v2 2/2] Add a network add/edit page Export this patch

This makes use of BOUNCER ADDNETWORK/CHANGENETWORK.

We merely send the message line, and discover the new/update network
thanks to bouncer-networks-notify and its exisiting logic.

Fixes: https://todo.sr.ht/~emersion/goguma/62
---
 lib/app.dart               |   5 ++
 lib/models.dart            |  35 ++++++++
 lib/page/edit_network.dart | 172 +++++++++++++++++++++++++++++++++++++
 lib/page/settings.dart     |  14 +++
 4 files changed, 226 insertions(+)
 create mode 100644 lib/page/edit_network.dart

diff --git a/lib/app.dart b/lib/app.dart
index c37ecad..0066008 100644
--- a/lib/app.dart
+++ b/lib/app.dart
@@ -14,6 +14,7 @@ import 'page/buffer_details.dart';
import 'page/buffer_list.dart';
import 'page/connect.dart';
import 'page/join.dart';
import 'page/edit_network.dart';
import 'page/settings.dart';

const _themeMode = ThemeMode.system;
@@ -218,6 +219,10 @@ class AppState extends State<App> with WidgetsBindingObserver {
				);
			};
			break;
		case EditNetworkPage.routeName:
			var network = settings.arguments as BouncerNetworkModel?;
			builder = (context) => EditNetworkPage(network: network);
			break;
		default:
			throw Exception('Unknown route ${settings.name}');
		}
diff --git a/lib/models.dart b/lib/models.dart
index 63bc3d5..e144e08 100644
--- a/lib/models.dart
+++ b/lib/models.dart
@@ -146,7 +146,14 @@ class BouncerNetworkModel extends ChangeNotifier {
	final String id;
	String? _name;
	String? _host;
	int? _port;
	bool? _tls;
	String? _nickname;
	String? _username;
	String? _realname;
	String? _pass;
	BouncerNetworkState _state = BouncerNetworkState.disconnected;
	String? _error;

	BouncerNetworkModel(this.id, Map<String, String?> attrs) {
		setAttrs(attrs);
@@ -154,7 +161,14 @@ class BouncerNetworkModel extends ChangeNotifier {

	String? get name => _name;
	String? get host => _host;
	int? get port => _port;
	bool? get tls => _tls;
	String? get nickname => _nickname;
	String? get username => _username;
	String? get realname => _realname;
	String? get pass => _pass;
	BouncerNetworkState get state => _state;
	String? get error => _error;

	void setAttrs(Map<String, String?> attrs) {
		for (var kv in attrs.entries) {
@@ -168,6 +182,27 @@ class BouncerNetworkModel extends ChangeNotifier {
			case 'state':
				_state = _parseBouncerNetworkState(kv.value!);
				break;
			case 'error':
				_error = kv.value;
				break;
			case 'port':
				_port = kv.value != null ? int.tryParse(kv.value!) : null;
				break;
			case 'tls':
				_tls = kv.value == '1';
				break;
			case 'nickname':
				_nickname = kv.value;
				break;
			case 'username':
				_username = kv.value;
				break;
			case 'realname':
				_realname = kv.value;
				break;
			case 'pass':
				_pass = kv.value;
				break;
			}
		}
		notifyListeners();
diff --git a/lib/page/edit_network.dart b/lib/page/edit_network.dart
new file mode 100644
index 0000000..b41cd5a
--- /dev/null
+++ b/lib/page/edit_network.dart
@@ -0,0 +1,172 @@
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';

import '../client_controller.dart';
import '../irc.dart';
import '../models.dart';

class EditNetworkPage extends StatefulWidget {
	static const routeName = '/network/edit';

	final BouncerNetworkModel? network;

	const EditNetworkPage({ Key? key, required this.network }) : super(key: key);

	@override
	EditNetworkPageState createState() => EditNetworkPageState();
}

class EditNetworkPageState extends State<EditNetworkPage> {
	final GlobalKey<FormState> _formKey = GlobalKey();

	late final TextEditingController _nameController;
	late final TextEditingController _urlController;
	late final TextEditingController _nicknameController;
	late final TextEditingController _usernameController;
	late final TextEditingController _realnameController;
	late final TextEditingController _passController;

	@override
	void initState() {
		super.initState();

		var network = widget.network;

		_nameController = TextEditingController(text: network?.name);

		var url = network?.host ?? '';
		if (network?.tls != false && (network?.port ?? 6697) != 6697) {
			url = url + ':${network?.port}';
		} else if (network?.tls == false) {
			url = 'irc+insecure://' + url;
			if ((network?.port ?? 6667) != 6667) {
				url = url + ':${network?.port}';
			}
		}

		_urlController = TextEditingController(text: url);
		_nicknameController = TextEditingController(text:  network?.nickname);
		_usernameController = TextEditingController(text: network?.username);
		_realnameController = TextEditingController(text: network?.realname);
		_passController = TextEditingController(text: network?.pass);
	}

	@override
	void dispose() {
		_urlController.dispose();
		_nicknameController.dispose();
		_usernameController.dispose();
		_realnameController.dispose();
		_passController.dispose();
		super.dispose();
	}

	void _submit() async {
		if (!_formKey.currentState!.validate()) {
			return;
		}

		Navigator.pop(context);

		var networkList = context.read<NetworkListModel>();

		NetworkModel? mainNetwork;
		for (var network in networkList.networks) {
			if (network.networkEntry.bouncerId == null) {
				mainNetwork = network;
				break;
			}
		}
		if (mainNetwork == null) {
			throw Exception('No main network found');
		}

		var client = context.read<ClientProvider>().get(mainNetwork);

		var params = <String>[];
		if (widget.network == null) {
			params.add('ADDNETWORK');
		} else {
			params.add('CHANGENETWORK');
			params.add(widget.network!.id);
		}

		Uri uri = parseServerUri(_urlController.text);
		var attrs = {
			'name': _nameController.text,
			'host': uri.host,
			'port': uri.hasPort ? uri.port.toString() : null,
			'tls': uri.scheme != 'irc+insecure' ? '1' : '0',
			'nickname': _nicknameController.text,
			'username': _usernameController.text,
			'realname': _realnameController.text,
			'pass': _passController.text,
		};

		params.add(formatIrcTags(attrs));
		var msg = IrcMessage('BOUNCER', params);
		client.send(msg);
	}

	@override
	Widget build(BuildContext context) {
		return Scaffold(
			appBar: AppBar(
				title: Text(widget.network == null ? 'Add network' : 'Edit network'),
			),
			body: Form(
				key: _formKey,
				child: Container(padding: EdgeInsets.all(10), child: Column(children: [
					TextFormField(
						controller: _nameController,
						decoration: InputDecoration(labelText: 'Name'),
						validator: (value) {
							if (value!.isEmpty) {
								return 'Required';
							}
							return null;
						},
					),
					TextFormField(
						keyboardType: TextInputType.url,
						controller: _urlController,
						decoration: InputDecoration(labelText: 'Server'),
						validator: (value) {
							if (value!.isEmpty) {
								return 'Required';
							}
							try {
								parseServerUri(value);
							} on FormatException catch(e) {
								return e.message;
							}
							return null;
						},
					),
					TextFormField(
						controller: _nicknameController,
						decoration: InputDecoration(labelText: 'Nickname (optional)'),
					),
					TextFormField(
						controller: _usernameController,
						decoration: InputDecoration(labelText: 'Username (optional)'),
					),
					TextFormField(
						controller: _realnameController,
						decoration: InputDecoration(labelText: 'Realname (optional)'),
					),
					TextFormField(
						obscureText: true,
						controller: _passController,
						decoration: InputDecoration(labelText: 'Password (optional)'),
					),
					SizedBox(height: 20),
					FloatingActionButton.extended(
						onPressed: _submit,
						label: Text(widget.network == null ? 'Add' : 'Save'),
					),
				])),
			),
		);
	}
}
diff --git a/lib/page/settings.dart b/lib/page/settings.dart
index 3bd6c98..9caa897 100644
--- a/lib/page/settings.dart
+++ b/lib/page/settings.dart
@@ -9,6 +9,7 @@ import '../database.dart';
import '../dialog/edit_profile.dart';
import '../irc.dart';
import '../models.dart';
import 'edit_network.dart';

class SettingsPage extends StatefulWidget {
	static const routeName = '/settings';
@@ -94,6 +95,13 @@ class _SettingsPageState extends State<SettingsPage> {
					),
				),
				Column(children: networks),
				ListTile(
					title: Text('Add network'),
					leading: Icon(Icons.add),
					onTap: () {
						Navigator.pushNamed(context, EditNetworkPage.routeName, arguments: null);
					},
				),
				Divider(),
				ListTile(
					title: Text('Compact message list'),
@@ -138,6 +146,9 @@ class _NetworkItem extends AnimatedWidget {
		String subtitle;
		if (network.bouncerNetwork != null && network.state == NetworkState.online) {
			subtitle = _bouncerNetworkStateDescription(network.bouncerNetwork!.state);
			if (network.bouncerNetwork?.error?.isNotEmpty == true) {
				subtitle = '$subtitle - ${network.bouncerNetwork!.error}';
			}
		} else {
			subtitle = _networkStateDescription(network.state);
		}
@@ -149,6 +160,9 @@ class _NetworkItem extends AnimatedWidget {
				mainAxisAlignment: MainAxisAlignment.center,
				children: const [Icon(Icons.hub)],
			),
			onTap: network.bouncerNetwork == null ? null : () {
				Navigator.pushNamed(context, EditNetworkPage.routeName, arguments: network.bouncerNetwork!);
			},
		);
	}
}
-- 
2.17.1
LGTM. Pushed, thanks!