Merge pull request #1712 from GetStream/release/v6.9.0

This commit is contained in:
Sahil Kumar
2023-08-17 15:38:51 +05:30
committed by GitHub
64 changed files with 921 additions and 562 deletions
+1 -1
View File
@@ -3,7 +3,7 @@ name: legacy_version_analyze
env: env:
# Note: The versions below should be manually updated after a new stable # Note: The versions below should be manually updated after a new stable
# version comes out. # version comes out.
flutter_version: "3.7.12" flutter_version: "3.10.6"
on: on:
push: push:
@@ -2,7 +2,7 @@ name: stream_flutter_workflow
env: env:
ACTIONS_ALLOW_UNSECURE_COMMANDS: 'true' ACTIONS_ALLOW_UNSECURE_COMMANDS: 'true'
flutter_version: "3.10.4" flutter_channel: "stable"
on: on:
pull_request: pull_request:
@@ -37,7 +37,7 @@ jobs:
uses: subosito/flutter-action@v2 uses: subosito/flutter-action@v2
with: with:
cache: true cache: true
flutter-version: ${{ env.flutter_version }} channel: ${{ env.flutter_channel }}
- name: "Install Tools" - name: "Install Tools"
run: | run: |
flutter pub global activate melos flutter pub global activate melos
+15
View File
@@ -1,3 +1,18 @@
## 6.8.0
🐞 Fixed
- Fixed `Channel.query` not initializing `ChannelState`.
✅ Added
- Added support for `channel.countUnreadMentions()` to get the count of unread messages mentioning the current user on a
channel. [#1692](https://github.com/GetStream/stream-chat-flutter/issues/1692)
🔄 Changed
- Updated minimum supported `SDK` version to Dart 3.0
## 6.7.0 ## 6.7.0
✅ Added ✅ Added
+2 -2
View File
@@ -5,8 +5,8 @@ publish_to: "none"
version: 1.0.0+1 version: 1.0.0+1
environment: environment:
sdk: '>=2.19.0 <4.0.0' sdk: ">=3.0.0 <4.0.0"
flutter: ">=3.7.0" flutter: ">=3.10.0"
dependencies: dependencies:
cupertino_icons: ^1.0.5 cupertino_icons: ^1.0.5
@@ -1339,26 +1339,6 @@ class Channel {
return _client.markChannelRead(id!, type, messageId: messageId); return _client.markChannelRead(id!, type, messageId: messageId);
} }
/// Loads the initial channel state and watches for changes.
Future<ChannelState> watch({bool presence = false}) async {
ChannelState response;
try {
response = await query(watch: true, presence: presence);
} catch (error, stackTrace) {
if (!_initializedCompleter.isCompleted) {
_initializedCompleter.completeError(error, stackTrace);
}
rethrow;
}
if (state == null) {
_initState(response);
}
return response;
}
void _initState(ChannelState channelState) { void _initState(ChannelState channelState) {
state = ChannelClientState(this, channelState); state = ChannelClientState(this, channelState);
@@ -1370,6 +1350,22 @@ class Channel {
} }
} }
/// Loads the initial channel state and watches for changes.
Future<ChannelState> watch({
bool presence = false,
PaginationParams? messagesPagination,
PaginationParams? membersPagination,
PaginationParams? watchersPagination,
}) {
return query(
watch: true,
presence: presence,
messagesPagination: messagesPagination,
membersPagination: membersPagination,
watchersPagination: watchersPagination,
);
}
/// Stop watching the channel. /// Stop watching the channel.
Future<EmptyResponse> stopWatching() async { Future<EmptyResponse> stopWatching() async {
_checkInitialized(); _checkInitialized();
@@ -1435,7 +1431,7 @@ class Channel {
); );
/// Creates a new channel. /// Creates a new channel.
Future<ChannelState> create() async => query(state: false); Future<ChannelState> create() => query(state: false);
/// Query the API, get messages, members or other channel fields. /// Query the API, get messages, members or other channel fields.
/// ///
@@ -1450,23 +1446,28 @@ class Channel {
PaginationParams? watchersPagination, PaginationParams? watchersPagination,
bool preferOffline = false, bool preferOffline = false,
}) async { }) async {
if (preferOffline && cid != null) { ChannelState? channelState;
final updatedState = await _client.chatPersistenceClient
?.getChannelStateByCid(cid!, messagePagination: messagesPagination);
if (updatedState != null &&
updatedState.messages != null &&
updatedState.messages!.isNotEmpty) {
if (this.state == null) {
_initState(updatedState);
} else {
this.state?.updateChannelState(updatedState);
}
return updatedState;
}
}
try { try {
final updatedState = await _client.queryChannel( // If we prefer offline, we first try to get the channel state from the
// offline storage.
if (preferOffline && !watch && cid != null) {
final persistenceClient = _client.chatPersistenceClient;
if (persistenceClient != null) {
final cachedState = await persistenceClient.getChannelStateByCid(
cid!,
messagePagination: messagesPagination,
);
// If the cached state contains messages, we can use it.
if (cachedState.messages?.isNotEmpty == true) {
channelState = cachedState;
}
}
}
// If we still don't have the channelState, we try to get it from the API.
channelState ??= await _client.queryChannel(
type, type,
channelId: id, channelId: id,
channelData: _extraData, channelData: _extraData,
@@ -1479,18 +1480,35 @@ class Channel {
); );
if (_id == null) { if (_id == null) {
_id = updatedState.channel!.id; _id = channelState.channel!.id;
_cid = updatedState.channel!.cid; _cid = channelState.channel!.cid;
} }
this.state?.updateChannelState(updatedState); // Initialize the channel state if it's not initialized yet.
return updatedState; if (this.state == null) {
} catch (e) { _initState(channelState);
if (_client.persistenceEnabled) { } else {
return _client.chatPersistenceClient!.getChannelStateByCid( // Otherwise, update the channel state.
cid!, this.state?.updateChannelState(channelState);
messagePagination: messagesPagination, }
);
return channelState;
} catch (e, stk) {
// If we failed to get the channel state from the API and we were not
// supposed to watch the channel, we will try to get the channel state
// from the offline storage.
if (watch == false) {
if (_client.persistenceEnabled) {
return _client.chatPersistenceClient!.getChannelStateByCid(
cid!,
messagePagination: messagesPagination,
);
}
}
// Otherwise, we will just rethrow the error.
if (!_initializedCompleter.isCompleted) {
_initializedCompleter.completeError(e, stk);
} }
rethrow; rethrow;
@@ -2334,6 +2352,26 @@ class ChannelClientState {
!isThreadMessage; !isThreadMessage;
} }
/// Counts the number of unread messages mentioning the current user.
///
/// **NOTE**: The method relies on the [Channel.messages] list and doesn't do
/// any API call. Therefore, the count might be not reliable as it relies on
/// the local data.
int countUnreadMentions() {
final lastRead = currentUserRead?.lastRead;
final userId = _channel.client.state.currentUser?.id;
var count = 0;
for (final message in messages) {
if (_countMessageAsUnread(message) &&
(lastRead == null || message.createdAt.isAfter(lastRead)) &&
message.mentionedUsers.any((user) => user.id == userId) == true) {
count++;
}
}
return count;
}
/// Update threads with updated information about messages. /// Update threads with updated information about messages.
void updateThreadInfo(String parentId, List<Message> messages) { void updateThreadInfo(String parentId, List<Message> messages) {
final newThreads = Map<String, List<Message>>.from(threads); final newThreads = Map<String, List<Message>>.from(threads);
@@ -1696,7 +1696,7 @@ class ClientState {
void updateUsers(List<User?> userList) { void updateUsers(List<User?> userList) {
final newUsers = { final newUsers = {
...users, ...users,
for (var user in userList) for (final user in userList)
if (user != null) user.id: user, if (user != null) user.id: user,
}; };
_usersController.add(newUsers); _usersController.add(newUsers);
@@ -2,7 +2,7 @@ import 'dart:async';
import 'package:uuid/uuid.dart'; import 'package:uuid/uuid.dart';
/// ///
class TimerHelper { mixin class TimerHelper {
final _uuid = const Uuid(); final _uuid = const Uuid();
late final _timers = <String, Timer>{}; late final _timers = <String, Timer>{};
+1 -1
View File
@@ -3,4 +3,4 @@ import 'package:stream_chat/src/client/client.dart';
/// Current package version /// Current package version
/// Used in [StreamChatClient] to build the `x-stream-client` header /// Used in [StreamChatClient] to build the `x-stream-client` header
// ignore: constant_identifier_names // ignore: constant_identifier_names
const PACKAGE_VERSION = '6.7.0'; const PACKAGE_VERSION = '6.8.0';
+13 -13
View File
@@ -1,24 +1,24 @@
name: stream_chat name: stream_chat
homepage: https://getstream.io/ homepage: https://getstream.io/
description: The official Dart client for Stream Chat, a service for building chat applications. description: The official Dart client for Stream Chat, a service for building chat applications.
version: 6.7.0 version: 6.8.0
repository: https://github.com/GetStream/stream-chat-flutter repository: https://github.com/GetStream/stream-chat-flutter
issue_tracker: https://github.com/GetStream/stream-chat-flutter/issues issue_tracker: https://github.com/GetStream/stream-chat-flutter/issues
environment: environment:
sdk: '>=2.19.0 <4.0.0' sdk: '>=3.0.0 <4.0.0'
dependencies: dependencies:
async: ^2.10.0 async: ^2.11.0
collection: ^1.17.0 collection: ^1.17.1
dio: ^5.2.1+1 dio: ^5.3.2
equatable: ^2.0.5 equatable: ^2.0.5
freezed_annotation: ^2.2.0 freezed_annotation: ^2.4.1
http_parser: ^4.0.2 http_parser: ^4.0.2
jose: ^0.3.3 jose: ^0.3.4
json_annotation: ^4.8.1 json_annotation: ^4.8.1
logging: ^1.2.0 logging: ^1.2.0
meta: ^1.8.0 meta: ^1.9.1
mime: ^1.0.4 mime: ^1.0.4
rate_limiter: ^1.0.0 rate_limiter: ^1.0.0
rxdart: ^0.27.7 rxdart: ^0.27.7
@@ -27,8 +27,8 @@ dependencies:
web_socket_channel: ^2.4.0 web_socket_channel: ^2.4.0
dev_dependencies: dev_dependencies:
build_runner: ^2.3.3 build_runner: ^2.4.6
freezed: ^2.4.0 freezed: ^2.4.2
json_serializable: ^6.6.2 json_serializable: ^6.7.1
mocktail: ^0.3.0 mocktail: ^1.0.0
test: ^1.24.3 test: ^1.24.6
@@ -975,7 +975,7 @@ void main() {
// skipping initial seed event -> {} users // skipping initial seed event -> {} users
client.state.usersStream.skip(1), client.state.usersStream.skip(1),
emitsInOrder([ emitsInOrder([
{for (var user in users) user.id: user}, {for (final user in users) user.id: user},
]), ]),
); );
@@ -1,3 +1,5 @@
// ignore_for_file: invalid_use_of_protected_member
import 'package:dio/dio.dart'; import 'package:dio/dio.dart';
import 'package:stream_chat/src/core/http/interceptor/additional_headers_interceptor.dart'; import 'package:stream_chat/src/core/http/interceptor/additional_headers_interceptor.dart';
import 'package:stream_chat/stream_chat.dart'; import 'package:stream_chat/stream_chat.dart';
@@ -1,3 +1,5 @@
// ignore_for_file: invalid_use_of_protected_member
import 'package:dio/dio.dart'; import 'package:dio/dio.dart';
import 'package:mocktail/mocktail.dart'; import 'package:mocktail/mocktail.dart';
import 'package:stream_chat/src/core/http/interceptor/auth_interceptor.dart'; import 'package:stream_chat/src/core/http/interceptor/auth_interceptor.dart';
@@ -1,3 +1,5 @@
// ignore_for_file: invalid_use_of_protected_member
import 'package:dio/dio.dart'; import 'package:dio/dio.dart';
import 'package:mocktail/mocktail.dart'; import 'package:mocktail/mocktail.dart';
import 'package:stream_chat/src/core/http/connection_id_manager.dart'; import 'package:stream_chat/src/core/http/connection_id_manager.dart';
+155 -60
View File
@@ -1,3 +1,38 @@
## 6.9.0
🐞 Fixed
- [[#1702]](https://github.com/GetStream/stream-chat-flutter/issues/1702)
Fixed `Message.replaceMentions` not treating `@usernames` as mentions.
- [[#1694]](https://github.com/GetStream/stream-chat-flutter/issues/1694) Fixed Video player buttons
getting covered by bottom toolbar.
✅ Added
- Added support for listening error events in AttachmentPickerBottomSheet.
- Added support for overriding the `MessageWidget.onReactionTap` callback.
- Added support for `StreamMessageInput.contentInsertionConfiguration` to specify the content insertion configuration.
[#1613](https://github.com/GetStream/stream-chat-flutter/issues/1613)
```dart
StreamMessageInput(
...,
contentInsertionConfiguration: ContentInsertionConfiguration(
onContentInserted: (content) {
// Do something with the content.
controller.addAttachment(...);
},
),
)
```
🔄 Changed
- Updated minimum supported `SDK` version to Flutter 3.10/Dart 3.0
- Updated `stream_chat_flutter_core` dependency
to [`6.8.0`](https://pub.dev/packages/stream_chat_flutter_core/changelog).
- Updated jiffy dependency to ^6.2.1.
## 6.8.1 ## 6.8.1
🐞 Fixed 🐞 Fixed
@@ -28,43 +63,57 @@
🐞 Fixed 🐞 Fixed
- [[#1620]](https://github.com/GetStream/stream-chat-flutter/issues/1620) Fixed messages Are Not Hard Deleting even - [[#1620]](https://github.com/GetStream/stream-chat-flutter/issues/1620) Fixed messages Are Not
Hard Deleting even
after overriding the `onConfirmDeleteTap` callback. after overriding the `onConfirmDeleteTap` callback.
- [[#1621]](https://github.com/GetStream/stream-chat-flutter/issues/1621) Fixed `createdAtStyle` null check error - [[#1621]](https://github.com/GetStream/stream-chat-flutter/issues/1621) Fixed `createdAtStyle`
null check error
in `SendingIndicatorBuilder`. in `SendingIndicatorBuilder`.
- [[#1069]](https://github.com/GetStream/stream-chat-flutter/issues/1069) Fixed message swipe to reply using same - [[#1069]](https://github.com/GetStream/stream-chat-flutter/issues/1069) Fixed message swipe to
direction for both current user and other users. It now uses `SwipeDirection.startToEnd` for current user reply using same
direction for both current user and other users. It now uses `SwipeDirection.startToEnd` for
current user
and `SwipeDirection.endToStart` for other users. and `SwipeDirection.endToStart` for other users.
- [[#1590]](https://github.com/GetStream/stream-chat-flutter/issues/1590) - [[#1590]](https://github.com/GetStream/stream-chat-flutter/issues/1590)
Fixed `StreamMessageWidget.showReactionPickerIndicator` not toggling the reaction picker indicator visibility. Fixed `StreamMessageWidget.showReactionPickerIndicator` not toggling the reaction picker indicator
- [[#1639]](https://github.com/GetStream/stream-chat-flutter/issues/1639) Fixed attachments not showing in gallery view visibility.
- [[#1639]](https://github.com/GetStream/stream-chat-flutter/issues/1639) Fixed attachments not
showing in gallery view
even after saving them to the device. even after saving them to the device.
> **Note** > **Note**
> This fix depends on the [image_gallery_saver](https://pub.dev/packages/image_gallery_saver) plugin. Make sure to add > This fix depends on the [image_gallery_saver](https://pub.dev/packages/image_gallery_saver)
plugin. Make sure to add
necessary permissions in your App as per the plugin documentation. necessary permissions in your App as per the plugin documentation.
- [[#1642]](https://github.com/GetStream/stream-chat-flutter/issues/1642) Fixed `StreamMessageWidget.widthFactor` not - [[#1642]](https://github.com/GetStream/stream-chat-flutter/issues/1642)
Fixed `StreamMessageWidget.widthFactor` not
working on web and desktop platforms. working on web and desktop platforms.
✅ Added ✅ Added
- Added support for customizing attachments in `StreamMessageInput`. Use various properties mentioned - Added support for customizing attachments in `StreamMessageInput`. Use various properties
mentioned
below. [#1511](https://github.com/GetStream/stream-chat-flutter/issues/1511) below. [#1511](https://github.com/GetStream/stream-chat-flutter/issues/1511)
* `StreamMessageInput.attachmentListBuilder` to customize the attachment list. * `StreamMessageInput.attachmentListBuilder` to customize the attachment list.
* `StreamMessageInput.fileAttachmentListBuilder` to customize the file attachment list. * `StreamMessageInput.fileAttachmentListBuilder` to customize the file attachment list.
* `StreamMessageInput.mediaAttachmentListBuilder` to customize the media attachment list. Includes images, videos * `StreamMessageInput.mediaAttachmentListBuilder` to customize the media attachment list.
Includes images, videos
and gifs. and gifs.
* `StreamMessageInput.fileAttachmentBuilder` to customize the file attachment item shown in `FileAttachmentList`. * `StreamMessageInput.fileAttachmentBuilder` to customize the file attachment item shown
in `FileAttachmentList`.
* `StreamMessageInput.mediaAttachmentBuilder` to customize the media attachment item shown in * `StreamMessageInput.mediaAttachmentBuilder` to customize the media attachment item shown in
`MediaAttachmentList`. `MediaAttachmentList`.
- Added `StreamMessageInput.quotedMessageAttachmentThumbnailBuilders` to customize the thumbnail builders for quoted - Added `StreamMessageInput.quotedMessageAttachmentThumbnailBuilders` to customize the thumbnail
builders for quoted
message attachments. message attachments.
🔄 Changed 🔄 Changed
- Deprecated `StreamMessageInput.attachmentThumbnailBuilders` in favor of `StreamMessageInput.mediaAttachmentBuilder`. - Deprecated `StreamMessageInput.attachmentThumbnailBuilders` in favor
- Deprecated `StreamMessageListView.onMessageSwiped`. Try wrapping the `MessageWidget` with a `Swipeable`, `Dismissible` of `StreamMessageInput.mediaAttachmentBuilder`.
- Deprecated `StreamMessageListView.onMessageSwiped`. Try wrapping the `MessageWidget` with
a `Swipeable`, `Dismissible`
or a custom widget to achieve the swipe to reply behaviour. or a custom widget to achieve the swipe to reply behaviour.
```dart ```dart
@@ -112,7 +161,8 @@
}, },
) )
``` ```
- Deprecated `StreamMessageWidget.showReactionPickerIndicator` in favor of `StreamMessageWidget.showReactionPicker`. - Deprecated `StreamMessageWidget.showReactionPickerIndicator` in favor
of `StreamMessageWidget.showReactionPicker`.
```diff ```diff
StreamMessageWidget( StreamMessageWidget(
@@ -129,16 +179,20 @@
🐞 Fixed 🐞 Fixed
- [[#1600]](https://github.com/GetStream/stream-chat-flutter/issues/1600) Fixed type `ImageDecoderCallback` not found - [[#1600]](https://github.com/GetStream/stream-chat-flutter/issues/1600) Fixed
type `ImageDecoderCallback` not found
error on pre-Flutter 3.10.0 versions. error on pre-Flutter 3.10.0 versions.
- [[#1605]](https://github.com/GetStream/stream-chat-flutter/issues/1605) Fixed Null exception is thrown on message list - [[#1605]](https://github.com/GetStream/stream-chat-flutter/issues/1605) Fixed Null exception is
thrown on message list
for unread messages when `ScrollToBottomButton` is pressed. for unread messages when `ScrollToBottomButton` is pressed.
- [[#1615]](https://github.com/GetStream/stream-chat-flutter/issues/1615) Fixed `StreamAttachmentPickerBottomSheet` not - [[#1615]](https://github.com/GetStream/stream-chat-flutter/issues/1615)
Fixed `StreamAttachmentPickerBottomSheet` not
able to find the `StreamChatTheme` when used in nested MaterialApp. able to find the `StreamChatTheme` when used in nested MaterialApp.
✅ Added ✅ Added
- Added support for `StreamMessageInput.allowedAttachmentPickerTypes` to specify the allowed attachment picker types. - Added support for `StreamMessageInput.allowedAttachmentPickerTypes` to specify the allowed
attachment picker types.
[#1601](https://github.com/GetStream/stream-chat-flutter/issues/1376) [#1601](https://github.com/GetStream/stream-chat-flutter/issues/1376)
```dart ```dart
@@ -151,7 +205,8 @@
) )
``` ```
- Added support for `StreamMessageWidget.onConfirmDeleteTap` to override the default action on delete confirmation. - Added support for `StreamMessageWidget.onConfirmDeleteTap` to override the default action on
delete confirmation.
[#1604](https://github.com/GetStream/stream-chat-flutter/issues/1604) [#1604](https://github.com/GetStream/stream-chat-flutter/issues/1604)
```dart ```dart
@@ -164,8 +219,10 @@
) )
``` ```
- Added support for `StreamMessageWidget.quotedMessageBuilder` and `StreamMessageInput.quotedMessageBuilder` to override - Added support for `StreamMessageWidget.quotedMessageBuilder`
the default quoted message widget. [#1547](https://github.com/GetStream/stream-chat-flutter/issues/1547) and `StreamMessageInput.quotedMessageBuilder` to override
the default quoted message
widget. [#1547](https://github.com/GetStream/stream-chat-flutter/issues/1547)
```dart ```dart
StreamMessageWidget( StreamMessageWidget(
@@ -179,7 +236,8 @@
) )
``` ```
- Added support for `StreamChannelAvatar.ownSpaceAvatarBuilder`, `StreamChannelAvatar.oneToOneAvatarBuilder` and - Added support
for `StreamChannelAvatar.ownSpaceAvatarBuilder`, `StreamChannelAvatar.oneToOneAvatarBuilder` and
`StreamChannelAvatar.groupAvatarBuilder` to override the default avatar `StreamChannelAvatar.groupAvatarBuilder` to override the default avatar
widget.[#1614](https://github.com/GetStream/stream-chat-flutter/issues/1614) widget.[#1614](https://github.com/GetStream/stream-chat-flutter/issues/1614)
@@ -211,10 +269,13 @@
🐞 Fixed 🐞 Fixed
- [[#1592]](https://github.com/GetStream/stream-chat-flutter/issues/1592) Fixed broken attachment download on web. - [[#1592]](https://github.com/GetStream/stream-chat-flutter/issues/1592) Fixed broken attachment
- [[#1591]](https://github.com/GetStream/stream-chat-flutter/issues/1591) Fixed `StreamChannelInfoBottomSheet` not download on web.
- [[#1591]](https://github.com/GetStream/stream-chat-flutter/issues/1591)
Fixed `StreamChannelInfoBottomSheet` not
rendering member list properly. rendering member list properly.
- [[#1427]](https://github.com/GetStream/stream-chat-flutter/issues/1427) Fixed unable to load asset error for - [[#1427]](https://github.com/GetStream/stream-chat-flutter/issues/1427) Fixed unable to load asset
error for
`packages/stream_chat_flutter/lib/svgs/video_call_icon.svg`. `packages/stream_chat_flutter/lib/svgs/video_call_icon.svg`.
🔄 Changed 🔄 Changed
@@ -227,30 +288,40 @@
- [[#1546]](https://github.com/GetStream/stream-chat-flutter/issues/1546) - [[#1546]](https://github.com/GetStream/stream-chat-flutter/issues/1546)
Fixed `StreamMessageInputTheme.linkHighlightColor` returning null for default theme. Fixed `StreamMessageInputTheme.linkHighlightColor` returning null for default theme.
- [[#1548]](https://github.com/GetStream/stream-chat-flutter/issues/1548) Fixed `StreamMessageInput` urlRegex only - [[#1548]](https://github.com/GetStream/stream-chat-flutter/issues/1548) Fixed `StreamMessageInput`
urlRegex only
matching the lowercase `http(s)|ftp`. matching the lowercase `http(s)|ftp`.
- [[#1542]](https://github.com/GetStream/stream-chat-flutter/issues/1542) Handle error thrown in `StreamMessageInput` - [[#1542]](https://github.com/GetStream/stream-chat-flutter/issues/1542) Handle error thrown
in `StreamMessageInput`
when unable to fetch a link preview. when unable to fetch a link preview.
- [[#1540]](https://github.com/GetStream/stream-chat-flutter/issues/1540) Use `CircularProgressIndicator.adaptive` - [[#1540]](https://github.com/GetStream/stream-chat-flutter/issues/1540)
Use `CircularProgressIndicator.adaptive`
instead of material indicator. instead of material indicator.
- [[#1490]](https://github.com/GetStream/stream-chat-flutter/issues/1490) Fixed `editMessageInputBuilder` property not - [[#1490]](https://github.com/GetStream/stream-chat-flutter/issues/1490)
Fixed `editMessageInputBuilder` property not
used in `MessageActionsModal.editMessage` option. used in `MessageActionsModal.editMessage` option.
- [[#1544]](https://github.com/GetStream/stream-chat-flutter/issues/1544) Fixed error thrown when unable to fetch - [[#1544]](https://github.com/GetStream/stream-chat-flutter/issues/1544) Fixed error thrown when
unable to fetch
image/data in Message link preview. image/data in Message link preview.
- [[#1482]](https://github.com/GetStream/stream-chat-flutter/issues/1482) Fixed `StreaChannelListTile` not showing - [[#1482]](https://github.com/GetStream/stream-chat-flutter/issues/1482)
Fixed `StreaChannelListTile` not showing
unread indicator when `currentUser` is not present in the initial member list. unread indicator when `currentUser` is not present in the initial member list.
- [[#1487]](https://github.com/GetStream/stream-chat-flutter/issues/1487) Use localized title - [[#1487]](https://github.com/GetStream/stream-chat-flutter/issues/1487) Use localized title
for `WebOrDesktopAttachmentPickerOption` in `StreamMessageInput`. for `WebOrDesktopAttachmentPickerOption` in `StreamMessageInput`.
- [[#1250]](https://github.com/GetStream/stream-chat-flutter/issues/1250) Fixed bottomRow widgetSpans getting resized - [[#1250]](https://github.com/GetStream/stream-chat-flutter/issues/1250) Fixed bottomRow
widgetSpans getting resized
twice when `textScaling` is enabled. twice when `textScaling` is enabled.
- [[#1498]](https://github.com/GetStream/stream-chat-flutter/issues/1498) Fixed `MessageInput` autocomplete not working - [[#1498]](https://github.com/GetStream/stream-chat-flutter/issues/1498) Fixed `MessageInput`
autocomplete not working
on non-mobile platforms. on non-mobile platforms.
- [[#1576]](https://github.com/GetStream/stream-chat-flutter/issues/1576) Temporary fix for `StreamMessageListView` - [[#1576]](https://github.com/GetStream/stream-chat-flutter/issues/1576) Temporary fix
for `StreamMessageListView`
getting broken when loaded at a particular message and a new message is added. getting broken when loaded at a particular message and a new message is added.
✅ Added ✅ Added
- Added support for `StreamMessageThemeData.urlAttachmentTextMaxLine` to specify the `.maxLines` for the url attachment - Added support for `StreamMessageThemeData.urlAttachmentTextMaxLine` to specify the `.maxLines` for
the url attachment
text. [#1543](https://github.com/GetStream/stream-chat-flutter/issues/1543) text. [#1543](https://github.com/GetStream/stream-chat-flutter/issues/1543)
🔄 Changed 🔄 Changed
@@ -265,24 +336,33 @@
🐞 Fixed 🐞 Fixed
- [[#1502]](https://github.com/GetStream/stream-chat-flutter/issues/1502) Fixed `isOnlyEmoji` method Detects Single - [[#1502]](https://github.com/GetStream/stream-chat-flutter/issues/1502) Fixed `isOnlyEmoji` method
Detects Single
Hangul Hangul
Consonants as Emoji. Consonants as Emoji.
- [[#1505]](https://github.com/GetStream/stream-chat-flutter/issues/1505) Fixed Message bubble disappears for Hangul - [[#1505]](https://github.com/GetStream/stream-chat-flutter/issues/1505) Fixed Message bubble
disappears for Hangul
Consonants. Consonants.
- [[#1476]](https://github.com/GetStream/stream-chat-flutter/issues/1476) Fixed `UserAvatarTransform.userAvatarBuilder` - [[#1476]](https://github.com/GetStream/stream-chat-flutter/issues/1476)
Fixed `UserAvatarTransform.userAvatarBuilder`
works only for otherUser. works only for otherUser.
- [[#1490]](https://github.com/GetStream/stream-chat-flutter/issues/1490) Fixed `editMessageInputBuilder` property not - [[#1490]](https://github.com/GetStream/stream-chat-flutter/issues/1490)
Fixed `editMessageInputBuilder` property not
used in message edit widget. used in message edit widget.
- [[#1523]](https://github.com/GetStream/stream-chat-flutter/issues/1523) Fixed `StreamMessageThemeData` not being - [[#1523]](https://github.com/GetStream/stream-chat-flutter/issues/1523)
Fixed `StreamMessageThemeData` not being
applied correctly. applied correctly.
- [[#1525]](https://github.com/GetStream/stream-chat-flutter/issues/1525) Fixed `StreamQuotedMessageWidget` message for - [[#1525]](https://github.com/GetStream/stream-chat-flutter/issues/1525)
Fixed `StreamQuotedMessageWidget` message for
deleted messages not being shown correctly. deleted messages not being shown correctly.
- [[#1529]](https://github.com/GetStream/stream-chat-flutter/issues/1529) Fixed `ClipboardData` requires non-nullable - [[#1529]](https://github.com/GetStream/stream-chat-flutter/issues/1529) Fixed `ClipboardData`
requires non-nullable
string as text on Flutter 3.10. string as text on Flutter 3.10.
- [[#1533]](https://github.com/GetStream/stream-chat-flutter/issues/1533) Fixed `StreamMessageListView` messages grouped - [[#1533]](https://github.com/GetStream/stream-chat-flutter/issues/1533)
Fixed `StreamMessageListView` messages grouped
incorrectly w.r.t. timestamp. incorrectly w.r.t. timestamp.
- [[#1532]](https://github.com/GetStream/stream-chat-flutter/issues/1532) Fixed `StreamMessageWidget` actions dialog - [[#1532]](https://github.com/GetStream/stream-chat-flutter/issues/1532)
Fixed `StreamMessageWidget` actions dialog
backdrop filter is cut off by safe area. backdrop filter is cut off by safe area.
✅ Added ✅ Added
@@ -330,7 +410,8 @@
🔄 Changed 🔄 Changed
- Updated `dart` sdk environment range to support `3.0.0`. - Updated `dart` sdk environment range to support `3.0.0`.
- Deprecated `MessageTheme.linkBackgroundColor` in favor of `MessageTheme.urlAttachmentBackgroundColor`. - Deprecated `MessageTheme.linkBackgroundColor` in favor
of `MessageTheme.urlAttachmentBackgroundColor`.
- Updated `stream_chat_flutter_core` dependency - Updated `stream_chat_flutter_core` dependency
to [`6.1.0`](https://pub.dev/packages/stream_chat_flutter_core/changelog). to [`6.1.0`](https://pub.dev/packages/stream_chat_flutter_core/changelog).
@@ -338,18 +419,23 @@
🐞 Fixed 🐞 Fixed
- [[#1456]](https://github.com/GetStream/stream-chat-flutter/issues/1456) Fixed logic for showing that a message was - [[#1456]](https://github.com/GetStream/stream-chat-flutter/issues/1456) Fixed logic for showing
that a message was
read using sending indicator. read using sending indicator.
- [[#1462]](https://github.com/GetStream/stream-chat-flutter/issues/1462) Fixed support for iPad in the share button for - [[#1462]](https://github.com/GetStream/stream-chat-flutter/issues/1462) Fixed support for iPad in
the share button for
images. images.
- [[#1475]](https://github.com/GetStream/stream-chat-flutter/issues/1475) Fixed typo to fix compilation. - [[#1475]](https://github.com/GetStream/stream-chat-flutter/issues/1475) Fixed typo to fix
compilation.
✅ Added ✅ Added
- Now it is possible to customize the max lines of the title of a url attachment. Before it was always 1 line. - Now it is possible to customize the max lines of the title of a url attachment. Before it was
always 1 line.
- Added `attachmentActionsModalBuilder` parameter to `StreamMessageWidget` that allows to - Added `attachmentActionsModalBuilder` parameter to `StreamMessageWidget` that allows to
customize `AttachmentActionsModal`. customize `AttachmentActionsModal`.
- Added `StreamMessageInput.sendMessageKeyPredicate` and `StreamMessageInput.clearQuotedMessageKeyPredicate` to - Added `StreamMessageInput.sendMessageKeyPredicate`
and `StreamMessageInput.clearQuotedMessageKeyPredicate` to
customize the keys used to send and clear the quoted message. customize the keys used to send and clear the quoted message.
🔄 Changed 🔄 Changed
@@ -358,7 +444,8 @@
🚀 Improved 🚀 Improved
- Improved draw of reaction options. [#1455](https://github.com/GetStream/stream-chat-flutter/pull/1455) - Improved draw of reaction
options. [#1455](https://github.com/GetStream/stream-chat-flutter/pull/1455)
## 5.3.0 ## 5.3.0
@@ -368,7 +455,8 @@
🐞 Fixed 🐞 Fixed
- [[#1424]](https://github.com/GetStream/stream-chat-flutter/issues/1424) Fixed a render issue when showing messages - [[#1424]](https://github.com/GetStream/stream-chat-flutter/issues/1424) Fixed a render issue when
showing messages
starting with 4 whitespaces. starting with 4 whitespaces.
- Fixed a bug where the `AttachmentPickerBottomSheet` was not able to identify the mobile browser. - Fixed a bug where the `AttachmentPickerBottomSheet` was not able to identify the mobile browser.
- Fixed uploading files on Windows - fixed temp file path. - Fixed uploading files on Windows - fixed temp file path.
@@ -381,7 +469,8 @@
✅ Added ✅ Added
- Added a new `bottomRowBuilderWithDefaultWidget` parameter to `StreamMessageWidget` which contains a third parameter ( - Added a new `bottomRowBuilderWithDefaultWidget` parameter to `StreamMessageWidget` which contains
a third parameter (
default `BottomRow` widget with `copyWith` method available) to allow easier customization. default `BottomRow` widget with `copyWith` method available) to allow easier customization.
🔄 Changed 🔄 Changed
@@ -391,18 +480,23 @@
- Updated `connectivity_plus` dependency to `^3.0.2` - Updated `connectivity_plus` dependency to `^3.0.2`
- Updated `dart_vlc` dependency to `^0.4.0` - Updated `dart_vlc` dependency to `^0.4.0`
- Updated `file_picker` dependency to `^5.2.4` - Updated `file_picker` dependency to `^5.2.4`
- Deprecated `StreamMessageWidget.bottomRowBuilder` in favor of `StreamMessageWidget.bottomRowBuilderWithDefaultWidget`. - Deprecated `StreamMessageWidget.bottomRowBuilder` in favor
of `StreamMessageWidget.bottomRowBuilderWithDefaultWidget`.
- Deprecated `StreamMessageWidget.deletedBottomRowBuilder` in favor - Deprecated `StreamMessageWidget.deletedBottomRowBuilder` in favor
of `StreamMessageWidget.bottomRowBuilderWithDefaultWidget`. of `StreamMessageWidget.bottomRowBuilderWithDefaultWidget`.
- Deprecated `StreamMessageWidget.usernameBuilder` in favor of `StreamMessageWidget.bottomRowBuilderWithDefaultWidget`. - Deprecated `StreamMessageWidget.usernameBuilder` in favor
of `StreamMessageWidget.bottomRowBuilderWithDefaultWidget`.
🐞 Fixed 🐞 Fixed
- [[#1379]](https://github.com/GetStream/stream-chat-flutter/issues/1379) Fixed "Issues with photo attachments on web", - [[#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. where the cached image attachment would not render while uploading.
- Fix render overflow issue with `MessageSearchListTileTitle`. It now uses `Text.rich` instead of `Row`. Better default - Fix render overflow issue with `MessageSearchListTileTitle`. It now uses `Text.rich` instead
of `Row`. Better default
behaviour and allows `TextOverflow`. behaviour and allows `TextOverflow`.
- [[1346]](https://github.com/GetStream/stream-chat-flutter/issues/1346) Fixed a render issue while uploading video on - [[1346]](https://github.com/GetStream/stream-chat-flutter/issues/1346) Fixed a render issue while
uploading video on
web. web.
- [[#1347]](https://github.com/GetStream/stream-chat-flutter/issues/1347) `onReply` not working - [[#1347]](https://github.com/GetStream/stream-chat-flutter/issues/1347) `onReply` not working
in `AttachmentActionsModal` which is used by `StreamImageAttachment` and `StreamImageGroup`. in `AttachmentActionsModal` which is used by `StreamImageAttachment` and `StreamImageGroup`.
@@ -445,7 +539,8 @@
* `textInputAction` * `textInputAction`
* `keyboardType` * `keyboardType`
* `textCapitalization` * `textCapitalization`
- Added `showStreamAttachmentPickerModalBottomSheet` to show the attachment picker modal bottom sheet. - Added `showStreamAttachmentPickerModalBottomSheet` to show the attachment picker modal bottom
sheet.
🔄 Changed 🔄 Changed
@@ -4,8 +4,8 @@ publish_to: 'none'
version: 1.0.0+1 version: 1.0.0+1
environment: environment:
sdk: '>=2.19.0 <4.0.0' sdk: ">=3.0.0 <4.0.0"
flutter: ">=3.7.0" flutter: ">=3.10.0"
dependencies: dependencies:
collection: ^1.15.0 collection: ^1.15.0
@@ -66,7 +66,8 @@ class StreamGroupAvatar extends StatelessWidget {
Widget avatar = GestureDetector( Widget avatar = GestureDetector(
onTap: onTap, onTap: onTap,
child: ClipRRect( child: ClipRRect(
borderRadius: borderRadius ?? previewTheme?.borderRadius, borderRadius:
borderRadius ?? previewTheme?.borderRadius ?? BorderRadius.zero,
child: Container( child: Container(
constraints: constraints ?? previewTheme?.constraints, constraints: constraints ?? previewTheme?.constraints,
decoration: BoxDecoration(color: colorTheme.accentPrimary), decoration: BoxDecoration(color: colorTheme.accentPrimary),
@@ -86,7 +86,8 @@ class StreamUserAvatar extends StatelessWidget {
final backupGradientAvatar = ClipRRect( final backupGradientAvatar = ClipRRect(
borderRadius: borderRadius ?? borderRadius: borderRadius ??
streamChatTheme.ownMessageTheme.avatarTheme?.borderRadius, streamChatTheme.ownMessageTheme.avatarTheme?.borderRadius ??
BorderRadius.zero,
child: streamChatConfig.defaultUserImage(context, user), child: streamChatConfig.defaultUserImage(context, user),
); );
@@ -106,9 +106,10 @@ class _ConnectedTitleState extends StatelessWidget {
style: textStyle, style: textStyle,
); );
} else { } else {
final lastActive = otherMember.user?.lastActive ?? DateTime.now();
alternativeWidget = Text( alternativeWidget = Text(
'${context.translations.userLastOnlineText} ' '${context.translations.userLastOnlineText} '
'${Jiffy(otherMember.user?.lastActive).fromNow()}', '${Jiffy.parseFromDateTime(lastActive).fromNow()}',
style: textStyle, style: textStyle,
); );
} }
@@ -349,16 +349,16 @@ class _Date extends StatelessWidget {
if (lastMessageAt.millisecondsSinceEpoch >= if (lastMessageAt.millisecondsSinceEpoch >=
startOfDay.millisecondsSinceEpoch) { startOfDay.millisecondsSinceEpoch) {
stringDate = Jiffy(lastMessageAt.toLocal()).jm; stringDate = Jiffy.parseFromDateTime(lastMessageAt.toLocal()).jm;
} else if (lastMessageAt.millisecondsSinceEpoch >= } else if (lastMessageAt.millisecondsSinceEpoch >=
startOfDay startOfDay
.subtract(const Duration(days: 1)) .subtract(const Duration(days: 1))
.millisecondsSinceEpoch) { .millisecondsSinceEpoch) {
stringDate = context.translations.yesterdayLabel; 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.parseFromDateTime(lastMessageAt.toLocal()).EEEE;
} else { } else {
stringDate = Jiffy(lastMessageAt.toLocal()).yMd; stringDate = Jiffy.parseFromDateTime(lastMessageAt.toLocal()).yMd;
} }
return Text( return Text(
@@ -111,7 +111,8 @@ class StreamChannelAvatar extends StatelessWidget {
initialData: channel.image, initialData: channel.image,
builder: (context, channelImage) { builder: (context, channelImage) {
Widget child = ClipRRect( Widget child = ClipRRect(
borderRadius: borderRadius ?? previewTheme?.borderRadius, borderRadius:
borderRadius ?? previewTheme?.borderRadius ?? BorderRadius.zero,
child: Container( child: Container(
constraints: constraints ?? previewTheme?.constraints, constraints: constraints ?? previewTheme?.constraints,
decoration: BoxDecoration(color: colorTheme.accentPrimary), decoration: BoxDecoration(color: colorTheme.accentPrimary),
@@ -3,13 +3,11 @@ import 'dart:io';
import 'package:cached_network_image/cached_network_image.dart'; import 'package:cached_network_image/cached_network_image.dart';
import 'package:chewie/chewie.dart'; import 'package:chewie/chewie.dart';
import 'package:contextmenu/contextmenu.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:photo_view/photo_view.dart'; import 'package:photo_view/photo_view.dart';
import 'package:shimmer/shimmer.dart'; import 'package:shimmer/shimmer.dart';
import 'package:stream_chat_flutter/platform_widget_builder/platform_widget_builder.dart'; import 'package:stream_chat_flutter/platform_widget_builder/platform_widget_builder.dart';
import 'package:stream_chat_flutter/src/context_menu_items/download_menu_item.dart';
import 'package:stream_chat_flutter/src/fullscreen_media/full_screen_media_widget.dart'; import 'package:stream_chat_flutter/src/fullscreen_media/full_screen_media_widget.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';
@@ -281,84 +279,83 @@ class _FullScreenMediaState extends State<StreamFullScreenMedia> {
final currentAttachmentPackage = final currentAttachmentPackage =
widget.mediaAttachmentPackages[index]; widget.mediaAttachmentPackages[index];
final attachment = currentAttachmentPackage.attachment; final attachment = currentAttachmentPackage.attachment;
if (attachment.type == 'image' || attachment.type == 'giphy') { return ValueListenableBuilder(
final imageUrl = attachment.imageUrl ?? valueListenable: _isDisplayingDetail,
attachment.assetUrl ?? builder: (context, isDisplayingDetail, child) {
attachment.thumbUrl; return AnimatedContainer(
return ValueListenableBuilder<bool>( duration: kThemeChangeDuration,
valueListenable: _isDisplayingDetail,
builder: (context, isDisplayingDetail, _) =>
AnimatedContainer(
color: isDisplayingDetail color: isDisplayingDetail
? StreamChannelHeaderTheme.of(context).color ? StreamChannelHeaderTheme.of(context).color
: Colors.black, : Colors.black,
duration: kThemeAnimationDuration, child: Builder(
child: ContextMenuArea( builder: (context) {
verticalPadding: 0, if (attachment.type == 'image' ||
builder: (_) => [ attachment.type == 'giphy') {
DownloadMenuItem( final imageUrl = attachment.imageUrl ??
attachment: attachment, attachment.assetUrl ??
), attachment.thumbUrl;
],
child: PhotoView( return PhotoView(
imageProvider: (imageUrl == null && imageProvider: (imageUrl == null &&
attachment.localUri != null && attachment.localUri != null &&
attachment.file?.bytes != null) attachment.file?.bytes != null)
? Image.memory(attachment.file!.bytes!).image ? Image.memory(attachment.file!.bytes!).image
: CachedNetworkImageProvider(imageUrl!), : CachedNetworkImageProvider(imageUrl!),
errorBuilder: (_, __, ___) => const AttachmentError(), errorBuilder: (_, __, ___) =>
loadingBuilder: (context, _) { const AttachmentError(),
final image = Image.asset( loadingBuilder: (context, _) {
'images/placeholder.png', final image = Image.asset(
fit: BoxFit.cover, 'images/placeholder.png',
package: 'stream_chat_flutter', fit: BoxFit.cover,
package: 'stream_chat_flutter',
);
final colorTheme =
StreamChatTheme.of(context).colorTheme;
return Shimmer.fromColors(
baseColor: colorTheme.disabled,
highlightColor: colorTheme.inputBg,
child: image,
);
},
maxScale: PhotoViewComputedScale.covered,
minScale: PhotoViewComputedScale.contained,
heroAttributes: PhotoViewHeroAttributes(
tag: widget.mediaAttachmentPackages,
),
backgroundDecoration: const BoxDecoration(
color: Colors.transparent,
),
); );
final colorTheme = } else if (attachment.type == 'video') {
StreamChatTheme.of(context).colorTheme; final controller = videoPackages[attachment.id]!;
return Shimmer.fromColors( if (!controller.initialized) {
baseColor: colorTheme.disabled, return const Center(
highlightColor: colorTheme.inputBg, child: CircularProgressIndicator.adaptive(),
child: image, );
}
final mediaQuery = MediaQuery.of(context);
final bottomPadding = mediaQuery.padding.bottom;
return AnimatedPadding(
duration: kThemeChangeDuration,
padding: EdgeInsets.symmetric(
vertical: isDisplayingDetail
? kToolbarHeight + bottomPadding
: 0,
),
child: Chewie(
controller: controller.chewieController!,
),
); );
}, }
maxScale: PhotoViewComputedScale.covered,
minScale: PhotoViewComputedScale.contained, return const SizedBox();
heroAttributes: PhotoViewHeroAttributes( },
tag: widget.mediaAttachmentPackages,
),
backgroundDecoration: const BoxDecoration(
color: Colors.transparent,
),
),
), ),
),
);
} else if (attachment.type == 'video') {
final controller = videoPackages[attachment.id]!;
if (!controller.initialized) {
return const Center(
child: CircularProgressIndicator.adaptive(),
); );
} },
return InkWell( );
onTap: switchDisplayingDetail,
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 50),
child: ContextMenuArea(
verticalPadding: 0,
builder: (_) => [
DownloadMenuItem(
attachment: attachment,
),
],
child: Chewie(
controller: controller.chewieController!,
),
),
),
);
}
return const SizedBox();
}, },
), ),
), ),
@@ -475,6 +472,7 @@ class VideoPackage {
videoPlayerController: _videoPlayerController, videoPlayerController: _videoPlayerController,
autoInitialize: _autoInitialize, autoInitialize: _autoInitialize,
showControls: _showControls, showControls: _showControls,
showOptions: false,
aspectRatio: _videoPlayerController.value.aspectRatio, aspectRatio: _videoPlayerController.value.aspectRatio,
); );
}); });
@@ -307,88 +307,95 @@ class _FullScreenMediaDesktopState extends State<FullScreenMediaDesktop> {
final currentAttachmentPackage = final currentAttachmentPackage =
widget.mediaAttachmentPackages[index]; widget.mediaAttachmentPackages[index];
final attachment = currentAttachmentPackage.attachment; final attachment = currentAttachmentPackage.attachment;
if (attachment.type == 'image' || attachment.type == 'giphy') {
final imageUrl = attachment.imageUrl ?? return ValueListenableBuilder(
attachment.assetUrl ?? valueListenable: _isDisplayingDetail,
attachment.thumbUrl; builder: (context, isDisplayingDetail, child) {
return ValueListenableBuilder<bool>( return AnimatedContainer(
valueListenable: _isDisplayingDetail, duration: kThemeChangeDuration,
builder: (context, isDisplayingDetail, _) =>
AnimatedContainer(
color: isDisplayingDetail color: isDisplayingDetail
? StreamChannelHeaderTheme.of(context).color ? StreamChannelHeaderTheme.of(context).color
: Colors.black, : Colors.black,
duration: kThemeAnimationDuration, child: Builder(
child: ContextMenuArea( builder: (context) {
verticalPadding: 0, if (attachment.type == 'image' ||
builder: (_) => [ attachment.type == 'giphy') {
DownloadMenuItem( final imageUrl = attachment.imageUrl ??
attachment: attachment, attachment.assetUrl ??
), attachment.thumbUrl;
],
child: PhotoView(
imageProvider: (imageUrl == null &&
attachment.localUri != null &&
attachment.file?.bytes != null)
? Image.memory(attachment.file!.bytes!).image
: CachedNetworkImageProvider(imageUrl!),
errorBuilder: (_, __, ___) => const AttachmentError(),
loadingBuilder: (context, _) {
final image = Image.asset(
'images/placeholder.png',
fit: BoxFit.cover,
package: 'stream_chat_flutter',
);
final colorTheme =
StreamChatTheme.of(context).colorTheme;
return Shimmer.fromColors(
baseColor: colorTheme.disabled,
highlightColor: colorTheme.inputBg,
child: image,
);
},
maxScale: PhotoViewComputedScale.covered,
minScale: PhotoViewComputedScale.contained,
heroAttributes: PhotoViewHeroAttributes(
tag: widget.mediaAttachmentPackages,
),
backgroundDecoration: const BoxDecoration(
color: Colors.transparent,
),
),
),
),
);
} else if (attachment.type == 'video') {
final package = videoPackages[attachment.id]!;
package.player.open(
Playlist(
medias: [
Media.network(package.attachment.assetUrl),
],
),
autoStart: widget.autoplayVideos,
);
return InkWell( return PhotoView(
onTap: switchDisplayingDetail, imageProvider: (imageUrl == null &&
child: Padding( attachment.localUri != null &&
padding: const EdgeInsets.symmetric(vertical: 50), attachment.file?.bytes != null)
child: ContextMenuArea( ? Image.memory(attachment.file!.bytes!).image
verticalPadding: 0, : CachedNetworkImageProvider(imageUrl!),
builder: (_) => [ errorBuilder: (_, __, ___) =>
DownloadMenuItem( const AttachmentError(),
attachment: attachment, loadingBuilder: (context, _) {
), final image = Image.asset(
], 'images/placeholder.png',
child: Video( fit: BoxFit.cover,
player: package.player, package: 'stream_chat_flutter',
), );
final colorTheme =
StreamChatTheme.of(context).colorTheme;
return Shimmer.fromColors(
baseColor: colorTheme.disabled,
highlightColor: colorTheme.inputBg,
child: image,
);
},
maxScale: PhotoViewComputedScale.covered,
minScale: PhotoViewComputedScale.contained,
heroAttributes: PhotoViewHeroAttributes(
tag: widget.mediaAttachmentPackages,
),
backgroundDecoration: const BoxDecoration(
color: Colors.transparent,
),
);
} else if (attachment.type == 'video') {
final package = videoPackages[attachment.id]!;
package.player.open(
Playlist(
medias: [
Media.network(package.attachment.assetUrl),
],
),
autoStart: widget.autoplayVideos,
);
final mediaQuery = MediaQuery.of(context);
final bottomPadding = mediaQuery.padding.bottom;
return AnimatedPadding(
duration: kThemeChangeDuration,
padding: EdgeInsets.symmetric(
vertical: isDisplayingDetail
? kToolbarHeight + bottomPadding
: 0,
),
child: ContextMenuArea(
verticalPadding: 0,
builder: (_) => [
DownloadMenuItem(
attachment: attachment,
),
],
child: Video(
player: package.player,
),
),
);
}
return const SizedBox();
},
), ),
), );
); },
} );
return const SizedBox();
}, },
), ),
), ),
@@ -611,13 +611,15 @@ class DefaultTranslations implements Translations {
} else if (date == yesterday) { } else if (date == yesterday) {
return 'yesterday'; return 'yesterday';
} else { } else {
return 'on ${Jiffy(date).MMMd}'; return 'on ${Jiffy.parseFromDateTime(date).MMMd}';
} }
} }
@override @override
String sentAtText({required DateTime date, required DateTime time}) => String sentAtText({required DateTime date, required DateTime time}) {
'Sent ${_getDay(date)} at ${Jiffy(time.toLocal()).format('HH:mm')}'; final atTime = Jiffy.parseFromDateTime(time.toLocal());
return 'Sent ${_getDay(date)} at ${atTime.jm}';
}
@override @override
String get todayLabel => 'Today'; String get todayLabel => 'Today';
@@ -698,6 +698,7 @@ Widget mobileAttachmentPickerBuilder({
ThumbnailFormat attachmentThumbnailFormat = ThumbnailFormat.jpeg, ThumbnailFormat attachmentThumbnailFormat = ThumbnailFormat.jpeg,
int attachmentThumbnailQuality = 100, int attachmentThumbnailQuality = 100,
double attachmentThumbnailScale = 1, double attachmentThumbnailScale = 1,
ErrorListener? onError,
}) { }) {
return StreamMobileAttachmentPickerBottomSheet( return StreamMobileAttachmentPickerBottomSheet(
controller: controller, controller: controller,
@@ -721,10 +722,15 @@ Widget mobileAttachmentPickerBuilder({
mediaThumbnailQuality: attachmentThumbnailQuality, mediaThumbnailQuality: attachmentThumbnailQuality,
mediaThumbnailScale: attachmentThumbnailScale, mediaThumbnailScale: attachmentThumbnailScale,
onMediaItemSelected: (media) async { onMediaItemSelected: (media) async {
if (selectedIds.contains(media.id)) { try {
return controller.removeAssetAttachment(media); if (selectedIds.contains(media.id)) {
return await controller.removeAssetAttachment(media);
}
return await controller.addAssetAttachment(media);
} catch (e, stk) {
if (onError != null) return onError.call(e, stk);
rethrow;
} }
return controller.addAssetAttachment(media);
}, },
); );
}, },
@@ -736,8 +742,15 @@ Widget mobileAttachmentPickerBuilder({
optionViewBuilder: (context, controller) { optionViewBuilder: (context, controller) {
return StreamFilePicker( return StreamFilePicker(
onFilePicked: (file) async { onFilePicked: (file) async {
if (file != null) await controller.addAttachment(file); try {
return Navigator.pop(context, controller.value); if (file != null) await controller.addAttachment(file);
return Navigator.pop(context, controller.value);
} catch (e, stk) {
Navigator.pop(context, controller.value);
if (onError != null) return onError.call(e, stk);
rethrow;
}
}, },
); );
}, },
@@ -749,10 +762,17 @@ Widget mobileAttachmentPickerBuilder({
optionViewBuilder: (context, controller) { optionViewBuilder: (context, controller) {
return StreamImagePicker( return StreamImagePicker(
onImagePicked: (image) async { onImagePicked: (image) async {
if (image != null) { try {
await controller.addAttachment(image); if (image != null) {
await controller.addAttachment(image);
}
return Navigator.pop(context, controller.value);
} catch (e, stk) {
Navigator.pop(context, controller.value);
if (onError != null) return onError.call(e, stk);
rethrow;
} }
return Navigator.pop(context, controller.value);
}, },
); );
}, },
@@ -764,10 +784,17 @@ Widget mobileAttachmentPickerBuilder({
optionViewBuilder: (context, controller) { optionViewBuilder: (context, controller) {
return StreamVideoPicker( return StreamVideoPicker(
onVideoPicked: (video) async { onVideoPicked: (video) async {
if (video != null) { try {
await controller.addAttachment(video); if (video != null) {
await controller.addAttachment(video);
}
return Navigator.pop(context, controller.value);
} catch (e, stk) {
Navigator.pop(context, controller.value);
if (onError != null) return onError.call(e, stk);
rethrow;
} }
return Navigator.pop(context, controller.value);
}, },
); );
}, },
@@ -787,6 +814,7 @@ Widget webOrDesktopAttachmentPickerBuilder({
ThumbnailFormat attachmentThumbnailFormat = ThumbnailFormat.jpeg, ThumbnailFormat attachmentThumbnailFormat = ThumbnailFormat.jpeg,
int attachmentThumbnailQuality = 100, int attachmentThumbnailQuality = 100,
double attachmentThumbnailScale = 1, double attachmentThumbnailScale = 1,
ErrorListener? onError,
}) { }) {
return StreamWebOrDesktopAttachmentPickerBottomSheet( return StreamWebOrDesktopAttachmentPickerBottomSheet(
controller: controller, controller: controller,
@@ -814,13 +842,20 @@ Widget webOrDesktopAttachmentPickerBuilder({
}.where((option) => option.supportedTypes.every(allowedTypes.contains)), }.where((option) => option.supportedTypes.every(allowedTypes.contains)),
}, },
onOptionTap: (context, controller, option) async { onOptionTap: (context, controller, option) async {
final attachment = await StreamAttachmentHandler.instance.pickFile( try {
type: option.type.fileType, final attachment = await StreamAttachmentHandler.instance.pickFile(
); type: option.type.fileType,
if (attachment != null) { );
await controller.addAttachment(attachment); if (attachment != null) {
await controller.addAttachment(attachment);
}
return Navigator.pop(context, controller.value);
} catch (e, stk) {
Navigator.pop(context, controller.value);
if (onError != null) return onError.call(e, stk);
rethrow;
} }
return Navigator.pop(context, controller.value);
}, },
); );
} }
@@ -69,6 +69,7 @@ Future<T?> showStreamAttachmentPickerModalBottomSheet<T>({
List<AttachmentPickerType> allowedTypes = AttachmentPickerType.values, List<AttachmentPickerType> allowedTypes = AttachmentPickerType.values,
List<Attachment>? initialAttachments, List<Attachment>? initialAttachments,
StreamAttachmentPickerController? controller, StreamAttachmentPickerController? controller,
ErrorListener? onError,
Color? backgroundColor, Color? backgroundColor,
double? elevation, double? elevation,
BoxConstraints? constraints, BoxConstraints? constraints,
@@ -117,6 +118,7 @@ Future<T?> showStreamAttachmentPickerModalBottomSheet<T>({
if (isWebOrDesktop) { if (isWebOrDesktop) {
return webOrDesktopAttachmentPickerBuilder.call( return webOrDesktopAttachmentPickerBuilder.call(
context: context, context: context,
onError: onError,
controller: controller, controller: controller,
allowedTypes: allowedTypes, allowedTypes: allowedTypes,
customOptions: customOptions?.map( customOptions: customOptions?.map(
@@ -131,6 +133,7 @@ Future<T?> showStreamAttachmentPickerModalBottomSheet<T>({
return mobileAttachmentPickerBuilder.call( return mobileAttachmentPickerBuilder.call(
context: context, context: context,
onError: onError,
controller: controller, controller: controller,
allowedTypes: allowedTypes, allowedTypes: allowedTypes,
customOptions: customOptions, customOptions: customOptions,
@@ -147,6 +147,7 @@ class StreamMessageInput extends StatefulWidget {
_defaultClearQuotedMessageKeyPredicate, _defaultClearQuotedMessageKeyPredicate,
this.ogPreviewFilter = _defaultOgPreviewFilter, this.ogPreviewFilter = _defaultOgPreviewFilter,
this.hintGetter = _defaultHintGetter, this.hintGetter = _defaultHintGetter,
this.contentInsertionConfiguration,
}); });
/// The predicate used to send a message on desktop/web /// The predicate used to send a message on desktop/web
@@ -340,6 +341,9 @@ class StreamMessageInput extends StatefulWidget {
/// Returns the hint text for the message input. /// Returns the hint text for the message input.
final HintGetter hintGetter; final HintGetter hintGetter;
/// {@macro flutter.widgets.editableText.contentInsertionConfiguration}
final ContentInsertionConfiguration? contentInsertionConfiguration;
static String? _defaultHintGetter( static String? _defaultHintGetter(
BuildContext context, BuildContext context,
HintType type, HintType type,
@@ -815,6 +819,7 @@ class StreamMessageInputState extends State<StreamMessageInput>
Future<void> _onAttachmentButtonPressed() async { Future<void> _onAttachmentButtonPressed() async {
final attachments = await showStreamAttachmentPickerModalBottomSheet( final attachments = await showStreamAttachmentPickerModalBottomSheet(
context: context, context: context,
onError: widget.onError,
allowedTypes: widget.allowedAttachmentPickerTypes, allowedTypes: widget.allowedAttachmentPickerTypes,
initialAttachments: _effectiveController.attachments, initialAttachments: _effectiveController.attachments,
); );
@@ -904,6 +909,8 @@ class StreamMessageInputState extends State<StreamMessageInput>
decoration: _getInputDecoration(context), decoration: _getInputDecoration(context),
textCapitalization: widget.textCapitalization, textCapitalization: widget.textCapitalization,
autocorrect: widget.autoCorrect, autocorrect: widget.autoCorrect,
contentInsertionConfiguration:
widget.contentInsertionConfiguration,
), ),
), ),
), ),
@@ -120,6 +120,7 @@ class StreamMessageTextField extends StatefulWidget {
this.restorationId, this.restorationId,
this.scribbleEnabled = true, this.scribbleEnabled = true,
this.enableIMEPersonalizedLearning = true, this.enableIMEPersonalizedLearning = true,
this.contentInsertionConfiguration,
}) : assert(obscuringCharacter.length == 1, ''), }) : assert(obscuringCharacter.length == 1, ''),
smartDashesType = smartDashesType ?? smartDashesType = smartDashesType ??
(obscureText ? SmartDashesType.disabled : SmartDashesType.enabled), (obscureText ? SmartDashesType.disabled : SmartDashesType.enabled),
@@ -526,6 +527,9 @@ class StreamMessageTextField extends StatefulWidget {
/// {@macro flutter.services.TextInputConfiguration.enableIMEPersonalizedLearning} /// {@macro flutter.services.TextInputConfiguration.enableIMEPersonalizedLearning}
final bool enableIMEPersonalizedLearning; final bool enableIMEPersonalizedLearning;
/// {@macro flutter.widgets.editableText.contentInsertionConfiguration}
final ContentInsertionConfiguration? contentInsertionConfiguration;
@override @override
_StreamMessageTextFieldState createState() => _StreamMessageTextFieldState(); _StreamMessageTextFieldState createState() => _StreamMessageTextFieldState();
@@ -622,6 +626,9 @@ class StreamMessageTextField extends StatefulWidget {
properties.add(DiagnosticsProperty<bool>( properties.add(DiagnosticsProperty<bool>(
'enableIMEPersonalizedLearning', enableIMEPersonalizedLearning, 'enableIMEPersonalizedLearning', enableIMEPersonalizedLearning,
defaultValue: true)); defaultValue: true));
properties.add(DiagnosticsProperty<ContentInsertionConfiguration>(
'contentInsertionConfiguration', contentInsertionConfiguration,
defaultValue: null));
} }
} }
@@ -727,6 +734,7 @@ class _StreamMessageTextFieldState extends State<StreamMessageTextField>
restorationId: widget.restorationId, restorationId: widget.restorationId,
scribbleEnabled: widget.scribbleEnabled, scribbleEnabled: widget.scribbleEnabled,
enableIMEPersonalizedLearning: widget.enableIMEPersonalizedLearning, enableIMEPersonalizedLearning: widget.enableIMEPersonalizedLearning,
contentInsertionConfiguration: widget.contentInsertionConfiguration,
); );
@override @override
@@ -669,14 +669,20 @@ class _StreamMessageListViewState extends State<StreamMessageListView> {
final isPartOfThread = message.replyCount! > 0 || final isPartOfThread = message.replyCount! > 0 ||
message.showInChannel == true; message.showInChannel == true;
final createdAt = message.createdAt.toLocal(); final createdAt = Jiffy.parseFromDateTime(
final nextCreatedAt = nextMessage.createdAt.toLocal(); message.createdAt.toLocal(),
if (!Jiffy(createdAt).isSame(nextCreatedAt, Units.DAY)) { );
final nextCreatedAt = Jiffy.parseFromDateTime(
nextMessage.createdAt.toLocal(),
);
if (!createdAt.isSame(nextCreatedAt, unit: Unit.day)) {
separator = _buildDateDivider(nextMessage); separator = _buildDateDivider(nextMessage);
} else { } else {
final hasTimeDiff = !Jiffy(createdAt).isSame( final hasTimeDiff = !createdAt.isSame(
nextCreatedAt, nextCreatedAt,
Units.MINUTE, unit: Unit.minute,
); );
final isNextUserSame = final isNextUserSame =
@@ -1071,10 +1077,12 @@ class _StreamMessageListViewState extends State<StreamMessageListView> {
var hasTimeDiff = false; var hasTimeDiff = false;
if (nextMessage != null) { if (nextMessage != null) {
hasTimeDiff = !Jiffy(message.createdAt.toLocal()).isSame( final createdAt = Jiffy.parseFromDateTime(message.createdAt.toLocal());
final nextCreatedAt = Jiffy.parseFromDateTime(
nextMessage.createdAt.toLocal(), nextMessage.createdAt.toLocal(),
Units.MINUTE,
); );
hasTimeDiff = !createdAt.isSame(nextCreatedAt, unit: Unit.minute);
} }
final hasFileAttachment = final hasFileAttachment =
@@ -162,8 +162,7 @@ class BottomRow extends StatelessWidget {
msg = context.translations.threadReplyCountText(replyCount); msg = context.translations.threadReplyCountText(replyCount);
} }
// ignore: prefer_function_declarations_over_variables Future<void> _onThreadTap() async {
final _onThreadTap = () async {
try { try {
var message = this.message; var message = this.message;
if (showInChannel) { if (showInChannel) {
@@ -172,12 +171,9 @@ class BottomRow extends StatelessWidget {
} }
return onThreadTap!(message); return onThreadTap!(message);
} catch (e, stk) { } catch (e, stk) {
print(e); debugPrint('Error while fetching message: $e, $stk');
print(stk);
// ignore: avoid_returning_null_for_void
return null;
} }
}; }
const usernameKey = Key('username'); const usernameKey = Key('username');
@@ -191,7 +187,7 @@ class BottomRow extends StatelessWidget {
), ),
if (showTimeStamp) if (showTimeStamp)
Text( Text(
Jiffy(message.createdAt.toLocal()).jm, Jiffy.parseFromDateTime(message.createdAt.toLocal()).jm,
style: messageTheme.createdAtStyle, style: messageTheme.createdAtStyle,
), ),
if (showSendingIndicator) if (showSendingIndicator)
@@ -55,6 +55,7 @@ class StreamMessageWidget extends StatefulWidget {
this.attachmentBorderRadiusGeometry, this.attachmentBorderRadiusGeometry,
this.onMentionTap, this.onMentionTap,
this.onMessageTap, this.onMessageTap,
this.onReactionsTap,
bool? showReactionPicker, bool? showReactionPicker,
@Deprecated('Use `showReactionPicker` instead') @Deprecated('Use `showReactionPicker` instead')
bool showReactionPickerIndicator = true, bool showReactionPickerIndicator = true,
@@ -563,6 +564,9 @@ class StreamMessageWidget extends StatefulWidget {
/// {@macro onMessageTap} /// {@macro onMessageTap}
final void Function(Message)? onMessageTap; final void Function(Message)? onMessageTap;
/// {@macro onReactionsTap}
final OnReactionsTap? onReactionsTap;
/// {@template customActions} /// {@template customActions}
/// List of custom actions shown on message long tap /// List of custom actions shown on message long tap
/// {@endtemplate} /// {@endtemplate}
@@ -657,6 +661,7 @@ class StreamMessageWidget extends StatefulWidget {
bool? translateUserAvatar, bool? translateUserAvatar,
OnQuotedMessageTap? onQuotedMessageTap, OnQuotedMessageTap? onQuotedMessageTap,
void Function(Message)? onMessageTap, void Function(Message)? onMessageTap,
OnReactionsTap? onReactionsTap,
List<StreamMessageAction>? customActions, List<StreamMessageAction>? customActions,
void Function(Message message, Attachment attachment)? onAttachmentTap, void Function(Message message, Attachment attachment)? onAttachmentTap,
Widget Function(BuildContext, User)? userAvatarBuilder, Widget Function(BuildContext, User)? userAvatarBuilder,
@@ -746,6 +751,7 @@ class StreamMessageWidget extends StatefulWidget {
translateUserAvatar: translateUserAvatar ?? this.translateUserAvatar, translateUserAvatar: translateUserAvatar ?? this.translateUserAvatar,
onQuotedMessageTap: onQuotedMessageTap ?? this.onQuotedMessageTap, onQuotedMessageTap: onQuotedMessageTap ?? this.onQuotedMessageTap,
onMessageTap: onMessageTap ?? this.onMessageTap, onMessageTap: onMessageTap ?? this.onMessageTap,
onReactionsTap: onReactionsTap ?? this.onReactionsTap,
customActions: customActions ?? this.customActions, customActions: customActions ?? this.customActions,
onAttachmentTap: onAttachmentTap ?? this.onAttachmentTap, onAttachmentTap: onAttachmentTap ?? this.onAttachmentTap,
userAvatarBuilder: userAvatarBuilder ?? this.userAvatarBuilder, userAvatarBuilder: userAvatarBuilder ?? this.userAvatarBuilder,
@@ -986,7 +992,11 @@ class _StreamMessageWidgetState extends State<StreamMessageWidget>
showPinHighlight: widget.showPinHighlight, showPinHighlight: widget.showPinHighlight,
showReactionPickerTail: widget.showReactionPickerTail, showReactionPickerTail: widget.showReactionPickerTail,
showReactions: showReactions, showReactions: showReactions,
onReactionsTap: () => _showMessageReactionsModal(context), onReactionsTap: () {
widget.onReactionsTap != null
? widget.onReactionsTap!(widget.message)
: _showMessageReactionsModal(context);
},
showUserAvatar: widget.showUserAvatar, showUserAvatar: widget.showUserAvatar,
streamChat: _streamChat, streamChat: _streamChat,
translateUserAvatar: widget.translateUserAvatar, translateUserAvatar: widget.translateUserAvatar,
@@ -20,17 +20,17 @@ class StreamDateDivider extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final createdAt = Jiffy(dateTime); final createdAt = Jiffy.parseFromDateTime(dateTime);
final now = Jiffy(DateTime.now()); final now = Jiffy.parseFromDateTime(DateTime.now());
var dayInfo = createdAt.MMMd; var dayInfo = createdAt.MMMd;
if (createdAt.isSame(now, Units.DAY)) { if (createdAt.isSame(now, unit: Unit.day)) {
dayInfo = context.translations.todayLabel; dayInfo = context.translations.todayLabel;
} else if (createdAt.isSame(now.subtract(days: 1), Units.DAY)) { } else if (createdAt.isSame(now.subtract(days: 1), unit: Unit.day)) {
dayInfo = context.translations.yesterdayLabel; dayInfo = context.translations.yesterdayLabel;
} else if (createdAt.isAfter(now.subtract(days: 7), Units.DAY)) { } else if (createdAt.isAfter(now.subtract(days: 7), unit: Unit.day)) {
dayInfo = createdAt.EEEE; dayInfo = createdAt.EEEE;
} else if (createdAt.isAfter(now.subtract(years: 1), Units.DAY)) { } else if (createdAt.isAfter(now.subtract(years: 1), unit: Unit.day)) {
dayInfo = createdAt.MMMd; dayInfo = createdAt.MMMd;
} }
@@ -287,16 +287,16 @@ class ChannelLastMessageDate extends StatelessWidget {
if (lastMessageAt.millisecondsSinceEpoch >= if (lastMessageAt.millisecondsSinceEpoch >=
startOfDay.millisecondsSinceEpoch) { startOfDay.millisecondsSinceEpoch) {
stringDate = Jiffy(lastMessageAt.toLocal()).jm; stringDate = Jiffy.parseFromDateTime(lastMessageAt.toLocal()).jm;
} else if (lastMessageAt.millisecondsSinceEpoch >= } else if (lastMessageAt.millisecondsSinceEpoch >=
startOfDay startOfDay
.subtract(const Duration(days: 1)) .subtract(const Duration(days: 1))
.millisecondsSinceEpoch) { .millisecondsSinceEpoch) {
stringDate = context.translations.yesterdayLabel; 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.parseFromDateTime(lastMessageAt.toLocal()).EEEE;
} else { } else {
stringDate = Jiffy(lastMessageAt.toLocal()).yMd; stringDate = Jiffy.parseFromDateTime(lastMessageAt.toLocal()).yMd;
} }
return Text( return Text(
@@ -227,9 +227,9 @@ class MessageSearchTileMessageDate extends StatelessWidget {
if (now.year != createdAt.year || if (now.year != createdAt.year ||
now.month != createdAt.month || now.month != createdAt.month ||
now.day != createdAt.day) { now.day != createdAt.day) {
stringDate = Jiffy(createdAt.toLocal()).yMd; stringDate = Jiffy.parseFromDateTime(createdAt.toLocal()).yMd;
} else { } else {
stringDate = Jiffy(createdAt.toLocal()).jm; stringDate = Jiffy.parseFromDateTime(createdAt.toLocal()).jm;
} }
return Text( return Text(
@@ -202,10 +202,9 @@ class MediaThumbnailProvider extends ImageProvider<MediaThumbnailProvider> {
} }
@override @override
@Deprecated('Will get replaced by loadImage in the next major version.') ImageStreamCompleter loadImage(
ImageStreamCompleter loadBuffer(
MediaThumbnailProvider key, MediaThumbnailProvider key,
DecoderBufferCallback decode, ImageDecoderCallback decode,
) { ) {
return MultiFrameImageStreamCompleter( return MultiFrameImageStreamCompleter(
codec: _loadAsync(key, decode), codec: _loadAsync(key, decode),
@@ -220,10 +219,9 @@ class MediaThumbnailProvider extends ImageProvider<MediaThumbnailProvider> {
); );
} }
@Deprecated('Will get replaced by loadImage in the next major version.')
Future<ui.Codec> _loadAsync( Future<ui.Codec> _loadAsync(
MediaThumbnailProvider key, MediaThumbnailProvider key,
DecoderBufferCallback decode, ImageDecoderCallback decode,
) async { ) async {
assert(key == this, '$key is not $this'); assert(key == this, '$key is not $this');
final bytes = await media.thumbnailDataWithSize( final bytes = await media.thumbnailDataWithSize(
@@ -176,11 +176,12 @@ class UserLastActive extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final chatTheme = StreamChatTheme.of(context); final chatTheme = StreamChatTheme.of(context);
final lastActive = user.lastActive ?? DateTime.now();
return Text( return Text(
user.online user.online
? context.translations.userOnlineText ? context.translations.userOnlineText
: '${context.translations.userLastOnlineText} ' : '${context.translations.userLastOnlineText} '
'${Jiffy(user.lastActive).fromNow()}', '${Jiffy.parseFromDateTime(lastActive).fromNow()}',
style: chatTheme.textTheme.footnote.copyWith( style: chatTheme.textTheme.footnote.copyWith(
color: chatTheme.colorTheme.textHighEmphasis.withOpacity(0.5), color: chatTheme.colorTheme.textHighEmphasis.withOpacity(0.5),
), ),
@@ -165,9 +165,9 @@ class StreamChatState extends State<StreamChat> {
@override @override
void didChangeDependencies() { void didChangeDependencies() {
final currentLocale = Localizations.localeOf(context).toString(); final currentLocale = Localizations.localeOf(context).toString();
final availableLocales = Jiffy.getAllAvailableLocales(); final availableLocales = Jiffy.getSupportedLocales();
if (availableLocales.contains(currentLocale)) { if (availableLocales.contains(currentLocale)) {
Jiffy.locale(currentLocale); Jiffy.setLocale(currentLocale);
} }
super.didChangeDependencies(); super.didChangeDependencies();
} }
@@ -74,11 +74,12 @@ class StreamUserItem extends StatelessWidget {
Widget _buildLastActive(BuildContext context) { Widget _buildLastActive(BuildContext context) {
final chatTheme = StreamChatTheme.of(context); final chatTheme = StreamChatTheme.of(context);
final lastActive = user.lastActive ?? DateTime.now();
return Text( return Text(
user.online user.online
? context.translations.userOnlineText ? context.translations.userOnlineText
: '${context.translations.userLastOnlineText} ' : '${context.translations.userLastOnlineText} '
'${Jiffy(user.lastActive).fromNow()}', '${Jiffy.parseFromDateTime(lastActive).fromNow()}',
style: chatTheme.textTheme.footnote.copyWith( style: chatTheme.textTheme.footnote.copyWith(
color: chatTheme.colorTheme.textHighEmphasis.withOpacity(0.5), color: chatTheme.colorTheme.textHighEmphasis.withOpacity(0.5),
), ),
@@ -352,12 +352,12 @@ extension MessageX on Message {
final userName = user.name; final userName = user.name;
if (linkify) { if (linkify) {
messageTextToRender = messageTextToRender?.replaceAll( messageTextToRender = messageTextToRender?.replaceAll(
'@$userId', RegExp('@($userId|$userName)'),
'[@$userName](@${userName.replaceAll(' ', '')})', '[@$userName]($userId)',
); );
} else { } else {
messageTextToRender = messageTextToRender?.replaceAll( messageTextToRender = messageTextToRender?.replaceAll(
'@$userId', RegExp('@($userId|$userName)'),
'@$userName', '@$userName',
); );
} }
@@ -230,6 +230,11 @@ typedef OnQuotedMessageTap = void Function(String?);
/// {@endtemplate} /// {@endtemplate}
typedef OnMessageTap = void Function(Message); typedef OnMessageTap = void Function(Message);
/// {@template onReactionsTap}
/// The action to perform when a message's reactions are tapped.
/// {@endtemplate}
typedef OnReactionsTap = void Function(Message);
/// {@template messageSearchItemTapCallback} /// {@template messageSearchItemTapCallback}
/// The action to perform when tapping or clicking on a user in a /// The action to perform when tapping or clicking on a user in a
// ignore: deprecated_member_use_from_same_package // ignore: deprecated_member_use_from_same_package
+19 -19
View File
@@ -1,47 +1,47 @@
name: stream_chat_flutter name: stream_chat_flutter
homepage: https://github.com/GetStream/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. description: Stream Chat official Flutter SDK. Build your own chat experience using Dart and Flutter.
version: 6.8.1 version: 6.9.0
repository: https://github.com/GetStream/stream-chat-flutter repository: https://github.com/GetStream/stream-chat-flutter
issue_tracker: https://github.com/GetStream/stream-chat-flutter/issues issue_tracker: https://github.com/GetStream/stream-chat-flutter/issues
environment: environment:
sdk: ">=2.19.0 <4.0.0" sdk: ">=3.0.0 <4.0.0"
flutter: ">=3.7.0" flutter: ">=3.10.0"
dependencies: dependencies:
cached_network_image: ^3.2.3 cached_network_image: ^3.2.3
chewie: ^1.7.0 chewie: ^1.7.0
collection: ^1.17.0 collection: ^1.17.1
contextmenu: ^3.0.0 contextmenu: ^3.0.0
dart_vlc: ^0.4.0 dart_vlc: ^0.4.0
desktop_drop: ^0.4.1 desktop_drop: ^0.4.1
diacritic: ^0.1.4 diacritic: ^0.1.4
dio: ^5.2.1+1 dio: ^5.3.2
ezanimation: ^0.6.0 ezanimation: ^0.6.0
file_picker: ^5.3.1 file_picker: ^5.3.3
file_selector: ^1.0.0 file_selector: ^1.0.0
flutter: flutter:
sdk: flutter sdk: flutter
flutter_markdown: ^0.6.15 flutter_markdown: ^0.6.17+1
flutter_portal: ^1.1.4 flutter_portal: ^1.1.4
flutter_svg: ^2.0.5 flutter_svg: ^2.0.7
http_parser: ^4.0.2 http_parser: ^4.0.2
image_gallery_saver: ^2.0.3 image_gallery_saver: ^2.0.3
image_picker: ^1.0.0 image_picker: ^1.0.2
jiffy: ^5.0.0 jiffy: ^6.2.1
lottie: ^2.3.2 lottie: ^2.6.0
meta: ^1.8.0 meta: ^1.9.1
path_provider: ^2.0.15 path_provider: ^2.1.0
photo_manager: ^2.6.0 photo_manager: ^2.7.1
photo_view: ^0.14.0 photo_view: ^0.14.0
rxdart: ^0.27.7 rxdart: ^0.27.7
share_plus: ^7.0.2 share_plus: ^7.1.0
shimmer: ^3.0.0 shimmer: ^3.0.0
stream_chat_flutter_core: ^6.7.0 stream_chat_flutter_core: ^6.8.0
synchronized: ^3.1.0 synchronized: ^3.1.0
thumblr: ^0.0.4 thumblr: ^0.0.4
url_launcher: ^6.1.11 url_launcher: ^6.1.12
video_player: ^2.7.0 video_player: ^2.7.0
video_player_macos: ^2.0.1 video_player_macos: ^2.0.1
video_thumbnail: ^0.5.3 video_thumbnail: ^0.5.3
@@ -73,5 +73,5 @@ dev_dependencies:
flutter_test: flutter_test:
sdk: flutter sdk: flutter
golden_toolkit: ^0.15.0 golden_toolkit: ^0.15.0
mocktail: ^0.3.0 mocktail: ^1.0.0
path: ^1.8.2 path: ^1.8.3
@@ -148,4 +148,126 @@ void main() {
expect('ㅎㅎㅎㅎ'.isOnlyEmoji, false); expect('ㅎㅎㅎㅎ'.isOnlyEmoji, false);
}); });
}); });
group('Message Extension Tests', () {
test('replaceMentions should replace user mentions with names and IDs', () {
final user1 = User(id: 'user1', name: 'Alice');
final user2 = User(id: 'user2', name: 'Bob');
final message = Message(
text: 'Hello, @user1 and @user2!',
mentionedUsers: [user1, user2],
);
final modifiedMessage = message.replaceMentions();
expect(modifiedMessage.text, contains('[@Alice](user1)'));
expect(modifiedMessage.text, contains('[@Bob](user2)'));
});
test('replaceMentions without linkify should not add links', () {
final user = User(id: 'user1', name: 'Alice');
final message = Message(
text: 'Hello, @user1!',
mentionedUsers: [user],
);
final modifiedMessage = message.replaceMentions(linkify: false);
expect(modifiedMessage.text, contains('@Alice'));
});
test('replaceMentions should handle mentions with usernames', () {
final user = User(id: 'user1', name: 'Alice');
final message = Message(
text: 'Hello, @Alice!',
mentionedUsers: [user],
);
final modifiedMessage = message.replaceMentions();
expect(modifiedMessage.text, contains('[@Alice](user1)'));
});
test(
'''replaceMentions without linkify should not change mentions with usernames''',
() {
final user = User(id: 'user1', name: 'Alice');
final message = Message(
text: 'Hello, @Alice!',
mentionedUsers: [user],
);
final modifiedMessage = message.replaceMentions(linkify: false);
expect(modifiedMessage.text, contains('@Alice'));
},
);
test(
'replaceMentions should replace mixed user mentions with names and IDs',
() {
final user1 = User(id: 'user1', name: 'Alice');
final user2 = User(id: 'user2', name: 'Bob');
final message = Message(
text: 'Hello, @user1 and @Bob!',
mentionedUsers: [user1, user2],
);
final modifiedMessage = message.replaceMentions();
expect(modifiedMessage.text, contains('[@Alice](user1)'));
expect(modifiedMessage.text, contains('[@Bob](user2)'));
},
);
test('replaceMentionsWithId should replace user names with IDs', () {
final user1 = User(id: 'user1', name: 'Alice');
final user2 = User(id: 'user2', name: 'Bob');
final message = Message(
text: 'Hello, @Alice and @Bob!',
mentionedUsers: [user1, user2],
);
final modifiedMessage = message.replaceMentionsWithId();
expect(modifiedMessage.text, contains('@user1'));
expect(modifiedMessage.text, contains('@user2'));
expect(modifiedMessage.text, isNot(contains('@Alice')));
expect(modifiedMessage.text, isNot(contains('@Bob')));
});
test(
'replaceMentionsWithId should not change message without mentions',
() {
final message = Message(
text: 'Hello, @Alice!',
);
final modifiedMessage = message.replaceMentionsWithId();
expect(modifiedMessage.text, equals('Hello, @Alice!'));
expect(modifiedMessage.text, isNot(contains('@user1')));
},
);
test('replaceMentionsWithId should handle message with only mention', () {
final user = User(id: 'user1', name: 'Alice');
final message = Message(
text: '@Alice',
mentionedUsers: [user],
);
final modifiedMessage = message.replaceMentionsWithId();
expect(modifiedMessage.text, contains('@user1'));
expect(modifiedMessage.text, isNot(contains('@Alice')));
});
});
} }
@@ -1,3 +1,8 @@
## 6.8.0
- Updated minimum supported `SDK` version to Flutter 3.10/Dart 3.0
- Updated `stream_chat` dependency to [`6.8.0`](https://pub.dev/packages/stream_chat/changelog).
## 6.7.0 ## 6.7.0
- Updated `stream_chat` dependency to [`6.7.0`](https://pub.dev/packages/stream_chat/changelog). - Updated `stream_chat` dependency to [`6.7.0`](https://pub.dev/packages/stream_chat/changelog).
@@ -4,8 +4,8 @@ publish_to: 'none'
version: 1.0.0+1 version: 1.0.0+1
environment: environment:
sdk: '>=2.19.0 <4.0.0' sdk: ">=3.0.0 <4.0.0"
flutter: ">=3.7.0" flutter: ">=3.10.0"
dependencies: dependencies:
cupertino_icons: ^1.0.3 cupertino_icons: ^1.0.3
+11 -11
View File
@@ -1,29 +1,29 @@
name: stream_chat_flutter_core name: stream_chat_flutter_core
homepage: https://github.com/GetStream/stream-chat-flutter homepage: https://github.com/GetStream/stream-chat-flutter
description: Stream Chat official Flutter SDK Core. Build your own chat experience using Dart and Flutter. description: Stream Chat official Flutter SDK Core. Build your own chat experience using Dart and Flutter.
version: 6.7.0 version: 6.8.0
repository: https://github.com/GetStream/stream-chat-flutter repository: https://github.com/GetStream/stream-chat-flutter
issue_tracker: https://github.com/GetStream/stream-chat-flutter/issues issue_tracker: https://github.com/GetStream/stream-chat-flutter/issues
environment: environment:
sdk: ">=2.19.0 <4.0.0" sdk: ">=3.0.0 <4.0.0"
flutter: ">=3.7.0" flutter: ">=3.10.0"
dependencies: dependencies:
collection: ^1.17.0 collection: ^1.17.1
connectivity_plus: ^4.0.1 connectivity_plus: ^4.0.2
flutter: flutter:
sdk: flutter sdk: flutter
freezed_annotation: ^2.2.0 freezed_annotation: ^2.4.1
meta: ^1.8.0 meta: ^1.9.1
rxdart: ^0.27.7 rxdart: ^0.27.7
stream_chat: ^6.7.0 stream_chat: ^6.8.0
dev_dependencies: dev_dependencies:
build_runner: ^2.3.3 build_runner: ^2.4.6
fake_async: ^1.3.1 fake_async: ^1.3.1
flutter_test: flutter_test:
sdk: flutter sdk: flutter
freezed: ^2.4.0 freezed: ^2.4.1
mocktail: ^0.3.0 mocktail: ^1.0.0
@@ -1,3 +1,8 @@
## 5.9.0
* Updated minimum supported `SDK` version to Flutter 3.10/Dart 3.0
* Updated `stream_chat_flutter` dependency to [`6.9.0`](https://pub.dev/packages/stream_chat_flutter/changelog).
## 5.8.0 ## 5.8.0
* Updated `stream_chat_flutter` dependency to [`6.8.0`](https://pub.dev/packages/stream_chat_flutter/changelog). * Updated `stream_chat_flutter` dependency to [`6.8.0`](https://pub.dev/packages/stream_chat_flutter/changelog).
@@ -270,13 +270,15 @@ class NnStreamChatLocalizations extends GlobalStreamChatLocalizations {
} else if (date == yesterday) { } else if (date == yesterday) {
return 'yesterday'; return 'yesterday';
} else { } else {
return 'on ${Jiffy(date).MMMd}'; return 'on ${Jiffy.parseFromDateTime(date).MMMd}';
} }
} }
@override @override
String sentAtText({required DateTime date, required DateTime time}) => String sentAtText({required DateTime date, required DateTime time}) {
'Sent ${_getDay(date)} at ${Jiffy(time.toLocal()).format('HH:mm')}'; final atTime = Jiffy.parseFromDateTime(time.toLocal());
return 'Sent ${_getDay(date)} at ${atTime.jm}';
}
@override @override
String get todayLabel => 'Today'; String get todayLabel => 'Today';
@@ -5,8 +5,8 @@ publish_to: 'none'
version: 1.0.0+1 version: 1.0.0+1
environment: environment:
sdk: '>=2.19.0 <4.0.0' sdk: ">=3.0.0 <4.0.0"
flutter: ">=3.7.0" flutter: ">=3.10.0"
dependencies: dependencies:
cupertino_icons: ^1.0.3 cupertino_icons: ^1.0.3
@@ -249,13 +249,15 @@ class StreamChatLocalizationsCa extends GlobalStreamChatLocalizations {
} else if (date == yesterday) { } else if (date == yesterday) {
return 'ahir'; return 'ahir';
} else { } else {
return 'el ${Jiffy(date).MMMd}'; return 'el ${Jiffy.parseFromDateTime(date).MMMd}';
} }
} }
@override @override
String sentAtText({required DateTime date, required DateTime time}) => String sentAtText({required DateTime date, required DateTime time}) {
'''Enviat el ${_getDay(date)} a les ${Jiffy(time.toLocal()).format('HH:mm')}'''; final atTime = Jiffy.parseFromDateTime(time.toLocal());
return 'Enviat el ${_getDay(date)} a les ${atTime.jm}';
}
@override @override
String get todayLabel => 'Avui'; String get todayLabel => 'Avui';
@@ -239,13 +239,15 @@ class StreamChatLocalizationsDe extends GlobalStreamChatLocalizations {
} else if (date == yesterday) { } else if (date == yesterday) {
return 'Gestern'; return 'Gestern';
} else { } else {
return 'am ${Jiffy(date).MMMd}'; return 'am ${Jiffy.parseFromDateTime(date).MMMd}';
} }
} }
@override @override
String sentAtText({required DateTime date, required DateTime time}) => String sentAtText({required DateTime date, required DateTime time}) {
'Gesendet ${_getDay(date)} am ${Jiffy(time.toLocal()).format('HH:mm')}'; final atTime = Jiffy.parseFromDateTime(time.toLocal());
return 'Gesendet ${_getDay(date)} am ${atTime.jm}';
}
@override @override
String get todayLabel => 'Heute'; String get todayLabel => 'Heute';
@@ -246,13 +246,15 @@ class StreamChatLocalizationsEn extends GlobalStreamChatLocalizations {
} else if (date == yesterday) { } else if (date == yesterday) {
return 'yesterday'; return 'yesterday';
} else { } else {
return 'on ${Jiffy(date).MMMd}'; return 'on ${Jiffy.parseFromDateTime(date).MMMd}';
} }
} }
@override @override
String sentAtText({required DateTime date, required DateTime time}) => String sentAtText({required DateTime date, required DateTime time}) {
'Sent ${_getDay(date)} at ${Jiffy(time.toLocal()).format('HH:mm')}'; final atTime = Jiffy.parseFromDateTime(time.toLocal());
return 'Sent ${_getDay(date)} at ${atTime.jm}';
}
@override @override
String get todayLabel => 'Today'; String get todayLabel => 'Today';
@@ -250,13 +250,15 @@ class StreamChatLocalizationsEs extends GlobalStreamChatLocalizations {
} else if (date == yesterday) { } else if (date == yesterday) {
return 'ayer'; return 'ayer';
} else { } else {
return 'el ${Jiffy(date).MMMd}'; return 'el ${Jiffy.parseFromDateTime(date).MMMd}';
} }
} }
@override @override
String sentAtText({required DateTime date, required DateTime time}) => String sentAtText({required DateTime date, required DateTime time}) {
'''Enviado el ${_getDay(date)} a las ${Jiffy(time.toLocal()).format('HH:mm')}'''; final atTime = Jiffy.parseFromDateTime(time.toLocal());
return 'Enviado el ${_getDay(date)} a las ${atTime.jm}';
}
@override @override
String get todayLabel => 'Hoy'; String get todayLabel => 'Hoy';
@@ -249,13 +249,15 @@ class StreamChatLocalizationsFr extends GlobalStreamChatLocalizations {
} else if (date == yesterday) { } else if (date == yesterday) {
return 'hier'; return 'hier';
} else { } else {
return 'le ${Jiffy(date).MMMd}'; return 'le ${Jiffy.parseFromDateTime(date).MMMd}';
} }
} }
@override @override
String sentAtText({required DateTime date, required DateTime time}) => String sentAtText({required DateTime date, required DateTime time}) {
'Envoyé ${_getDay(date)} à ${Jiffy(time.toLocal()).format('HH:mm')}'; final atTime = Jiffy.parseFromDateTime(time.toLocal());
return 'Envoyé ${_getDay(date)} à ${atTime.jm}';
}
@override @override
String get todayLabel => "Aujourd'hui"; String get todayLabel => "Aujourd'hui";
@@ -243,13 +243,15 @@ class StreamChatLocalizationsHi extends GlobalStreamChatLocalizations {
} else if (date == yesterday) { } else if (date == yesterday) {
return 'कल'; return 'कल';
} else { } else {
return '${Jiffy(date).MMMd} को'; return '${Jiffy.parseFromDateTime(date).MMMd} को';
} }
} }
@override @override
String sentAtText({required DateTime date, required DateTime time}) => String sentAtText({required DateTime date, required DateTime time}) {
'${_getDay(date)} ${Jiffy(time.toLocal()).format('HH:mm')} बजे भेजा गया'; final atTime = Jiffy.parseFromDateTime(time.toLocal());
return '${_getDay(date)} ${atTime.jm} बजे भेजा गया';
}
@override @override
String get todayLabel => 'आज'; String get todayLabel => 'आज';
@@ -252,13 +252,15 @@ Il file è troppo grande per essere caricato. Il limite è di $limitInMB MB.''';
} else if (date == yesterday) { } else if (date == yesterday) {
return 'ieri'; return 'ieri';
} else { } else {
return 'il ${Jiffy(date).MMMd}'; return 'il ${Jiffy.parseFromDateTime(date).MMMd}';
} }
} }
@override @override
String sentAtText({required DateTime date, required DateTime time}) => String sentAtText({required DateTime date, required DateTime time}) {
"Inviato ${_getDay(date)} alle ${Jiffy(time.toLocal()).format('HH:mm')}"; final atTime = Jiffy.parseFromDateTime(time.toLocal());
return 'Inviato ${_getDay(date)} alle ${atTime.jm}';
}
@override @override
String get todayLabel => 'Oggi'; String get todayLabel => 'Oggi';
@@ -236,13 +236,15 @@ class StreamChatLocalizationsJa extends GlobalStreamChatLocalizations {
} else if (date == yesterday) { } else if (date == yesterday) {
return '昨日'; return '昨日';
} else { } else {
return '${Jiffy(date).MMMd}'; return '${Jiffy.parseFromDateTime(date).MMMd}';
} }
} }
@override @override
String sentAtText({required DateTime date, required DateTime time}) => String sentAtText({required DateTime date, required DateTime time}) {
'${_getDay(date)}${Jiffy(time.toLocal()).format('HH:mm')}に送信しました '; final atTime = Jiffy.parseFromDateTime(time.toLocal());
return '${_getDay(date)}${atTime.jm}に送信しました ';
}
@override @override
String get todayLabel => '今日'; String get todayLabel => '今日';
@@ -235,13 +235,15 @@ class StreamChatLocalizationsKo extends GlobalStreamChatLocalizations {
} else if (date == yesterday) { } else if (date == yesterday) {
return '어제'; return '어제';
} else { } else {
return '${Jiffy(date).MMMd}'; return '${Jiffy.parseFromDateTime(date).MMMd}';
} }
} }
@override @override
String sentAtText({required DateTime date, required DateTime time}) => String sentAtText({required DateTime date, required DateTime time}) {
'${_getDay(date)} ${Jiffy(time.toLocal()).format('HH:mm')}에 보냈습니다'; final atTime = Jiffy.parseFromDateTime(time.toLocal());
return '${_getDay(date)} ${atTime.jm}에 보냈습니다';
}
@override @override
String get todayLabel => '오늘'; String get todayLabel => '오늘';
@@ -242,13 +242,15 @@ class StreamChatLocalizationsNo extends GlobalStreamChatLocalizations {
} else if (date == yesterday) { } else if (date == yesterday) {
return 'i går'; return 'i går';
} else { } else {
return '${Jiffy(date).MMMd}'; return '${Jiffy.parseFromDateTime(date).MMMd}';
} }
} }
@override @override
String sentAtText({required DateTime date, required DateTime time}) => String sentAtText({required DateTime date, required DateTime time}) {
'Sent ${_getDay(date)} kl. ${Jiffy(time.toLocal()).format('HH:mm')}'; final atTime = Jiffy.parseFromDateTime(time.toLocal());
return 'Sent ${_getDay(date)} kl. ${atTime.jm}';
}
@override @override
String get todayLabel => 'I dag'; String get todayLabel => 'I dag';
@@ -244,13 +244,15 @@ class StreamChatLocalizationsPt extends GlobalStreamChatLocalizations {
} else if (date == yesterday) { } else if (date == yesterday) {
return 'Ontem'; return 'Ontem';
} else { } else {
return 'o ${Jiffy(date).MMMd}'; return 'o ${Jiffy.parseFromDateTime(date).MMMd}';
} }
} }
@override @override
String sentAtText({required DateTime date, required DateTime time}) => String sentAtText({required DateTime date, required DateTime time}) {
'''Enviado ${_getDay(date)} às ${Jiffy(time.toLocal()).format('HH:mm')}'''; final atTime = Jiffy.parseFromDateTime(time.toLocal());
return 'Enviado ${_getDay(date)} às ${atTime.jm}';
}
@override @override
String get todayLabel => 'Hoje'; String get todayLabel => 'Hoje';
@@ -1,20 +1,20 @@
name: stream_chat_localizations name: stream_chat_localizations
description: The Official localizations for Stream Chat Flutter, a service for building chat applications description: The Official localizations for Stream Chat Flutter, a service for building chat applications
version: 5.8.0 version: 5.9.0
homepage: https://github.com/GetStream/stream-chat-flutter homepage: https://github.com/GetStream/stream-chat-flutter
repository: https://github.com/GetStream/stream-chat-flutter repository: https://github.com/GetStream/stream-chat-flutter
issue_tracker: https://github.com/GetStream/stream-chat-flutter/issues issue_tracker: https://github.com/GetStream/stream-chat-flutter/issues
environment: environment:
sdk: ">=2.19.0 <4.0.0" sdk: ">=3.0.0 <4.0.0"
flutter: ">=3.7.0" flutter: ">=3.10.0"
dependencies: dependencies:
flutter: flutter:
sdk: flutter sdk: flutter
flutter_localizations: flutter_localizations:
sdk: flutter sdk: flutter
stream_chat_flutter: ^6.8.0 stream_chat_flutter: ^6.9.0
dev_dependencies: dev_dependencies:
flutter_test: flutter_test:
@@ -1,3 +1,8 @@
## 6.8.0
- Updated minimum supported `SDK` version to Flutter 3.10/Dart 3.0
- Updated `stream_chat` dependency to [`6.8.0`](https://pub.dev/packages/stream_chat/changelog).
## 6.7.0 ## 6.7.0
- [[#1683]](https://github.com/GetStream/stream-chat-flutter/issues/1683) Fixed SqliteException no such - [[#1683]](https://github.com/GetStream/stream-chat-flutter/issues/1683) Fixed SqliteException no such
@@ -4,8 +4,8 @@ publish_to: 'none'
version: 1.0.0+1 version: 1.0.0+1
environment: environment:
sdk: '>=2.19.0 <4.0.0' sdk: ">=3.0.0 <4.0.0"
flutter: ">=3.7.0" flutter: ">=3.10.0"
dependencies: dependencies:
cupertino_icons: ^1.0.3 cupertino_icons: ^1.0.3
@@ -41,16 +41,13 @@ class $ChannelsTable extends Channels
.withConverter<Map<String, dynamic>>($ChannelsTable.$converterconfig); .withConverter<Map<String, dynamic>>($ChannelsTable.$converterconfig);
static const VerificationMeta _frozenMeta = const VerificationMeta('frozen'); static const VerificationMeta _frozenMeta = const VerificationMeta('frozen');
@override @override
late final GeneratedColumn<bool> frozen = late final GeneratedColumn<bool> frozen = GeneratedColumn<bool>(
GeneratedColumn<bool>('frozen', aliasedName, false, 'frozen', aliasedName, false,
type: DriftSqlType.bool, type: DriftSqlType.bool,
requiredDuringInsert: false, requiredDuringInsert: false,
defaultConstraints: GeneratedColumn.constraintsDependsOnDialect({ defaultConstraints:
SqlDialect.sqlite: 'CHECK ("frozen" IN (0, 1))', GeneratedColumn.constraintIsAlways('CHECK ("frozen" IN (0, 1))'),
SqlDialect.mysql: '', defaultValue: const Constant(false));
SqlDialect.postgres: '',
}),
defaultValue: const Constant(false));
static const VerificationMeta _lastMessageAtMeta = static const VerificationMeta _lastMessageAtMeta =
const VerificationMeta('lastMessageAt'); const VerificationMeta('lastMessageAt');
@override @override
@@ -726,28 +723,22 @@ class $MessagesTable extends Messages
static const VerificationMeta _showInChannelMeta = static const VerificationMeta _showInChannelMeta =
const VerificationMeta('showInChannel'); const VerificationMeta('showInChannel');
@override @override
late final GeneratedColumn<bool> showInChannel = late final GeneratedColumn<bool> showInChannel = GeneratedColumn<bool>(
GeneratedColumn<bool>('show_in_channel', aliasedName, true, 'show_in_channel', aliasedName, true,
type: DriftSqlType.bool, type: DriftSqlType.bool,
requiredDuringInsert: false, requiredDuringInsert: false,
defaultConstraints: GeneratedColumn.constraintsDependsOnDialect({ defaultConstraints: GeneratedColumn.constraintIsAlways(
SqlDialect.sqlite: 'CHECK ("show_in_channel" IN (0, 1))', 'CHECK ("show_in_channel" IN (0, 1))'));
SqlDialect.mysql: '',
SqlDialect.postgres: '',
}));
static const VerificationMeta _shadowedMeta = static const VerificationMeta _shadowedMeta =
const VerificationMeta('shadowed'); const VerificationMeta('shadowed');
@override @override
late final GeneratedColumn<bool> shadowed = late final GeneratedColumn<bool> shadowed = GeneratedColumn<bool>(
GeneratedColumn<bool>('shadowed', aliasedName, false, 'shadowed', aliasedName, false,
type: DriftSqlType.bool, type: DriftSqlType.bool,
requiredDuringInsert: false, requiredDuringInsert: false,
defaultConstraints: GeneratedColumn.constraintsDependsOnDialect({ defaultConstraints:
SqlDialect.sqlite: 'CHECK ("shadowed" IN (0, 1))', GeneratedColumn.constraintIsAlways('CHECK ("shadowed" IN (0, 1))'),
SqlDialect.mysql: '', defaultValue: const Constant(false));
SqlDialect.postgres: '',
}),
defaultValue: const Constant(false));
static const VerificationMeta _commandMeta = static const VerificationMeta _commandMeta =
const VerificationMeta('command'); const VerificationMeta('command');
@override @override
@@ -797,16 +788,13 @@ class $MessagesTable extends Messages
type: DriftSqlType.string, requiredDuringInsert: false); type: DriftSqlType.string, requiredDuringInsert: false);
static const VerificationMeta _pinnedMeta = const VerificationMeta('pinned'); static const VerificationMeta _pinnedMeta = const VerificationMeta('pinned');
@override @override
late final GeneratedColumn<bool> pinned = late final GeneratedColumn<bool> pinned = GeneratedColumn<bool>(
GeneratedColumn<bool>('pinned', aliasedName, false, 'pinned', aliasedName, false,
type: DriftSqlType.bool, type: DriftSqlType.bool,
requiredDuringInsert: false, requiredDuringInsert: false,
defaultConstraints: GeneratedColumn.constraintsDependsOnDialect({ defaultConstraints:
SqlDialect.sqlite: 'CHECK ("pinned" IN (0, 1))', GeneratedColumn.constraintIsAlways('CHECK ("pinned" IN (0, 1))'),
SqlDialect.mysql: '', defaultValue: const Constant(false));
SqlDialect.postgres: '',
}),
defaultValue: const Constant(false));
static const VerificationMeta _pinnedAtMeta = static const VerificationMeta _pinnedAtMeta =
const VerificationMeta('pinnedAt'); const VerificationMeta('pinnedAt');
@override @override
@@ -2004,28 +1992,22 @@ class $PinnedMessagesTable extends PinnedMessages
static const VerificationMeta _showInChannelMeta = static const VerificationMeta _showInChannelMeta =
const VerificationMeta('showInChannel'); const VerificationMeta('showInChannel');
@override @override
late final GeneratedColumn<bool> showInChannel = late final GeneratedColumn<bool> showInChannel = GeneratedColumn<bool>(
GeneratedColumn<bool>('show_in_channel', aliasedName, true, 'show_in_channel', aliasedName, true,
type: DriftSqlType.bool, type: DriftSqlType.bool,
requiredDuringInsert: false, requiredDuringInsert: false,
defaultConstraints: GeneratedColumn.constraintsDependsOnDialect({ defaultConstraints: GeneratedColumn.constraintIsAlways(
SqlDialect.sqlite: 'CHECK ("show_in_channel" IN (0, 1))', 'CHECK ("show_in_channel" IN (0, 1))'));
SqlDialect.mysql: '',
SqlDialect.postgres: '',
}));
static const VerificationMeta _shadowedMeta = static const VerificationMeta _shadowedMeta =
const VerificationMeta('shadowed'); const VerificationMeta('shadowed');
@override @override
late final GeneratedColumn<bool> shadowed = late final GeneratedColumn<bool> shadowed = GeneratedColumn<bool>(
GeneratedColumn<bool>('shadowed', aliasedName, false, 'shadowed', aliasedName, false,
type: DriftSqlType.bool, type: DriftSqlType.bool,
requiredDuringInsert: false, requiredDuringInsert: false,
defaultConstraints: GeneratedColumn.constraintsDependsOnDialect({ defaultConstraints:
SqlDialect.sqlite: 'CHECK ("shadowed" IN (0, 1))', GeneratedColumn.constraintIsAlways('CHECK ("shadowed" IN (0, 1))'),
SqlDialect.mysql: '', defaultValue: const Constant(false));
SqlDialect.postgres: '',
}),
defaultValue: const Constant(false));
static const VerificationMeta _commandMeta = static const VerificationMeta _commandMeta =
const VerificationMeta('command'); const VerificationMeta('command');
@override @override
@@ -2075,16 +2057,13 @@ class $PinnedMessagesTable extends PinnedMessages
type: DriftSqlType.string, requiredDuringInsert: false); type: DriftSqlType.string, requiredDuringInsert: false);
static const VerificationMeta _pinnedMeta = const VerificationMeta('pinned'); static const VerificationMeta _pinnedMeta = const VerificationMeta('pinned');
@override @override
late final GeneratedColumn<bool> pinned = late final GeneratedColumn<bool> pinned = GeneratedColumn<bool>(
GeneratedColumn<bool>('pinned', aliasedName, false, 'pinned', aliasedName, false,
type: DriftSqlType.bool, type: DriftSqlType.bool,
requiredDuringInsert: false, requiredDuringInsert: false,
defaultConstraints: GeneratedColumn.constraintsDependsOnDialect({ defaultConstraints:
SqlDialect.sqlite: 'CHECK ("pinned" IN (0, 1))', GeneratedColumn.constraintIsAlways('CHECK ("pinned" IN (0, 1))'),
SqlDialect.mysql: '', defaultValue: const Constant(false));
SqlDialect.postgres: '',
}),
defaultValue: const Constant(false));
static const VerificationMeta _pinnedAtMeta = static const VerificationMeta _pinnedAtMeta =
const VerificationMeta('pinnedAt'); const VerificationMeta('pinnedAt');
@override @override
@@ -3928,28 +3907,22 @@ class $UsersTable extends Users with TableInfo<$UsersTable, UserEntity> {
type: DriftSqlType.dateTime, requiredDuringInsert: false); type: DriftSqlType.dateTime, requiredDuringInsert: false);
static const VerificationMeta _onlineMeta = const VerificationMeta('online'); static const VerificationMeta _onlineMeta = const VerificationMeta('online');
@override @override
late final GeneratedColumn<bool> online = late final GeneratedColumn<bool> online = GeneratedColumn<bool>(
GeneratedColumn<bool>('online', aliasedName, false, 'online', aliasedName, false,
type: DriftSqlType.bool, type: DriftSqlType.bool,
requiredDuringInsert: false, requiredDuringInsert: false,
defaultConstraints: GeneratedColumn.constraintsDependsOnDialect({ defaultConstraints:
SqlDialect.sqlite: 'CHECK ("online" IN (0, 1))', GeneratedColumn.constraintIsAlways('CHECK ("online" IN (0, 1))'),
SqlDialect.mysql: '', defaultValue: const Constant(false));
SqlDialect.postgres: '',
}),
defaultValue: const Constant(false));
static const VerificationMeta _bannedMeta = const VerificationMeta('banned'); static const VerificationMeta _bannedMeta = const VerificationMeta('banned');
@override @override
late final GeneratedColumn<bool> banned = late final GeneratedColumn<bool> banned = GeneratedColumn<bool>(
GeneratedColumn<bool>('banned', aliasedName, false, 'banned', aliasedName, false,
type: DriftSqlType.bool, type: DriftSqlType.bool,
requiredDuringInsert: false, requiredDuringInsert: false,
defaultConstraints: GeneratedColumn.constraintsDependsOnDialect({ defaultConstraints:
SqlDialect.sqlite: 'CHECK ("banned" IN (0, 1))', GeneratedColumn.constraintIsAlways('CHECK ("banned" IN (0, 1))'),
SqlDialect.mysql: '', defaultValue: const Constant(false));
SqlDialect.postgres: '',
}),
defaultValue: const Constant(false));
static const VerificationMeta _extraDataMeta = static const VerificationMeta _extraDataMeta =
const VerificationMeta('extraData'); const VerificationMeta('extraData');
@override @override
@@ -4388,54 +4361,42 @@ class $MembersTable extends Members
static const VerificationMeta _invitedMeta = static const VerificationMeta _invitedMeta =
const VerificationMeta('invited'); const VerificationMeta('invited');
@override @override
late final GeneratedColumn<bool> invited = late final GeneratedColumn<bool> invited = GeneratedColumn<bool>(
GeneratedColumn<bool>('invited', aliasedName, false, 'invited', aliasedName, false,
type: DriftSqlType.bool, type: DriftSqlType.bool,
requiredDuringInsert: false, requiredDuringInsert: false,
defaultConstraints: GeneratedColumn.constraintsDependsOnDialect({ defaultConstraints:
SqlDialect.sqlite: 'CHECK ("invited" IN (0, 1))', GeneratedColumn.constraintIsAlways('CHECK ("invited" IN (0, 1))'),
SqlDialect.mysql: '', defaultValue: const Constant(false));
SqlDialect.postgres: '',
}),
defaultValue: const Constant(false));
static const VerificationMeta _bannedMeta = const VerificationMeta('banned'); static const VerificationMeta _bannedMeta = const VerificationMeta('banned');
@override @override
late final GeneratedColumn<bool> banned = late final GeneratedColumn<bool> banned = GeneratedColumn<bool>(
GeneratedColumn<bool>('banned', aliasedName, false, 'banned', aliasedName, false,
type: DriftSqlType.bool, type: DriftSqlType.bool,
requiredDuringInsert: false, requiredDuringInsert: false,
defaultConstraints: GeneratedColumn.constraintsDependsOnDialect({ defaultConstraints:
SqlDialect.sqlite: 'CHECK ("banned" IN (0, 1))', GeneratedColumn.constraintIsAlways('CHECK ("banned" IN (0, 1))'),
SqlDialect.mysql: '', defaultValue: const Constant(false));
SqlDialect.postgres: '',
}),
defaultValue: const Constant(false));
static const VerificationMeta _shadowBannedMeta = static const VerificationMeta _shadowBannedMeta =
const VerificationMeta('shadowBanned'); const VerificationMeta('shadowBanned');
@override @override
late final GeneratedColumn<bool> shadowBanned = late final GeneratedColumn<bool> shadowBanned = GeneratedColumn<bool>(
GeneratedColumn<bool>('shadow_banned', aliasedName, false, 'shadow_banned', aliasedName, false,
type: DriftSqlType.bool, type: DriftSqlType.bool,
requiredDuringInsert: false, requiredDuringInsert: false,
defaultConstraints: GeneratedColumn.constraintsDependsOnDialect({ defaultConstraints: GeneratedColumn.constraintIsAlways(
SqlDialect.sqlite: 'CHECK ("shadow_banned" IN (0, 1))', 'CHECK ("shadow_banned" IN (0, 1))'),
SqlDialect.mysql: '', defaultValue: const Constant(false));
SqlDialect.postgres: '',
}),
defaultValue: const Constant(false));
static const VerificationMeta _isModeratorMeta = static const VerificationMeta _isModeratorMeta =
const VerificationMeta('isModerator'); const VerificationMeta('isModerator');
@override @override
late final GeneratedColumn<bool> isModerator = late final GeneratedColumn<bool> isModerator = GeneratedColumn<bool>(
GeneratedColumn<bool>('is_moderator', aliasedName, false, 'is_moderator', aliasedName, false,
type: DriftSqlType.bool, type: DriftSqlType.bool,
requiredDuringInsert: false, requiredDuringInsert: false,
defaultConstraints: GeneratedColumn.constraintsDependsOnDialect({ defaultConstraints: GeneratedColumn.constraintIsAlways(
SqlDialect.sqlite: 'CHECK ("is_moderator" IN (0, 1))', 'CHECK ("is_moderator" IN (0, 1))'),
SqlDialect.mysql: '', defaultValue: const Constant(false));
SqlDialect.postgres: '',
}),
defaultValue: const Constant(false));
static const VerificationMeta _createdAtMeta = static const VerificationMeta _createdAtMeta =
const VerificationMeta('createdAt'); const VerificationMeta('createdAt');
@override @override
+11 -11
View File
@@ -1,29 +1,29 @@
name: stream_chat_persistence name: stream_chat_persistence
homepage: https://github.com/GetStream/stream-chat-flutter homepage: https://github.com/GetStream/stream-chat-flutter
description: Official Stream Chat Persistence library. Build your own chat experience using Dart and Flutter. description: Official Stream Chat Persistence library. Build your own chat experience using Dart and Flutter.
version: 6.7.0 version: 6.8.0
repository: https://github.com/GetStream/stream-chat-flutter repository: https://github.com/GetStream/stream-chat-flutter
issue_tracker: https://github.com/GetStream/stream-chat-flutter/issues issue_tracker: https://github.com/GetStream/stream-chat-flutter/issues
environment: environment:
sdk: ">=2.19.0 <4.0.0" sdk: ">=3.0.0 <4.0.0"
flutter: ">=3.7.0" flutter: ">=3.10.0"
dependencies: dependencies:
drift: ^2.8.0 drift: ^2.11.0
flutter: flutter:
sdk: flutter sdk: flutter
logging: ^1.2.0 logging: ^1.2.0
meta: ^1.8.0 meta: ^1.9.1
path: ^1.8.2 path: ^1.8.3
path_provider: ^2.0.15 path_provider: ^2.1.0
sqlite3_flutter_libs: ^0.5.15 sqlite3_flutter_libs: ^0.5.15
stream_chat: ^6.7.0 stream_chat: ^6.8.0
dev_dependencies: dev_dependencies:
build_runner: ^2.3.3 build_runner: ^2.4.6
drift_dev: ^2.8.3 drift_dev: ^2.11.0
flutter_test: flutter_test:
sdk: flutter sdk: flutter
mocktail: ^0.3.0 mocktail: ^1.0.0
+1 -1
View File
@@ -1,7 +1,7 @@
name: stream_chat_flutter_workspace name: stream_chat_flutter_workspace
environment: environment:
sdk: '>=2.19.0 <4.0.0' sdk: '>=3.0.0 <4.0.0'
dev_dependencies: dev_dependencies:
melos: ^3.1.0 melos: ^3.1.0