Merge branch 'develop' into chore/android-versions

This commit is contained in:
Rafal Adasiewicz
2023-01-25 14:45:08 +01:00
committed by GitHub
25 changed files with 967 additions and 232 deletions
+10
View File
@@ -1,3 +1,13 @@
## 5.2.0
✅ Added
- Added `Huawei` and `Xiaomi` PushProviders.
🐞 Fixed
- Fixed initializing last synced date.
## 5.1.0
✅ Added
@@ -453,6 +453,15 @@ class StreamChatClient {
if (persistenceEnabled) {
await sync(cids: cids, lastSyncAt: _lastSyncedAt);
}
} else {
// channels are empty, assuming it's a fresh start
// and making sure `lastSyncAt` is initialized
if (persistenceEnabled) {
final lastSyncAt = await _chatPersistenceClient?.getLastSyncAt();
if (lastSyncAt == null) {
await _chatPersistenceClient?.updateLastSyncAt(DateTime.now());
}
}
}
handleEvent(Event(
type: EventType.connectionRecovered,
@@ -6,6 +6,12 @@ enum PushProvider {
/// Send notifications using Google's Firebase Cloud Messaging
firebase,
/// Send notifications using Huawei's Push Kit
huawei,
/// Send notifications using Xiaomi's Mi Push Service
xiaomi,
/// Send notifications using Apple's Push Notification service
apn,
}
+1 -1
View File
@@ -3,4 +3,4 @@ import 'package:stream_chat/src/client/client.dart';
/// Current package version
/// Used in [StreamChatClient] to build the `x-stream-client` header
// ignore: constant_identifier_names
const PACKAGE_VERSION = '5.1.0';
const PACKAGE_VERSION = '5.2.0';
+1 -1
View File
@@ -1,7 +1,7 @@
name: stream_chat
homepage: https://getstream.io/
description: The official Dart client for Stream Chat, a service for building chat applications.
version: 5.1.0
version: 5.2.0
repository: https://github.com/GetStream/stream-chat-flutter
issue_tracker: https://github.com/GetStream/stream-chat-flutter/issues
@@ -516,6 +516,9 @@ void main() {
});
setUp(() async {
when(() => persistence.updateLastSyncAt(any()))
.thenAnswer((_) => Future.value());
when(persistence.getLastSyncAt).thenAnswer((_) async => null);
client = StreamChatClient(apiKey, chatApi: api, ws: ws)
..chatPersistenceClient = persistence;
await client.connectUser(user, token);
@@ -532,9 +535,12 @@ void main() {
test(
'''should update persistence connectionInfo and lastSync when sync succeeds''',
() async {
// persistence.updateLastSyncAt might be called
// when connecting the user.
// Resetting the logs so we start counting invocations correctly.
reset(persistence);
const cids = ['test-cid-1', 'test-cid-2', 'test-cid-3'];
final lastSyncAt = DateTime.now();
when(() => api.general.sync(cids, lastSyncAt))
.thenAnswer((_) async => SyncResponse()
..events = [
@@ -567,6 +573,10 @@ void main() {
test(
'should work fine if persistence contains sync params',
() async {
// persistence.updateLastSyncAt might be called
// when connecting the user.
// Resetting the logs so we start counting invocations correctly.
reset(persistence);
const cids = ['test-cid-1', 'test-cid-2', 'test-cid-3'];
final lastSyncAt = DateTime.now();
@@ -22,26 +22,37 @@ void main() {
test('addDevice should work', () async {
const deviceId = 'test-device-id';
const pushProvider = PushProvider.firebase;
const pushProvidersMap = {
'apn': PushProvider.apn,
'firebase': PushProvider.firebase,
'huawei': PushProvider.huawei,
'xiaomi': PushProvider.xiaomi,
};
const path = '/devices';
when(() => client.post(
path,
data: {
'id': deviceId,
'push_provider': pushProvider.name,
},
))
.thenAnswer(
(_) async => successResponse(path, data: <String, dynamic>{}));
for (final pushProviderMapEntry in pushProvidersMap.entries) {
final data = {
'id': deviceId,
'push_provider': pushProviderMapEntry.key,
};
when(() {
return client.post(
path,
data: data,
);
}).thenAnswer(
(_) async => successResponse(path, data: <String, dynamic>{}));
final res = await deviceApi.addDevice(deviceId, pushProvider);
final res =
await deviceApi.addDevice(deviceId, pushProviderMapEntry.value);
expect(res, isNotNull);
expect(res, isNotNull);
verify(() => client.post(path, data: any(named: 'data'))).called(1);
verify(() => client.post(path, data: data)).called(1);
}
verifyNoMoreInteractions(client);
expect(pushProvidersMap.length, PushProvider.values.length,
reason: 'All PushProvider should be tested');
});
test('addDevice should work with pushProviderName', () async {
+16 -2
View File
@@ -1,10 +1,24 @@
## Upcomming
## Upcoming
🐞 Fixed
- [[#1424]](https://github.com/GetStream/stream-chat-flutter/issues/1424) Fixed a render issue when showing messages starting with 4 whitespaces.
- Fixed a bug where the `AttachmentPickerBottomSheet` was not able to identify the mobile browser.
## 5.2.0
✅ Added
- Added a new `bottomRowBuilderWithDefaultWidget` parameter to `StreamMessageWidget` which contains a third parameter (default `BottomRow` widget with `copyWith` method available) to allow easier customization.
🔄 Changed
- Updated `lottie` dependency to `^2.0.0`
- Updated `desktop_drop` dependency to `^0.4.0`
- Updated `connectivity_plus` dependency to `^3.0.2`
- Updated `dart_vlc` dependency to `^0.4.0`
- Updated `file_picker` dependency to `^5.2.4`
- Deprecated `StreamMessageWidget.bottomRowBuilder` in favor of `StreamMessageWidget.bottomRowBuilderWithDefaultWidget`.
- Deprecated `StreamMessageWidget.deletedBottomRowBuilder` in favor of `StreamMessageWidget.bottomRowBuilderWithDefaultWidget`.
- Deprecated `StreamMessageWidget.usernameBuilder` in favor of `StreamMessageWidget.bottomRowBuilderWithDefaultWidget`.
🐞 Fixed
- [[#1379]](https://github.com/GetStream/stream-chat-flutter/issues/1379) Fixed "Issues with photo attachments on web", where the cached image attachment would not render while uploading.
@@ -1318,4 +1332,4 @@ The property showVideoFullScreen was added mainly because of this issue brianega
## 0.0.1
- First release
- First release
@@ -8,7 +8,6 @@
#include <dart_vlc/dart_vlc_plugin.h>
#include <desktop_drop/desktop_drop_plugin.h>
#include <file_selector_linux/file_selector_plugin.h>
#include <screen_retriever/screen_retriever_plugin.h>
#include <sqlite3_flutter_libs/sqlite3_flutter_libs_plugin.h>
#include <url_launcher_linux/url_launcher_plugin.h>
@@ -21,9 +20,6 @@ void fl_register_plugins(FlPluginRegistry* registry) {
g_autoptr(FlPluginRegistrar) desktop_drop_registrar =
fl_plugin_registry_get_registrar_for_plugin(registry, "DesktopDropPlugin");
desktop_drop_plugin_register_with_registrar(desktop_drop_registrar);
g_autoptr(FlPluginRegistrar) file_selector_linux_registrar =
fl_plugin_registry_get_registrar_for_plugin(registry, "FileSelectorPlugin");
file_selector_plugin_register_with_registrar(file_selector_linux_registrar);
g_autoptr(FlPluginRegistrar) screen_retriever_registrar =
fl_plugin_registry_get_registrar_for_plugin(registry, "ScreenRetrieverPlugin");
screen_retriever_plugin_register_with_registrar(screen_retriever_registrar);
@@ -5,7 +5,6 @@
list(APPEND FLUTTER_PLUGIN_LIST
dart_vlc
desktop_drop
file_selector_linux
screen_retriever
sqlite3_flutter_libs
url_launcher_linux
@@ -1,7 +1,7 @@
import 'dart:async';
import 'package:flutter/foundation.dart' show kIsWeb, defaultTargetPlatform;
import 'package:flutter/material.dart';
import 'package:stream_chat_flutter/platform_widget_builder/src/platform_widget.dart';
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
/// Shows a modal material design bottom sheet.
@@ -107,44 +107,34 @@ Future<T?> showStreamAttachmentPickerModalBottomSheet<T>({
controller: controller,
initialAttachments: initialAttachments,
builder: (context, controller, child) {
return PlatformWidget(
web: (context) {
return webOrDesktopAttachmentPickerBuilder.call(
context: context,
controller: controller,
customOptions: customOptions?.map(
WebOrDesktopAttachmentPickerOption.fromAttachmentPickerOption,
),
attachmentThumbnailSize: attachmentThumbnailSize,
attachmentThumbnailFormat: attachmentThumbnailFormat,
attachmentThumbnailQuality: attachmentThumbnailQuality,
attachmentThumbnailScale: attachmentThumbnailScale,
);
},
mobile: (context) {
return mobileAttachmentPickerBuilder.call(
context: context,
controller: controller,
customOptions: customOptions,
attachmentThumbnailSize: attachmentThumbnailSize,
attachmentThumbnailFormat: attachmentThumbnailFormat,
attachmentThumbnailQuality: attachmentThumbnailQuality,
attachmentThumbnailScale: attachmentThumbnailScale,
);
},
desktop: (context) {
return webOrDesktopAttachmentPickerBuilder.call(
context: context,
controller: controller,
customOptions: customOptions?.map(
WebOrDesktopAttachmentPickerOption.fromAttachmentPickerOption,
),
attachmentThumbnailSize: attachmentThumbnailSize,
attachmentThumbnailFormat: attachmentThumbnailFormat,
attachmentThumbnailQuality: attachmentThumbnailQuality,
attachmentThumbnailScale: attachmentThumbnailScale,
);
},
final currentPlatform = defaultTargetPlatform;
final isWebOrDesktop = kIsWeb ||
currentPlatform == TargetPlatform.macOS ||
currentPlatform == TargetPlatform.linux ||
currentPlatform == TargetPlatform.windows;
if (isWebOrDesktop) {
return webOrDesktopAttachmentPickerBuilder.call(
context: context,
controller: controller,
customOptions: customOptions?.map(
WebOrDesktopAttachmentPickerOption.fromAttachmentPickerOption,
),
attachmentThumbnailSize: attachmentThumbnailSize,
attachmentThumbnailFormat: attachmentThumbnailFormat,
attachmentThumbnailQuality: attachmentThumbnailQuality,
attachmentThumbnailScale: attachmentThumbnailScale,
);
}
return mobileAttachmentPickerBuilder.call(
context: context,
controller: controller,
customOptions: customOptions,
attachmentThumbnailSize: attachmentThumbnailSize,
attachmentThumbnailFormat: attachmentThumbnailFormat,
attachmentThumbnailQuality: attachmentThumbnailQuality,
attachmentThumbnailScale: attachmentThumbnailScale,
);
},
);
@@ -32,6 +32,7 @@ class BottomRow extends StatelessWidget {
this.deletedBottomRowBuilder,
this.onThreadTap,
this.usernameBuilder,
this.sendingIndicatorBuilder,
});
/// {@macro messageIsDeleted}
@@ -88,6 +89,61 @@ class BottomRow extends StatelessWidget {
/// {@macro usernameBuilder}
final Widget Function(BuildContext, Message)? usernameBuilder;
/// {@macro sendingIndicatorBuilder}
final Widget Function(BuildContext, Message)? sendingIndicatorBuilder;
/// {@template copyWith}
/// Creates a copy of [BottomRow] with specified attributes
/// overridden.
/// {@endtemplate}
BottomRow copyWith({
Key? key,
bool? isDeleted,
Message? message,
bool? showThreadReplyIndicator,
bool? showInChannel,
bool? showTimeStamp,
bool? showUsername,
bool? reverse,
bool? showSendingIndicator,
bool? hasUrlAttachments,
bool? isGiphy,
bool? isOnlyEmoji,
StreamMessageThemeData? messageTheme,
StreamChatThemeData? streamChatTheme,
bool? hasNonUrlAttachments,
StreamChatState? streamChat,
Widget Function(BuildContext, Message)? deletedBottomRowBuilder,
void Function(Message)? onThreadTap,
Widget Function(BuildContext, Message)? usernameBuilder,
Widget Function(BuildContext, Message)? sendingIndicatorBuilder,
}) =>
BottomRow(
key: key ?? this.key,
isDeleted: isDeleted ?? this.isDeleted,
message: message ?? this.message,
showThreadReplyIndicator:
showThreadReplyIndicator ?? this.showThreadReplyIndicator,
showInChannel: showInChannel ?? this.showInChannel,
showTimeStamp: showTimeStamp ?? this.showTimeStamp,
showUsername: showUsername ?? this.showUsername,
reverse: reverse ?? this.reverse,
showSendingIndicator: showSendingIndicator ?? this.showSendingIndicator,
hasUrlAttachments: hasUrlAttachments ?? this.hasUrlAttachments,
isGiphy: isGiphy ?? this.isGiphy,
isOnlyEmoji: isOnlyEmoji ?? this.isOnlyEmoji,
messageTheme: messageTheme ?? this.messageTheme,
streamChatTheme: streamChatTheme ?? this.streamChatTheme,
hasNonUrlAttachments: hasNonUrlAttachments ?? this.hasNonUrlAttachments,
streamChat: streamChat ?? this.streamChat,
deletedBottomRowBuilder:
deletedBottomRowBuilder ?? this.deletedBottomRowBuilder,
onThreadTap: onThreadTap ?? this.onThreadTap,
usernameBuilder: usernameBuilder ?? this.usernameBuilder,
sendingIndicatorBuilder:
sendingIndicatorBuilder ?? this.sendingIndicatorBuilder,
);
@override
Widget build(BuildContext context) {
if (isDeleted) {
@@ -147,13 +203,14 @@ class BottomRow extends StatelessWidget {
),
if (showSendingIndicator)
WidgetSpan(
child: SendingIndicatorWrapper(
messageTheme: messageTheme,
message: message,
hasNonUrlAttachments: hasNonUrlAttachments,
streamChat: streamChat,
streamChatTheme: streamChatTheme,
),
child: sendingIndicatorBuilder?.call(context, message) ??
SendingIndicatorWrapper(
messageTheme: messageTheme,
message: message,
hasNonUrlAttachments: hasNonUrlAttachments,
streamChat: streamChat,
streamChatTheme: streamChatTheme,
),
),
]);
@@ -40,7 +40,8 @@ class StreamMessageText extends StatelessWidget {
.translate(language)
.replaceMentions()
.text
?.replaceAll('\n', '\n\n');
?.replaceAll('\n', '\n\n')
.trim();
final themeData = Theme.of(context);
return MarkdownBody(
data: messageText ?? '',
@@ -8,6 +8,7 @@ import 'package:stream_chat_flutter/src/context_menu_items/context_menu_reaction
import 'package:stream_chat_flutter/src/context_menu_items/stream_chat_context_menu_item.dart';
import 'package:stream_chat_flutter/src/dialogs/dialogs.dart';
import 'package:stream_chat_flutter/src/message_actions_modal/message_actions_modal.dart';
import 'package:stream_chat_flutter/src/message_widget/bottom_row.dart';
import 'package:stream_chat_flutter/src/message_widget/message_widget_content.dart';
import 'package:stream_chat_flutter/src/message_widget/reactions/message_reactions_modal.dart';
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
@@ -79,8 +80,15 @@ class StreamMessageWidget extends StatefulWidget {
this.userAvatarBuilder,
this.editMessageInputBuilder,
this.textBuilder,
this.bottomRowBuilder,
this.deletedBottomRowBuilder,
@Deprecated('''
Use [bottomRowBuilderWithDefaultWidget] instead.
Will be removed in the next major version.
''') this.bottomRowBuilder,
this.bottomRowBuilderWithDefaultWidget,
@Deprecated('''
Use [bottomRowBuilderWithDefaultWidget] instead.
Will be removed in the next major version.
''') this.deletedBottomRowBuilder,
this.customAttachmentBuilders,
this.padding,
this.textPadding = const EdgeInsets.symmetric(
@@ -92,11 +100,18 @@ class StreamMessageWidget extends StatefulWidget {
this.onQuotedMessageTap,
this.customActions = const [],
this.onAttachmentTap,
this.usernameBuilder,
@Deprecated('''
Use [bottomRowBuilderWithDefaultWidget] instead.
Will be removed in the next major version.
''') this.usernameBuilder,
this.imageAttachmentThumbnailSize = const Size(400, 400),
this.imageAttachmentThumbnailResizeType = 'clip',
this.imageAttachmentThumbnailCropType = 'center',
}) : attachmentBuilders = {
}) : assert(
bottomRowBuilder == null || bottomRowBuilderWithDefaultWidget == null,
'You can only use one of the two bottom row builders',
),
attachmentBuilders = {
'image': (context, message, attachments) {
final border = RoundedRectangleBorder(
side: attachmentBorderSide ??
@@ -306,7 +321,13 @@ class StreamMessageWidget extends StatefulWidget {
/// {@template bottomRowBuilder}
/// Widget builder for building a bottom row below the message
/// {@endtemplate}
final Widget Function(BuildContext, Message)? bottomRowBuilder;
final BottomRowBuilder? bottomRowBuilder;
/// {@template bottomRowBuilderWithDefaultWidget}
/// Widget builder for building a bottom row below the message.
/// Also contains the default bottom row widget.
/// {@endtemplate}
final BottomRowBuilderWithDefaultWidget? bottomRowBuilderWithDefaultWidget;
/// {@template deletedBottomRowBuilder}
/// Widget builder for building a bottom row below a deleted message
@@ -537,9 +558,19 @@ class StreamMessageWidget extends StatefulWidget {
void Function(Message)? onReplyTap,
Widget Function(BuildContext, Message)? editMessageInputBuilder,
Widget Function(BuildContext, Message)? textBuilder,
Widget Function(BuildContext, Message)? usernameBuilder,
Widget Function(BuildContext, Message)? bottomRowBuilder,
Widget Function(BuildContext, Message)? deletedBottomRowBuilder,
@Deprecated('''
Use [bottomRowBuilderWithDefaultWidget] instead.
Will be removed in the next major version.
''') Widget Function(BuildContext, Message)? usernameBuilder,
@Deprecated('''
Use [bottomRowBuilderWithDefaultWidget] instead.
Will be removed in the next major version.
''') BottomRowBuilder? bottomRowBuilder,
BottomRowBuilderWithDefaultWidget? bottomRowBuilderWithDefaultWidget,
@Deprecated('''
Use [bottomRowBuilderWithDefaultWidget] instead.
Will be removed in the next major version.
''') Widget Function(BuildContext, Message)? deletedBottomRowBuilder,
void Function(BuildContext, Message)? onMessageActions,
Message? message,
StreamMessageThemeData? messageTheme,
@@ -587,6 +618,29 @@ class StreamMessageWidget extends StatefulWidget {
String? imageAttachmentThumbnailResizeType,
String? imageAttachmentThumbnailCropType,
}) {
assert(
bottomRowBuilder == null || bottomRowBuilderWithDefaultWidget == null,
'You can only use one of the two bottom row builders',
);
var _bottomRowBuilderWithDefaultWidget =
bottomRowBuilderWithDefaultWidget ??
this.bottomRowBuilderWithDefaultWidget;
_bottomRowBuilderWithDefaultWidget ??= (context, message, defaultWidget) {
final _bottomRowBuilder = bottomRowBuilder ?? this.bottomRowBuilder;
if (_bottomRowBuilder != null) {
return _bottomRowBuilder(context, message);
}
return defaultWidget.copyWith(
onThreadTap: onThreadTap ?? this.onThreadTap,
usernameBuilder: usernameBuilder ?? this.usernameBuilder,
deletedBottomRowBuilder:
deletedBottomRowBuilder ?? this.deletedBottomRowBuilder,
);
};
return StreamMessageWidget(
key: key ?? this.key,
onMentionTap: onMentionTap ?? this.onMentionTap,
@@ -595,10 +649,7 @@ class StreamMessageWidget extends StatefulWidget {
editMessageInputBuilder:
editMessageInputBuilder ?? this.editMessageInputBuilder,
textBuilder: textBuilder ?? this.textBuilder,
usernameBuilder: usernameBuilder ?? this.usernameBuilder,
bottomRowBuilder: bottomRowBuilder ?? this.bottomRowBuilder,
deletedBottomRowBuilder:
deletedBottomRowBuilder ?? this.deletedBottomRowBuilder,
bottomRowBuilderWithDefaultWidget: _bottomRowBuilderWithDefaultWidget,
onMessageActions: onMessageActions ?? this.onMessageActions,
message: message ?? this.message,
messageTheme: messageTheme ?? this.messageTheme,
@@ -838,52 +889,69 @@ class _StreamMessageWidgetState extends State<StreamMessageWidget>
? Alignment.centerRight
: Alignment.centerLeft,
widthFactor: widget.widthFactor,
child: MessageWidgetContent(
streamChatTheme: _streamChatTheme,
showUsername: showUsername,
showTimeStamp: showTimeStamp,
showThreadReplyIndicator: showThreadReplyIndicator,
showSendingIndicator: showSendingIndicator,
showInChannel: showInChannel,
isGiphy: isGiphy,
isOnlyEmoji: isOnlyEmoji,
hasUrlAttachments: hasUrlAttachments,
messageTheme: widget.messageTheme,
reverse: widget.reverse,
message: widget.message,
hasNonUrlAttachments: hasNonUrlAttachments,
shouldShowReactions: shouldShowReactions,
hasQuotedMessage: hasQuotedMessage,
textPadding: widget.textPadding,
attachmentBuilders: widget.attachmentBuilders,
attachmentPadding: widget.attachmentPadding,
avatarWidth: avatarWidth,
bottomRowPadding: bottomRowPadding,
isFailedState: isFailedState,
isPinned: isPinned,
messageWidget: widget,
showBottomRow: showBottomRow,
showPinHighlight: widget.showPinHighlight,
showReactionPickerIndicator:
widget.showReactionPickerIndicator,
showReactions: showReactions,
showUserAvatar: widget.showUserAvatar,
streamChat: _streamChat,
translateUserAvatar: widget.translateUserAvatar,
deletedBottomRowBuilder: widget.deletedBottomRowBuilder,
onThreadTap: widget.onThreadTap,
shape: widget.shape,
borderSide: widget.borderSide,
borderRadiusGeometry: widget.borderRadiusGeometry,
textBuilder: widget.textBuilder,
onLinkTap: widget.onLinkTap,
onMentionTap: widget.onMentionTap,
onQuotedMessageTap: widget.onQuotedMessageTap,
bottomRowBuilder: widget.bottomRowBuilder,
onUserAvatarTap: widget.onUserAvatarTap,
userAvatarBuilder: widget.userAvatarBuilder,
usernameBuilder: widget.usernameBuilder,
),
child: Builder(builder: (context) {
var _bottomRowBuilderWithDefaultWidget =
widget.bottomRowBuilderWithDefaultWidget;
_bottomRowBuilderWithDefaultWidget ??=
(context, message, defaultWidget) {
final _bottomRowBuilder = widget.bottomRowBuilder;
if (_bottomRowBuilder != null) {
return _bottomRowBuilder(context, message);
}
return defaultWidget.copyWith(
onThreadTap: widget.onThreadTap,
usernameBuilder: widget.usernameBuilder,
deletedBottomRowBuilder: widget.deletedBottomRowBuilder,
);
};
return MessageWidgetContent(
streamChatTheme: _streamChatTheme,
showUsername: showUsername,
showTimeStamp: showTimeStamp,
showThreadReplyIndicator: showThreadReplyIndicator,
showSendingIndicator: showSendingIndicator,
showInChannel: showInChannel,
isGiphy: isGiphy,
isOnlyEmoji: isOnlyEmoji,
hasUrlAttachments: hasUrlAttachments,
messageTheme: widget.messageTheme,
reverse: widget.reverse,
message: widget.message,
hasNonUrlAttachments: hasNonUrlAttachments,
shouldShowReactions: shouldShowReactions,
hasQuotedMessage: hasQuotedMessage,
textPadding: widget.textPadding,
attachmentBuilders: widget.attachmentBuilders,
attachmentPadding: widget.attachmentPadding,
avatarWidth: avatarWidth,
bottomRowPadding: bottomRowPadding,
isFailedState: isFailedState,
isPinned: isPinned,
messageWidget: widget,
showBottomRow: showBottomRow,
showPinHighlight: widget.showPinHighlight,
showReactionPickerIndicator:
widget.showReactionPickerIndicator,
showReactions: showReactions,
showUserAvatar: widget.showUserAvatar,
streamChat: _streamChat,
translateUserAvatar: widget.translateUserAvatar,
shape: widget.shape,
borderSide: widget.borderSide,
borderRadiusGeometry: widget.borderRadiusGeometry,
textBuilder: widget.textBuilder,
onLinkTap: widget.onLinkTap,
onMentionTap: widget.onMentionTap,
onQuotedMessageTap: widget.onQuotedMessageTap,
bottomRowBuilderWithDefaultWidget:
_bottomRowBuilderWithDefaultWidget,
onUserAvatarTap: widget.onUserAvatarTap,
userAvatarBuilder: widget.userAvatarBuilder,
);
}),
),
),
),
@@ -4,6 +4,18 @@ import 'package:stream_chat_flutter/src/message_widget/message_widget_content_co
import 'package:stream_chat_flutter/src/message_widget/reactions/desktop_reactions_builder.dart';
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
/// Signature for the builder function that will be called when the message
/// bottom row is built. Includes the [Message].
typedef BottomRowBuilder = Widget Function(BuildContext, Message);
/// Signature for the builder function that will be called when the message
/// bottom row is built. Includes the [Message] and the default [BottomRow].
typedef BottomRowBuilderWithDefaultWidget = Widget Function(
BuildContext,
Message,
BottomRow,
);
/// {@template messageWidgetContent}
/// The main content of a [StreamMessageWidget].
///
@@ -51,12 +63,28 @@ class MessageWidgetContent extends StatelessWidget {
this.onMentionTap,
this.onLinkTap,
this.textBuilder,
this.bottomRowBuilder,
this.onThreadTap,
this.deletedBottomRowBuilder,
@Deprecated('''
Use [bottomRowBuilderWithDefaultWidget] instead.
Will be removed in the next major version.
''') this.bottomRowBuilder,
this.bottomRowBuilderWithDefaultWidget,
@Deprecated('''
Use [bottomRowBuilderWithDefaultWidget] instead.
Will be removed in the next major version.
''') this.onThreadTap,
@Deprecated('''
Use [bottomRowBuilderWithDefaultWidget] instead.
Will be removed in the next major version.
''') this.deletedBottomRowBuilder,
this.userAvatarBuilder,
this.usernameBuilder,
});
@Deprecated('''
Use [bottomRowBuilderWithDefaultWidget] instead.
Will be removed in the next major version.
''') this.usernameBuilder,
}) : assert(
bottomRowBuilder == null || bottomRowBuilderWithDefaultWidget == null,
'You can only use one of the two bottom row builders',
);
/// {@macro reverse}
final bool reverse;
@@ -152,7 +180,10 @@ class MessageWidgetContent extends StatelessWidget {
final double bottomRowPadding;
/// {@macro bottomRowBuilder}
final Widget Function(BuildContext, Message)? bottomRowBuilder;
final BottomRowBuilder? bottomRowBuilder;
/// {@macro bottomRowBuilderWithDefaultWidget}
final BottomRowBuilderWithDefaultWidget? bottomRowBuilderWithDefaultWidget;
/// {@macro showInChannelIndicator}
final bool showInChannel;
@@ -207,30 +238,7 @@ class MessageWidgetContent extends StatelessWidget {
right: reverse ? bottomRowPadding : 0,
bottom: isPinned && showPinHighlight ? 6.0 : 0.0,
),
child: bottomRowBuilder?.call(
context,
message,
) ??
BottomRow(
message: message,
reverse: reverse,
messageTheme: messageTheme,
hasUrlAttachments: hasUrlAttachments,
isOnlyEmoji: isOnlyEmoji,
isDeleted: message.isDeleted,
isGiphy: isGiphy,
showInChannel: showInChannel,
showSendingIndicator: showSendingIndicator,
showThreadReplyIndicator: showThreadReplyIndicator,
showTimeStamp: showTimeStamp,
showUsername: showUsername,
streamChatTheme: streamChatTheme,
onThreadTap: onThreadTap,
deletedBottomRowBuilder: deletedBottomRowBuilder,
streamChat: streamChat,
hasNonUrlAttachments: hasNonUrlAttachments,
usernameBuilder: usernameBuilder,
),
child: _buildBottomRow(context),
),
Padding(
padding: EdgeInsets.only(
@@ -457,4 +465,39 @@ class MessageWidgetContent extends StatelessWidget {
),
);
}
Widget _buildBottomRow(BuildContext context) {
final defaultWidget = BottomRow(
message: message,
reverse: reverse,
messageTheme: messageTheme,
hasUrlAttachments: hasUrlAttachments,
isOnlyEmoji: isOnlyEmoji,
isDeleted: message.isDeleted,
isGiphy: isGiphy,
showInChannel: showInChannel,
showSendingIndicator: showSendingIndicator,
showThreadReplyIndicator: showThreadReplyIndicator,
showTimeStamp: showTimeStamp,
showUsername: showUsername,
streamChatTheme: streamChatTheme,
onThreadTap: onThreadTap,
deletedBottomRowBuilder: deletedBottomRowBuilder,
streamChat: streamChat,
hasNonUrlAttachments: hasNonUrlAttachments,
usernameBuilder: usernameBuilder,
);
if (bottomRowBuilder != null) {
return bottomRowBuilder!(context, message);
} else if (bottomRowBuilderWithDefaultWidget != null) {
return bottomRowBuilderWithDefaultWidget!(
context,
message,
defaultWidget,
);
}
return defaultWidget;
}
}
+5 -5
View File
@@ -1,12 +1,12 @@
name: stream_chat_flutter
homepage: https://github.com/GetStream/stream-chat-flutter
description: Stream Chat official Flutter SDK. Build your own chat experience using Dart and Flutter.
version: 5.1.0
version: 5.2.0
repository: https://github.com/GetStream/stream-chat-flutter
issue_tracker: https://github.com/GetStream/stream-chat-flutter/issues
environment:
sdk: '>=2.17.0 <3.0.0'
sdk: ">=2.17.0 <3.0.0"
flutter: ">=1.17.0"
dependencies:
@@ -14,12 +14,12 @@ dependencies:
chewie: ^1.3.4
collection: ^1.15.0
contextmenu: ^3.0.0
dart_vlc: ^0.3.0
dart_vlc: ^0.4.0
desktop_drop: ^0.4.0
diacritic: ^0.1.3
dio: ^4.0.6
ezanimation: ^0.6.0
file_picker: ^4.1.3
file_picker: ^5.2.4
file_selector: ^0.9.0
flutter:
sdk: flutter
@@ -39,7 +39,7 @@ dependencies:
rxdart: ^0.27.0
share_plus: ^4.5.0
shimmer: ^2.0.0
stream_chat_flutter_core: ^5.1.0
stream_chat_flutter_core: ^5.2.0
synchronized: ^3.0.0
thumblr: ^0.0.4
url_launcher: ^6.1.0
@@ -163,7 +163,7 @@ void main() {
);
testWidgets(
'tapping on reply should pop',
'tapping on reply should invoke callback',
(WidgetTester tester) async {
final client = MockClient();
final clientState = MockClientState();
@@ -174,7 +174,7 @@ void main() {
final themeData = ThemeData();
final streamTheme = StreamChatThemeData.fromTheme(themeData);
final mockObserver = MockNavigatorObserver();
final mockCallback = MockVoidCallback();
final attachment = Attachment(
type: 'image',
@@ -192,7 +192,6 @@ void main() {
await tester.pumpWidget(
MaterialApp(
theme: themeData,
navigatorObservers: [mockObserver],
home: StreamChat(
streamChatThemeData: streamTheme,
client: client,
@@ -200,13 +199,14 @@ void main() {
child: AttachmentActionsModal(
message: message,
attachment: attachment,
onReply: mockCallback,
),
),
),
),
);
await tester.tap(find.text('Reply'));
verify(() => mockObserver.didPop(any(), any()));
verify(mockCallback.call);
},
);
@@ -1,4 +1,4 @@
## Upcomming
## 5.2.0
🔄 Changed
@@ -1,7 +1,7 @@
name: stream_chat_flutter_core
homepage: https://github.com/GetStream/stream-chat-flutter
description: Stream Chat official Flutter SDK Core. Build your own chat experience using Dart and Flutter.
version: 5.1.0
version: 5.2.0
repository: https://github.com/GetStream/stream-chat-flutter
issue_tracker: https://github.com/GetStream/stream-chat-flutter/issues
@@ -17,7 +17,7 @@ dependencies:
freezed_annotation: ^2.0.3
meta: ^1.3.0
rxdart: ^0.27.0
stream_chat: ^5.1.0
stream_chat: ^5.2.0
dev_dependencies:
build_runner: ^2.0.1
dart_code_metrics: ^4.4.0
@@ -1,3 +1,13 @@
## Upcoming
✅ Added
* Added support for [Catalan](https://github.com/GetStream/stream-chat-flutter/blob/master/packages/stream_chat_localizations/lib/src/stream_chat_localizations_ca.dart) locale.
🔄 Changed
* Some of the `Spanish` translations have been updated/changed for better understanding.
## 4.0.0
🔄 Changed
@@ -35,6 +35,7 @@ At the moment we support the following languages:
- [Italian](https://github.com/GetStream/stream-chat-flutter/blob/master/packages/stream_chat_localizations/lib/src/stream_chat_localizations_it.dart)
- [French](https://github.com/GetStream/stream-chat-flutter/blob/master/packages/stream_chat_localizations/lib/src/stream_chat_localizations_fr.dart)
- [Spanish](https://github.com/GetStream/stream-chat-flutter/blob/master/packages/stream_chat_localizations/lib/src/stream_chat_localizations_es.dart)
- [Catalan](https://github.com/GetStream/stream-chat-flutter/blob/master/packages/stream_chat_localizations/lib/src/stream_chat_localizations_ca.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)
@@ -75,6 +76,7 @@ class MyApp extends StatelessWidget {
Locale('fr'),
Locale('it'),
Locale('es'),
Locale('ca'),
Locale('ja'),
Locale('ko'),
Locale('pt'),
@@ -123,6 +125,7 @@ Example:
<string>fr</string>
<string>it</string>
<string>es</string>
<string>ca</string>
<string>ja</string>
<string>ko</string>
<string>pt</string>
@@ -3,6 +3,7 @@ import 'package:flutter/material.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
part 'stream_chat_localizations_ca.dart';
part 'stream_chat_localizations_de.dart';
part 'stream_chat_localizations_en.dart';
part 'stream_chat_localizations_es.dart';
@@ -28,6 +29,7 @@ const kStreamChatSupportedLanguages = {
'fr',
'it',
'es',
'ca',
'ja',
'ko',
'pt',
@@ -65,6 +67,8 @@ GlobalStreamChatLocalizations? getStreamChatTranslation(Locale locale) {
return const StreamChatLocalizationsIt();
case 'es':
return const StreamChatLocalizationsEs();
case 'ca':
return const StreamChatLocalizationsCa();
case 'ja':
return const StreamChatLocalizationsJa();
case 'ko':
@@ -0,0 +1,451 @@
part of 'stream_chat_localizations.dart';
/// The translations for Catalan (`ca`).
class StreamChatLocalizationsCa extends GlobalStreamChatLocalizations {
/// Create an instance of the translation bundle for Catalan.
const StreamChatLocalizationsCa({super.localeName = 'ca'});
@override
String get launchUrlError => "No s'ha pogut obrir la url";
@override
String get loadingUsersError => "Error de càrrega de l'usuari";
@override
String get noUsersLabel => 'Actualment no hi ha usuaris';
@override
String get retryLabel => 'Torna-ho a provar';
@override
String get userLastOnlineText => 'Última vegada en línia';
@override
String get userOnlineText => 'En línia';
@override
String userTypingText(Iterable<User> users) {
if (users.isEmpty) return '';
final first = users.first;
if (users.length == 1) {
return '${first.name} està escrivint';
}
return '${first.name} y ${users.length - 1} estan escrivint';
}
@override
String get threadReplyLabel => 'Respon al fil';
@override
String get onlyVisibleToYouText => 'Només visible per vostè';
@override
String threadReplyCountText(int count) => '$count respostes al fil';
@override
String attachmentsUploadProgressText({
required int remaining,
required int total,
}) =>
'Transferència en curs $remaining/$total ...';
@override
String pinnedByUserText({
required User pinnedBy,
required User currentUser,
}) {
final pinnedByCurrentUser = currentUser.id == pinnedBy.id;
if (pinnedByCurrentUser) return 'Fixat per tu';
return 'Fixat per ${pinnedBy.name}';
}
@override
String get sendMessagePermissionError =>
'No tens permís per enviar missatges';
@override
String get emptyMessagesText => 'Actualment no hi ha missatges';
@override
String get genericErrorText => 'Hi ha hagut un problema';
@override
String get loadingMessagesError =>
'Hi ha hagut un error mentres carregava el missatge';
@override
String resultCountText(int count) => '$count resultats';
@override
String get messageDeletedText => 'Aquest missatge ha estat esborrat.';
@override
String get messageDeletedLabel => 'Missatge esborrat';
@override
String get messageReactionsLabel => 'Reaccions dels missatges';
@override
String get emptyChatMessagesText => 'Encara no hi ha missatges...';
@override
String threadSeparatorText(int replyCount) {
if (replyCount == 1) return '1 resposta';
return '$replyCount respostes';
}
@override
String get connectedLabel => 'Connectat';
@override
String get disconnectedLabel => 'Desconnectat';
@override
String get reconnectingLabel => 'Reconnectant...';
@override
String get alsoSendAsDirectMessageLabel =>
'Enviar també com a missatge directe';
@override
String get addACommentOrSendLabel => 'Afegir un comentari o enviar';
@override
String get searchGifLabel => 'Cerca de GIFs';
@override
String get writeAMessageLabel => 'Escriure un missatge';
@override
String get instantCommandsLabel => 'Commandes instantànies';
@override
String fileTooLargeAfterCompressionError(double limitInMB) =>
'El fitxer és massa gran descargar-lo. '
'La mida màxima del fitxer és de $limitInMB MB. '
'Hem intentat comprimir-lo, pero ha estat suficient.';
@override
String fileTooLargeError(double limitInMB) =>
'El fitxer és massa gran per descargar-lo. '
'El límit de mida dels fitxers és de $limitInMB MB.';
@override
String get couldNotReadBytesFromFileError =>
"No s'han pogut llegir els bytes del fitxer.";
@override
String get addAFileLabel => 'Afegir un fitxer';
@override
String get photoFromCameraLabel => 'Foto de la càmera';
@override
String get uploadAFileLabel => 'Transferir un fitxer';
@override
String get uploadAPhotoLabel => 'Pujar una foto';
@override
String get uploadAVideoLabel => 'Pujar un vídeo';
@override
String get videoFromCameraLabel => 'Vídeo de la càmera';
@override
String get okLabel => 'Vale';
@override
String get somethingWentWrongError => 'Alguna cosa ha anat malament';
@override
String get addMoreFilesLabel => 'Afegir més fitxers';
@override
String get enablePhotoAndVideoAccessMessage =>
"Si us plau, permeti l'accés a les seves fotos"
'\ni vídeos per a que pugui compartir-los.';
@override
String get allowGalleryAccessMessage => "Permetre l'accés a la galeria";
@override
String get flagMessageLabel => 'Reportar un missatge';
@override
String get flagMessageQuestion =>
"¿Vol enviar una còpia d'aquest missatge a un"
'\nmoderador per una major investigació?';
@override
String get flagLabel => 'REPORTAR';
@override
String get cancelLabel => 'CANCELAR';
@override
String get flagMessageSuccessfulLabel => 'Missatge reportat';
@override
String get flagMessageSuccessfulText =>
'Aquest missatge ha estat reportat a un moderador.';
@override
String get deleteLabel => 'ESBORRAR';
@override
String get deleteMessageLabel => 'Esborrar el missatge';
@override
String get deleteMessageQuestion =>
'¿Estàs segur de que vols esborrar aquest\nmissatge de forma permanent?';
@override
String get operationCouldNotBeCompletedText =>
"L'operació no s'ha pogut completar.";
@override
String get replyLabel => 'Respondre';
@override
String togglePinUnpinText({required bool pinned}) {
if (pinned) return 'Desfixar de la conversa';
return 'Fixar a la conversa';
}
@override
String toggleDeleteRetryDeleteMessageText({required bool isDeleteFailed}) {
if (isDeleteFailed) return 'Reintentar esborrar el misssatge';
return 'Esborrar el misssatge';
}
@override
String get copyMessageLabel => 'Copiar el misssatge';
@override
String get editMessageLabel => 'Editar el misssatge';
@override
String toggleResendOrResendEditedMessage({required bool isUpdateFailed}) {
if (isUpdateFailed) return 'Reenviar el missatge modificat';
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 'avui';
} else if (date == yesterday) {
return 'ahir';
} else {
return 'el ${Jiffy(date).MMMd}';
}
}
@override
String sentAtText({required DateTime date, required DateTime time}) =>
'''Enviat el ${_getDay(date)} a les ${Jiffy(time.toLocal()).format('HH:mm')}''';
@override
String get todayLabel => 'Avui';
@override
String get yesterdayLabel => 'Ahir';
@override
String get channelIsMutedText => 'El canal està silenciat';
@override
String get noTitleText => 'Sense títol';
@override
String get letsStartChattingLabel => '¡Comencem a parlar!';
@override
String get sendingFirstMessageLabel =>
'Qué li sembla enviar el seu primer missatge a un amic?';
@override
String get startAChatLabel => 'Iniciar una conversa';
@override
String get loadingChannelsError => 'Error al carregar els canals';
@override
String get deleteConversationLabel => 'Esborrar la conversa';
@override
String get deleteConversationQuestion =>
'Estàs segur de que vols esborrar aquesta conversa?';
@override
String get streamChatLabel => 'Stream Chat';
@override
String get searchingForNetworkText => 'Buscant xarxa';
@override
String get offlineLabel => 'Sense connexió...';
@override
String get tryAgainLabel => 'Torna-ho a provar';
@override
String membersCountText(int count) {
if (count == 1) return '1 membre';
return '$count membres';
}
@override
String watchersCountText(int count) {
if (count == 1) return '1 En línea';
return '$count En línea';
}
@override
String get viewInfoLabel => 'Veure informació';
@override
String get leaveGroupLabel => 'Sortir del Grup';
@override
String get leaveLabel => 'SORTIR';
@override
String get leaveConversationLabel => 'Sortir de la conversa';
@override
String get leaveConversationQuestion =>
"Estàs segur de que vol sortir d'aquesta conversa?";
@override
String get showInChatLabel => 'Mostrar al chat';
@override
String get saveImageLabel => 'Guardar la imatge';
@override
String get saveVideoLabel => 'Guardar el vídeo';
@override
String get uploadErrorLabel => 'ERROR DE TRANSFERENCIA';
@override
String get giphyLabel => 'Giphy';
@override
String get shuffleLabel => 'Remenar';
@override
String get sendLabel => 'Enviar';
@override
String get withText => 'amb';
@override
String get inText => 'a';
@override
String get youText => 'Vostè';
@override
String galleryPaginationText({
required int currentPage,
required int totalPages,
}) =>
'${currentPage + 1} de $totalPages';
@override
String get fileText => 'Fitxer';
@override
String get replyToMessageLabel => 'Respondre al missatge';
@override
String attachmentLimitExceedError(int limit) =>
'No és possible afegir més de $limit fitxers adjunts';
@override
String get viewLibrary => 'Veure llibreria';
@override
String get slowModeOnLabel => 'Mode lent activat';
@override
String get downloadLabel => 'Descarregar';
@override
String toggleMuteUnmuteUserText({required bool isMuted}) {
if (isMuted) {
return "Activar so de l'usuari";
} else {
return 'Silenciar usuari';
}
}
@override
String toggleMuteUnmuteGroupQuestion({required bool isMuted}) {
if (isMuted) {
return "Estàs segur de que vols activar el so d'aquest grup?";
} else {
return 'Estàs segur de que vols silenciar aquest grup?';
}
}
@override
String toggleMuteUnmuteUserQuestion({required bool isMuted}) {
if (isMuted) {
return "Estàs segur de que vols activar el so d'aquest usuari";
} else {
return 'Estàs seguro de que vols silenciar aquest usuari?';
}
}
@override
String toggleMuteUnmuteAction({required bool isMuted}) {
if (isMuted) {
return 'ACTIVAR SO';
} else {
return 'SILENCIAR';
}
}
@override
String toggleMuteUnmuteGroupText({required bool isMuted}) {
if (isMuted) {
return 'Activar so del grup';
} else {
return 'Silenciar grup';
}
}
@override
String get linkDisabledDetails =>
'No es permet enviar enllaços a aquesta conversa.';
@override
String get linkDisabledError => 'Els enllaços estan deshabilitats';
@override
String unreadMessagesSeparatorText(int unreadCount) {
if (unreadCount == 1) {
return '1 missatge no llegit';
}
return '$unreadCount missatges no llegits';
}
@override
String get enableFileAccessMessage => "Habiliti l'accés als fitxers"
'\nper poder compartir-los amb amics.';
@override
String get allowFileAccessMessage => "Permetre l'accés als fitxers";
}
@@ -84,7 +84,7 @@ class StreamChatLocalizationsEs extends GlobalStreamChatLocalizations {
String get messageDeletedLabel => 'Mensaje borrado';
@override
String get messageReactionsLabel => 'Reacciones a los mensajes';
String get messageReactionsLabel => 'Reacciones de los mensajes';
@override
String get emptyChatMessagesText => 'Todavía no hay charlas aquí...';
@@ -165,7 +165,7 @@ class StreamChatLocalizationsEs extends GlobalStreamChatLocalizations {
@override
String get enablePhotoAndVideoAccessMessage =>
'Por favor, permita el acceso a sus fotos'
'\ny vídeos para que puedas compartirlos con sus amigos.';
'\ny vídeos para que pueda compartirlos con sus amigos.';
@override
String get allowGalleryAccessMessage => 'Permitir el acceso a su galería';
@@ -206,11 +206,11 @@ class StreamChatLocalizationsEs extends GlobalStreamChatLocalizations {
'La operación no pudo completarse.';
@override
String get replyLabel => 'Respuesta';
String get replyLabel => 'Responder';
@override
String togglePinUnpinText({required bool pinned}) {
if (pinned) return 'Desfijar a la conversación';
if (pinned) return 'Desfijar de la conversación';
return 'Fijar a la conversación';
}
@@ -389,14 +389,14 @@ No es posible añadir más de $limit archivos adjuntos
if (isMuted) {
return 'No silenciar usuario';
} else {
return 'Usuario mudo';
return 'Silenciar usuario';
}
}
@override
String toggleMuteUnmuteGroupQuestion({required bool isMuted}) {
if (isMuted) {
return '¿Estás seguro de que quieres activar el silencio de este grupo?';
return '¿Estás seguro de que quieres activar el sonido de este grupo?';
} else {
return '¿Estás seguro de que quieres silenciar a este grupo?';
}