mirror of
https://github.com/Iconica-Development/flutter_chat.git
synced 2025-05-18 18:33:49 +02:00
feat: add semantics for texts
This commit is contained in:
parent
6ecf073f15
commit
30fc7b4368
13 changed files with 456 additions and 125 deletions
|
@ -169,6 +169,9 @@ typedef ChatMessageBuilder = Widget? Function(
|
|||
MessageModel? previousMessage,
|
||||
UserModel? sender,
|
||||
Function(UserModel sender) onPressSender,
|
||||
String semanticIdTitle,
|
||||
String semanticIdText,
|
||||
String semanticIdTime,
|
||||
);
|
||||
|
||||
/// The group avatar builder
|
||||
|
|
|
@ -2,6 +2,7 @@ import "package:cached_network_image/cached_network_image.dart";
|
|||
import "package:chat_repository_interface/chat_repository_interface.dart";
|
||||
import "package:flutter/material.dart";
|
||||
import "package:flutter_chat/src/config/chat_builders.dart";
|
||||
import "package:flutter_chat/src/config/chat_semantics.dart";
|
||||
import "package:flutter_chat/src/config/chat_translations.dart";
|
||||
|
||||
/// The chat options
|
||||
|
@ -13,6 +14,7 @@ class ChatOptions {
|
|||
this.groupChatEnabled = true,
|
||||
this.enableLoadingIndicator = true,
|
||||
this.translations = const ChatTranslations.empty(),
|
||||
this.semantics = const ChatSemantics.standard(),
|
||||
this.builders = const ChatBuilders(),
|
||||
this.spacing = const ChatSpacing(),
|
||||
this.paginationControls = const ChatPaginationControls(),
|
||||
|
@ -43,6 +45,9 @@ class ChatOptions {
|
|||
/// [translations] is the chat translations.
|
||||
final ChatTranslations translations;
|
||||
|
||||
/// [semantics] is the chat semantics.
|
||||
final ChatSemantics semantics;
|
||||
|
||||
/// [builders] is the chat builders.
|
||||
final ChatBuilders builders;
|
||||
|
||||
|
|
124
packages/flutter_chat/lib/src/config/chat_semantics.dart
Normal file
124
packages/flutter_chat/lib/src/config/chat_semantics.dart
Normal file
|
@ -0,0 +1,124 @@
|
|||
// SPDX-FileCopyrightText: 2022 Iconica
|
||||
//
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
// ignore_for_file: public_member_api_docs
|
||||
|
||||
/// 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
|
||||
/// if you want to be sure to have all translations specified
|
||||
/// If you just want the default values use the empty constructor
|
||||
/// and optionally override the values with the copyWith method
|
||||
const ChatSemantics({
|
||||
required this.profileTitle,
|
||||
required this.profileDescription,
|
||||
required this.chatUnreadMessages,
|
||||
required this.chatChatTitle,
|
||||
required this.chatNoMessages,
|
||||
required this.newChatGetUsersError,
|
||||
required this.newGroupChatMemberAmount,
|
||||
required this.newGroupChatGetUsersError,
|
||||
required this.newChatUserListUserFullName,
|
||||
required this.chatBubbleTitle,
|
||||
required this.chatBubbleTime,
|
||||
required this.chatBubbleText,
|
||||
required this.chatsChatTitle,
|
||||
required this.chatsChatSubTitle,
|
||||
required this.chatsChatLastUsed,
|
||||
required this.chatsChatUnreadMessages,
|
||||
});
|
||||
|
||||
/// Default translations for the chat component view
|
||||
const ChatSemantics.standard({
|
||||
this.profileTitle = "text_profile_title",
|
||||
this.profileDescription = "text_profile_description",
|
||||
this.chatUnreadMessages = "text_unread_messages",
|
||||
this.chatChatTitle = "text_chat_title",
|
||||
this.chatNoMessages = "text_no_messages",
|
||||
this.newChatGetUsersError = "text_get_users_error",
|
||||
this.newGroupChatMemberAmount = "text_member_amount",
|
||||
this.newGroupChatGetUsersError = "text_get_users_error",
|
||||
this.newChatUserListUserFullName = _defaultNewChatUserListUserFullName,
|
||||
this.chatBubbleTitle = _defaultChatBubbleTitle,
|
||||
this.chatBubbleTime = _defaultChatBubbleTime,
|
||||
this.chatBubbleText = _defaultChatBubbleText,
|
||||
this.chatsChatTitle = _defaultChatsChatTitle,
|
||||
this.chatsChatSubTitle = _defaultChatsChatSubTitle,
|
||||
this.chatsChatLastUsed = _defaultChatsChatLastUsed,
|
||||
this.chatsChatUnreadMessages = _defaultChatsChatUnreadMessages,
|
||||
});
|
||||
|
||||
// Text
|
||||
final String profileTitle;
|
||||
final String profileDescription;
|
||||
final String chatUnreadMessages;
|
||||
final String chatChatTitle;
|
||||
final String chatNoMessages;
|
||||
final String newChatGetUsersError;
|
||||
final String newGroupChatMemberAmount;
|
||||
final String newGroupChatGetUsersError;
|
||||
|
||||
// Indexed text
|
||||
final String Function(int index) newChatUserListUserFullName;
|
||||
final String Function(int index) chatBubbleTitle;
|
||||
final String Function(int index) chatBubbleTime;
|
||||
final String Function(int index) chatBubbleText;
|
||||
final String Function(int index) chatsChatTitle;
|
||||
final String Function(int index) chatsChatSubTitle;
|
||||
final String Function(int index) chatsChatLastUsed;
|
||||
final String Function(int index) chatsChatUnreadMessages;
|
||||
|
||||
ChatSemantics copyWith({
|
||||
String? profileTitle,
|
||||
String? profileDescription,
|
||||
String? chatUnreadMessages,
|
||||
String? chatChatTitle,
|
||||
String? chatNoMessages,
|
||||
String? newChatGetUsersError,
|
||||
String? newGroupChatMemberAmount,
|
||||
String? newGroupChatGetUsersError,
|
||||
String Function(int)? newChatUserListUserFullName,
|
||||
String Function(int)? chatBubbleTitle,
|
||||
String Function(int)? chatBubbleTime,
|
||||
String Function(int)? chatBubbleText,
|
||||
String Function(int)? chatsChatTitle,
|
||||
String Function(int)? chatsChatSubTitle,
|
||||
String Function(int)? chatsChatLastUsed,
|
||||
String Function(int)? chatsChatUnreadMessages,
|
||||
}) =>
|
||||
ChatSemantics(
|
||||
profileTitle: profileTitle ?? this.profileTitle,
|
||||
profileDescription: profileDescription ?? this.profileDescription,
|
||||
chatUnreadMessages: chatUnreadMessages ?? this.chatUnreadMessages,
|
||||
chatChatTitle: chatChatTitle ?? this.chatChatTitle,
|
||||
chatNoMessages: chatNoMessages ?? this.chatNoMessages,
|
||||
newChatGetUsersError: newChatGetUsersError ?? this.newChatGetUsersError,
|
||||
newGroupChatMemberAmount:
|
||||
newGroupChatMemberAmount ?? this.newGroupChatMemberAmount,
|
||||
newGroupChatGetUsersError:
|
||||
newGroupChatGetUsersError ?? this.newGroupChatGetUsersError,
|
||||
newChatUserListUserFullName:
|
||||
newChatUserListUserFullName ?? this.newChatUserListUserFullName,
|
||||
chatBubbleTitle: chatBubbleTitle ?? this.chatBubbleTitle,
|
||||
chatBubbleTime: chatBubbleTime ?? this.chatBubbleTime,
|
||||
chatBubbleText: chatBubbleText ?? this.chatBubbleText,
|
||||
chatsChatTitle: chatsChatTitle ?? this.chatsChatTitle,
|
||||
chatsChatSubTitle: chatsChatSubTitle ?? this.chatsChatSubTitle,
|
||||
chatsChatLastUsed: chatsChatLastUsed ?? this.chatsChatLastUsed,
|
||||
chatsChatUnreadMessages:
|
||||
chatsChatUnreadMessages ?? this.chatsChatUnreadMessages,
|
||||
);
|
||||
}
|
||||
|
||||
String _defaultNewChatUserListUserFullName(int index) =>
|
||||
"text_user_fullname_$index";
|
||||
String _defaultChatBubbleTitle(int index) => "text_chat_bubble_title_$index";
|
||||
String _defaultChatBubbleTime(int index) => "text_chat_bubble_time_$index";
|
||||
String _defaultChatBubbleText(int index) => "text_chat_bubble_text_$index";
|
||||
String _defaultChatsChatTitle(int index) => "text_chat_title_$index";
|
||||
String _defaultChatsChatSubTitle(int index) => "text_chat_sub_title_$index";
|
||||
String _defaultChatsChatLastUsed(int index) => "text_chat_last_used_$index";
|
||||
String _defaultChatsChatUnreadMessages(int index) =>
|
||||
"text_chat_unread_messages_$index";
|
|
@ -1,6 +1,7 @@
|
|||
import "dart:async";
|
||||
|
||||
import "package:flutter/material.dart";
|
||||
import "package:flutter_accessibility/flutter_accessibility.dart";
|
||||
import "package:flutter_chat/flutter_chat.dart";
|
||||
|
||||
/// A widget representing an entry point for a chat UI.
|
||||
|
@ -16,6 +17,7 @@ class FlutterChatEntryWidget extends StatefulWidget {
|
|||
this.iconColor = Colors.black,
|
||||
this.counterBackgroundColor = Colors.red,
|
||||
this.textStyle,
|
||||
this.semanticIdUnreadMessages = "text_unread_messages_count",
|
||||
super.key,
|
||||
});
|
||||
|
||||
|
@ -46,6 +48,9 @@ class FlutterChatEntryWidget extends StatefulWidget {
|
|||
/// The chat options
|
||||
final ChatOptions? options;
|
||||
|
||||
/// Semantic Id for the unread messages text
|
||||
final String semanticIdUnreadMessages;
|
||||
|
||||
@override
|
||||
State<FlutterChatEntryWidget> createState() => _FlutterChatEntryWidgetState();
|
||||
}
|
||||
|
@ -121,6 +126,9 @@ class _FlutterChatEntryWidgetState extends State<FlutterChatEntryWidget> {
|
|||
color: widget.counterBackgroundColor,
|
||||
),
|
||||
child: Center(
|
||||
child: CustomSemantics(
|
||||
identifier: widget.semanticIdUnreadMessages,
|
||||
value: snapshot.data?.toString() ?? "0",
|
||||
child: Text(
|
||||
snapshot.data?.toString() ?? "0",
|
||||
style: widget.textStyle,
|
||||
|
@ -128,6 +136,7 @@ class _FlutterChatEntryWidgetState extends State<FlutterChatEntryWidget> {
|
|||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
|
|
@ -4,6 +4,7 @@ import "dart:typed_data";
|
|||
import "package:chat_repository_interface/chat_repository_interface.dart";
|
||||
import "package:flutter/material.dart";
|
||||
import "package:flutter_chat/src/config/chat_options.dart";
|
||||
import "package:flutter_accessibility/flutter_accessibility.dart";
|
||||
import "package:flutter_chat/src/config/screen_types.dart";
|
||||
import "package:flutter_chat/src/screens/chat_detail/widgets/chat_bottom.dart";
|
||||
import "package:flutter_chat/src/screens/chat_detail/widgets/chat_widgets.dart";
|
||||
|
@ -213,12 +214,16 @@ class _ChatAppBar extends StatelessWidget implements PreferredSizeWidget {
|
|||
highlightColor: Colors.transparent,
|
||||
hoverColor: Colors.transparent,
|
||||
onTap: onPressChatTitle,
|
||||
child: CustomSemantics(
|
||||
identifier: options.semantics.chatChatTitle,
|
||||
value: chatTitle ?? "",
|
||||
child: options.builders.chatTitleBuilder?.call(chatTitle ?? "") ??
|
||||
Text(
|
||||
chatTitle ?? "",
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -458,6 +463,9 @@ class _ChatBody extends HookWidget {
|
|||
previousMessage: prevMsg,
|
||||
sender: userMap[msg.senderId],
|
||||
onPressSender: onPressUserProfile,
|
||||
semanticIdTitle: options.semantics.chatBubbleTitle(index),
|
||||
semanticIdTime: options.semantics.chatBubbleTime(index),
|
||||
semanticIdText: options.semantics.chatBubbleText(index),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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/screens/chat_detail/widgets/default_message_builder.dart";
|
||||
import "package:flutter_chat/src/util/scope.dart";
|
||||
import "package:flutter_hooks/flutter_hooks.dart";
|
||||
|
@ -18,16 +19,23 @@ class ChatNoMessages extends HookWidget {
|
|||
@override
|
||||
Widget build(BuildContext context) {
|
||||
var chatScope = ChatScope.of(context);
|
||||
var translations = chatScope.options.translations;
|
||||
var options = chatScope.options;
|
||||
var translations = options.translations;
|
||||
var theme = Theme.of(context);
|
||||
|
||||
return Center(
|
||||
child: CustomSemantics(
|
||||
identifier: options.semantics.chatNoMessages,
|
||||
value: isGroupChat
|
||||
? translations.writeFirstMessageInGroupChat
|
||||
: translations.writeMessageToStartChat,
|
||||
child: Text(
|
||||
isGroupChat
|
||||
? translations.writeFirstMessageInGroupChat
|
||||
: translations.writeMessageToStartChat,
|
||||
style: theme.textTheme.bodySmall,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -39,6 +47,9 @@ class ChatBubble extends HookWidget {
|
|||
required this.message,
|
||||
required this.sender,
|
||||
required this.onPressSender,
|
||||
required this.semanticIdTitle,
|
||||
required this.semanticIdText,
|
||||
required this.semanticIdTime,
|
||||
this.previousMessage,
|
||||
super.key,
|
||||
});
|
||||
|
@ -56,6 +67,15 @@ class ChatBubble extends HookWidget {
|
|||
/// Callback function when a message sender is pressed.
|
||||
final Function(UserModel user) onPressSender;
|
||||
|
||||
/// Semantic id for message title
|
||||
final String semanticIdTitle;
|
||||
|
||||
/// Semantic id for message time
|
||||
final String semanticIdTime;
|
||||
|
||||
/// Semantic id for message text
|
||||
final String semanticIdText;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
var chatScope = ChatScope.of(context);
|
||||
|
@ -67,12 +87,18 @@ class ChatBubble extends HookWidget {
|
|||
previousMessage,
|
||||
sender,
|
||||
onPressSender,
|
||||
semanticIdTitle,
|
||||
semanticIdTime,
|
||||
semanticIdText,
|
||||
) ??
|
||||
DefaultChatMessageBuilder(
|
||||
message: message,
|
||||
previousMessage: previousMessage,
|
||||
sender: sender,
|
||||
onPressSender: onPressSender,
|
||||
semanticIdTitle: semanticIdTitle,
|
||||
semanticIdTime: semanticIdTime,
|
||||
semanticIdText: semanticIdText,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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/config/chat_options.dart";
|
||||
import "package:flutter_chat/src/services/date_formatter.dart";
|
||||
import "package:flutter_chat/src/util/scope.dart";
|
||||
|
@ -14,6 +15,9 @@ class DefaultChatMessageBuilder extends StatelessWidget {
|
|||
required this.previousMessage,
|
||||
required this.sender,
|
||||
required this.onPressSender,
|
||||
required this.semanticIdTitle,
|
||||
required this.semanticIdText,
|
||||
required this.semanticIdTime,
|
||||
super.key,
|
||||
});
|
||||
|
||||
|
@ -30,6 +34,15 @@ class DefaultChatMessageBuilder extends StatelessWidget {
|
|||
/// The function that is called when the sender is clicked
|
||||
final Function(UserModel user) onPressSender;
|
||||
|
||||
/// Semantic id for message title
|
||||
final String semanticIdTitle;
|
||||
|
||||
/// Semantic id for message time
|
||||
final String semanticIdTime;
|
||||
|
||||
/// Semantic id for message text
|
||||
final String semanticIdText;
|
||||
|
||||
/// implements [ChatMessageBuilder]
|
||||
static Widget builder(
|
||||
BuildContext context,
|
||||
|
@ -37,12 +50,18 @@ class DefaultChatMessageBuilder extends StatelessWidget {
|
|||
MessageModel? previousMessage,
|
||||
UserModel? sender,
|
||||
Function(UserModel sender) onPressSender,
|
||||
String semanticIdTitle,
|
||||
String semanticIdText,
|
||||
String semanticIdTime,
|
||||
) =>
|
||||
DefaultChatMessageBuilder(
|
||||
message: message,
|
||||
previousMessage: previousMessage,
|
||||
sender: sender,
|
||||
onPressSender: onPressSender,
|
||||
semanticIdTitle: semanticIdTitle,
|
||||
semanticIdTime: semanticIdTime,
|
||||
semanticIdText: semanticIdText,
|
||||
);
|
||||
|
||||
/// Merges the [MessageTheme] from the themeresolver with the [MessageTheme]
|
||||
|
@ -87,6 +106,9 @@ class DefaultChatMessageBuilder extends StatelessWidget {
|
|||
message: message,
|
||||
messageTheme: messageTheme,
|
||||
sender: sender,
|
||||
semanticIdTitle: semanticIdTitle,
|
||||
semanticIdTime: semanticIdTime,
|
||||
semanticIdText: semanticIdText,
|
||||
);
|
||||
|
||||
var messagePadding = messageTheme.messageSidePadding!;
|
||||
|
@ -126,6 +148,9 @@ class _ChatMessageBubble extends StatelessWidget {
|
|||
required this.previousMessage,
|
||||
required this.messageTheme,
|
||||
required this.sender,
|
||||
required this.semanticIdTitle,
|
||||
required this.semanticIdTime,
|
||||
required this.semanticIdText,
|
||||
});
|
||||
|
||||
final bool isSameSender;
|
||||
|
@ -134,6 +159,9 @@ class _ChatMessageBubble extends StatelessWidget {
|
|||
final MessageModel? previousMessage;
|
||||
final MessageTheme messageTheme;
|
||||
final UserModel? sender;
|
||||
final String semanticIdTitle;
|
||||
final String semanticIdTime;
|
||||
final String semanticIdText;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
|
@ -154,9 +182,13 @@ class _ChatMessageBubble extends StatelessWidget {
|
|||
|
||||
var senderTitle =
|
||||
options.senderTitleResolver?.call(sender) ?? sender?.firstName ?? "";
|
||||
var senderTitleText = Text(
|
||||
var senderTitleText = CustomSemantics(
|
||||
identifier: semanticIdTitle,
|
||||
value: senderTitle,
|
||||
child: Text(
|
||||
senderTitle,
|
||||
style: theme.textTheme.titleMedium,
|
||||
),
|
||||
);
|
||||
|
||||
var messageTimeRow = Row(
|
||||
|
@ -164,6 +196,9 @@ class _ChatMessageBubble extends StatelessWidget {
|
|||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(right: 8, bottom: 4),
|
||||
child: CustomSemantics(
|
||||
identifier: semanticIdTime,
|
||||
value: messageTime,
|
||||
child: Text(
|
||||
messageTime,
|
||||
style: textTheme.bodySmall?.copyWith(
|
||||
|
@ -172,6 +207,7 @@ class _ChatMessageBubble extends StatelessWidget {
|
|||
textAlign: TextAlign.end,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
|
||||
|
@ -207,6 +243,9 @@ class _ChatMessageBubble extends StatelessWidget {
|
|||
right: 12,
|
||||
bottom: 4,
|
||||
),
|
||||
child: Semantics(
|
||||
identifier: semanticIdText,
|
||||
value: message.text,
|
||||
child: Text(
|
||||
message.text!,
|
||||
style: textTheme.bodyLarge?.copyWith(
|
||||
|
@ -215,6 +254,7 @@ class _ChatMessageBubble extends StatelessWidget {
|
|||
textAlign: messageTheme.textAlignment,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
if (messageTheme.showTime!) ...[
|
||||
messageTimeRow,
|
||||
|
@ -257,6 +297,7 @@ class _DefaultChatImage extends StatelessWidget {
|
|||
options.imageProviderResolver(context, Uri.parse(imageUrl)),
|
||||
fit: BoxFit.fitWidth,
|
||||
errorBuilder: (context, error, stackTrace) => Text(
|
||||
// TODO: Non-replaceable text
|
||||
"Something went wrong with loading the image",
|
||||
style: textTheme.bodyLarge?.copyWith(
|
||||
color: messageTheme.textColor,
|
||||
|
|
|
@ -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/config/screen_types.dart";
|
||||
import "package:flutter_chat/src/util/scope.dart";
|
||||
import "package:flutter_hooks/flutter_hooks.dart";
|
||||
|
@ -50,7 +51,10 @@ class ChatProfileScreen extends HookWidget {
|
|||
? chatModel?.chatName ?? options.translations.groupNameEmpty
|
||||
: "";
|
||||
|
||||
var appBar = _AppBar(title: chatTitle);
|
||||
var appBar = _AppBar(
|
||||
title: chatTitle,
|
||||
semanticId: options.semantics.profileTitle,
|
||||
);
|
||||
|
||||
var body = _Body(
|
||||
user: userModel,
|
||||
|
@ -79,16 +83,22 @@ class ChatProfileScreen extends HookWidget {
|
|||
class _AppBar extends StatelessWidget implements PreferredSizeWidget {
|
||||
const _AppBar({
|
||||
required this.title,
|
||||
required this.semanticId,
|
||||
});
|
||||
|
||||
final String title;
|
||||
final String semanticId;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
var theme = Theme.of(context);
|
||||
return AppBar(
|
||||
iconTheme: theme.appBarTheme.iconTheme,
|
||||
title: Text(title),
|
||||
title: CustomSemantics(
|
||||
identifier: semanticId,
|
||||
value: title,
|
||||
child: Text(title),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -247,10 +257,14 @@ class _Body extends StatelessWidget {
|
|||
style: theme.textTheme.titleMedium,
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
Text(
|
||||
CustomSemantics(
|
||||
identifier: options.semantics.profileDescription,
|
||||
value: chat!.description ?? "",
|
||||
child: Text(
|
||||
chat!.description ?? "",
|
||||
style: theme.textTheme.bodyMedium,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
Text(
|
||||
options.translations.chatProfileUsers,
|
||||
|
|
|
@ -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/config/chat_translations.dart";
|
||||
import "package:flutter_chat/src/config/screen_types.dart";
|
||||
import "package:flutter_chat/src/services/date_formatter.dart";
|
||||
|
@ -92,6 +93,9 @@ class _AppBar extends StatelessWidget implements PreferredSizeWidget {
|
|||
visible: (snapshot.data ?? 0) > 0,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(right: 22.0),
|
||||
child: CustomSemantics(
|
||||
identifier: options.semantics.chatUnreadMessages,
|
||||
value: "${snapshot.data ?? 0} ${translations.chatsUnread}",
|
||||
child: Text(
|
||||
"${snapshot.data ?? 0} ${translations.chatsUnread}",
|
||||
style: theme.textTheme.bodySmall,
|
||||
|
@ -100,6 +104,7 @@ class _AppBar extends StatelessWidget implements PreferredSizeWidget {
|
|||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
@ -166,7 +171,8 @@ class _BodyState extends State<_Body> {
|
|||
}
|
||||
return Column(
|
||||
children: [
|
||||
for (ChatModel chat in snapshot.data ?? []) ...[
|
||||
for (var (index, ChatModel chat)
|
||||
in (snapshot.data ?? []).indexed) ...[
|
||||
DecoratedBox(
|
||||
decoration: BoxDecoration(
|
||||
border: Border(
|
||||
|
@ -177,11 +183,27 @@ class _BodyState extends State<_Body> {
|
|||
),
|
||||
),
|
||||
child: Builder(
|
||||
builder: (context) => !chat.canBeDeleted
|
||||
builder: (context) {
|
||||
var semantics = options.semantics;
|
||||
|
||||
var chatItem = _ChatItem(
|
||||
chat: chat,
|
||||
onPressChat: widget.onPressChat,
|
||||
semanticIdTitle:
|
||||
semantics.chatsChatTitle(index),
|
||||
semanticIdSubTitle:
|
||||
semantics.chatsChatSubTitle(index),
|
||||
semanticIdLastUsed:
|
||||
semantics.chatsChatLastUsed(index),
|
||||
semanticIdUnreadMessages:
|
||||
semantics.chatsChatUnreadMessages(index),
|
||||
);
|
||||
|
||||
return !chat.canBeDeleted
|
||||
? Dismissible(
|
||||
confirmDismiss: (_) async {
|
||||
await options
|
||||
.builders.deleteChatDialogBuilder
|
||||
await options.builders
|
||||
.deleteChatDialogBuilder
|
||||
?.call(context, chat) ??
|
||||
_deleteDialog(
|
||||
chat,
|
||||
|
@ -228,15 +250,10 @@ class _BodyState extends State<_Body> {
|
|||
key: ValueKey(
|
||||
chat.id,
|
||||
),
|
||||
child: _ChatItem(
|
||||
chat: chat,
|
||||
onPressChat: widget.onPressChat,
|
||||
),
|
||||
child: chatItem,
|
||||
)
|
||||
: _ChatItem(
|
||||
chat: chat,
|
||||
onPressChat: widget.onPressChat,
|
||||
),
|
||||
: chatItem;
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
|
@ -282,10 +299,18 @@ class _ChatItem extends StatelessWidget {
|
|||
const _ChatItem({
|
||||
required this.chat,
|
||||
required this.onPressChat,
|
||||
required this.semanticIdTitle,
|
||||
required this.semanticIdSubTitle,
|
||||
required this.semanticIdLastUsed,
|
||||
required this.semanticIdUnreadMessages,
|
||||
});
|
||||
|
||||
final ChatModel chat;
|
||||
final Function(ChatModel chat) onPressChat;
|
||||
final String semanticIdTitle;
|
||||
final String semanticIdSubTitle;
|
||||
final String semanticIdLastUsed;
|
||||
final String semanticIdUnreadMessages;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
|
@ -295,16 +320,23 @@ class _ChatItem extends StatelessWidget {
|
|||
options: options,
|
||||
);
|
||||
var theme = Theme.of(context);
|
||||
|
||||
var chatListItem = _ChatListItem(
|
||||
chat: chat,
|
||||
dateFormatter: dateFormatter,
|
||||
semanticIdTitle: semanticIdTitle,
|
||||
semanticIdSubTitle: semanticIdSubTitle,
|
||||
semanticIdLastUsed: semanticIdLastUsed,
|
||||
semanticIdUnreadMessages: semanticIdUnreadMessages,
|
||||
);
|
||||
|
||||
return InkWell(
|
||||
onTap: () {
|
||||
onPressChat(chat);
|
||||
},
|
||||
child: options.builders.chatRowContainerBuilder?.call(
|
||||
context,
|
||||
_ChatListItem(
|
||||
chat: chat,
|
||||
dateFormatter: dateFormatter,
|
||||
),
|
||||
chatListItem,
|
||||
) ??
|
||||
DecoratedBox(
|
||||
decoration: BoxDecoration(
|
||||
|
@ -318,10 +350,7 @@ class _ChatItem extends StatelessWidget {
|
|||
),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(12),
|
||||
child: _ChatListItem(
|
||||
chat: chat,
|
||||
dateFormatter: dateFormatter,
|
||||
),
|
||||
child: chatListItem,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
@ -332,10 +361,18 @@ class _ChatListItem extends StatelessWidget {
|
|||
const _ChatListItem({
|
||||
required this.chat,
|
||||
required this.dateFormatter,
|
||||
required this.semanticIdTitle,
|
||||
required this.semanticIdSubTitle,
|
||||
required this.semanticIdLastUsed,
|
||||
required this.semanticIdUnreadMessages,
|
||||
});
|
||||
|
||||
final ChatModel chat;
|
||||
final DateFormatter dateFormatter;
|
||||
final String semanticIdTitle;
|
||||
final String semanticIdSubTitle;
|
||||
final String semanticIdLastUsed;
|
||||
final String semanticIdUnreadMessages;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
|
@ -366,6 +403,10 @@ class _ChatListItem extends StatelessWidget {
|
|||
|
||||
return _ChatRow(
|
||||
title: chat.chatName ?? translations.groupNameEmpty,
|
||||
semanticIdTitle: semanticIdTitle,
|
||||
semanticIdSubTitle: semanticIdSubTitle,
|
||||
semanticIdLastUsed: semanticIdLastUsed,
|
||||
semanticIdUnreadMessages: semanticIdUnreadMessages,
|
||||
unreadMessages:
|
||||
showUnreadMessageCount ? chat.unreadMessageCount : 0,
|
||||
subTitle: data != null
|
||||
|
@ -441,6 +482,10 @@ class _ChatListItem extends StatelessWidget {
|
|||
return _ChatRow(
|
||||
unreadMessages:
|
||||
showUnreadMessageCount ? chat.unreadMessageCount : 0,
|
||||
semanticIdTitle: semanticIdTitle,
|
||||
semanticIdSubTitle: semanticIdSubTitle,
|
||||
semanticIdLastUsed: semanticIdLastUsed,
|
||||
semanticIdUnreadMessages: semanticIdUnreadMessages,
|
||||
avatar: options.builders.userAvatarBuilder?.call(
|
||||
context,
|
||||
otherUser,
|
||||
|
@ -536,6 +581,10 @@ Future<bool?> _deleteDialog(
|
|||
class _ChatRow extends StatelessWidget {
|
||||
const _ChatRow({
|
||||
required this.title,
|
||||
required this.semanticIdTitle,
|
||||
required this.semanticIdSubTitle,
|
||||
required this.semanticIdLastUsed,
|
||||
required this.semanticIdUnreadMessages,
|
||||
this.unreadMessages = 0,
|
||||
this.lastUsed,
|
||||
this.subTitle,
|
||||
|
@ -544,15 +593,19 @@ class _ChatRow extends StatelessWidget {
|
|||
|
||||
/// The title of the chat.
|
||||
final String title;
|
||||
final String semanticIdTitle;
|
||||
|
||||
/// The number of unread messages in the chat.
|
||||
final int unreadMessages;
|
||||
final String semanticIdUnreadMessages;
|
||||
|
||||
/// The last time the chat was used.
|
||||
final String? lastUsed;
|
||||
final String semanticIdLastUsed;
|
||||
|
||||
/// The subtitle of the chat.
|
||||
final String? subTitle;
|
||||
final String semanticIdSubTitle;
|
||||
|
||||
/// The avatar associated with the chat.
|
||||
final Widget? avatar;
|
||||
|
@ -573,15 +626,22 @@ class _ChatRow extends StatelessWidget {
|
|||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
CustomSemantics(
|
||||
identifier: semanticIdTitle,
|
||||
value: title,
|
||||
child: Text(
|
||||
title,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: theme.textTheme.titleMedium,
|
||||
),
|
||||
),
|
||||
if (subTitle != null) ...[
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 3.0),
|
||||
child: CustomSemantics(
|
||||
identifier: semanticIdSubTitle,
|
||||
value: subTitle,
|
||||
child: Text(
|
||||
subTitle!,
|
||||
style: theme.textTheme.bodySmall,
|
||||
|
@ -589,6 +649,7 @@ class _ChatRow extends StatelessWidget {
|
|||
maxLines: 2,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
|
@ -601,11 +662,15 @@ class _ChatRow extends StatelessWidget {
|
|||
if (lastUsed != null) ...[
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(bottom: 4.0),
|
||||
child: CustomSemantics(
|
||||
identifier: semanticIdLastUsed,
|
||||
value: lastUsed,
|
||||
child: Text(
|
||||
lastUsed!,
|
||||
style: theme.textTheme.labelSmall,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
if (unreadMessages > 0) ...[
|
||||
Container(
|
||||
|
@ -616,6 +681,9 @@ class _ChatRow extends StatelessWidget {
|
|||
shape: BoxShape.circle,
|
||||
),
|
||||
child: Center(
|
||||
child: CustomSemantics(
|
||||
identifier: semanticIdUnreadMessages,
|
||||
value: unreadMessages.toString(),
|
||||
child: Text(
|
||||
unreadMessages.toString(),
|
||||
style: const TextStyle(
|
||||
|
@ -624,6 +692,7 @@ class _ChatRow extends StatelessWidget {
|
|||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
|
|
|
@ -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/config/screen_types.dart";
|
||||
import "package:flutter_chat/src/screens/creation/widgets/search_field.dart";
|
||||
import "package:flutter_chat/src/screens/creation/widgets/search_icon.dart";
|
||||
|
@ -211,10 +212,17 @@ class _Body extends StatelessWidget {
|
|||
// ignore: discarded_futures
|
||||
stream: service.getAllUsers(),
|
||||
builder: (context, snapshot) {
|
||||
var chatScope = ChatScope.of(context);
|
||||
var options = chatScope.options;
|
||||
|
||||
if (snapshot.connectionState == ConnectionState.waiting) {
|
||||
return const Center(child: CircularProgressIndicator());
|
||||
} else if (snapshot.hasError) {
|
||||
return Text("Error: ${snapshot.error}");
|
||||
return CustomSemantics(
|
||||
identifier: options.semantics.newChatGetUsersError,
|
||||
value: "Error: ${snapshot.error}",
|
||||
child: Text("Error: ${snapshot.error}"),
|
||||
);
|
||||
} else if (snapshot.hasData) {
|
||||
return UserList(
|
||||
users: snapshot.data!,
|
||||
|
|
|
@ -2,6 +2,7 @@ import "dart:typed_data";
|
|||
|
||||
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/config/screen_types.dart";
|
||||
import "package:flutter_chat/src/screens/creation/widgets/default_image_picker.dart";
|
||||
import "package:flutter_chat/src/util/scope.dart";
|
||||
|
@ -301,11 +302,16 @@ class _BodyState extends State<_Body> {
|
|||
const SizedBox(
|
||||
height: 16,
|
||||
),
|
||||
Text(
|
||||
CustomSemantics(
|
||||
identifier: options.semantics.newGroupChatMemberAmount,
|
||||
value: "${translations.selectedMembersHeader}"
|
||||
"${users.length}",
|
||||
child: Text(
|
||||
"${translations.selectedMembersHeader}"
|
||||
"${users.length}",
|
||||
style: theme.textTheme.titleMedium,
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
height: 12,
|
||||
),
|
||||
|
|
|
@ -196,10 +196,17 @@ class _Body extends StatelessWidget {
|
|||
// ignore: discarded_futures
|
||||
stream: service.getAllUsers(),
|
||||
builder: (context, snapshot) {
|
||||
var chatScope = ChatScope.of(context);
|
||||
var options = chatScope.options;
|
||||
|
||||
if (snapshot.connectionState == ConnectionState.waiting) {
|
||||
return const Center(child: CircularProgressIndicator());
|
||||
} else if (snapshot.hasError) {
|
||||
return Text("Error: ${snapshot.error}");
|
||||
return Semantics(
|
||||
identifier: options.semantics.newGroupChatGetUsersError,
|
||||
value: "Error: ${snapshot.error}",
|
||||
child: Text("Error: ${snapshot.error}"),
|
||||
);
|
||||
} else if (snapshot.hasData) {
|
||||
return Stack(
|
||||
children: [
|
||||
|
|
|
@ -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_profile/flutter_profile.dart";
|
||||
|
||||
|
@ -106,10 +107,15 @@ class _UserListState extends State<UserList> {
|
|||
const SizedBox(
|
||||
width: 12,
|
||||
),
|
||||
Text(
|
||||
CustomSemantics(
|
||||
identifier: options.semantics
|
||||
.newChatUserListUserFullName(index),
|
||||
value: user.fullname ?? translations.anonymousUser,
|
||||
child: Text(
|
||||
user.fullname ?? translations.anonymousUser,
|
||||
style: theme.textTheme.titleMedium,
|
||||
),
|
||||
),
|
||||
if (widget.creatingGroup) ...[
|
||||
const Spacer(),
|
||||
Checkbox(
|
||||
|
@ -154,10 +160,15 @@ class _UserListState extends State<UserList> {
|
|||
const SizedBox(
|
||||
width: 12,
|
||||
),
|
||||
Text(
|
||||
CustomSemantics(
|
||||
identifier: options.semantics
|
||||
.newChatUserListUserFullName(index),
|
||||
value: user.fullname ?? translations.anonymousUser,
|
||||
child: Text(
|
||||
user.fullname ?? translations.anonymousUser,
|
||||
style: theme.textTheme.titleMedium,
|
||||
),
|
||||
),
|
||||
if (widget.creatingGroup) ...[
|
||||
const Spacer(),
|
||||
Checkbox(
|
||||
|
|
Loading…
Reference in a new issue