feat(ui): URL-shaped keyboard + lowercase enforcement for server host

The server-host TextField on the connect form accepts a hostname or
hostname:port pair (e.g. kr.teamspeak.app:9987). Two improvements:

  1. keyboardType: TextInputType.url surfaces '.', '/', ':' on the
     primary on-screen keyboard plane so the user does not have to
     switch to the symbols pane mid-address. Matches iOS Safari's
     URL bar.

  2. textCapitalization.none + autocorrect/enableSuggestions=false
     prevents iOS from auto-capitalising the first letter or
     'correcting' 'kr.teamspeak.app' to something else.

  3. inputFormatters belt-and-braces:
       * deny whitespace (handles tab-indented paste)
       * lowercase pipeline (handles uppercase paste)

  4. Visual affordances: prefix dns icon + 'host[:port]' hint.

Nickname field intentionally unchanged \u2014 may contain unicode,
mixed case, spaces.

flutter build ios --release --no-codesign: 30.7 s, Runner.app
30.0 MB. flutter analyze clean (6 pre-existing deprecation
warnings on Radio.groupValue/onChanged).
This commit is contained in:
EdisonJwa
2026-05-16 18:05:41 +08:00
parent 4ee2b3850a
commit fa94b9438f
+31
View File
@@ -1294,11 +1294,42 @@ class _ConnectFormState extends State<_ConnectForm> {
controller: widget.hostCtl,
focusNode: _hostFocus,
onTap: () => _hostFocus.requestFocus(),
// Server addresses are URL-shaped: hostname or
// hostname:port, all-lowercase ASCII, never user-
// friendly prose. Configure the on-screen keyboard
// accordingly:
// * keyboardType: .url \u2014 surfaces ".", "/",
// ":" on the primary keyboard plane so the user
// doesn't have to switch to the symbols pane to
// type kr.teamspeak.app:9987.
// * textInputAction: .next \u2014 return key advances
// to the nickname field.
// * autocorrect / enableSuggestions: false \u2014 iOS
// should not autocorrect 'kr.teamspeak.app' to
// 'kr.teamspeak.lap' or suggest 'KR' in caps.
// * textCapitalization: .none \u2014 don't capitalise the
// first letter the way iOS does for sentences.
// * inputFormatters: deny whitespace and uppercase\u2014
// belt-and-braces in case the user pastes from a
// formatted source (e.g. tab-indented copy).
keyboardType: TextInputType.url,
textCapitalization: TextCapitalization.none,
textInputAction: TextInputAction.next,
autocorrect: false,
enableSuggestions: false,
inputFormatters: [
FilteringTextInputFormatter.deny(RegExp(r'\s')),
TextInputFormatter.withFunction(
(oldValue, newValue) => newValue.copyWith(
text: newValue.text.toLowerCase(),
selection: newValue.selection,
),
),
],
decoration: InputDecoration(
labelText: l10n.fieldServerHost,
hintText: 'host[:port]',
prefixIcon: const Icon(Icons.dns_outlined),
border: const OutlineInputBorder(),
),
),