mirror of
https://github.com/we-promise/sure.git
synced 2026-07-12 21:05:20 +00:00
Adds SureTextField, the form-control primitive following SureButton in the mobile design-system sequence: a TextFormField wrapper that builds a complete, brightness-aware InputDecoration from the active SureColors palette (filled bg-container, borderSecondary hairline -> borderPrimary on focus, destructive error border, textSubdued placeholder, radiusLg corners) with a DS-style label rendered above the field and associated to it for screen readers (Material labelText parity via ExcludeSemantics + Semantics(label:)). Migrates the custom proxy-headers editor off raw TextFormField as proof. Filter chips and a segmented control will follow as separate PRs. Part of #2235.
152 lines
3.7 KiB
Dart
152 lines
3.7 KiB
Dart
import 'package:flutter/material.dart';
|
|
|
|
import '../models/custom_proxy_header.dart';
|
|
import 'sure_text_field.dart';
|
|
|
|
class CustomProxyHeadersEditor extends StatefulWidget {
|
|
final List<CustomProxyHeader> initialHeaders;
|
|
final ValueChanged<List<CustomProxyHeader>> onChanged;
|
|
|
|
const CustomProxyHeadersEditor({
|
|
super.key,
|
|
required this.initialHeaders,
|
|
required this.onChanged,
|
|
});
|
|
|
|
@override
|
|
State<CustomProxyHeadersEditor> createState() =>
|
|
_CustomProxyHeadersEditorState();
|
|
}
|
|
|
|
class _CustomProxyHeadersEditorState extends State<CustomProxyHeadersEditor> {
|
|
late List<_HeaderDraft> _drafts;
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
_drafts = widget.initialHeaders
|
|
.map((header) => _HeaderDraft(name: header.name, value: header.value))
|
|
.toList();
|
|
}
|
|
|
|
void _notifyChanged() {
|
|
widget.onChanged(
|
|
_drafts
|
|
.map(
|
|
(draft) => CustomProxyHeader(
|
|
name: draft.name.text,
|
|
value: draft.value.text,
|
|
),
|
|
)
|
|
.where((header) => header.isComplete)
|
|
.toList(),
|
|
);
|
|
}
|
|
|
|
void _addHeader() {
|
|
setState(() => _drafts.add(_HeaderDraft()));
|
|
}
|
|
|
|
void _removeHeader(int index) {
|
|
setState(() {
|
|
final draft = _drafts.removeAt(index);
|
|
draft.dispose();
|
|
});
|
|
_notifyChanged();
|
|
}
|
|
|
|
@override
|
|
void dispose() {
|
|
for (final draft in _drafts) {
|
|
draft.dispose();
|
|
}
|
|
super.dispose();
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return Column(
|
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
|
children: [
|
|
for (var index = 0; index < _drafts.length; index++) ...[
|
|
_HeaderRow(
|
|
draft: _drafts[index],
|
|
onChanged: _notifyChanged,
|
|
onRemove: () => _removeHeader(index),
|
|
),
|
|
const SizedBox(height: 12),
|
|
],
|
|
OutlinedButton.icon(
|
|
onPressed: _addHeader,
|
|
icon: const Icon(Icons.add),
|
|
label: const Text('Add header'),
|
|
),
|
|
],
|
|
);
|
|
}
|
|
}
|
|
|
|
class _HeaderRow extends StatelessWidget {
|
|
final _HeaderDraft draft;
|
|
final VoidCallback onChanged;
|
|
final VoidCallback onRemove;
|
|
|
|
const _HeaderRow({
|
|
required this.draft,
|
|
required this.onChanged,
|
|
required this.onRemove,
|
|
});
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return Row(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Expanded(
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
|
children: [
|
|
SureTextField(
|
|
controller: draft.name,
|
|
label: 'Header name',
|
|
hint: 'X-Auth-Token',
|
|
validator: (value) =>
|
|
CustomProxyHeader.validateName(value ?? ''),
|
|
onChanged: (_) => onChanged(),
|
|
),
|
|
const SizedBox(height: 12),
|
|
SureTextField(
|
|
controller: draft.value,
|
|
label: 'Header value',
|
|
obscureText: true,
|
|
validator: (value) =>
|
|
CustomProxyHeader.validateValue(value ?? ''),
|
|
onChanged: (_) => onChanged(),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
IconButton(
|
|
tooltip: 'Remove header',
|
|
icon: const Icon(Icons.delete_outline),
|
|
onPressed: onRemove,
|
|
),
|
|
],
|
|
);
|
|
}
|
|
}
|
|
|
|
class _HeaderDraft {
|
|
final TextEditingController name;
|
|
final TextEditingController value;
|
|
|
|
_HeaderDraft({String name = '', String value = ''})
|
|
: name = TextEditingController(text: name),
|
|
value = TextEditingController(text: value);
|
|
|
|
void dispose() {
|
|
name.dispose();
|
|
value.dispose();
|
|
}
|
|
}
|