From fa94b9438fb173ce8048d452a826e62d256bcb61 Mon Sep 17 00:00:00 2001 From: EdisonJwa Date: Sat, 16 May 2026 18:05:41 +0800 Subject: [PATCH] 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). --- apps/chanora_flutter/lib/main.dart | 31 ++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/apps/chanora_flutter/lib/main.dart b/apps/chanora_flutter/lib/main.dart index 98493dd..4735606 100644 --- a/apps/chanora_flutter/lib/main.dart +++ b/apps/chanora_flutter/lib/main.dart @@ -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(), ), ),