From b3b8b1828eb4cda26e75f39db7a64dc4706e1d73 Mon Sep 17 00:00:00 2001 From: Jacques Date: Thu, 27 Feb 2025 17:25:20 +0100 Subject: [PATCH] feat: add semantics for text fields --- .../lib/src/config/chat_semantics.dart | 32 ++++- .../chat_detail/widgets/chat_bottom.dart | 81 ++++++------ .../src/screens/creation/new_chat_screen.dart | 1 + .../creation/new_group_chat_overview.dart | 118 ++++++++++-------- .../creation/new_group_chat_screen.dart | 1 + .../creation/widgets/search_field.dart | 31 +++-- 6 files changed, 158 insertions(+), 106 deletions(-) diff --git a/packages/flutter_chat/lib/src/config/chat_semantics.dart b/packages/flutter_chat/lib/src/config/chat_semantics.dart index 1c4240f..bb8bb4e 100644 --- a/packages/flutter_chat/lib/src/config/chat_semantics.dart +++ b/packages/flutter_chat/lib/src/config/chat_semantics.dart @@ -7,9 +7,9 @@ /// Class that holds all the semantic ids for the chat component view and /// the corresponding userstory class ChatSemantics { - /// ChatTranslations constructor where everything is required use this + /// ChatSemantics constructor where everything is required use this /// if you want to be sure to have all translations specified - /// If you just want the default values use the empty constructor + /// If you just want the default values use the standard constructor /// and optionally override the values with the copyWith method const ChatSemantics({ required this.profileTitle, @@ -28,6 +28,11 @@ class ChatSemantics { required this.chatsChatSubTitle, required this.chatsChatLastUsed, required this.chatsChatUnreadMessages, + required this.chatMessageInput, + required this.newChatNameInput, + required this.newChatBioInput, + required this.newChatSearchInput, + required this.newGroupChatSearchInput, }); /// Default translations for the chat component view @@ -48,6 +53,11 @@ class ChatSemantics { this.chatsChatSubTitle = _defaultChatsChatSubTitle, this.chatsChatLastUsed = _defaultChatsChatLastUsed, this.chatsChatUnreadMessages = _defaultChatsChatUnreadMessages, + this.chatMessageInput = "input_text_message", + this.newChatNameInput = "input_text_name", + this.newChatBioInput = "input_text_bio", + this.newChatSearchInput = "input_text_search", + this.newGroupChatSearchInput = "input_text_search", }); // Text @@ -70,6 +80,13 @@ class ChatSemantics { final String Function(int index) chatsChatLastUsed; final String Function(int index) chatsChatUnreadMessages; + // Input texts + final String chatMessageInput; + final String newChatNameInput; + final String newChatBioInput; + final String newChatSearchInput; + final String newGroupChatSearchInput; + ChatSemantics copyWith({ String? profileTitle, String? profileDescription, @@ -87,6 +104,11 @@ class ChatSemantics { String Function(int)? chatsChatSubTitle, String Function(int)? chatsChatLastUsed, String Function(int)? chatsChatUnreadMessages, + String? chatMessageInput, + String? newChatNameInput, + String? newChatBioInput, + String? newChatSearchInput, + String? newGroupChatSearchInput, }) => ChatSemantics( profileTitle: profileTitle ?? this.profileTitle, @@ -109,6 +131,12 @@ class ChatSemantics { chatsChatLastUsed: chatsChatLastUsed ?? this.chatsChatLastUsed, chatsChatUnreadMessages: chatsChatUnreadMessages ?? this.chatsChatUnreadMessages, + chatMessageInput: chatMessageInput ?? this.chatMessageInput, + newChatNameInput: newChatNameInput ?? this.newChatNameInput, + newChatBioInput: newChatBioInput ?? this.newChatBioInput, + newChatSearchInput: newChatSearchInput ?? this.newChatSearchInput, + newGroupChatSearchInput: + newGroupChatSearchInput ?? this.newGroupChatSearchInput, ); } diff --git a/packages/flutter_chat/lib/src/screens/chat_detail/widgets/chat_bottom.dart b/packages/flutter_chat/lib/src/screens/chat_detail/widgets/chat_bottom.dart index 5daef17..54d34e0 100644 --- a/packages/flutter_chat/lib/src/screens/chat_detail/widgets/chat_bottom.dart +++ b/packages/flutter_chat/lib/src/screens/chat_detail/widgets/chat_bottom.dart @@ -1,5 +1,6 @@ import "package:chat_repository_interface/chat_repository_interface.dart"; import "package:flutter/material.dart"; +import "package:flutter_accessibility/flutter_accessibility.dart"; import "package:flutter_chat/src/util/scope.dart"; import "package:flutter_hooks/flutter_hooks.dart"; @@ -91,47 +92,51 @@ class ChatBottomInputSection extends HookWidget { var defaultInputField = Stack( children: [ - TextField( - textAlign: TextAlign.start, - textAlignVertical: TextAlignVertical.center, - style: theme.textTheme.bodySmall, - textCapitalization: TextCapitalization.sentences, - textInputAction: TextInputAction.newline, - keyboardType: TextInputType.multiline, - maxLines: null, - controller: textController, - enabled: !isLoading, - decoration: InputDecoration( - enabledBorder: OutlineInputBorder( - borderRadius: BorderRadius.circular(25), - borderSide: const BorderSide(color: Colors.black), - ), - focusedBorder: OutlineInputBorder( - borderRadius: BorderRadius.circular(25), - borderSide: const BorderSide(color: Colors.black), - ), - contentPadding: const EdgeInsets.only( - left: 16, - top: 16, - bottom: 16, - ), - // this ensures that that there is space at the end of the textfield - suffixIcon: AbsorbPointer( - child: Opacity( - opacity: 0.0, - child: messageSendButtons, + CustomSemantics( + identifier: options.semantics.chatMessageInput, + isTextField: true, + child: TextField( + textAlign: TextAlign.start, + textAlignVertical: TextAlignVertical.center, + style: theme.textTheme.bodySmall, + textCapitalization: TextCapitalization.sentences, + textInputAction: TextInputAction.newline, + keyboardType: TextInputType.multiline, + maxLines: null, + controller: textController, + enabled: !isLoading, + decoration: InputDecoration( + enabledBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(25), + borderSide: const BorderSide(color: Colors.black), + ), + focusedBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(25), + borderSide: const BorderSide(color: Colors.black), + ), + contentPadding: const EdgeInsets.only( + left: 16, + top: 16, + bottom: 16, + ), + // this ensures that that there is space at the end of the textfield + suffixIcon: AbsorbPointer( + child: Opacity( + opacity: 0.0, + child: messageSendButtons, + ), + ), + hintText: options.translations.messagePlaceholder, + hintStyle: theme.textTheme.bodyMedium, + fillColor: Colors.white, + filled: true, + border: const OutlineInputBorder( + borderRadius: BorderRadius.all(Radius.circular(25)), + borderSide: BorderSide.none, ), ), - hintText: options.translations.messagePlaceholder, - hintStyle: theme.textTheme.bodyMedium, - fillColor: Colors.white, - filled: true, - border: const OutlineInputBorder( - borderRadius: BorderRadius.all(Radius.circular(25)), - borderSide: BorderSide.none, - ), + onSubmitted: (_) async => onSubmitField(), ), - onSubmitted: (_) async => onSubmitField(), ), Positioned( right: 0, diff --git a/packages/flutter_chat/lib/src/screens/creation/new_chat_screen.dart b/packages/flutter_chat/lib/src/screens/creation/new_chat_screen.dart index d85dda0..297f53f 100644 --- a/packages/flutter_chat/lib/src/screens/creation/new_chat_screen.dart +++ b/packages/flutter_chat/lib/src/screens/creation/new_chat_screen.dart @@ -139,6 +139,7 @@ class _AppBar extends StatelessWidget implements PreferredSizeWidget { onSearch: onSearch, focusNode: focusNode, text: options.translations.newChatTitle, + semanticId: options.semantics.newChatSearchInput, ), actions: [ SearchIcon( diff --git a/packages/flutter_chat/lib/src/screens/creation/new_group_chat_overview.dart b/packages/flutter_chat/lib/src/screens/creation/new_group_chat_overview.dart index 626745f..ae510fb 100644 --- a/packages/flutter_chat/lib/src/screens/creation/new_group_chat_overview.dart +++ b/packages/flutter_chat/lib/src/screens/creation/new_group_chat_overview.dart @@ -226,37 +226,41 @@ class _BodyState extends State<_Body> { const SizedBox( height: 12, ), - TextFormField( - style: theme.textTheme.bodySmall, - controller: _chatNameController, - decoration: InputDecoration( - fillColor: Colors.white, - filled: true, - hintText: translations.groupNameHintText, - hintStyle: theme.textTheme.bodyMedium, - enabledBorder: OutlineInputBorder( - borderRadius: BorderRadius.circular(12), - borderSide: const BorderSide( - color: Colors.transparent, + CustomSemantics( + identifier: options.semantics.newChatNameInput, + isTextField: true, + child: TextFormField( + style: theme.textTheme.bodySmall, + controller: _chatNameController, + decoration: InputDecoration( + fillColor: Colors.white, + filled: true, + hintText: translations.groupNameHintText, + hintStyle: theme.textTheme.bodyMedium, + enabledBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(12), + borderSide: const BorderSide( + color: Colors.transparent, + ), + ), + focusedBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(12), + borderSide: const BorderSide( + color: Colors.transparent, + ), ), ), - focusedBorder: OutlineInputBorder( - borderRadius: BorderRadius.circular(12), - borderSide: const BorderSide( - color: Colors.transparent, - ), - ), - ), - validator: (value) { - if (value == null || value.isEmpty) { - return translations.groupNameValidatorEmpty; - } - if (value.length > 15) { - return translations.groupNameValidatorTooLong; - } + validator: (value) { + if (value == null || value.isEmpty) { + return translations.groupNameValidatorEmpty; + } + if (value.length > 15) { + return translations.groupNameValidatorTooLong; + } - return null; - }, + return null; + }, + ), ), const SizedBox( height: 16, @@ -268,36 +272,40 @@ class _BodyState extends State<_Body> { const SizedBox( height: 12, ), - TextFormField( - style: theme.textTheme.bodySmall, - controller: _bioController, - minLines: null, - maxLines: 5, - decoration: InputDecoration( - fillColor: Colors.white, - filled: true, - hintText: translations.groupBioHintText, - hintStyle: theme.textTheme.bodyMedium, - enabledBorder: OutlineInputBorder( - borderRadius: BorderRadius.circular(12), - borderSide: const BorderSide( - color: Colors.transparent, + CustomSemantics( + identifier: options.semantics.newChatBioInput, + isTextField: true, + child: TextFormField( + style: theme.textTheme.bodySmall, + controller: _bioController, + minLines: null, + maxLines: 5, + decoration: InputDecoration( + fillColor: Colors.white, + filled: true, + hintText: translations.groupBioHintText, + hintStyle: theme.textTheme.bodyMedium, + enabledBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(12), + borderSide: const BorderSide( + color: Colors.transparent, + ), + ), + focusedBorder: OutlineInputBorder( + borderRadius: BorderRadius.circular(12), + borderSide: const BorderSide( + color: Colors.transparent, + ), ), ), - focusedBorder: OutlineInputBorder( - borderRadius: BorderRadius.circular(12), - borderSide: const BorderSide( - color: Colors.transparent, - ), - ), - ), - validator: (value) { - if (value == null || value.isEmpty) { - return translations.groupBioValidatorEmpty; - } + validator: (value) { + if (value == null || value.isEmpty) { + return translations.groupBioValidatorEmpty; + } - return null; - }, + return null; + }, + ), ), const SizedBox( height: 16, diff --git a/packages/flutter_chat/lib/src/screens/creation/new_group_chat_screen.dart b/packages/flutter_chat/lib/src/screens/creation/new_group_chat_screen.dart index 2f75d95..7d082c2 100644 --- a/packages/flutter_chat/lib/src/screens/creation/new_group_chat_screen.dart +++ b/packages/flutter_chat/lib/src/screens/creation/new_group_chat_screen.dart @@ -149,6 +149,7 @@ class _AppBar extends StatelessWidget implements PreferredSizeWidget { onSearch: onSearch, focusNode: focusNode, text: options.translations.newGroupChatTitle, + semanticId: options.semantics.newGroupChatSearchInput, ), actions: [ SearchIcon( diff --git a/packages/flutter_chat/lib/src/screens/creation/widgets/search_field.dart b/packages/flutter_chat/lib/src/screens/creation/widgets/search_field.dart index 48dc703..3e6de47 100644 --- a/packages/flutter_chat/lib/src/screens/creation/widgets/search_field.dart +++ b/packages/flutter_chat/lib/src/screens/creation/widgets/search_field.dart @@ -1,4 +1,5 @@ import "package:flutter/material.dart"; +import "package:flutter_accessibility/flutter_accessibility.dart"; import "package:flutter_chat/src/util/scope.dart"; /// The search field widget @@ -9,6 +10,7 @@ class SearchField extends StatelessWidget { required this.onSearch, required this.focusNode, required this.text, + required this.semanticId, super.key, }); @@ -24,6 +26,9 @@ class SearchField extends StatelessWidget { /// The text to display in the search field final String text; + /// Semantic id for search field + final String semanticId; + @override Widget build(BuildContext context) { var chatScope = ChatScope.of(context); @@ -32,20 +37,24 @@ class SearchField extends StatelessWidget { var translations = options.translations; if (isSearching) { - return TextField( - focusNode: focusNode, - onChanged: onSearch, - decoration: InputDecoration( - hintText: translations.searchPlaceholder, - hintStyle: theme.textTheme.bodyMedium, - focusedBorder: UnderlineInputBorder( - borderSide: BorderSide( - color: theme.colorScheme.primary, + return CustomSemantics( + identifier: semanticId, + isTextField: true, + child: TextField( + focusNode: focusNode, + onChanged: onSearch, + decoration: InputDecoration( + hintText: translations.searchPlaceholder, + hintStyle: theme.textTheme.bodyMedium, + focusedBorder: UnderlineInputBorder( + borderSide: BorderSide( + color: theme.colorScheme.primary, + ), ), ), + style: theme.textTheme.bodySmall, + cursorColor: theme.textSelectionTheme.cursorColor ?? Colors.white, ), - style: theme.textTheme.bodySmall, - cursorColor: theme.textSelectionTheme.cursorColor ?? Colors.white, ); }