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
|
/// Class that holds all the semantic ids for the chat component view and
|
||||||
/// the corresponding userstory
|
/// the corresponding userstory
|
||||||
class ChatSemantics {
|
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 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
|
/// and optionally override the values with the copyWith method
|
||||||
const ChatSemantics({
|
const ChatSemantics({
|
||||||
required this.profileTitle,
|
required this.profileTitle,
|
||||||
|
@ -28,6 +28,11 @@ class ChatSemantics {
|
||||||
required this.chatsChatSubTitle,
|
required this.chatsChatSubTitle,
|
||||||
required this.chatsChatLastUsed,
|
required this.chatsChatLastUsed,
|
||||||
required this.chatsChatUnreadMessages,
|
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
|
/// Default translations for the chat component view
|
||||||
|
@ -48,6 +53,11 @@ class ChatSemantics {
|
||||||
this.chatsChatSubTitle = _defaultChatsChatSubTitle,
|
this.chatsChatSubTitle = _defaultChatsChatSubTitle,
|
||||||
this.chatsChatLastUsed = _defaultChatsChatLastUsed,
|
this.chatsChatLastUsed = _defaultChatsChatLastUsed,
|
||||||
this.chatsChatUnreadMessages = _defaultChatsChatUnreadMessages,
|
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
|
// Text
|
||||||
|
@ -70,6 +80,13 @@ class ChatSemantics {
|
||||||
final String Function(int index) chatsChatLastUsed;
|
final String Function(int index) chatsChatLastUsed;
|
||||||
final String Function(int index) chatsChatUnreadMessages;
|
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({
|
ChatSemantics copyWith({
|
||||||
String? profileTitle,
|
String? profileTitle,
|
||||||
String? profileDescription,
|
String? profileDescription,
|
||||||
|
@ -87,6 +104,11 @@ class ChatSemantics {
|
||||||
String Function(int)? chatsChatSubTitle,
|
String Function(int)? chatsChatSubTitle,
|
||||||
String Function(int)? chatsChatLastUsed,
|
String Function(int)? chatsChatLastUsed,
|
||||||
String Function(int)? chatsChatUnreadMessages,
|
String Function(int)? chatsChatUnreadMessages,
|
||||||
|
String? chatMessageInput,
|
||||||
|
String? newChatNameInput,
|
||||||
|
String? newChatBioInput,
|
||||||
|
String? newChatSearchInput,
|
||||||
|
String? newGroupChatSearchInput,
|
||||||
}) =>
|
}) =>
|
||||||
ChatSemantics(
|
ChatSemantics(
|
||||||
profileTitle: profileTitle ?? this.profileTitle,
|
profileTitle: profileTitle ?? this.profileTitle,
|
||||||
|
@ -109,6 +131,12 @@ class ChatSemantics {
|
||||||
chatsChatLastUsed: chatsChatLastUsed ?? this.chatsChatLastUsed,
|
chatsChatLastUsed: chatsChatLastUsed ?? this.chatsChatLastUsed,
|
||||||
chatsChatUnreadMessages:
|
chatsChatUnreadMessages:
|
||||||
chatsChatUnreadMessages ?? this.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:chat_repository_interface/chat_repository_interface.dart";
|
||||||
import "package:flutter/material.dart";
|
import "package:flutter/material.dart";
|
||||||
|
import "package:flutter_accessibility/flutter_accessibility.dart";
|
||||||
import "package:flutter_chat/src/util/scope.dart";
|
import "package:flutter_chat/src/util/scope.dart";
|
||||||
import "package:flutter_hooks/flutter_hooks.dart";
|
import "package:flutter_hooks/flutter_hooks.dart";
|
||||||
|
|
||||||
|
@ -91,47 +92,51 @@ class ChatBottomInputSection extends HookWidget {
|
||||||
|
|
||||||
var defaultInputField = Stack(
|
var defaultInputField = Stack(
|
||||||
children: [
|
children: [
|
||||||
TextField(
|
CustomSemantics(
|
||||||
textAlign: TextAlign.start,
|
identifier: options.semantics.chatMessageInput,
|
||||||
textAlignVertical: TextAlignVertical.center,
|
isTextField: true,
|
||||||
style: theme.textTheme.bodySmall,
|
child: TextField(
|
||||||
textCapitalization: TextCapitalization.sentences,
|
textAlign: TextAlign.start,
|
||||||
textInputAction: TextInputAction.newline,
|
textAlignVertical: TextAlignVertical.center,
|
||||||
keyboardType: TextInputType.multiline,
|
style: theme.textTheme.bodySmall,
|
||||||
maxLines: null,
|
textCapitalization: TextCapitalization.sentences,
|
||||||
controller: textController,
|
textInputAction: TextInputAction.newline,
|
||||||
enabled: !isLoading,
|
keyboardType: TextInputType.multiline,
|
||||||
decoration: InputDecoration(
|
maxLines: null,
|
||||||
enabledBorder: OutlineInputBorder(
|
controller: textController,
|
||||||
borderRadius: BorderRadius.circular(25),
|
enabled: !isLoading,
|
||||||
borderSide: const BorderSide(color: Colors.black),
|
decoration: InputDecoration(
|
||||||
),
|
enabledBorder: OutlineInputBorder(
|
||||||
focusedBorder: OutlineInputBorder(
|
borderRadius: BorderRadius.circular(25),
|
||||||
borderRadius: BorderRadius.circular(25),
|
borderSide: const BorderSide(color: Colors.black),
|
||||||
borderSide: const BorderSide(color: Colors.black),
|
),
|
||||||
),
|
focusedBorder: OutlineInputBorder(
|
||||||
contentPadding: const EdgeInsets.only(
|
borderRadius: BorderRadius.circular(25),
|
||||||
left: 16,
|
borderSide: const BorderSide(color: Colors.black),
|
||||||
top: 16,
|
),
|
||||||
bottom: 16,
|
contentPadding: const EdgeInsets.only(
|
||||||
),
|
left: 16,
|
||||||
// this ensures that that there is space at the end of the textfield
|
top: 16,
|
||||||
suffixIcon: AbsorbPointer(
|
bottom: 16,
|
||||||
child: Opacity(
|
),
|
||||||
opacity: 0.0,
|
// this ensures that that there is space at the end of the textfield
|
||||||
child: messageSendButtons,
|
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,
|
onSubmitted: (_) async => onSubmitField(),
|
||||||
hintStyle: theme.textTheme.bodyMedium,
|
|
||||||
fillColor: Colors.white,
|
|
||||||
filled: true,
|
|
||||||
border: const OutlineInputBorder(
|
|
||||||
borderRadius: BorderRadius.all(Radius.circular(25)),
|
|
||||||
borderSide: BorderSide.none,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
onSubmitted: (_) async => onSubmitField(),
|
|
||||||
),
|
),
|
||||||
Positioned(
|
Positioned(
|
||||||
right: 0,
|
right: 0,
|
||||||
|
|
|
@ -139,6 +139,7 @@ class _AppBar extends StatelessWidget implements PreferredSizeWidget {
|
||||||
onSearch: onSearch,
|
onSearch: onSearch,
|
||||||
focusNode: focusNode,
|
focusNode: focusNode,
|
||||||
text: options.translations.newChatTitle,
|
text: options.translations.newChatTitle,
|
||||||
|
semanticId: options.semantics.newChatSearchInput,
|
||||||
),
|
),
|
||||||
actions: [
|
actions: [
|
||||||
SearchIcon(
|
SearchIcon(
|
||||||
|
|
|
@ -226,37 +226,41 @@ class _BodyState extends State<_Body> {
|
||||||
const SizedBox(
|
const SizedBox(
|
||||||
height: 12,
|
height: 12,
|
||||||
),
|
),
|
||||||
TextFormField(
|
CustomSemantics(
|
||||||
style: theme.textTheme.bodySmall,
|
identifier: options.semantics.newChatNameInput,
|
||||||
controller: _chatNameController,
|
isTextField: true,
|
||||||
decoration: InputDecoration(
|
child: TextFormField(
|
||||||
fillColor: Colors.white,
|
style: theme.textTheme.bodySmall,
|
||||||
filled: true,
|
controller: _chatNameController,
|
||||||
hintText: translations.groupNameHintText,
|
decoration: InputDecoration(
|
||||||
hintStyle: theme.textTheme.bodyMedium,
|
fillColor: Colors.white,
|
||||||
enabledBorder: OutlineInputBorder(
|
filled: true,
|
||||||
borderRadius: BorderRadius.circular(12),
|
hintText: translations.groupNameHintText,
|
||||||
borderSide: const BorderSide(
|
hintStyle: theme.textTheme.bodyMedium,
|
||||||
color: Colors.transparent,
|
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(
|
validator: (value) {
|
||||||
borderRadius: BorderRadius.circular(12),
|
if (value == null || value.isEmpty) {
|
||||||
borderSide: const BorderSide(
|
return translations.groupNameValidatorEmpty;
|
||||||
color: Colors.transparent,
|
}
|
||||||
),
|
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(
|
const SizedBox(
|
||||||
height: 16,
|
height: 16,
|
||||||
|
@ -268,36 +272,40 @@ class _BodyState extends State<_Body> {
|
||||||
const SizedBox(
|
const SizedBox(
|
||||||
height: 12,
|
height: 12,
|
||||||
),
|
),
|
||||||
TextFormField(
|
CustomSemantics(
|
||||||
style: theme.textTheme.bodySmall,
|
identifier: options.semantics.newChatBioInput,
|
||||||
controller: _bioController,
|
isTextField: true,
|
||||||
minLines: null,
|
child: TextFormField(
|
||||||
maxLines: 5,
|
style: theme.textTheme.bodySmall,
|
||||||
decoration: InputDecoration(
|
controller: _bioController,
|
||||||
fillColor: Colors.white,
|
minLines: null,
|
||||||
filled: true,
|
maxLines: 5,
|
||||||
hintText: translations.groupBioHintText,
|
decoration: InputDecoration(
|
||||||
hintStyle: theme.textTheme.bodyMedium,
|
fillColor: Colors.white,
|
||||||
enabledBorder: OutlineInputBorder(
|
filled: true,
|
||||||
borderRadius: BorderRadius.circular(12),
|
hintText: translations.groupBioHintText,
|
||||||
borderSide: const BorderSide(
|
hintStyle: theme.textTheme.bodyMedium,
|
||||||
color: Colors.transparent,
|
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(
|
validator: (value) {
|
||||||
borderRadius: BorderRadius.circular(12),
|
if (value == null || value.isEmpty) {
|
||||||
borderSide: const BorderSide(
|
return translations.groupBioValidatorEmpty;
|
||||||
color: Colors.transparent,
|
}
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
validator: (value) {
|
|
||||||
if (value == null || value.isEmpty) {
|
|
||||||
return translations.groupBioValidatorEmpty;
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
},
|
},
|
||||||
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(
|
const SizedBox(
|
||||||
height: 16,
|
height: 16,
|
||||||
|
|
|
@ -149,6 +149,7 @@ class _AppBar extends StatelessWidget implements PreferredSizeWidget {
|
||||||
onSearch: onSearch,
|
onSearch: onSearch,
|
||||||
focusNode: focusNode,
|
focusNode: focusNode,
|
||||||
text: options.translations.newGroupChatTitle,
|
text: options.translations.newGroupChatTitle,
|
||||||
|
semanticId: options.semantics.newGroupChatSearchInput,
|
||||||
),
|
),
|
||||||
actions: [
|
actions: [
|
||||||
SearchIcon(
|
SearchIcon(
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import "package:flutter/material.dart";
|
import "package:flutter/material.dart";
|
||||||
|
import "package:flutter_accessibility/flutter_accessibility.dart";
|
||||||
import "package:flutter_chat/src/util/scope.dart";
|
import "package:flutter_chat/src/util/scope.dart";
|
||||||
|
|
||||||
/// The search field widget
|
/// The search field widget
|
||||||
|
@ -9,6 +10,7 @@ class SearchField extends StatelessWidget {
|
||||||
required this.onSearch,
|
required this.onSearch,
|
||||||
required this.focusNode,
|
required this.focusNode,
|
||||||
required this.text,
|
required this.text,
|
||||||
|
required this.semanticId,
|
||||||
super.key,
|
super.key,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -24,6 +26,9 @@ class SearchField extends StatelessWidget {
|
||||||
/// The text to display in the search field
|
/// The text to display in the search field
|
||||||
final String text;
|
final String text;
|
||||||
|
|
||||||
|
/// Semantic id for search field
|
||||||
|
final String semanticId;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
var chatScope = ChatScope.of(context);
|
var chatScope = ChatScope.of(context);
|
||||||
|
@ -32,20 +37,24 @@ class SearchField extends StatelessWidget {
|
||||||
var translations = options.translations;
|
var translations = options.translations;
|
||||||
|
|
||||||
if (isSearching) {
|
if (isSearching) {
|
||||||
return TextField(
|
return CustomSemantics(
|
||||||
focusNode: focusNode,
|
identifier: semanticId,
|
||||||
onChanged: onSearch,
|
isTextField: true,
|
||||||
decoration: InputDecoration(
|
child: TextField(
|
||||||
hintText: translations.searchPlaceholder,
|
focusNode: focusNode,
|
||||||
hintStyle: theme.textTheme.bodyMedium,
|
onChanged: onSearch,
|
||||||
focusedBorder: UnderlineInputBorder(
|
decoration: InputDecoration(
|
||||||
borderSide: BorderSide(
|
hintText: translations.searchPlaceholder,
|
||||||
color: theme.colorScheme.primary,
|
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