diff --git a/packages/stream_chat/CHANGELOG.md b/packages/stream_chat/CHANGELOG.md index 7afc4000..809fb3a3 100644 --- a/packages/stream_chat/CHANGELOG.md +++ b/packages/stream_chat/CHANGELOG.md @@ -5,6 +5,9 @@ - [[#857]](https://github.com/GetStream/stream-chat-flutter/issues/857) Channel now listens for member ban/unban and updates the channel state with the latest data. - [[#748]](https://github.com/GetStream/stream-chat-flutter/issues/748) `Message.user` are now also included while saving users in persistence. +- [[#871]](https://github.com/GetStream/stream-chat-flutter/issues/871) Fixed thread message deletion. +- [[#846]](https://github.com/GetStream/stream-chat-flutter/issues/846) Fixed `message.ownReactions` getting truncated when receiving a reaction event. +- Add check for invalid image URLs 🔄 Changed @@ -19,10 +22,6 @@ - Fixed `unreadCount` after removing user from a channel. - Added `client.queryBannedUsers`, `channel.queryBannedUsers` endpoint for querying banned users. -🐞 Fixed - -- [[#871]](https://github.com/GetStream/stream-chat-flutter/issues/871) Fixed thread message deletion. - ## 3.3.1 🐞 Fixed @@ -767,4 +766,4 @@ ## 0.0.2 -- first beta version \ No newline at end of file +- first beta version diff --git a/packages/stream_chat/lib/src/client/channel.dart b/packages/stream_chat/lib/src/client/channel.dart index 460cef3f..622cb151 100644 --- a/packages/stream_chat/lib/src/client/channel.dart +++ b/packages/stream_chat/lib/src/client/channel.dart @@ -841,7 +841,7 @@ class Channel { final now = DateTime.now(); final user = _client.state.currentUser; - final latestReactions = [...message.latestReactions ?? []]; + var latestReactions = [...message.latestReactions ?? []]; if (enforceUnique) { latestReactions.removeWhere((it) => it.userId == user!.id); } @@ -855,10 +855,17 @@ class Channel { extraData: extraData, ); - // Inserting at the 0th index as it's the latest reaction - latestReactions.insert(0, newReaction); - final ownReactions = [...latestReactions] - ..removeWhere((it) => it.userId != user!.id); + latestReactions = (latestReactions + // Inserting at the 0th index as it's the latest reaction + ..insert(0, newReaction)) + .take(10) + .toList(); + final ownReactions = enforceUnique + ? [newReaction] + : [ + ...message.ownReactions ?? [], + newReaction, + ]; final newMessage = message.copyWith( reactionCounts: {...message.reactionCounts ?? {}} @@ -898,7 +905,6 @@ class Channel { Reaction reaction, ) async { final type = reaction.type; - final user = _client.state.currentUser; final reactionCounts = {...message.reactionCounts ?? {}}; if (reactionCounts.containsKey(type)) { @@ -915,8 +921,11 @@ class Channel { r.type == reaction.type && r.messageId == reaction.messageId); - final ownReactions = [...latestReactions] - ..removeWhere((it) => it.userId != user!.id); + final ownReactions = message.ownReactions + ?..removeWhere((r) => + r.userId == reaction.userId && + r.type == reaction.type && + r.messageId == reaction.messageId); final newMessage = message.copyWith( reactionCounts: reactionCounts..removeWhere((_, value) => value == 0), @@ -1545,16 +1554,21 @@ class ChannelClientState { if (url == null || !url.contains('')) { return false; } - final uri = Uri.parse(url); - if (!uri.host.endsWith('stream-io-cdn.com') || - uri.queryParameters['Expires'] == null) { + try { + final uri = Uri.parse(url); + if (!uri.host.endsWith('stream-io-cdn.com') || + uri.queryParameters['Expires'] == null) { + return false; + } + final secondsFromEpoch = + int.parse(uri.queryParameters['Expires']!); + final expiration = DateTime.fromMillisecondsSinceEpoch( + secondsFromEpoch * 1000, + ); + return expiration.isBefore(DateTime.now()); + } catch (_) { return false; } - final secondsFromEpoch = - int.parse(uri.queryParameters['Expires']!); - final expiration = - DateTime.fromMillisecondsSinceEpoch(secondsFromEpoch * 1000); - return expiration.isBefore(DateTime.now()); })) .map((e) => e.id) .toList(); @@ -1702,10 +1716,19 @@ class ChannelClientState { void _listenReactionDeleted() { _subscriptions.add(_channel.on(EventType.reactionDeleted).listen((event) { - final userId = _channel.client.state.currentUser!.id; + final oldMessage = + messages.firstWhereOrNull((it) => it.id == event.message?.id); + final reaction = event.reaction; + final ownReactions = oldMessage?.ownReactions + ?.whereNot((it) => + it.type == reaction?.type && + it.score == reaction?.score && + it.messageId == reaction?.messageId && + it.userId == reaction?.userId && + it.extraData == reaction?.extraData) + .toList(growable: false); final message = event.message!.copyWith( - ownReactions: [...event.message!.latestReactions!] - ..removeWhere((it) => it.userId != userId), + ownReactions: ownReactions, ); addMessage(message); })); @@ -1713,10 +1736,10 @@ class ChannelClientState { void _listenReactions() { _subscriptions.add(_channel.on(EventType.reactionNew).listen((event) { - final userId = _channel.client.state.currentUser!.id; + final oldMessage = + messages.firstWhereOrNull((it) => it.id == event.message?.id); final message = event.message!.copyWith( - ownReactions: [...event.message!.latestReactions!] - ..removeWhere((it) => it.userId != userId), + ownReactions: oldMessage?.ownReactions, ); addMessage(message); })); @@ -1729,10 +1752,11 @@ class ChannelClientState { EventType.reactionUpdated, ) .listen((event) { - final userId = _channel.client.state.currentUser!.id; + final oldMessage = + messages.firstWhereOrNull((it) => it.id == event.message?.id); + final message = event.message!.copyWith( - ownReactions: [...event.message!.latestReactions!] - ..removeWhere((it) => it.userId != userId), + ownReactions: oldMessage?.ownReactions, ); addMessage(message); diff --git a/packages/stream_chat_flutter/pubspec.yaml b/packages/stream_chat_flutter/pubspec.yaml index 86dcd0f6..a6663415 100644 --- a/packages/stream_chat_flutter/pubspec.yaml +++ b/packages/stream_chat_flutter/pubspec.yaml @@ -23,7 +23,7 @@ dependencies: flutter_markdown: ^0.6.1 flutter_portal: ^0.4.0 flutter_slidable: ^0.6.0 - flutter_svg: ^0.23.0+1 + flutter_svg: ^1.0.1 http_parser: ^4.0.0 image_gallery_saver: ^1.7.0 image_picker: ^0.8.2 diff --git a/packages/stream_chat_localizations/CHANGELOG.md b/packages/stream_chat_localizations/CHANGELOG.md index b1f010dd..4de6746a 100644 --- a/packages/stream_chat_localizations/CHANGELOG.md +++ b/packages/stream_chat_localizations/CHANGELOG.md @@ -1,4 +1,8 @@ -## 2.1.0 +## Upcoming + +✅ Added + +* Added support for [Portuguese](https://github.com/GetStream/stream-chat-flutter/blob/master/packages/stream_chat_localizations/lib/src/stream_chat_localizations_pt.dart) locale. 🔄 Changed diff --git a/packages/stream_chat_localizations/README.md b/packages/stream_chat_localizations/README.md index 131d6b13..34680d35 100644 --- a/packages/stream_chat_localizations/README.md +++ b/packages/stream_chat_localizations/README.md @@ -37,6 +37,7 @@ At the moment we support the following languages: - [Spanish](https://github.com/GetStream/stream-chat-flutter/blob/master/packages/stream_chat_localizations/lib/src/stream_chat_localizations_es.dart) - [Japanese](https://github.com/GetStream/stream-chat-flutter/blob/master/packages/stream_chat_localizations/lib/src/stream_chat_localizations_ja.dart) - [Korean](https://github.com/GetStream/stream-chat-flutter/blob/master/packages/stream_chat_localizations/lib/src/stream_chat_localizations_ko.dart) +- [Portuguese](https://github.com/GetStream/stream-chat-flutter/blob/master/packages/stream_chat_localizations/lib/src/stream_chat_localizations_pt.dart) More languages will be added in the future. Feel free to [contribute](https://github.com/GetStream/stream-chat-flutter/blob/master/CONTRIBUTING.md) to add more languages. @@ -74,6 +75,7 @@ class MyApp extends StatelessWidget { Locale('es'), Locale('ja'), Locale('ko'), + Locale('pt'), ], // Add GlobalStreamChatLocalizations.delegates localizationsDelegates: GlobalStreamChatLocalizations.delegates, @@ -112,13 +114,14 @@ Example: ```xml CFBundleLocalizations - en - nb - fr - it - es + en + hi + fr + it + es ja ko + pt ``` diff --git a/packages/stream_chat_localizations/example/ios/Runner/Info.plist b/packages/stream_chat_localizations/example/ios/Runner/Info.plist index c9bfb029..c2a9248d 100644 --- a/packages/stream_chat_localizations/example/ios/Runner/Info.plist +++ b/packages/stream_chat_localizations/example/ios/Runner/Info.plist @@ -4,10 +4,14 @@ CFBundleLocalizations - en - it - fr - hi + en + hi + fr + it + es + ja + ko + pt CFBundleDevelopmentRegion $(DEVELOPMENT_LANGUAGE) diff --git a/packages/stream_chat_localizations/example/lib/main.dart b/packages/stream_chat_localizations/example/lib/main.dart index c71ceb01..82d7e92c 100644 --- a/packages/stream_chat_localizations/example/lib/main.dart +++ b/packages/stream_chat_localizations/example/lib/main.dart @@ -75,6 +75,7 @@ class MyApp extends StatelessWidget { Locale('es'), Locale('ja'), Locale('ko'), + Locale('pt'), ], // Add GlobalStreamChatLocalizations.delegates localizationsDelegates: GlobalStreamChatLocalizations.delegates, diff --git a/packages/stream_chat_localizations/example/lib/override_lang.dart b/packages/stream_chat_localizations/example/lib/override_lang.dart index a52fcf9f..6f311544 100644 --- a/packages/stream_chat_localizations/example/lib/override_lang.dart +++ b/packages/stream_chat_localizations/example/lib/override_lang.dart @@ -100,6 +100,7 @@ class MyApp extends StatelessWidget { Locale('es'), Locale('ja'), Locale('ko'), + Locale('pt'), ], // Add overridden "CustomStreamChatLocalizationsEn.delegate" along with // "GlobalStreamChatLocalizations.delegates" diff --git a/packages/stream_chat_localizations/lib/src/stream_chat_localizations.dart b/packages/stream_chat_localizations/lib/src/stream_chat_localizations.dart index b9ac541e..0c7ce68e 100644 --- a/packages/stream_chat_localizations/lib/src/stream_chat_localizations.dart +++ b/packages/stream_chat_localizations/lib/src/stream_chat_localizations.dart @@ -17,6 +17,8 @@ part 'stream_chat_localizations_ko.dart'; part 'stream_chat_localizations_hi.dart'; +part 'stream_chat_localizations_pt.dart'; + /// The set of supported languages, as language code strings. /// /// The [GlobalStreamChatLocalizations.delegate] can generate localizations for @@ -33,6 +35,7 @@ const kStreamChatSupportedLanguages = { 'es', 'ja', 'ko', + 'pt', }; /// Creates a [GlobalStreamChatLocalizations] instance for the given `locale`. @@ -69,6 +72,8 @@ GlobalStreamChatLocalizations? getStreamChatTranslation(Locale locale) { return const StreamChatLocalizationsJa(); case 'ko': return const StreamChatLocalizationsKo(); + case 'pt': + return const StreamChatLocalizationsPt(); } } diff --git a/packages/stream_chat_localizations/lib/src/stream_chat_localizations_pt.dart b/packages/stream_chat_localizations/lib/src/stream_chat_localizations_pt.dart new file mode 100644 index 00000000..44c02081 --- /dev/null +++ b/packages/stream_chat_localizations/lib/src/stream_chat_localizations_pt.dart @@ -0,0 +1,374 @@ +part of 'stream_chat_localizations.dart'; + +/// The translations for Portuguese (`pt`). +class StreamChatLocalizationsPt extends GlobalStreamChatLocalizations { + /// Create an instance of the translation bundle for Portuguese. + const StreamChatLocalizationsPt({String localeName = 'pt'}) + : super(localeName: localeName); + + @override + String get launchUrlError => 'O URL nĂŁo pĂŽde ser aberto'; + + @override + String get loadingUsersError => 'Erro de carregamento do usuĂĄrio'; + + @override + String get noUsersLabel => 'Nenhum usuĂĄrio atualmente'; + + @override + String get retryLabel => 'Tente novamente'; + + @override + String get userLastOnlineText => 'Última vez on-line'; + + @override + String get userOnlineText => 'Online'; + + @override + String userTypingText(Iterable users) { + if (users.isEmpty) return ''; + final first = users.first; + if (users.length == 1) { + return '${first.name} estĂĄ digitando'; + } + return '${first.name} e ${users.length - 1} estĂŁo digitando'; + } + + @override + String get threadReplyLabel => 'Responder na conversa'; + + @override + String get onlyVisibleToYouText => 'VisĂ­vel apenas para vocĂȘ'; + + @override + String threadReplyCountText(int count) => '$count respostas na conversa'; + + @override + String attachmentsUploadProgressText({ + required int remaining, + required int total, + }) => + 'TranferĂȘncia em andamento $remaining/$total ...'; + + @override + String pinnedByUserText({ + required User pinnedBy, + required User currentUser, + }) { + final pinnedByCurrentUser = currentUser.id == pinnedBy.id; + if (pinnedByCurrentUser) return 'Definido por vocĂȘ'; + return 'Definido por ${pinnedBy.name}'; + } + + @override + String get emptyMessagesText => 'NĂŁo hĂĄ mensagens'; + + @override + String get genericErrorText => 'Ocorreu um problema'; + + @override + String get loadingMessagesError => 'Ocorreu um problema ao carregar mensagem'; + + @override + String resultCountText(int count) => '$count resultados'; + + @override + String get messageDeletedText => 'Esta mensagem foi excluĂ­da.'; + + @override + String get messageDeletedLabel => 'Mensagem excluĂ­da'; + + @override + String get messageReactionsLabel => 'ReaçÔes Ă s mensagens'; + + @override + String get emptyChatMessagesText => 'Ainda nĂŁo hĂĄ mensagens aqui...'; + + @override + String threadSeparatorText(int replyCount) { + if (replyCount == 1) return '1 resposta'; + return '$replyCount respostas'; + } + + @override + String get connectedLabel => 'Conectado'; + + @override + String get disconnectedLabel => 'Desconectado'; + + @override + String get reconnectingLabel => 'Reconectando...'; + + @override + String get alsoSendAsDirectMessageLabel => + 'Enviar tambĂ©m como mensagem direta'; + + @override + String get addACommentOrSendLabel => 'Adicionar um comnetĂĄrio ou enviar'; + + @override + String get searchGifLabel => 'Pesquisar GIFs'; + + @override + String get writeAMessageLabel => 'Escrever uma mensagem'; + + @override + String get instantCommandsLabel => 'Comandos instantĂąneos'; + + @override + String fileTooLargeAfterCompressionError(double limitInMB) => + 'O arquivo Ă© muito grande para carregamento. ' + 'O tamanho mĂĄximo do arquivo Ă© de $limitInMB MB. ' + 'Tentamos comprimi-lo, mas nĂŁo foi suficiente.'; + + @override + String fileTooLargeError(double limitInMB) => + 'O arquivo Ă© muito grande para carregamento. ' + 'O tamanho mĂĄximo dos arquivos Ă© de $limitInMB MB.'; + + @override + String emojiMatchingQueryText(String query) => + 'Emoji correspondente a "$query"'; + + @override + String get addAFileLabel => 'Adicionar um arquivo'; + + @override + String get photoFromCameraLabel => 'Foto da cĂąmera'; + + @override + String get uploadAFileLabel => 'Transferir um arquivo'; + + @override + String get uploadAPhotoLabel => 'Carregar uma foto'; + + @override + String get uploadAVideoLabel => 'Carregar um vĂ­deo'; + + @override + String get videoFromCameraLabel => 'VĂ­deo da cĂąmera'; + + @override + String get okLabel => 'OK'; + + @override + String get somethingWentWrongError => 'Algo deu errado'; + + @override + String get addMoreFilesLabel => 'Adicionar mais arquivos'; + + @override + String get enablePhotoAndVideoAccessMessage => + 'Por favor, permita o acesso a suas fotos' + '\ne vĂ­deos para que possa compartilhar com sua rede.'; + + @override + String get allowGalleryAccessMessage => 'Permitir acesso Ă  sua galeria'; + + @override + String get flagMessageLabel => 'Denunciar mensagem'; + + @override + String get flagMessageQuestion => 'Gostaria de enviar esta mensagem ao' + '\nmoderador para maior investigação?'; + + @override + String get flagLabel => 'DENUNCIAR'; + + @override + String get cancelLabel => 'CANCELAR'; + + @override + String get flagMessageSuccessfulLabel => 'Mensagem denunciada'; + + @override + String get flagMessageSuccessfulText => + 'Esta mensagem foi enviada a um moderador.'; + + @override + String get deleteLabel => 'APAGAR'; + + @override + String get deleteMessageLabel => 'Apagar mensagem'; + + @override + String get deleteMessageQuestion => + 'VocĂȘ tem certeza que deseja apagar essa\nmensagem permanentemente?'; + + @override + String get operationCouldNotBeCompletedText => + 'A operação nĂŁo pode ser completada.'; + + @override + String get replyLabel => 'Resposta'; + + @override + String togglePinUnpinText({required bool pinned}) { + if (pinned) return 'Desafixar na conversa'; + return 'Fixar na conversa'; + } + + @override + String toggleDeleteRetryDeleteMessageText({required bool isDeleteFailed}) { + if (isDeleteFailed) return 'Repetir apagar mensagem'; + return 'Apagar mensagem'; + } + + @override + String get copyMessageLabel => 'Copiar mensagem'; + + @override + String get editMessageLabel => 'Editar mensagem'; + + @override + String toggleResendOrResendEditedMessage({required bool isUpdateFailed}) { + if (isUpdateFailed) return 'Reenviar mensagem alterada'; + return 'Reenviar'; + } + + @override + String get photosLabel => 'Fotos'; + + String _getDay(DateTime dateTime) { + final now = DateTime.now(); + final today = DateTime(now.year, now.month, now.day); + final yesterday = DateTime(now.year, now.month, now.day - 1); + + final date = DateTime(dateTime.year, dateTime.month, dateTime.day); + + if (date == today) { + return 'Hoje'; + } else if (date == yesterday) { + return 'Ontem'; + } else { + return 'o ${Jiffy(date).MMMd}'; + } + } + + @override + String sentAtText({required DateTime date, required DateTime time}) => + '''Enviado ${_getDay(date)} Ă s ${Jiffy(time.toLocal()).format('HH:mm')}'''; + + @override + String get todayLabel => 'Hoje'; + + @override + String get yesterdayLabel => 'Ontem'; + + @override + String get channelIsMutedText => 'O canal estĂĄ silenciado'; + + @override + String get noTitleText => 'Sem tĂ­tulo'; + + @override + String get letsStartChattingLabel => 'Vamos começar a conversar!'; + + @override + String get sendingFirstMessageLabel => + 'Que tal enviar sua primeira mensagem a um amigo?'; + + @override + String get startAChatLabel => 'Iniciar uma conversa'; + + @override + String get loadingChannelsError => 'Erro ao carregar os canais'; + + @override + String get deleteConversationLabel => 'Apagar a conversa'; + + @override + String get deleteConversationQuestion => + 'Tem certeza que deseja apagar essa conversa?'; + + @override + String get streamChatLabel => 'Stream Chat'; + + @override + String get searchingForNetworkText => 'Pesquisando rede'; + + @override + String get offlineLabel => 'Sem conexĂŁo...'; + + @override + String get tryAgainLabel => 'Tente novamente'; + + @override + String membersCountText(int count) { + if (count == 1) return '1 membro'; + return '$count membros'; + } + + @override + String watchersCountText(int count) { + if (count == 1) return '1 online'; + return '$count online'; + } + + @override + String get viewInfoLabel => 'Ver informação'; + + @override + String get leaveGroupLabel => 'Sair do grupo'; + + @override + String get leaveLabel => 'SAIR'; + + @override + String get leaveConversationLabel => 'Sair da conversa'; + + @override + String get leaveConversationQuestion => + 'Tem certeza que deseja sair dessa conversa?'; + + @override + String get showInChatLabel => 'Mostrar no chat'; + + @override + String get saveImageLabel => 'Salvar imagem'; + + @override + String get saveVideoLabel => 'Salvar vĂ­deo'; + + @override + String get uploadErrorLabel => 'ERRO DE TRANSFERÊNCIA'; + + @override + String get giphyLabel => 'Giphy'; + + @override + String get shuffleLabel => 'Misturar'; + + @override + String get sendLabel => 'Enviar'; + + @override + String get withText => 'com'; + + @override + String get inText => 'em'; + + @override + String get youText => 'VocĂȘ'; + + @override + String galleryPaginationText({ + required int currentPage, + required int totalPages, + }) => + '${currentPage + 1} de $totalPages'; + + @override + String get fileText => 'Arquivo'; + + @override + String get replyToMessageLabel => 'Responder Ă  mensagem'; + + @override + String attachmentLimitExceedError(int limit) => ''' +NĂŁo Ă© possĂ­vel adicionar mais de $limit arquivos de uma vez + '''; + + @override + String get slowModeOnLabel => 'Modo lento ativado'; +}