import 'package:aves/model/settings/settings.dart'; import 'package:aves/widgets/common/extensions/build_context.dart'; import 'package:aves/widgets/dialogs/aves_dialog.dart'; import 'package:aves_map/aves_map.dart'; import 'package:flutter/material.dart'; class MapStyleEditorDialog extends StatefulWidget { static const routeName = '/dialog/map_style_editor'; final EntryMapStyle? initialValue; const MapStyleEditorDialog({ super.key, this.initialValue, }); @override State createState() => _MapStyleEditorDialogState(); } class _MapStyleEditorDialogState extends State { late final Set _otherCustomStyles; final TextEditingController _nameController = TextEditingController(); final TextEditingController _urlController = TextEditingController(); final TextEditingController _subdomainsController = TextEditingController(); final TextEditingController _userAgentController = TextEditingController(); final ValueNotifier _isValidNotifier = ValueNotifier(false); @override void initState() { super.initState(); final initialValue = widget.initialValue; _otherCustomStyles = settings.customMapStyles.where((v) => v != initialValue).toSet(); _nameController.text = initialValue?.name ?? ''; _urlController.text = initialValue?.url ?? ''; _subdomainsController.text = initialValue?.subdomains.join(',') ?? ''; _userAgentController.text = initialValue?.userAgent ?? ''; _validate(); } @override void dispose() { _nameController.dispose(); _urlController.dispose(); _subdomainsController.dispose(); _userAgentController.dispose(); _isValidNotifier.dispose(); super.dispose(); } @override Widget build(BuildContext context) { final l10n = context.l10n; return AvesDialog( scrollableContent: [ Padding( padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 16), child: TextField( controller: _nameController, decoration: InputDecoration( labelText: l10n.mapStyleEditorDialogName, ), autofocus: true, onChanged: (_) => _validate(), ), ), Padding( padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 16), child: TextField( controller: _urlController, decoration: InputDecoration( labelText: l10n.mapStyleEditorDialogUrl, ), keyboardType: TextInputType.url, onChanged: (_) => _validate(), ), ), Padding( padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 16), child: TextField( controller: _subdomainsController, decoration: InputDecoration( labelText: l10n.mapStyleEditorDialogSubdomains, ), ), ), Padding( padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 16), child: TextField( controller: _userAgentController, decoration: InputDecoration( labelText: l10n.mapStyleEditorDialogUserAgent, ), ), ), ], actions: [ const CancelButton(), ValueListenableBuilder( valueListenable: _isValidNotifier, builder: (context, isValid, child) { return TextButton( onPressed: isValid ? () => _submit(context) : null, child: Text(widget.initialValue != null ? l10n.applyButtonLabel : l10n.createButtonLabel), ); }, ), ], ); } EntryMapStyle _buildStyle() { final name = _nameController.text.trim(); final url = _urlController.text.trim(); final subdomains = _subdomainsController.text.split(',').map((v) => v.trim()).toList(); final userAgent = _userAgentController.text.trim(); return EntryMapStyle( key: 'custom_$name', name: name, url: url, subdomains: subdomains, userAgent: userAgent.isNotEmpty ? userAgent : null, ); } Future _validate() async { final style = _buildStyle(); final name = style.name ?? ''; final url = style.url ?? ''; if (name.isEmpty || url.isEmpty) { _isValidNotifier.value = false; return; } final key = style.key; final duplicate = _otherCustomStyles.any((v) => v.key == key || v.url == url); _isValidNotifier.value = !duplicate; } void _submit(BuildContext context) { if (_isValidNotifier.value) { Navigator.maybeOf(context)?.pop(_buildStyle()); } } }