add remaining translation strings

Signed-off-by: xsahil03x <[email protected]>
This commit is contained in:
Sahil Kumar
2021-07-12 15:01:36 +05:30
committed by xsahil03x
parent ffb46ecdca
commit a48389a814
35 changed files with 1104 additions and 301 deletions
@@ -1,6 +1,7 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:stream_chat_flutter/src/upload_progress_indicator.dart'; import 'package:stream_chat_flutter/src/upload_progress_indicator.dart';
import 'package:stream_chat_flutter/stream_chat_flutter.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart';
import 'package:stream_chat_flutter/src/extension.dart';
/// Widget to build in progress /// Widget to build in progress
typedef InProgressBuilder = Widget Function(BuildContext, int, int); typedef InProgressBuilder = Widget Function(BuildContext, int, int);
@@ -226,7 +227,7 @@ class _FailedState extends StatelessWidget {
horizontal: 12, horizontal: 12,
), ),
child: Text( child: Text(
'UPLOAD ERROR', context.translations.uploadErrorLabel,
style: theme.textTheme.footnote.copyWith( style: theme.textTheme.footnote.copyWith(
color: theme.colorTheme.white, color: theme.colorTheme.white,
), ),
@@ -7,9 +7,9 @@ import 'package:stream_chat_flutter/src/upload_progress_indicator.dart';
import 'package:stream_chat_flutter/src/utils.dart'; import 'package:stream_chat_flutter/src/utils.dart';
import 'package:stream_chat_flutter/src/video_thumbnail_image.dart'; import 'package:stream_chat_flutter/src/video_thumbnail_image.dart';
import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart';
import 'package:stream_chat_flutter/src/extension.dart';
// ignore: always_use_package_imports import 'package:stream_chat_flutter/src/attachment/attachment_widget.dart';
import 'attachment_widget.dart';
/// Widget for displaying file attachments /// Widget for displaying file attachments
class FileAttachment extends AttachmentWidget { class FileAttachment extends AttachmentWidget {
@@ -285,7 +285,10 @@ class FileAttachment extends AttachmentWidget {
progressIndicatorColor: theme.colorTheme.accentBlue, progressIndicatorColor: theme.colorTheme.accentBlue,
), ),
success: () => Text(fileSize(size), style: textStyle), success: () => Text(fileSize(size), style: textStyle),
failed: (_) => Text('UPLOAD ERROR', style: textStyle), failed: (_) => Text(
context.translations.uploadErrorLabel,
style: textStyle,
),
); );
} }
} }
@@ -4,6 +4,7 @@ import 'package:shimmer/shimmer.dart';
import 'package:stream_chat_flutter/src/attachment/attachment_widget.dart'; import 'package:stream_chat_flutter/src/attachment/attachment_widget.dart';
import 'package:stream_chat_flutter/stream_chat_flutter.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart';
import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart';
import 'package:stream_chat_flutter/src/extension.dart';
/// Widget for showing a GIF attachment /// Widget for showing a GIF attachment
class GiphyAttachment extends AttachmentWidget { class GiphyAttachment extends AttachmentWidget {
@@ -71,9 +72,9 @@ class GiphyAttachment extends AttachmentWidget {
children: [ children: [
StreamSvgIcon.giphyIcon(), StreamSvgIcon.giphyIcon(),
const SizedBox(width: 8), const SizedBox(width: 8),
const Text( Text(
'Giphy', context.translations.giphyLabel,
style: TextStyle(fontWeight: FontWeight.bold), style: const TextStyle(fontWeight: FontWeight.bold),
), ),
const SizedBox(width: 8), const SizedBox(width: 8),
if (attachment.title != null) if (attachment.title != null)
@@ -134,7 +135,7 @@ class GiphyAttachment extends AttachmentWidget {
}); });
}, },
child: Text( child: Text(
'Cancel', context.translations.cancelLabel.toLowerCase(),
style: StreamChatTheme.of(context) style: StreamChatTheme.of(context)
.textTheme .textTheme
.bodyBold .bodyBold
@@ -166,7 +167,7 @@ class GiphyAttachment extends AttachmentWidget {
}); });
}, },
child: Text( child: Text(
'Shuffle', context.translations.shuffleLabel,
style: StreamChatTheme.of(context) style: StreamChatTheme.of(context)
.textTheme .textTheme
.bodyBold .bodyBold
@@ -199,7 +200,7 @@ class GiphyAttachment extends AttachmentWidget {
}); });
}, },
child: Text( child: Text(
'Send', context.translations.sendLabel,
style: TextStyle( style: TextStyle(
color: StreamChatTheme.of(context) color: StreamChatTheme.of(context)
.colorTheme .colorTheme
@@ -234,7 +235,7 @@ class GiphyAttachment extends AttachmentWidget {
width: 8, width: 8,
), ),
Text( Text(
'Only visible to you', context.translations.onlyVisibleToYouText,
style: StreamChatTheme.of(context) style: StreamChatTheme.of(context)
.textTheme .textTheme
.footnote .footnote
@@ -339,7 +340,7 @@ class GiphyAttachment extends AttachmentWidget {
size: 16, size: 16,
), ),
Text( Text(
'GIPHY', context.translations.giphyLabel.toUpperCase(),
style: TextStyle( style: TextStyle(
color: StreamChatTheme.of(context).colorTheme.white, color: StreamChatTheme.of(context).colorTheme.white,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
@@ -48,7 +48,7 @@ class AttachmentActionsModal extends StatelessWidget {
child: _buildPage(context), child: _buildPage(context),
); );
Widget _buildPage(context) { Widget _buildPage(BuildContext context) {
final theme = StreamChatTheme.of(context); final theme = StreamChatTheme.of(context);
return Column( return Column(
crossAxisAlignment: CrossAxisAlignment.end, crossAxisAlignment: CrossAxisAlignment.end,
@@ -69,7 +69,7 @@ class AttachmentActionsModal extends StatelessWidget {
children: [ children: [
_buildButton( _buildButton(
context, context,
'Reply', context.translations.replyLabel,
StreamSvgIcon.iconCurveLineLeftUp( StreamSvgIcon.iconCurveLineLeftUp(
size: 24, size: 24,
color: theme.colorTheme.grey, color: theme.colorTheme.grey,
@@ -80,7 +80,7 @@ class AttachmentActionsModal extends StatelessWidget {
), ),
_buildButton( _buildButton(
context, context,
'Show in Chat', context.translations.showInChatLabel,
StreamSvgIcon.eye( StreamSvgIcon.eye(
size: 24, size: 24,
color: theme.colorTheme.black, color: theme.colorTheme.black,
@@ -89,8 +89,9 @@ class AttachmentActionsModal extends StatelessWidget {
), ),
_buildButton( _buildButton(
context, context,
// ignore: lines_longer_than_80_chars message.attachments[currentIndex].type == 'video'
'Save ${message.attachments[currentIndex].type == 'video' ? 'Video' : 'Image'}', ? context.translations.saveVideoLabel
: context.translations.saveImageLabel,
StreamSvgIcon.iconSave( StreamSvgIcon.iconSave(
size: 24, size: 24,
color: theme.colorTheme.grey, color: theme.colorTheme.grey,
@@ -141,7 +142,7 @@ class AttachmentActionsModal extends StatelessWidget {
if (StreamChat.of(context).user?.id == message.user?.id) if (StreamChat.of(context).user?.id == message.user?.id)
_buildButton( _buildButton(
context, context,
'Delete', context.translations.deleteLabel,
StreamSvgIcon.delete( StreamSvgIcon.delete(
size: 24, size: 24,
color: theme.colorTheme.accentRed, color: theme.colorTheme.accentRed,
@@ -1,6 +1,7 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:stream_chat_flutter/src/channel_info.dart'; import 'package:stream_chat_flutter/src/channel_info.dart';
import 'package:stream_chat_flutter/stream_chat_flutter.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart';
import 'package:stream_chat_flutter/src/extension.dart';
/// Bottom Sheet with options /// Bottom Sheet with options
class ChannelBottomSheet extends StatefulWidget { class ChannelBottomSheet extends StatefulWidget {
@@ -149,7 +150,7 @@ class _ChannelBottomSheetState extends State<ChannelBottomSheet> {
color: _streamChatThemeData.colorTheme.grey, color: _streamChatThemeData.colorTheme.grey,
), ),
), ),
title: 'View Info', title: context.translations.viewInfoLabel,
onTap: widget.onViewInfoTap, onTap: widget.onViewInfoTap,
), ),
if (!channel.isDistinct) if (!channel.isDistinct)
@@ -160,7 +161,7 @@ class _ChannelBottomSheetState extends State<ChannelBottomSheet> {
color: _streamChatThemeData.colorTheme.grey, color: _streamChatThemeData.colorTheme.grey,
), ),
), ),
title: 'Leave Group', title: context.translations.leaveGroupLabel,
onTap: () async { onTap: () async {
setState(() { setState(() {
_showActions = false; _showActions = false;
@@ -179,7 +180,7 @@ class _ChannelBottomSheetState extends State<ChannelBottomSheet> {
color: _streamChatThemeData.colorTheme.accentRed, color: _streamChatThemeData.colorTheme.accentRed,
), ),
), ),
title: 'Delete Conversation', title: context.translations.deleteConversationLabel,
titleColor: _streamChatThemeData.colorTheme.accentRed, titleColor: _streamChatThemeData.colorTheme.accentRed,
onTap: () async { onTap: () async {
setState(() { setState(() {
@@ -198,7 +199,7 @@ class _ChannelBottomSheetState extends State<ChannelBottomSheet> {
color: _streamChatThemeData.colorTheme.grey, color: _streamChatThemeData.colorTheme.grey,
), ),
), ),
title: 'Cancel', title: context.translations.cancelLabel,
onTap: () { onTap: () {
Navigator.pop(context); Navigator.pop(context);
}, },
@@ -219,10 +220,10 @@ class _ChannelBottomSheetState extends State<ChannelBottomSheet> {
Future<void> _showDeleteDialog() async { Future<void> _showDeleteDialog() async {
final res = await showConfirmationDialog( final res = await showConfirmationDialog(
context, context,
title: 'Delete Conversation', title: context.translations.deleteConversationLabel,
okText: 'DELETE', okText: context.translations.deleteLabel,
question: 'Are you sure you want to delete this conversation?', question: context.translations.deleteConversationQuestion,
cancelText: 'CANCEL', cancelText: context.translations.cancelLabel,
icon: StreamSvgIcon.delete( icon: StreamSvgIcon.delete(
color: _streamChatThemeData.colorTheme.accentRed, color: _streamChatThemeData.colorTheme.accentRed,
), ),
@@ -237,10 +238,10 @@ class _ChannelBottomSheetState extends State<ChannelBottomSheet> {
Future<void> _showLeaveDialog() async { Future<void> _showLeaveDialog() async {
final res = await showConfirmationDialog( final res = await showConfirmationDialog(
context, context,
title: 'Leave conversation', title: context.translations.leaveConversationLabel,
okText: 'LEAVE', okText: context.translations.leaveLabel,
question: 'Are you sure you want to leave this conversation?', question: context.translations.leaveConversationQuestion,
cancelText: 'CANCEL', cancelText: context.translations.cancelLabel,
icon: StreamSvgIcon.userRemove( icon: StreamSvgIcon.userRemove(
color: _streamChatThemeData.colorTheme.accentRed, color: _streamChatThemeData.colorTheme.accentRed,
), ),
@@ -6,6 +6,7 @@ import 'package:stream_chat_flutter/src/info_tile.dart';
import 'package:stream_chat_flutter/src/stream_chat_theme.dart'; import 'package:stream_chat_flutter/src/stream_chat_theme.dart';
import 'package:stream_chat_flutter/stream_chat_flutter.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart';
import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart';
import 'package:stream_chat_flutter/src/extension.dart';
/// ![screenshot](https://raw.githubusercontent.com/GetStream/stream-chat-flutter/master/screenshots/channel_header.png) /// ![screenshot](https://raw.githubusercontent.com/GetStream/stream-chat-flutter/master/screenshots/channel_header.png)
/// ![screenshot](https://raw.githubusercontent.com/GetStream/stream-chat-flutter/master/screenshots/channel_header_paint.png) /// ![screenshot](https://raw.githubusercontent.com/GetStream/stream-chat-flutter/master/screenshots/channel_header_paint.png)
@@ -121,14 +122,14 @@ class ChannelHeader extends StatelessWidget implements PreferredSizeWidget {
switch (status) { switch (status) {
case ConnectionStatus.connected: case ConnectionStatus.connected:
statusString = 'Connected'; statusString = context.translations.connectedLabel;
showStatus = false; showStatus = false;
break; break;
case ConnectionStatus.connecting: case ConnectionStatus.connecting:
statusString = 'Reconnecting...'; statusString = context.translations.reconnectingLabel;
break; break;
case ConnectionStatus.disconnected: case ConnectionStatus.disconnected:
statusString = 'Disconnected'; statusString = context.translations.disconnectedLabel;
break; break;
} }
@@ -2,6 +2,7 @@ import 'package:collection/collection.dart' show IterableExtension;
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:jiffy/jiffy.dart'; import 'package:jiffy/jiffy.dart';
import 'package:stream_chat_flutter/stream_chat_flutter.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart';
import 'package:stream_chat_flutter/src/extension.dart';
/// Widget which shows channel info /// Widget which shows channel info
class ChannelInfo extends StatelessWidget { class ChannelInfo extends StatelessWidget {
@@ -55,10 +56,13 @@ class ChannelInfo extends StatelessWidget {
) { ) {
Widget? alternativeWidget; Widget? alternativeWidget;
if (channel.memberCount != null && channel.memberCount! > 2) { final memberCount = channel.memberCount;
var text = '${channel.memberCount} Members'; if (memberCount != null && memberCount > 2) {
var text = context.translations.membersCountText(memberCount);
final watcherCount = channel.state?.watcherCount ?? 0; final watcherCount = channel.state?.watcherCount ?? 0;
if (watcherCount > 0) text += ' $watcherCount Online'; if (watcherCount > 0) {
text += ' ${context.translations.watchersCountText(watcherCount)}';
}
alternativeWidget = Text( alternativeWidget = Text(
text, text,
style: StreamChatTheme.of(context) style: StreamChatTheme.of(context)
@@ -75,12 +79,13 @@ class ChannelInfo extends StatelessWidget {
if (otherMember != null) { if (otherMember != null) {
if (otherMember.user?.online == true) { if (otherMember.user?.online == true) {
alternativeWidget = Text( alternativeWidget = Text(
'Online', context.translations.userOnlineText,
style: textStyle, style: textStyle,
); );
} else { } else {
alternativeWidget = Text( alternativeWidget = Text(
'Last seen ${Jiffy(otherMember.user?.lastActive).fromNow()}', context.translations.userLastOnlineText +
Jiffy(otherMember.user?.lastActive).fromNow(),
style: textStyle, style: textStyle,
); );
} }
@@ -111,7 +116,7 @@ class ChannelInfo extends StatelessWidget {
), ),
const SizedBox(width: 10), const SizedBox(width: 10),
Text( Text(
'Searching for Network', context.translations.searchingForNetworkLabel,
style: textStyle, style: textStyle,
), ),
], ],
@@ -125,7 +130,7 @@ class ChannelInfo extends StatelessWidget {
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
Text( Text(
'Offline...', context.translations.offlineLabel,
style: textStyle, style: textStyle,
), ),
TextButton( TextButton(
@@ -141,7 +146,7 @@ class ChannelInfo extends StatelessWidget {
..closeConnection() ..closeConnection()
..openConnection(), ..openConnection(),
child: Text( child: Text(
'Try Again', context.translations.tryAgainLabel,
style: textStyle?.copyWith( style: textStyle?.copyWith(
color: StreamChatTheme.of(context).colorTheme.accentBlue, color: StreamChatTheme.of(context).colorTheme.accentBlue,
), ),
@@ -5,6 +5,7 @@ import 'package:flutter_svg/flutter_svg.dart';
import 'package:stream_chat_flutter/src/stream_neumorphic_button.dart'; import 'package:stream_chat_flutter/src/stream_neumorphic_button.dart';
import 'package:stream_chat_flutter/stream_chat_flutter.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart';
import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart';
import 'package:stream_chat_flutter/src/extension.dart';
/// Widget builder for title /// Widget builder for title
typedef TitleBuilder = Widget Function( typedef TitleBuilder = Widget Function(
@@ -103,21 +104,20 @@ class ChannelListHeader extends StatelessWidget implements PreferredSizeWidget {
switch (status) { switch (status) {
case ConnectionStatus.connected: case ConnectionStatus.connected:
statusString = 'Connected'; statusString = context.translations.connectedLabel;
showStatus = false; showStatus = false;
break; break;
case ConnectionStatus.connecting: case ConnectionStatus.connecting:
statusString = 'Reconnecting...'; statusString = context.translations.reconnectingLabel;
break; break;
case ConnectionStatus.disconnected: case ConnectionStatus.disconnected:
statusString = 'Disconnected'; statusString = context.translations.disconnectedLabel;
break; break;
} }
final chatThemeData = StreamChatTheme.of(context); final chatThemeData = StreamChatTheme.of(context);
return InfoTile( return InfoTile(
// ignore: avoid_bool_literals_in_conditional_expressions showMessage: showConnectionStateTile && showStatus,
showMessage: showConnectionStateTile ? showStatus : false,
message: statusString, message: statusString,
child: AppBar( child: AppBar(
textTheme: Theme.of(context).textTheme, textTheme: Theme.of(context).textTheme,
@@ -207,7 +207,7 @@ class ChannelListHeader extends StatelessWidget implements PreferredSizeWidget {
Widget _buildConnectedTitleState(BuildContext context) { Widget _buildConnectedTitleState(BuildContext context) {
final chatThemeData = StreamChatTheme.of(context); final chatThemeData = StreamChatTheme.of(context);
return Text( return Text(
'Stream Chat', context.translations.streamChatLabel,
style: chatThemeData.textTheme.headlineBold.copyWith( style: chatThemeData.textTheme.headlineBold.copyWith(
color: chatThemeData.colorTheme.black, color: chatThemeData.colorTheme.black,
), ),
@@ -226,7 +226,7 @@ class ChannelListHeader extends StatelessWidget implements PreferredSizeWidget {
), ),
const SizedBox(width: 10), const SizedBox(width: 10),
Text( Text(
'Searching for Network', context.translations.searchingForNetworkLabel,
style: StreamChatTheme.of(context) style: StreamChatTheme.of(context)
.channelListHeaderTheme .channelListHeaderTheme
.title .title
@@ -247,7 +247,7 @@ class ChannelListHeader extends StatelessWidget implements PreferredSizeWidget {
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
Text( Text(
'Offline...', context.translations.offlineLabel,
style: chatThemeData.channelListHeaderTheme.title?.copyWith( style: chatThemeData.channelListHeaderTheme.title?.copyWith(
fontSize: 16, fontSize: 16,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
@@ -258,7 +258,7 @@ class ChannelListHeader extends StatelessWidget implements PreferredSizeWidget {
..closeConnection() ..closeConnection()
..openConnection(), ..openConnection(),
child: Text( child: Text(
'Try Again', context.translations.tryAgainLabel,
style: chatThemeData.channelListHeaderTheme.title?.copyWith( style: chatThemeData.channelListHeaderTheme.title?.copyWith(
fontSize: 16, fontSize: 16,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
@@ -7,6 +7,7 @@ import 'package:stream_chat_flutter/src/channel_bottom_sheet.dart';
import 'package:stream_chat_flutter/src/stream_svg_icon.dart'; import 'package:stream_chat_flutter/src/stream_svg_icon.dart';
import 'package:stream_chat_flutter/stream_chat_flutter.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart';
import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart';
import 'package:stream_chat_flutter/src/extension.dart';
/// Callback called when tapping on a channel /// Callback called when tapping on a channel
typedef ChannelTapCallback = void Function(Channel, Widget?); typedef ChannelTapCallback = void Function(Channel, Widget?);
@@ -202,6 +203,7 @@ class _ChannelListViewState extends State<ChannelListView> {
final _slideController = SlidableController(); final _slideController = SlidableController();
late final _defaultController = ChannelListController(); late final _defaultController = ChannelListController();
ChannelListController get _channelListController => ChannelListController get _channelListController =>
widget.channelListController ?? _defaultController; widget.channelListController ?? _defaultController;
@@ -296,7 +298,7 @@ class _ChannelListViewState extends State<ChannelListView> {
Padding( Padding(
padding: const EdgeInsets.all(8), padding: const EdgeInsets.all(8),
child: Text( child: Text(
'Lets start chatting!', context.translations.letsStartChattingLabel,
style: chatThemeData.textTheme.headline, style: chatThemeData.textTheme.headline,
), ),
), ),
@@ -306,7 +308,7 @@ class _ChannelListViewState extends State<ChannelListView> {
horizontal: 52, horizontal: 52,
), ),
child: Text( child: Text(
'How about sending your first message to a friend?', context.translations.sendingFirstMessageLabel,
textAlign: TextAlign.center, textAlign: TextAlign.center,
style: chatThemeData.textTheme.body.copyWith( style: chatThemeData.textTheme.body.copyWith(
color: chatThemeData.colorTheme.grey, color: chatThemeData.colorTheme.grey,
@@ -325,7 +327,7 @@ class _ChannelListViewState extends State<ChannelListView> {
child: TextButton( child: TextButton(
onPressed: widget.onStartChatPressed, onPressed: widget.onStartChatPressed,
child: Text( child: Text(
'Start a chat', context.translations.startAChatLabel,
style: chatThemeData.textTheme.bodyBold.copyWith( style: chatThemeData.textTheme.bodyBold.copyWith(
color: chatThemeData.colorTheme.accentBlue, color: chatThemeData.colorTheme.accentBlue,
), ),
@@ -461,9 +463,9 @@ class _ChannelListViewState extends State<ChannelListView> {
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[ children: <Widget>[
Text.rich( Text.rich(
const TextSpan( TextSpan(
children: [ children: [
WidgetSpan( const WidgetSpan(
child: Padding( child: Padding(
padding: EdgeInsets.only( padding: EdgeInsets.only(
right: 2, right: 2,
@@ -471,14 +473,14 @@ class _ChannelListViewState extends State<ChannelListView> {
child: Icon(Icons.error_outline), child: Icon(Icons.error_outline),
), ),
), ),
TextSpan(text: 'Error loading channels'), TextSpan(text: context.translations.loadingChannelsError),
], ],
), ),
style: Theme.of(context).textTheme.headline6, style: Theme.of(context).textTheme.headline6,
), ),
TextButton( TextButton(
onPressed: () => _channelListController.loadData!(), onPressed: () => _channelListController.loadData!(),
child: const Text('Retry'), child: Text(context.translations.retryLabel),
), ),
], ],
), ),
@@ -558,12 +560,12 @@ class _ChannelListViewState extends State<ChannelListView> {
: () async { : () async {
final res = await showConfirmationDialog( final res = await showConfirmationDialog(
context, context,
title: 'Delete Conversation', title:
okText: 'DELETE', context.translations.deleteConversationLabel,
question: question: context
// ignore: lines_longer_than_80_chars .translations.deleteConversationQuestion,
'Are you sure you want to delete this conversation?', okText: context.translations.deleteLabel,
cancelText: 'CANCEL', cancelText: context.translations.cancelLabel,
icon: StreamSvgIcon.delete( icon: StreamSvgIcon.delete(
color: chatThemeData.colorTheme.accentRed, color: chatThemeData.colorTheme.accentRed,
), ),
@@ -666,10 +668,10 @@ class _ChannelListViewState extends State<ChannelListView> {
.colorTheme .colorTheme
.accentRed .accentRed
.withOpacity(.2), .withOpacity(.2),
child: const Padding( child: Padding(
padding: EdgeInsets.symmetric(vertical: 16), padding: const EdgeInsets.symmetric(vertical: 16),
child: Center( child: Center(
child: Text('Error loading channels'), child: Text(context.translations.loadingChannelsError),
), ),
), ),
), ),
@@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart'; import 'package:flutter/rendering.dart';
import 'package:stream_chat_flutter/stream_chat_flutter.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart';
import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart';
import 'package:stream_chat_flutter/src/extension.dart';
/// It shows the current [Channel] name using a [Text] widget. /// It shows the current [Channel] name using a [Text] widget.
/// ///
@@ -44,8 +45,10 @@ class ChannelName extends StatelessWidget {
) => ) =>
LayoutBuilder( LayoutBuilder(
builder: (context, constraints) { builder: (context, constraints) {
var title = 'No title'; var title = context.translations.noTitleText;
if (extraData['name'] == null) { if (extraData['name'] != null) {
title = extraData['name'];
} else {
final otherMembers = final otherMembers =
members?.where((member) => member.userId != client.user!.id); members?.where((member) => member.userId != client.user!.id);
if (otherMembers?.length == 1) { if (otherMembers?.length == 1) {
@@ -71,8 +74,6 @@ class ChannelName extends StatelessWidget {
title = '${currentMembers.map((e) => e.user?.name).join(', ')} ' title = '${currentMembers.map((e) => e.user?.name).join(', ')} '
'${exceedingMembers > 0 ? '+ $exceedingMembers' : ''}'; '${exceedingMembers > 0 ? '+ $exceedingMembers' : ''}';
} }
} else {
title = extraData['name'];
} }
return Text( return Text(
@@ -6,6 +6,7 @@ import 'package:jiffy/jiffy.dart';
import 'package:stream_chat_flutter/src/stream_svg_icon.dart'; import 'package:stream_chat_flutter/src/stream_svg_icon.dart';
import 'package:stream_chat_flutter/stream_chat_flutter.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart';
import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart';
import 'package:stream_chat_flutter/src/extension.dart';
/// ![screenshot](https://raw.githubusercontent.com/GetStream/stream-chat-flutter/master/screenshots/channel_preview.png) /// ![screenshot](https://raw.githubusercontent.com/GetStream/stream-chat-flutter/master/screenshots/channel_preview.png)
/// ![screenshot](https://raw.githubusercontent.com/GetStream/stream-chat-flutter/master/screenshots/channel_preview_paint.png) /// ![screenshot](https://raw.githubusercontent.com/GetStream/stream-chat-flutter/master/screenshots/channel_preview_paint.png)
@@ -183,7 +184,7 @@ class ChannelPreview extends StatelessWidget {
startOfDay startOfDay
.subtract(const Duration(days: 1)) .subtract(const Duration(days: 1))
.millisecondsSinceEpoch) { .millisecondsSinceEpoch) {
stringDate = 'Yesterday'; stringDate = context.translations.yesterdayLabel;
} else if (startOfDay.difference(lastMessageAt).inDays < 7) { } else if (startOfDay.difference(lastMessageAt).inDays < 7) {
stringDate = Jiffy(lastMessageAt.toLocal()).EEEE; stringDate = Jiffy(lastMessageAt.toLocal()).EEEE;
} else { } else {
@@ -208,7 +209,7 @@ class ChannelPreview extends StatelessWidget {
size: 16, size: 16,
), ),
Text( Text(
' Channel is muted', context.translations.channelIsMutedText,
style: chatThemeData.channelPreviewTheme.subtitle, style: chatThemeData.channelPreviewTheme.subtitle,
), ),
], ],
@@ -1,6 +1,7 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:jiffy/jiffy.dart'; import 'package:jiffy/jiffy.dart';
import 'package:stream_chat_flutter/src/stream_chat_theme.dart'; import 'package:stream_chat_flutter/src/stream_chat_theme.dart';
import 'package:stream_chat_flutter/src/extension.dart';
/// It shows a date divider depending on the date difference /// It shows a date divider depending on the date difference
class DateDivider extends StatelessWidget { class DateDivider extends StatelessWidget {
@@ -24,10 +25,10 @@ class DateDivider extends StatelessWidget {
String dayInfo; String dayInfo;
if (Jiffy(createdAt).isSame(now, Units.DAY)) { if (Jiffy(createdAt).isSame(now, Units.DAY)) {
dayInfo = 'Today'; dayInfo = context.translations.todayLabel;
} else if (Jiffy(createdAt) } else if (Jiffy(createdAt)
.isSame(now.subtract(const Duration(days: 1)), Units.DAY)) { .isSame(now.subtract(const Duration(days: 1)), Units.DAY)) {
dayInfo = 'Yesterday'; dayInfo = context.translations.yesterdayLabel;
} else if (Jiffy(createdAt).isAfter( } else if (Jiffy(createdAt).isAfter(
now.subtract(const Duration(days: 7)), now.subtract(const Duration(days: 7)),
Units.DAY, Units.DAY,
@@ -1,5 +1,6 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:stream_chat_flutter/src/stream_chat_theme.dart'; import 'package:stream_chat_flutter/src/stream_chat_theme.dart';
import 'package:stream_chat_flutter/src/extension.dart';
/// Widget to display deleted message /// Widget to display deleted message
class DeletedMessage extends StatelessWidget { class DeletedMessage extends StatelessWidget {
@@ -49,7 +50,7 @@ class DeletedMessage extends StatelessWidget {
horizontal: 16, horizontal: 16,
), ),
child: Text( child: Text(
'Message deleted', context.translations.messageDeletedLabel,
style: messageTheme.messageText?.copyWith( style: messageTheme.messageText?.copyWith(
fontStyle: FontStyle.italic, fontStyle: FontStyle.italic,
color: messageTheme.createdAt?.color, color: messageTheme.createdAt?.color,
@@ -2,6 +2,7 @@ import 'package:characters/characters.dart';
import 'package:file_picker/file_picker.dart'; import 'package:file_picker/file_picker.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:stream_chat_flutter/src/emoji/emoji.dart'; import 'package:stream_chat_flutter/src/emoji/emoji.dart';
import 'package:stream_chat_flutter/src/localization/translations.dart';
import 'package:stream_chat_flutter/stream_chat_flutter.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart';
final _emojiChars = Emoji.chars(); final _emojiChars = Emoji.chars();
@@ -104,7 +105,8 @@ extension BuildContextX on BuildContext {
double get textScaleFactor => double get textScaleFactor =>
MediaQuery.maybeOf(this)?.textScaleFactor ?? 1.0; MediaQuery.maybeOf(this)?.textScaleFactor ?? 1.0;
StreamChatLocalizations? get translations => StreamChatLocalizations.of(this); Translations get translations =>
StreamChatLocalizations.of(this) ?? DefaultTranslations.instance;
} }
/// Extension on [BorderRadius] /// Extension on [BorderRadius]
@@ -10,6 +10,7 @@ import 'package:stream_chat_flutter/src/image_footer.dart';
import 'package:stream_chat_flutter/src/image_header.dart'; import 'package:stream_chat_flutter/src/image_header.dart';
import 'package:stream_chat_flutter/stream_chat_flutter.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart';
import 'package:video_player/video_player.dart'; import 'package:video_player/video_player.dart';
import 'package:stream_chat_flutter/src/extension.dart';
/// Return action for coming back from pages /// Return action for coming back from pages
enum ReturnActionType { enum ReturnActionType {
@@ -182,9 +183,11 @@ class _FullScreenMediaState extends State<FullScreenMedia>
children: [ children: [
ImageHeader( ImageHeader(
userName: widget.userName, userName: widget.userName,
sentAt: // TODO: Fix this
// ignore: lines_longer_than_80_chars sentAt: context.translations.sentAtText(
'Sent ${getDay(widget.message.createdAt.toLocal())} at ${Jiffy(widget.message.createdAt.toLocal()).format('HH:mm')}', date: widget.message.createdAt,
time: widget.message.createdAt,
),
onBackPressed: () { onBackPressed: () {
Navigator.of(context).pop(); Navigator.of(context).pop();
}, },
@@ -11,6 +11,7 @@ import 'package:stream_chat_flutter/src/stream_chat_theme.dart';
import 'package:stream_chat_flutter/src/video_thumbnail_image.dart'; import 'package:stream_chat_flutter/src/video_thumbnail_image.dart';
import 'package:stream_chat_flutter/stream_chat_flutter.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart';
import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart';
import 'package:stream_chat_flutter/src/extension.dart';
/// Footer widget for media display /// Footer widget for media display
class ImageFooter extends StatefulWidget implements PreferredSizeWidget { class ImageFooter extends StatefulWidget implements PreferredSizeWidget {
@@ -190,7 +191,7 @@ class _ImageFooterState extends State<ImageFooter> {
child: Padding( child: Padding(
padding: const EdgeInsets.all(16), padding: const EdgeInsets.all(16),
child: Text( child: Text(
'Photos', context.translations.photosLabel,
style: chatThemeData.textTheme.headlineBold, style: chatThemeData.textTheme.headlineBold,
), ),
), ),
@@ -0,0 +1,36 @@
import 'package:flutter/widgets.dart';
import 'package:stream_chat_flutter/src/localization/translations.dart'
show Translations;
/// Defines the localized resource values used by the StreamChatFlutter widgets.
///
/// See also:
///
/// * [GlobalStreamChatLocalizations], which provides material localizations
/// for many languages.
abstract class StreamChatLocalizations implements Translations {
/// The `StreamChatLocalizations` from the closest [Localizations] instance
/// that encloses the given context.
///
/// If no [StreamChatLocalizations] are available in the given `context`, this
/// method returns null.
///
/// This method is just a convenient shorthand for:
/// `Localizations.of<StreamChatLocalizations>(
/// context,
/// StreamChatLocalizations
/// )`.
///
/// References to the localized resources defined by this class are typically
/// written in terms of this method. For example:
///
/// ```dart
/// tooltip: StreamChatLocalizations.of(context).streamChatLabel,
/// ```
static StreamChatLocalizations? of(BuildContext context) =>
Localizations.of<StreamChatLocalizations>(
context,
StreamChatLocalizations,
);
}
@@ -0,0 +1,517 @@
import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'
show User;
abstract class Translations {
String get launchUrlError;
String get loadingUsersError;
String get retryLabel;
String get noUsersLabel;
String get userOnlineText;
String get userLastOnlineText;
String userTypingText(Iterable<User> users);
String get threadReplyLabel;
String get onlyVisibleToYouText;
String threadReplyCountText(int count);
String attachmentsUploadProgressText({
required int remaining,
required int total,
});
String pinnedByUserText({
required User pinnedBy,
required User currentUser,
});
String get emptyMessagesText;
String get genericErrorText;
String get loadingMessagesError;
String resultCountText(int count);
String get messageDeletedText;
String get messageDeletedLabel;
String get messageReactionsText;
String get emptyChatMessagesText;
String threadSeparatorText(int replyCount);
String get connectedLabel;
String get disconnectedLabel;
String get reconnectingLabel;
String get alsoSendAsDirectMessageLabel;
String get searchGifLabel;
String get addACommentOrSendLabel;
String get writeAMessageLabel;
String get instantCommandsLabel;
String get fileTooLargeAfterCompressionError;
String get fileTooLargeError;
String emojiMatchingQueryText(String query);
String get addAFileLabel;
String get uploadAPhotoLabel;
String get uploadAVideoLabel;
String get photoFromCameraLabel;
String get videoFromCameraLabel;
String get uploadAFileLabel;
String get somethingWentWrongLabel;
String get okLabel;
String get addMoreFilesLabel;
String get enablePhotoAndVideoAccessMessage;
String get allowGalleryAccessMessage;
String get flagMessageLabel;
String get flagMessageQuestion;
String get flagLabel;
String get cancelLabel;
String get flagMessageSuccessfulLabel;
String get flagMessageSuccessfulText;
String get deleteMessageLabel;
String get deleteMessageQuestion;
String get deleteLabel;
String get operationCouldNotBeCompletedText;
String get replyLabel;
String togglePinUnpinText({required bool pinned});
String toggleDeleteRetryDeleteMessageText({required bool isDeleteFailed});
String get copyMessageLabel;
String get editMessageLabel;
String toggleResendOrResendEditedMessage({required bool isUpdateFailed});
String get photosLabel;
String sentAtText({required DateTime date, required DateTime time});
String get todayLabel;
String get yesterdayLabel;
String get channelIsMutedText;
String get noTitleText;
String get letsStartChattingLabel;
String get sendingFirstMessageLabel;
String get startAChatLabel;
String get loadingChannelsError;
// title: 'Delete Conversation',
// okText: 'DELETE',
// question:
// 'Are you sure you want to delete this conversation?',
String get deleteConversationLabel;
String get deleteConversationQuestion;
String get streamChatLabel;
String get searchingForNetworkLabel;
String get offlineLabel;
String get tryAgainLabel;
String membersCountText(int count);
String watchersCountText(int count);
String get viewInfoLabel;
String get leaveGroupLabel;
String get leaveLabel;
String get leaveConversationLabel;
String get leaveConversationQuestion;
String get showInChatLabel;
String get saveImageLabel;
String get saveVideoLabel;
String get uploadErrorLabel;
String get giphyLabel;
String get shuffleLabel;
String get sendLabel;
}
class DefaultTranslations implements Translations {
const DefaultTranslations._();
static const instance = DefaultTranslations._();
@override
String get launchUrlError => 'Cannot launch the url';
@override
String get loadingUsersError => 'Error loading users';
@override
String get noUsersLabel => 'There are no users currently';
@override
String get retryLabel => 'Retry';
@override
String get userLastOnlineText => 'Last online';
@override
String get userOnlineText => 'Online';
@override
String userTypingText(Iterable<User> users) {
if (users.isEmpty) return '';
final first = users.first;
if (users.length == 1) {
return '${first.name} is typing';
}
return '${first.name} and ${users.length - 1} more are typing';
}
@override
String get threadReplyLabel => 'Thread Reply';
@override
String get onlyVisibleToYouText => 'Only visible to you';
@override
String threadReplyCountText(int count) => '$count Thread Replies';
@override
String attachmentsUploadProgressText({
required int remaining,
required int total,
}) =>
'Uploading $remaining/$total ...';
@override
String pinnedByUserText({
required User pinnedBy,
required User currentUser,
}) {
final pinnedByCurrentUser = currentUser.id == pinnedBy.id;
if (pinnedByCurrentUser) return 'Pinned by You';
return 'Pinned by ${pinnedBy.name}';
}
@override
String get emptyMessagesText => 'There are no messages currently';
@override
String get genericErrorText => 'Something went wrong';
@override
String get loadingMessagesError => 'Error loading messages';
@override
String resultCountText(int count) => '$count results';
@override
String get messageDeletedText => 'This message was deleted.';
@override
String get messageDeletedLabel => 'Message deleted';
@override
String get messageReactionsText => 'Message Reactions';
@override
String get emptyChatMessagesText => 'No chats here yet...';
@override
String threadSeparatorText(int replyCount) {
if (replyCount == 1) return '1 Reply';
return '$replyCount Replies';
}
@override
String get connectedLabel => 'Connected';
@override
String get disconnectedLabel => 'Disconnected';
@override
String get reconnectingLabel => 'Reconnecting...';
@override
String get alsoSendAsDirectMessageLabel => 'Also send as direct message';
@override
String get addACommentOrSendLabel => 'Add a comment or send';
@override
String get searchGifLabel => 'Search GIFs';
@override
String get writeAMessageLabel => 'Write a message';
@override
String get instantCommandsLabel => 'Instant Commands';
@override
String get fileTooLargeAfterCompressionError =>
'The file is too large to upload. '
'The file size limit is 20MB. '
'We tried compressing it, but it was not enough.';
@override
String get fileTooLargeError =>
'The file is too large to upload. The file size limit is 20MB.';
@override
String emojiMatchingQueryText(String query) => 'Emoji matching "$query"';
@override
String get addAFileLabel => 'Add a file';
@override
String get photoFromCameraLabel => 'Photo from camera';
@override
String get uploadAFileLabel => 'Upload a file';
@override
String get uploadAPhotoLabel => 'Upload a photo';
@override
String get uploadAVideoLabel => 'Upload a video';
@override
String get videoFromCameraLabel => 'Video from camera';
@override
String get okLabel => 'OK';
@override
String get somethingWentWrongLabel => 'Something went wrong';
@override
String get addMoreFilesLabel => 'Add more files';
@override
String get enablePhotoAndVideoAccessMessage =>
'Please enable access to your photos'
'\nand videos so you can share them with friends.';
@override
String get allowGalleryAccessMessage => 'Allow access to your gallery';
@override
String get flagMessageLabel => 'Flag Message';
@override
String get flagMessageQuestion =>
'Do you want to send a copy of this message to a'
'\nmoderator for further investigation?';
@override
String get flagLabel => 'FLAG';
@override
String get cancelLabel => 'CANCEL';
@override
String get flagMessageSuccessfulLabel => 'Message flagged';
@override
String get flagMessageSuccessfulText =>
'The message has been reported to a moderator.';
@override
String get deleteLabel => 'DELETE';
@override
String get deleteMessageLabel => 'Delete Message';
@override
String get deleteMessageQuestion =>
'Are you sure you want to permanently delete this\nmessage?';
@override
String get operationCouldNotBeCompletedText =>
'The operation couldn\'t be completed.';
@override
String get replyLabel => 'Reply';
@override
String togglePinUnpinText({required bool pinned}) {
if (pinned) return 'Unpin from Conversation';
return 'Pin to Conversation';
}
@override
String toggleDeleteRetryDeleteMessageText({required bool isDeleteFailed}) {
if (isDeleteFailed) return 'Retry Deleting Message';
return 'Delete Message';
}
@override
String get copyMessageLabel => 'Copy Message';
@override
String get editMessageLabel => 'Edit Message';
@override
String toggleResendOrResendEditedMessage({required bool isUpdateFailed}) {
if (isUpdateFailed) return 'Resend Edited Message';
return 'Resend';
}
@override
String get photosLabel => 'Photos';
@override
String sentAtText({required DateTime date, required DateTime time}) =>
'Sent $date at $time';
@override
String get todayLabel => 'Today';
@override
String get yesterdayLabel => 'Yesterday';
@override
String get channelIsMutedText => ' Channel is muted';
@override
String get noTitleText => 'No title';
@override
String get letsStartChattingLabel => 'Lets start chatting!';
@override
String get sendingFirstMessageLabel =>
'How about sending your first message to a friend?';
@override
String get startAChatLabel => 'Start a chat';
@override
String get loadingChannelsError => 'Error loading channels';
@override
String get deleteConversationLabel => 'Delete Conversation';
@override
String get deleteConversationQuestion =>
'Are you sure you want to delete this conversation?';
@override
String get streamChatLabel => 'Stream Chat';
@override
String get searchingForNetworkLabel => 'Searching for Network';
@override
String get offlineLabel => 'Offline...';
@override
String get tryAgainLabel => 'Try Again';
@override
String membersCountText(int count) {
if (count == 1) return '1 Member';
return '$count Members';
}
@override
String watchersCountText(int count) {
if (count == 1) return '1 Online';
return '$count Online';
}
@override
String get viewInfoLabel => 'View Info';
@override
String get leaveGroupLabel => 'Leave Group';
@override
String get leaveLabel => 'LEAVE';
@override
String get leaveConversationLabel => 'Leave conversation';
@override
String get leaveConversationQuestion =>
'Are you sure you want to leave this conversation?';
@override
String get showInChatLabel => 'Show in Chat';
@override
String get saveImageLabel => 'Save Image';
@override
String get saveVideoLabel => 'Save Video';
@override
String get uploadErrorLabel => 'UPLOAD ERROR';
@override
String get giphyLabel => 'Giphy';
@override
String get shuffleLabel => 'Shuffle';
@override
String get sendLabel => 'Send';
}
@@ -268,16 +268,14 @@ class _MessageActionsModalState extends State<MessageActionsModal> {
final streamChatThemeData = StreamChatTheme.of(context); final streamChatThemeData = StreamChatTheme.of(context);
final answer = await showConfirmationDialog( final answer = await showConfirmationDialog(
context, context,
title: 'Flag Message', title: context.translations.flagMessageLabel,
icon: StreamSvgIcon.flag( icon: StreamSvgIcon.flag(
color: streamChatThemeData.colorTheme.accentRed, color: streamChatThemeData.colorTheme.accentRed,
size: 24, size: 24,
), ),
question: question: context.translations.flagMessageQuestion,
// ignore: lines_longer_than_80_chars okText: context.translations.okLabel,
'Do you want to send a copy of this message to a\nmoderator for further investigation?', cancelText: context.translations.cancelLabel,
okText: 'FLAG',
cancelText: 'CANCEL',
); );
final theme = streamChatThemeData; final theme = streamChatThemeData;
@@ -290,9 +288,9 @@ class _MessageActionsModalState extends State<MessageActionsModal> {
color: theme.colorTheme.accentRed, color: theme.colorTheme.accentRed,
size: 24, size: 24,
), ),
details: 'The message has been reported to a moderator.', details: context.translations.flagMessageSuccessfulText,
title: 'Message flagged', title: context.translations.flagMessageSuccessfulLabel,
okText: 'OK', okText: context.translations.okLabel,
); );
} catch (err) { } catch (err) {
if (err is StreamChatNetworkError && if (err is StreamChatNetworkError &&
@@ -303,9 +301,9 @@ class _MessageActionsModalState extends State<MessageActionsModal> {
color: theme.colorTheme.accentRed, color: theme.colorTheme.accentRed,
size: 24, size: 24,
), ),
details: 'The message has been reported to a moderator.', details: context.translations.flagMessageSuccessfulText,
title: 'Message flagged', title: context.translations.flagMessageSuccessfulLabel,
okText: 'OK', okText: context.translations.okLabel,
); );
} else { } else {
_showErrorAlert(); _showErrorAlert();
@@ -335,14 +333,14 @@ class _MessageActionsModalState extends State<MessageActionsModal> {
}); });
final answer = await showConfirmationDialog( final answer = await showConfirmationDialog(
context, context,
title: 'Delete message', title: context.translations.deleteMessageLabel,
icon: StreamSvgIcon.flag( icon: StreamSvgIcon.flag(
color: StreamChatTheme.of(context).colorTheme.accentRed, color: StreamChatTheme.of(context).colorTheme.accentRed,
size: 24, size: 24,
), ),
question: 'Are you sure you want to permanently delete this\nmessage?', question: context.translations.deleteMessageQuestion,
okText: 'DELETE', okText: context.translations.deleteLabel,
cancelText: 'CANCEL', cancelText: context.translations.cancelLabel,
); );
if (answer == true) { if (answer == true) {
@@ -366,9 +364,9 @@ class _MessageActionsModalState extends State<MessageActionsModal> {
color: StreamChatTheme.of(context).colorTheme.accentRed, color: StreamChatTheme.of(context).colorTheme.accentRed,
size: 24, size: 24,
), ),
details: 'The operation couldn\'t be completed.', details: context.translations.operationCouldNotBeCompletedText,
title: 'Something went wrong', title: context.translations.somethingWentWrongLabel,
okText: 'OK', okText: context.translations.okLabel,
); );
} }
@@ -390,7 +388,7 @@ class _MessageActionsModalState extends State<MessageActionsModal> {
), ),
const SizedBox(width: 16), const SizedBox(width: 16),
Text( Text(
'Reply', context.translations.replyLabel,
style: streamChatThemeData.textTheme.body, style: streamChatThemeData.textTheme.body,
), ),
], ],
@@ -412,7 +410,7 @@ class _MessageActionsModalState extends State<MessageActionsModal> {
), ),
const SizedBox(width: 16), const SizedBox(width: 16),
Text( Text(
'Flag Message', context.translations.flagMessageLabel,
style: streamChatThemeData.textTheme.body, style: streamChatThemeData.textTheme.body,
), ),
], ],
@@ -435,7 +433,9 @@ class _MessageActionsModalState extends State<MessageActionsModal> {
), ),
const SizedBox(width: 16), const SizedBox(width: 16),
Text( Text(
'${widget.message.pinned ? 'Unpin from' : 'Pin to'} Conversation', context.translations.togglePinUnpinText(
pinned: widget.message.pinned,
),
style: streamChatThemeData.textTheme.body, style: streamChatThemeData.textTheme.body,
), ),
], ],
@@ -458,7 +458,9 @@ class _MessageActionsModalState extends State<MessageActionsModal> {
), ),
const SizedBox(width: 16), const SizedBox(width: 16),
Text( Text(
isDeleteFailed ? 'Retry Deleting Message' : 'Delete Message', context.translations.toggleDeleteRetryDeleteMessageText(
isDeleteFailed: isDeleteFailed,
),
style: StreamChatTheme.of(context) style: StreamChatTheme.of(context)
.textTheme .textTheme
.body .body
@@ -487,7 +489,7 @@ class _MessageActionsModalState extends State<MessageActionsModal> {
), ),
const SizedBox(width: 16), const SizedBox(width: 16),
Text( Text(
'Copy Message', context.translations.copyMessageLabel,
style: streamChatThemeData.textTheme.body, style: streamChatThemeData.textTheme.body,
), ),
], ],
@@ -512,7 +514,7 @@ class _MessageActionsModalState extends State<MessageActionsModal> {
), ),
const SizedBox(width: 16), const SizedBox(width: 16),
Text( Text(
'Edit Message', context.translations.editMessageLabel,
style: streamChatThemeData.textTheme.body, style: streamChatThemeData.textTheme.body,
), ),
], ],
@@ -544,7 +546,9 @@ class _MessageActionsModalState extends State<MessageActionsModal> {
), ),
const SizedBox(width: 16), const SizedBox(width: 16),
Text( Text(
isUpdateFailed ? 'Resend Edited Message' : 'Resend', context.translations.toggleResendOrResendEditedMessage(
isUpdateFailed: isUpdateFailed,
),
style: streamChatThemeData.textTheme.body, style: streamChatThemeData.textTheme.body,
), ),
], ],
@@ -588,8 +592,8 @@ class _MessageActionsModalState extends State<MessageActionsModal> {
color: streamChatThemeData.colorTheme.greyGainsboro, color: streamChatThemeData.colorTheme.greyGainsboro,
), ),
), ),
const Text( Text(
'Edit Message', context.translations.editMessageLabel,
style: TextStyle(fontWeight: FontWeight.bold), style: TextStyle(fontWeight: FontWeight.bold),
), ),
IconButton( IconButton(
@@ -636,7 +640,7 @@ class _MessageActionsModalState extends State<MessageActionsModal> {
), ),
const SizedBox(width: 16), const SizedBox(width: 16),
Text( Text(
'Thread Reply', context.translations.threadReplyLabel,
style: streamChatThemeData.textTheme.body, style: streamChatThemeData.textTheme.body,
), ),
], ],
@@ -24,6 +24,7 @@ import 'package:stream_chat_flutter/stream_chat_flutter.dart';
import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart';
import 'package:substring_highlight/substring_highlight.dart'; import 'package:substring_highlight/substring_highlight.dart';
import 'package:video_compress/video_compress.dart'; import 'package:video_compress/video_compress.dart';
import 'package:stream_chat_flutter/src/extension.dart';
/// Builder for attachment thumbnails /// Builder for attachment thumbnails
typedef AttachmentThumbnailBuilder = Widget Function( typedef AttachmentThumbnailBuilder = Widget Function(
@@ -229,16 +230,12 @@ class MessageInput extends StatefulWidget {
/// Use this method to get the current [StreamChatState] instance /// Use this method to get the current [StreamChatState] instance
static MessageInputState of(BuildContext context) { static MessageInputState of(BuildContext context) {
MessageInputState? messageInputState; MessageInputState? messageInputState;
messageInputState = context.findAncestorStateOfType<MessageInputState>(); messageInputState = context.findAncestorStateOfType<MessageInputState>();
assert(
if (messageInputState == null) { messageInputState != null,
throw Exception( 'You must have a MessageInput widget as ancestor of your widget tree',
// ignore: lines_longer_than_80_chars );
'You must have a MessageInput widget as ancestor of your widget tree'); return messageInputState!;
}
return messageInputState;
} }
} }
@@ -440,7 +437,7 @@ class MessageInputState extends State<MessageInput> {
Padding( Padding(
padding: const EdgeInsets.symmetric(horizontal: 12), padding: const EdgeInsets.symmetric(horizontal: 12),
child: Text( child: Text(
'Also send as direct message', context.translations.alsoSendAsDirectMessageLabel,
style: _streamChatTheme.textTheme.footnote.copyWith( style: _streamChatTheme.textTheme.footnote.copyWith(
color: _streamChatTheme.colorTheme.black.withOpacity(0.5), color: _streamChatTheme.colorTheme.black.withOpacity(0.5),
), ),
@@ -564,7 +561,7 @@ class MessageInputState extends State<MessageInput> {
style: _streamChatTheme.messageInputTheme.inputTextStyle, style: _streamChatTheme.messageInputTheme.inputTextStyle,
autofocus: widget.autofocus, autofocus: widget.autofocus,
textAlignVertical: TextAlignVertical.center, textAlignVertical: TextAlignVertical.center,
decoration: _getInputDecoration(), decoration: _getInputDecoration(context),
textCapitalization: TextCapitalization.sentences, textCapitalization: TextCapitalization.sentences,
), ),
) )
@@ -576,11 +573,11 @@ class MessageInputState extends State<MessageInput> {
); );
} }
InputDecoration _getInputDecoration() { InputDecoration _getInputDecoration(BuildContext context) {
final passedDecoration = _streamChatTheme.messageInputTheme.inputDecoration; final passedDecoration = _streamChatTheme.messageInputTheme.inputDecoration;
return InputDecoration( return InputDecoration(
isDense: true, isDense: true,
hintText: _getHint(), hintText: _getHint(context),
hintStyle: _streamChatTheme.messageInputTheme.inputTextStyle!.copyWith( hintStyle: _streamChatTheme.messageInputTheme.inputTextStyle!.copyWith(
color: _streamChatTheme.colorTheme.grey, color: _streamChatTheme.colorTheme.grey,
), ),
@@ -729,14 +726,14 @@ class MessageInputState extends State<MessageInput> {
); );
} }
String _getHint() { String _getHint(BuildContext context) {
if (_commandEnabled && _chosenCommand!.name == 'giphy') { if (_commandEnabled && _chosenCommand!.name == 'giphy') {
return 'Search GIFs'; return context.translations.searchGifLabel;
} }
if (_attachments.isNotEmpty) { if (_attachments.isNotEmpty) {
return 'Add a comment or send'; return context.translations.addACommentOrSendLabel;
} }
return 'Write a message'; return context.translations.writeAMessageLabel;
} }
void _checkEmoji(String s, BuildContext context) { void _checkEmoji(String s, BuildContext context) {
@@ -859,7 +856,7 @@ class MessageInputState extends State<MessageInput> {
), ),
), ),
Text( Text(
'Instant Commands', context.translations.instantCommandsLabel,
style: TextStyle( style: TextStyle(
color: color:
_streamChatTheme.colorTheme.black.withOpacity(.5), _streamChatTheme.colorTheme.black.withOpacity(.5),
@@ -1114,8 +1111,7 @@ class MessageInputState extends State<MessageInput> {
if (mediaInfo.filesize! > widget.maxAttachmentSize) { if (mediaInfo.filesize! > widget.maxAttachmentSize) {
_showErrorAlert( _showErrorAlert(
// ignore: lines_longer_than_80_chars context.translations.fileTooLargeAfterCompressionError,
'The file is too large to upload. The file size limit is 20MB. We tried compressing it, but it was not enough.',
); );
return; return;
} }
@@ -1126,9 +1122,7 @@ class MessageInputState extends State<MessageInput> {
path: mediaInfo.path, path: mediaInfo.path,
); );
} else { } else {
_showErrorAlert( _showErrorAlert(context.translations.fileTooLargeError);
'The file is too large to upload. The file size limit is 20MB.',
);
return; return;
} }
} }
@@ -1396,7 +1390,9 @@ class MessageInputState extends State<MessageInput> {
), ),
Flexible( Flexible(
child: Text( child: Text(
'Emoji matching "$query"', context.translations.emojiMatchingQueryText(
query,
),
style: TextStyle( style: TextStyle(
color: _streamChatTheme.colorTheme.black color: _streamChatTheme.colorTheme.black
.withOpacity(.5), .withOpacity(.5),
@@ -1744,17 +1740,17 @@ class MessageInputState extends State<MessageInput> {
builder: (_) => Column( builder: (_) => Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: <Widget>[ children: <Widget>[
const ListTile( ListTile(
title: Text( title: Text(
'Add a file', context.translations.addAFileLabel,
style: TextStyle( style: const TextStyle(
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
), ),
), ),
), ),
ListTile( ListTile(
leading: const Icon(Icons.image), leading: const Icon(Icons.image),
title: const Text('Upload a photo'), title: Text(context.translations.uploadAPhotoLabel),
onTap: () { onTap: () {
pickFile(DefaultAttachmentTypes.image); pickFile(DefaultAttachmentTypes.image);
Navigator.pop(context); Navigator.pop(context);
@@ -1762,7 +1758,7 @@ class MessageInputState extends State<MessageInput> {
), ),
ListTile( ListTile(
leading: const Icon(Icons.video_library), leading: const Icon(Icons.video_library),
title: const Text('Upload a video'), title: Text(context.translations.uploadAVideoLabel),
onTap: () { onTap: () {
pickFile(DefaultAttachmentTypes.video); pickFile(DefaultAttachmentTypes.video);
Navigator.pop(context); Navigator.pop(context);
@@ -1771,7 +1767,7 @@ class MessageInputState extends State<MessageInput> {
if (!kIsWeb) if (!kIsWeb)
ListTile( ListTile(
leading: const Icon(Icons.camera_alt), leading: const Icon(Icons.camera_alt),
title: const Text('Photo from camera'), title: Text(context.translations.photoFromCameraLabel),
onTap: () { onTap: () {
pickFile(DefaultAttachmentTypes.image, true); pickFile(DefaultAttachmentTypes.image, true);
Navigator.pop(context); Navigator.pop(context);
@@ -1780,7 +1776,7 @@ class MessageInputState extends State<MessageInput> {
if (!kIsWeb) if (!kIsWeb)
ListTile( ListTile(
leading: const Icon(Icons.videocam), leading: const Icon(Icons.videocam),
title: const Text('Video from camera'), title: Text(context.translations.videoFromCameraLabel),
onTap: () { onTap: () {
pickFile(DefaultAttachmentTypes.video, true); pickFile(DefaultAttachmentTypes.video, true);
Navigator.pop(context); Navigator.pop(context);
@@ -1788,7 +1784,7 @@ class MessageInputState extends State<MessageInput> {
), ),
ListTile( ListTile(
leading: const Icon(Icons.insert_drive_file), leading: const Icon(Icons.insert_drive_file),
title: const Text('Upload a file'), title: Text(context.translations.uploadAFileLabel),
onTap: () { onTap: () {
pickFile(DefaultAttachmentTypes.file); pickFile(DefaultAttachmentTypes.file);
Navigator.pop(context); Navigator.pop(context);
@@ -1888,8 +1884,7 @@ class MessageInputState extends State<MessageInput> {
if (mediaInfo.filesize! > widget.maxAttachmentSize) { if (mediaInfo.filesize! > widget.maxAttachmentSize) {
_showErrorAlert( _showErrorAlert(
// ignore: lines_longer_than_80_chars context.translations.fileTooLargeAfterCompressionError,
'The file is too large to upload. The file size limit is 20MB. We tried compressing it, but it was not enough.',
); );
return; return;
} }
@@ -1900,9 +1895,7 @@ class MessageInputState extends State<MessageInput> {
path: mediaInfo.path, path: mediaInfo.path,
); );
} else { } else {
_showErrorAlert( _showErrorAlert(context.translations.fileTooLargeError);
'The file is too large to upload. The file size limit is 20MB.',
);
return; return;
} }
} }
@@ -2079,7 +2072,7 @@ class MessageInputState extends State<MessageInput> {
height: 26, height: 26,
), ),
Text( Text(
'Something went wrong', context.translations.somethingWentWrongLabel,
style: _streamChatTheme.textTheme.headlineBold, style: _streamChatTheme.textTheme.headlineBold,
), ),
const SizedBox( const SizedBox(
@@ -2107,7 +2100,7 @@ class MessageInputState extends State<MessageInput> {
Navigator.of(context).pop(); Navigator.of(context).pop();
}, },
child: Text( child: Text(
'OK', context.translations.okLabel,
style: _streamChatTheme.textTheme.bodyBold style: _streamChatTheme.textTheme.bodyBold
.copyWith(color: _streamChatTheme.colorTheme.accentBlue), .copyWith(color: _streamChatTheme.colorTheme.accentBlue),
), ),
@@ -2253,7 +2246,7 @@ class __PickerWidgetState extends State<_PickerWidget> {
color: widget.streamChatTheme.colorTheme.whiteSmoke, color: widget.streamChatTheme.colorTheme.whiteSmoke,
alignment: Alignment.center, alignment: Alignment.center,
child: Text( child: Text(
'Add more files', context.translations.addMoreFilesLabel,
style: TextStyle( style: TextStyle(
color: widget.streamChatTheme.colorTheme.accentBlue, color: widget.streamChatTheme.colorTheme.accentBlue,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
@@ -2285,8 +2278,7 @@ class __PickerWidgetState extends State<_PickerWidget> {
color: widget.streamChatTheme.colorTheme.greyGainsboro, color: widget.streamChatTheme.colorTheme.greyGainsboro,
), ),
Text( Text(
// ignore: lines_longer_than_80_chars context.translations.enablePhotoAndVideoAccessMessage,
'Please enable access to your photos \nand videos so you can share them with friends.',
style: widget.streamChatTheme.textTheme.body.copyWith( style: widget.streamChatTheme.textTheme.body.copyWith(
color: widget.streamChatTheme.colorTheme.grey), color: widget.streamChatTheme.colorTheme.grey),
textAlign: TextAlign.center, textAlign: TextAlign.center,
@@ -2294,7 +2286,7 @@ class __PickerWidgetState extends State<_PickerWidget> {
const SizedBox(height: 6), const SizedBox(height: 6),
Center( Center(
child: Text( child: Text(
'Allow access to your gallery', context.translations.allowGalleryAccessMessage,
style: widget.streamChatTheme.textTheme.bodyBold.copyWith( style: widget.streamChatTheme.textTheme.bodyBold.copyWith(
color: widget.streamChatTheme.colorTheme.accentBlue, color: widget.streamChatTheme.colorTheme.accentBlue,
), ),
@@ -17,6 +17,7 @@ import 'package:stream_chat_flutter/src/system_message.dart';
import 'package:stream_chat_flutter/stream_chat_flutter.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart';
import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart';
import 'package:visibility_detector/visibility_detector.dart'; import 'package:visibility_detector/visibility_detector.dart';
import 'package:stream_chat_flutter/src/extension.dart';
/// Widget builder for message /// Widget builder for message
/// [defaultMessageWidget] is the default [MessageWidget] configuration /// [defaultMessageWidget] is the default [MessageWidget] configuration
@@ -353,6 +354,7 @@ class _MessageListViewState extends State<MessageListView> {
bool _inBetweenList = false; bool _inBetweenList = false;
late final _defaultController = MessageListController(); late final _defaultController = MessageListController();
MessageListController get _messageListController => MessageListController get _messageListController =>
widget.messageListController ?? _defaultController; widget.messageListController ?? _defaultController;
@@ -366,7 +368,7 @@ class _MessageListViewState extends State<MessageListView> {
emptyBuilder: widget.emptyBuilder ?? emptyBuilder: widget.emptyBuilder ??
(context) => Center( (context) => Center(
child: Text( child: Text(
'No chats here yet...', context.translations.emptyChatMessagesText,
style: _streamTheme.textTheme.footnote.copyWith( style: _streamTheme.textTheme.footnote.copyWith(
color: _streamTheme.colorTheme.black.withOpacity(.5)), color: _streamTheme.colorTheme.black.withOpacity(.5)),
), ),
@@ -378,7 +380,7 @@ class _MessageListViewState extends State<MessageListView> {
errorWidgetBuilder: widget.errorWidgetBuilder ?? errorWidgetBuilder: widget.errorWidgetBuilder ??
(BuildContext context, Object error) => Center( (BuildContext context, Object error) => Center(
child: Text( child: Text(
'Something went wrong', context.translations.genericErrorText,
style: _streamTheme.textTheme.footnote.copyWith( style: _streamTheme.textTheme.footnote.copyWith(
color: _streamTheme.colorTheme.black.withOpacity(.5)), color: _streamTheme.colorTheme.black.withOpacity(.5)),
), ),
@@ -423,14 +425,14 @@ class _MessageListViewState extends State<MessageListView> {
var showStatus = true; var showStatus = true;
switch (status) { switch (status) {
case ConnectionStatus.connected: case ConnectionStatus.connected:
statusString = 'Connected'; statusString = context.translations.connectedLabel;
showStatus = false; showStatus = false;
break; break;
case ConnectionStatus.connecting: case ConnectionStatus.connecting:
statusString = 'Reconnecting...'; statusString = context.translations.reconnectingLabel;
break; break;
case ConnectionStatus.disconnected: case ConnectionStatus.disconnected:
statusString = 'Disconnected'; statusString = context.translations.disconnectedLabel;
break; break;
} }
@@ -619,7 +621,7 @@ class _MessageListViewState extends State<MessageListView> {
return widget.threadSeparatorBuilder!.call(context); return widget.threadSeparatorBuilder!.call(context);
} }
final replyCount = widget.parentMessage!.replyCount; final replyCount = widget.parentMessage!.replyCount!;
return DecoratedBox( return DecoratedBox(
decoration: BoxDecoration( decoration: BoxDecoration(
gradient: _streamTheme.colorTheme.bgGradient, gradient: _streamTheme.colorTheme.bgGradient,
@@ -627,7 +629,7 @@ class _MessageListViewState extends State<MessageListView> {
child: Padding( child: Padding(
padding: const EdgeInsets.all(8), padding: const EdgeInsets.all(8),
child: Text( child: Text(
'$replyCount ${replyCount == 1 ? 'Reply' : 'Replies'}', context.translations.threadSeparatorText(replyCount),
textAlign: TextAlign.center, textAlign: TextAlign.center,
style: _streamTheme.channelTheme.channelHeaderTheme.subtitle, style: _streamTheme.channelTheme.channelHeaderTheme.subtitle,
), ),
@@ -1253,8 +1255,8 @@ class _LoadingIndicator extends StatelessWidget {
initialData: false, initialData: false,
errorBuilder: (context, error) => Container( errorBuilder: (context, error) => Container(
color: streamTheme.colorTheme.accentRed.withOpacity(.2), color: streamTheme.colorTheme.accentRed.withOpacity(.2),
child: const Center( child: Center(
child: Text('Error loading messages'), child: Text(context.translations.loadingMessagesError),
), ),
), ),
builder: (context, data) { builder: (context, data) {
@@ -7,6 +7,7 @@ import 'package:stream_chat_flutter/src/stream_chat.dart';
import 'package:stream_chat_flutter/src/user_avatar.dart'; import 'package:stream_chat_flutter/src/user_avatar.dart';
import 'package:stream_chat_flutter/stream_chat_flutter.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart';
import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart';
import 'package:stream_chat_flutter/src/extension.dart';
/// Modal widget for displaying message reactions /// Modal widget for displaying message reactions
class MessageReactionsModal extends StatelessWidget { class MessageReactionsModal extends StatelessWidget {
@@ -154,7 +155,7 @@ class MessageReactionsModal extends StatelessWidget {
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
Text( Text(
'Message Reactions', context.translations.messageReactionsText,
style: chatThemeData.textTheme.headlineBold, style: chatThemeData.textTheme.headlineBold,
), ),
const SizedBox(height: 16), const SizedBox(height: 16),
@@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
import 'package:jiffy/jiffy.dart'; import 'package:jiffy/jiffy.dart';
import 'package:stream_chat_flutter/stream_chat_flutter.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart';
import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart';
import 'package:stream_chat_flutter/src/extension.dart';
/// It shows the current [Message] preview. /// It shows the current [Message] preview.
/// ///
@@ -98,7 +99,7 @@ class MessageSearchItem extends StatelessWidget {
Widget _buildSubtitle(BuildContext context, Message message) { Widget _buildSubtitle(BuildContext context, Message message) {
var text = message.text; var text = message.text;
if (message.isDeleted) { if (message.isDeleted) {
text = 'This message was deleted.'; text = context.translations.messageDeletedText;
} else if (message.attachments.isNotEmpty) { } else if (message.attachments.isNotEmpty) {
final parts = <String>[ final parts = <String>[
...message.attachments.map((e) { ...message.attachments.map((e) {
@@ -3,6 +3,7 @@ import 'package:stream_chat_flutter/src/info_tile.dart';
import 'package:stream_chat_flutter/src/message_search_item.dart'; import 'package:stream_chat_flutter/src/message_search_item.dart';
import 'package:stream_chat_flutter/stream_chat_flutter.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart';
import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart';
import 'package:stream_chat_flutter/src/extension.dart';
/// Callback called when tapping on a user /// Callback called when tapping on a user
typedef MessageSearchItemTapCallback = void Function(GetMessageResponse); typedef MessageSearchItemTapCallback = void Function(GetMessageResponse);
@@ -140,6 +141,7 @@ class MessageSearchListView extends StatefulWidget {
class _MessageSearchListViewState extends State<MessageSearchListView> { class _MessageSearchListViewState extends State<MessageSearchListView> {
late final _defaultController = MessageSearchListController(); late final _defaultController = MessageSearchListController();
MessageSearchListController get _messageSearchListController => MessageSearchListController get _messageSearchListController =>
widget.messageSearchListController ?? _defaultController; widget.messageSearchListController ?? _defaultController;
@@ -160,8 +162,8 @@ class _MessageSearchListViewState extends State<MessageSearchListView> {
constraints: BoxConstraints( constraints: BoxConstraints(
minHeight: viewportConstraints.maxHeight, minHeight: viewportConstraints.maxHeight,
), ),
child: const Center( child: Center(
child: Text('There are no messages currently'), child: Text(context.translations.emptyMessagesText),
), ),
), ),
), ),
@@ -175,7 +177,7 @@ class _MessageSearchListViewState extends State<MessageSearchListView> {
showMessage: widget.showErrorTile, showMessage: widget.showErrorTile,
tileAnchor: Alignment.topCenter, tileAnchor: Alignment.topCenter,
childAnchor: Alignment.topCenter, childAnchor: Alignment.topCenter,
message: 'An error occurred.', message: context.translations.genericErrorText,
child: Container(), child: Container(),
); );
}, },
@@ -226,10 +228,10 @@ class _MessageSearchListViewState extends State<MessageSearchListView> {
.colorTheme .colorTheme
.accentRed .accentRed
.withOpacity(.2), .withOpacity(.2),
child: const Padding( child: Padding(
padding: EdgeInsets.symmetric(vertical: 16), padding: const EdgeInsets.symmetric(vertical: 16),
child: Center( child: Center(
child: Text('Error loading messages'), child: Text(context.translations.loadingMessagesError),
), ),
), ),
); );
@@ -292,7 +294,7 @@ class _MessageSearchListViewState extends State<MessageSearchListView> {
horizontal: 8, horizontal: 8,
), ),
child: Text( child: Text(
'${items.length} results', context.translations.resultCountText(items.length),
style: TextStyle( style: TextStyle(
color: chatThemeData.colorTheme.grey, color: chatThemeData.colorTheme.grey,
), ),
@@ -3,7 +3,6 @@ import 'package:flutter/material.dart';
import 'package:flutter_markdown/flutter_markdown.dart'; import 'package:flutter_markdown/flutter_markdown.dart';
import 'package:stream_chat_flutter/stream_chat_flutter.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart';
import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart';
import 'package:stream_chat_flutter/src/extension.dart';
/// Text widget to display in message /// Text widget to display in message
class MessageText extends StatelessWidget { class MessageText extends StatelessWidget {
@@ -30,8 +29,6 @@ class MessageText extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final texts = context.translations?.launchUrlError ?? 'defaultValue';
return Text(texts);
final text = _replaceMentions(message.text ?? '').replaceAll('\n', '\n\n'); final text = _replaceMentions(message.text ?? '').replaceAll('\n', '\n\n');
final themeData = Theme.of(context); final themeData = Theme.of(context);
@@ -16,6 +16,7 @@ import 'package:stream_chat_flutter/src/quoted_message_widget.dart';
import 'package:stream_chat_flutter/src/reaction_bubble.dart'; import 'package:stream_chat_flutter/src/reaction_bubble.dart';
import 'package:stream_chat_flutter/src/url_attachment.dart'; import 'package:stream_chat_flutter/src/url_attachment.dart';
import 'package:stream_chat_flutter/stream_chat_flutter.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart';
import 'package:stream_chat_flutter/src/extension.dart';
/// Widget builder for building attachments /// Widget builder for building attachments
typedef AttachmentBuilder = Widget Function( typedef AttachmentBuilder = Widget Function(
@@ -840,7 +841,7 @@ class _MessageWidgetState extends State<MessageWidget>
), ),
const SizedBox(width: 8), const SizedBox(width: 8),
Text( Text(
'Only visible to you', context.translations.onlyVisibleToYouText,
style: chatThemeData.textTheme.footnote style: chatThemeData.textTheme.footnote
.copyWith(color: chatThemeData.colorTheme.grey), .copyWith(color: chatThemeData.colorTheme.grey),
), ),
@@ -854,9 +855,9 @@ class _MessageWidgetState extends State<MessageWidget>
final showThreadParticipants = threadParticipants?.isNotEmpty == true; final showThreadParticipants = threadParticipants?.isNotEmpty == true;
final replyCount = widget.message.replyCount; final replyCount = widget.message.replyCount;
var msg = 'Thread Reply'; var msg = context.translations.threadReplyLabel;
if (showThreadReplyIndicator && replyCount! > 1) { if (showThreadReplyIndicator && replyCount! > 1) {
msg = '$replyCount Thread Replies'; msg = context.translations.threadReplyCountText(replyCount);
} }
// ignore: prefer_function_declarations_over_variables // ignore: prefer_function_declarations_over_variables
@@ -1201,7 +1202,10 @@ class _MessageWidgetState extends State<MessageWidget>
); );
} }
return Text( return Text(
'Uploading $uploadRemaining/$totalAttachments ...', context.translations.attachmentsUploadProgressText(
remaining: uploadRemaining,
total: totalAttachments,
),
style: style, style: style,
); );
} }
@@ -1275,8 +1279,8 @@ class _MessageWidgetState extends State<MessageWidget>
} }
Widget _buildPinnedMessage(Message message) { Widget _buildPinnedMessage(Message message) {
final pinnedBy = message.pinnedBy; final pinnedBy = message.pinnedBy!;
final pinnedByMe = _streamChat.user!.id == pinnedBy!.id; final currentUser = _streamChat.user!;
return Padding( return Padding(
padding: const EdgeInsets.only(left: 8, right: 8, top: 4, bottom: 8), padding: const EdgeInsets.only(left: 8, right: 8, top: 4, bottom: 8),
@@ -1290,7 +1294,10 @@ class _MessageWidgetState extends State<MessageWidget>
width: 4, width: 4,
), ),
Text( Text(
'Pinned by ${pinnedByMe ? 'You' : pinnedBy.name}', context.translations.pinnedByUserText(
pinnedBy: pinnedBy,
currentUser: currentUser,
),
style: TextStyle( style: TextStyle(
color: _streamChatTheme.colorTheme.grey, color: _streamChatTheme.colorTheme.grey,
fontSize: 13, fontSize: 13,
@@ -1,96 +0,0 @@
import 'package:flutter/widgets.dart';
// TODO : Fix localization instructions
// ADDING A NEW STRING
//
// If you (someone contributing to the Stream Chat Flutter) want to add a new
// string to the StreamChatLocalizations object (e.g. because you've added a new
// widget and it has a tooltip), follow these steps:
//
// 1. Add the new getter to StreamChatLocalizations below.
//
// 2. Implement a default value in DefaultMaterialLocalizations below.
//
// 3. Add a test to test/material/localizations_test.dart that verifies that
// this new value is implemented.
//
// 4. Update the flutter_localizations package. To add a new string to the
// flutter_localizations package, you must first add it to the English
// translations (lib/src/l10n/en.json), including a description.
//
// Then you need to add new entries for the string to all of the other
// language locale files by running:
// ```
// dart dev/tools/localization/bin/gen_missing_localizations.dart
// ```
// Which will copy the english strings into the other locales as placeholders
// until they can be translated.
//
// Finally you need to re-generate lib/src/l10n/localizations.dart by running:
// ```
// dart dev/tools/localization/bin/gen_localizations.dart --overwrite
// ```
//
// There is a README file with further information in the lib/src/l10n/
// directory.
//
// 5. If you are a Google employee, you should then also follow the instructions
// at go/flutter-l10n. If you're not, don't worry about it.
//
// UPDATING AN EXISTING STRING
//
// If you (someone contributing to the Flutter framework) want to modify an
// existing string in the MaterialLocalizations objects, follow these steps:
//
// 1. Modify the default value of the relevant getter(s) in
// DefaultMaterialLocalizations below.
//
// 2. Update the flutter_localizations package. Modify the out-of-date English
// strings in lib/src/l10n/material_en.arb.
//
// You also need to re-generate lib/src/l10n/localizations.dart by running:
// ```
// dart dev/tools/localization/bin/gen_localizations.dart --overwrite
// ```
//
// This script may result in your updated getters being created in newer
// locales and set to the old value of the strings. This is to be expected.
// Leave them as they were generated, and they will be picked up for
// translation.
//
// There is a README file with further information in the lib/src/l10n/
// directory.
//
// 3. If you are a Google employee, you should then also follow the instructions
// at go/flutter-l10n. If you're not, don't worry about it.
/// Defines the localized resource values used by the StreamChatFlutter widgets.
///
/// See also:
///
/// * [GlobalStreamChatLocalizations], which provides material localizations
/// for many languages.
abstract class StreamChatLocalizations {
/// The `StreamChatLocalizations` from the closest [Localizations] instance
/// that encloses the given context.
///
/// If no [StreamChatLocalizations] are available in the given `context`, this
/// method returns null.
///
/// This method is just a convenient shorthand for:
/// `Localizations.of<StreamChatLocalizations>(context, StreamChatLocalizations)`.
///
/// References to the localized resources defined by this class are typically
/// written in terms of this method. For example:
///
/// ```dart
/// tooltip: StreamChatLocalizations.of(context).backButtonTooltip,
/// ```
static StreamChatLocalizations? of(BuildContext context) =>
Localizations.of<StreamChatLocalizations>(
context,
StreamChatLocalizations,
);
String get launchUrlError;
}
@@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
import 'package:stream_chat_flutter/src/stream_chat_theme.dart'; import 'package:stream_chat_flutter/src/stream_chat_theme.dart';
import 'package:stream_chat_flutter/stream_chat_flutter.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart';
import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart';
import 'package:stream_chat_flutter/src/extension.dart';
/// ![screenshot](https://raw.githubusercontent.com/GetStream/stream-chat-flutter/master/screenshots/thread_header.png) /// ![screenshot](https://raw.githubusercontent.com/GetStream/stream-chat-flutter/master/screenshots/thread_header.png)
/// ![screenshot](https://raw.githubusercontent.com/GetStream/stream-chat-flutter/master/screenshots/thread_header_paint.png) /// ![screenshot](https://raw.githubusercontent.com/GetStream/stream-chat-flutter/master/screenshots/thread_header_paint.png)
@@ -149,7 +150,7 @@ class ThreadHeader extends StatelessWidget implements PreferredSizeWidget {
children: [ children: [
title ?? title ??
Text( Text(
'Thread Reply', context.translations.threadReplyLabel,
style: chatThemeData.channelTheme.channelHeaderTheme.title, style: chatThemeData.channelTheme.channelHeaderTheme.title,
), ),
const SizedBox(height: 2), const SizedBox(height: 2),
@@ -1,6 +1,7 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:lottie/lottie.dart'; import 'package:lottie/lottie.dart';
import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart';
import 'package:stream_chat_flutter/src/extension.dart';
/// Widget to show the current list of typing users /// Widget to show the current list of typing users
class TypingIndicator extends StatelessWidget { class TypingIndicator extends StatelessWidget {
@@ -63,8 +64,7 @@ class TypingIndicator extends StatelessWidget {
height: 4, height: 4,
), ),
Text( Text(
// ignore: lines_longer_than_80_chars context.translations.userTypingText(data),
' ${data.elementAt(0).name}${data.length == 1 ? '' : ' and ${data.length - 1} more'} ${data.length == 1 ? 'is' : 'are'} typing',
maxLines: 1, maxLines: 1,
style: style, style: style,
), ),
@@ -4,6 +4,7 @@ import 'package:stream_chat_flutter/src/stream_svg_icon.dart';
import 'package:stream_chat_flutter/src/user_list_view.dart'; import 'package:stream_chat_flutter/src/user_list_view.dart';
import 'package:stream_chat_flutter/stream_chat_flutter.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart';
import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart';
import 'package:stream_chat_flutter/src/extension.dart';
/// ///
/// It shows the current [User] preview. /// It shows the current [User] preview.
@@ -86,12 +87,13 @@ class UserItem extends StatelessWidget {
); );
} }
Widget _buildLastActive(context) { Widget _buildLastActive(BuildContext context) {
final chatTheme = StreamChatTheme.of(context); final chatTheme = StreamChatTheme.of(context);
return Text( return Text(
user.online == true user.online == true
? 'Online' ? context.translations.userOnlineText
: 'Last online ${Jiffy(user.lastActive).fromNow()}', : context.translations.userLastOnlineText +
Jiffy(user.lastActive).fromNow(),
style: chatTheme.textTheme.footnote style: chatTheme.textTheme.footnote
.copyWith(color: chatTheme.colorTheme.black.withOpacity(.5)), .copyWith(color: chatTheme.colorTheme.black.withOpacity(.5)),
); );
@@ -1,6 +1,7 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:stream_chat_flutter/stream_chat_flutter.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart';
import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart';
import 'package:stream_chat_flutter/src/extension.dart';
/// Callback called when tapping on a user /// Callback called when tapping on a user
typedef UserTapCallback = void Function(User, Widget?); typedef UserTapCallback = void Function(User, Widget?);
@@ -155,6 +156,7 @@ class _UserListViewState extends State<UserListView>
bool get _isListView => widget.crossAxisCount == 1; bool get _isListView => widget.crossAxisCount == 1;
late final _defaultController = UserListController(); late final _defaultController = UserListController();
UserListController get _userListController => UserListController get _userListController =>
widget.userListController ?? _defaultController; widget.userListController ?? _defaultController;
@@ -207,9 +209,9 @@ class _UserListViewState extends State<UserListView>
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[ children: <Widget>[
Text.rich( Text.rich(
const TextSpan( TextSpan(
children: [ children: [
WidgetSpan( const WidgetSpan(
child: Padding( child: Padding(
padding: EdgeInsets.only( padding: EdgeInsets.only(
right: 2, right: 2,
@@ -217,14 +219,14 @@ class _UserListViewState extends State<UserListView>
child: Icon(Icons.error_outline), child: Icon(Icons.error_outline),
), ),
), ),
TextSpan(text: 'Error loading users'), TextSpan(text: context.translations.loadingUsersError),
], ],
), ),
style: Theme.of(context).textTheme.headline6, style: Theme.of(context).textTheme.headline6,
), ),
TextButton( TextButton(
onPressed: () => _userListController.loadData!(), onPressed: () => _userListController.loadData!(),
child: const Text('Retry'), child: Text(context.translations.retryLabel),
), ),
], ],
), ),
@@ -237,8 +239,8 @@ class _UserListViewState extends State<UserListView>
constraints: BoxConstraints( constraints: BoxConstraints(
minHeight: viewportConstraints.maxHeight, minHeight: viewportConstraints.maxHeight,
), ),
child: const Center( child: Center(
child: Text('There are no users currently'), child: Text(context.translations.noUsersLabel),
), ),
), ),
), ),
@@ -387,10 +389,10 @@ class _UserListViewState extends State<UserListView>
.colorTheme .colorTheme
.accentRed .accentRed
.withOpacity(.2), .withOpacity(.2),
child: const Padding( child: Padding(
padding: EdgeInsets.symmetric(vertical: 16), padding: const EdgeInsets.symmetric(vertical: 16),
child: Center( child: Center(
child: Text('Error loading users'), child: Text(context.translations.loadingUsersError),
), ),
), ),
); );
@@ -4,17 +4,15 @@ import 'package:flutter/material.dart';
import 'package:stream_chat_flutter/stream_chat_flutter.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart';
import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart';
import 'package:url_launcher/url_launcher.dart'; import 'package:url_launcher/url_launcher.dart';
import 'package:stream_chat_flutter/src/extension.dart';
/// Launch URL /// Launch URL
Future<void> launchURL(BuildContext context, String? url) async { Future<void> launchURL(BuildContext context, String? url) async {
if (url != null && await canLaunch(url)) { if (url != null && await canLaunch(url)) {
await launch(url); await launch(url);
} else { } else {
// ignore: deprecated_member_use ScaffoldMessenger.of(context).showSnackBar(
Scaffold.of(context).showSnackBar( SnackBar(content: Text(context.translations.launchUrlError)),
const SnackBar(
content: Text('Cannot launch the url'),
),
); );
} }
} }
@@ -15,6 +15,7 @@ export 'src/full_screen_media.dart';
export 'src/image_footer.dart'; export 'src/image_footer.dart';
export 'src/image_header.dart'; export 'src/image_header.dart';
export 'src/info_tile.dart'; export 'src/info_tile.dart';
export 'src/localization/stream_chat_localizations.dart';
export 'src/mention_tile.dart'; export 'src/mention_tile.dart';
export 'src/message_action.dart'; export 'src/message_action.dart';
export 'src/message_input.dart'; export 'src/message_input.dart';
@@ -28,7 +29,6 @@ export 'src/reaction_icon.dart';
export 'src/reaction_picker.dart'; export 'src/reaction_picker.dart';
export 'src/sending_indicator.dart'; export 'src/sending_indicator.dart';
export 'src/stream_chat.dart'; export 'src/stream_chat.dart';
export 'src/stream_chat_localizations.dart';
export 'src/stream_chat_theme.dart'; export 'src/stream_chat_theme.dart';
export 'src/stream_neumorphic_button.dart'; export 'src/stream_neumorphic_button.dart';
export 'src/stream_svg_icon.dart'; export 'src/stream_svg_icon.dart';
@@ -2,7 +2,7 @@ import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_localizations/flutter_localizations.dart'; import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:stream_chat_flutter/stream_chat_flutter.dart' import 'package:stream_chat_flutter/stream_chat_flutter.dart'
show StreamChatLocalizations; show StreamChatLocalizations, User;
part 'stream_chat_localizations_en.dart'; part 'stream_chat_localizations_en.dart';
@@ -113,9 +113,6 @@ abstract class GlobalStreamChatLocalizations
GlobalMaterialLocalizations.delegate, GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate, GlobalWidgetsLocalizations.delegate,
]; ];
@override
String get launchUrlError;
} }
class _StreamChatLocalizationsDelegate class _StreamChatLocalizationsDelegate
@@ -8,4 +8,319 @@ class StreamChatLocalizationsEn extends GlobalStreamChatLocalizations {
@override @override
String get launchUrlError => 'Cannot launch the url'; String get launchUrlError => 'Cannot launch the url';
@override
String get loadingUsersError => 'Error loading users';
@override
String get noUsersLabel => 'There are no users currently';
@override
String get retryLabel => 'Retry';
@override
String get userLastOnlineText => 'Last online';
@override
String get userOnlineText => 'Online';
@override
String userTypingText(Iterable<User> users) {
if (users.isEmpty) return '';
final first = users.first;
if (users.length == 1) {
return '${first.name} is typing';
}
return '${first.name} and ${users.length - 1} more are typing';
}
@override
String get threadReplyLabel => 'Thread Reply';
@override
String get onlyVisibleToYouText => 'Only visible to you';
@override
String threadReplyCountText(int count) => '$count Thread Replies';
@override
String attachmentsUploadProgressText({
required int remaining,
required int total,
}) =>
'Uploading $remaining/$total ...';
@override
String pinnedByUserText({
required User pinnedBy,
required User currentUser,
}) {
final pinnedByCurrentUser = currentUser.id == pinnedBy.id;
if (pinnedByCurrentUser) return 'Pinned by You';
return 'Pinned by ${pinnedBy.name}';
}
@override
String get emptyMessagesText => 'There are no messages currently';
@override
String get genericErrorText => 'Something went wrong';
@override
String get loadingMessagesError => 'Error loading messages';
@override
String resultCountText(int count) => '$count results';
@override
String get messageDeletedText => 'This message was deleted.';
@override
String get messageDeletedLabel => 'Message deleted';
@override
String get messageReactionsText => 'Message Reactions';
@override
String get emptyChatMessagesText => 'No chats here yet...';
@override
String threadSeparatorText(int replyCount) {
if (replyCount == 1) return '1 Reply';
return '$replyCount Replies';
}
@override
String get connectedLabel => 'Connected';
@override
String get disconnectedLabel => 'Disconnected';
@override
String get reconnectingLabel => 'Reconnecting...';
@override
String get alsoSendAsDirectMessageLabel => 'Also send as direct message';
@override
String get addACommentOrSendLabel => 'Add a comment or send';
@override
String get searchGifLabel => 'Search GIFs';
@override
String get writeAMessageLabel => 'Write a message';
@override
String get instantCommandsLabel => 'Instant Commands';
@override
String get fileTooLargeAfterCompressionError =>
'The file is too large to upload. '
'The file size limit is 20MB. '
'We tried compressing it, but it was not enough.';
@override
String get fileTooLargeError =>
'The file is too large to upload. The file size limit is 20MB.';
@override
String emojiMatchingQueryText(String query) => 'Emoji matching "$query"';
@override
String get addAFileLabel => 'Add a file';
@override
String get photoFromCameraLabel => 'Photo from camera';
@override
String get uploadAFileLabel => 'Upload a file';
@override
String get uploadAPhotoLabel => 'Upload a photo';
@override
String get uploadAVideoLabel => 'Upload a video';
@override
String get videoFromCameraLabel => 'Video from camera';
@override
String get okLabel => 'OK';
@override
String get somethingWentWrongLabel => 'Something went wrong';
@override
String get addMoreFilesLabel => 'Add more files';
@override
String get enablePhotoAndVideoAccessMessage =>
'Please enable access to your photos'
'\nand videos so you can share them with friends.';
@override
String get allowGalleryAccessMessage => 'Allow access to your gallery';
@override
String get flagMessageLabel => 'Flag Message';
@override
String get flagMessageQuestion =>
'Do you want to send a copy of this message to a'
'\nmoderator for further investigation?';
@override
String get flagLabel => 'FLAG';
@override
String get cancelLabel => 'CANCEL';
@override
String get flagMessageSuccessfulLabel => 'Message flagged';
@override
String get flagMessageSuccessfulText =>
'The message has been reported to a moderator.';
@override
String get deleteLabel => 'DELETE';
@override
String get deleteMessageLabel => 'Delete Message';
@override
String get deleteMessageQuestion =>
'Are you sure you want to permanently delete this\nmessage?';
@override
String get operationCouldNotBeCompletedText =>
'The operation couldn\'t be completed.';
@override
String get replyLabel => 'Reply';
@override
String togglePinUnpinText({required bool pinned}) {
if (pinned) return 'Unpin from Conversation';
return 'Pin to Conversation';
}
@override
String toggleDeleteRetryDeleteMessageText({required bool isDeleteFailed}) {
if (isDeleteFailed) return 'Retry Deleting Message';
return 'Delete Message';
}
@override
String get copyMessageLabel => 'Copy Message';
@override
String get editMessageLabel => 'Edit Message';
@override
String toggleResendOrResendEditedMessage({required bool isUpdateFailed}) {
if (isUpdateFailed) return 'Resend Edited Message';
return 'Resend';
}
@override
String get photosLabel => 'Photos';
@override
String sentAtText({required DateTime date, required DateTime time}) =>
'Sent $date at $time';
@override
String get todayLabel => 'Today';
@override
String get yesterdayLabel => 'Yesterday';
@override
String get channelIsMutedText => ' Channel is muted';
@override
String get noTitleText => 'No title';
@override
String get letsStartChattingLabel => 'Lets start chatting!';
@override
String get sendingFirstMessageLabel =>
'How about sending your first message to a friend?';
@override
String get startAChatLabel => 'Start a chat';
@override
String get loadingChannelsError => 'Error loading channels';
@override
String get deleteConversationLabel => 'Delete Conversation';
@override
String get deleteConversationQuestion =>
'Are you sure you want to delete this conversation?';
@override
String get streamChatLabel => 'Stream Chat';
@override
String get searchingForNetworkLabel => 'Searching for Network';
@override
String get offlineLabel => 'Offline...';
@override
String get tryAgainLabel => 'Try Again';
@override
String membersCountText(int count) {
if (count == 1) return '1 Member';
return '$count Members';
}
@override
String watchersCountText(int count) {
if (count == 1) return '1 Online';
return '$count Online';
}
@override
String get viewInfoLabel => 'View Info';
@override
String get leaveGroupLabel => 'Leave Group';
@override
String get leaveLabel => 'LEAVE';
@override
String get leaveConversationLabel => 'Leave conversation';
@override
String get leaveConversationQuestion =>
'Are you sure you want to leave this conversation?';
@override
String get showInChatLabel => 'Show in Chat';
@override
String get saveImageLabel => 'Save Image';
@override
String get saveVideoLabel => 'Save Video';
@override
String get uploadErrorLabel => 'UPLOAD ERROR';
@override
String get giphyLabel => 'Giphy';
@override
String get shuffleLabel => 'Shuffle';
@override
String get sendLabel => 'Send';
} }