mirror of
https://github.com/Iconica-Development/flutter_chat.git
synced 2025-05-18 18:33:49 +02:00
feat: add semantics for text fields
This commit is contained in:
parent
30fc7b4368
commit
b3b8b1828e
6 changed files with 158 additions and 106 deletions
|
@ -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,
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -139,6 +139,7 @@ class _AppBar extends StatelessWidget implements PreferredSizeWidget {
|
|||
onSearch: onSearch,
|
||||
focusNode: focusNode,
|
||||
text: options.translations.newChatTitle,
|
||||
semanticId: options.semantics.newChatSearchInput,
|
||||
),
|
||||
actions: [
|
||||
SearchIcon(
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -149,6 +149,7 @@ class _AppBar extends StatelessWidget implements PreferredSizeWidget {
|
|||
onSearch: onSearch,
|
||||
focusNode: focusNode,
|
||||
text: options.translations.newGroupChatTitle,
|
||||
semanticId: options.semantics.newGroupChatSearchInput,
|
||||
),
|
||||
actions: [
|
||||
SearchIcon(
|
||||
|
|
|
@ -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,
|
||||
);
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue