Merge branch 'GetStream:develop' into develop
This commit is contained in:
@@ -10,6 +10,9 @@
|
|||||||
- [[#890]](https://github.com/GetStream/stream-chat-flutter/pull/890) Fixed Reactions not updating on thread messages.
|
- [[#890]](https://github.com/GetStream/stream-chat-flutter/pull/890) Fixed Reactions not updating on thread messages.
|
||||||
Thanks [bstolinski](https://github.com/bstolinski).
|
Thanks [bstolinski](https://github.com/bstolinski).
|
||||||
- [[#897]](https://github.com/GetStream/stream-chat-flutter/issues/897) Fixed error type mis-match in `AuthInterceptor`.
|
- [[#897]](https://github.com/GetStream/stream-chat-flutter/issues/897) Fixed error type mis-match in `AuthInterceptor`.
|
||||||
|
- [[#891]](https://github.com/GetStream/stream-chat-flutter/pull/891) Fixed reply counter for parent message not
|
||||||
|
updating correctly after deleting thread message.
|
||||||
|
- Fix `channelState.copyWith` with respect to pinnedMessages.
|
||||||
|
|
||||||
## 3.4.0
|
## 3.4.0
|
||||||
|
|
||||||
|
|||||||
@@ -407,7 +407,7 @@ class Channel {
|
|||||||
if (index != -1) {
|
if (index != -1) {
|
||||||
final newAttachments = [...message!.attachments]..[index] = attachment;
|
final newAttachments = [...message!.attachments]..[index] = attachment;
|
||||||
final updatedMessage = message!.copyWith(attachments: newAttachments);
|
final updatedMessage = message!.copyWith(attachments: newAttachments);
|
||||||
state?.addMessage(updatedMessage);
|
state?.updateMessage(updatedMessage);
|
||||||
// updating original message for next iteration
|
// updating original message for next iteration
|
||||||
message = message!.merge(updatedMessage);
|
message = message!.merge(updatedMessage);
|
||||||
}
|
}
|
||||||
@@ -512,7 +512,7 @@ class Channel {
|
|||||||
).toList(),
|
).toList(),
|
||||||
);
|
);
|
||||||
|
|
||||||
state!.addMessage(message);
|
state!.updateMessage(message);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (message.attachments.any((it) => !it.uploadState.isSuccess)) {
|
if (message.attachments.any((it) => !it.uploadState.isSuccess)) {
|
||||||
@@ -535,7 +535,7 @@ class Channel {
|
|||||||
type,
|
type,
|
||||||
skipPush: skipPush,
|
skipPush: skipPush,
|
||||||
);
|
);
|
||||||
state!.addMessage(response.message);
|
state!.updateMessage(response.message);
|
||||||
if (cooldown > 0) cooldownStartedAt = DateTime.now();
|
if (cooldown > 0) cooldownStartedAt = DateTime.now();
|
||||||
return response;
|
return response;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@@ -571,7 +571,7 @@ class Channel {
|
|||||||
).toList(),
|
).toList(),
|
||||||
);
|
);
|
||||||
|
|
||||||
state?.addMessage(message);
|
state?.updateMessage(message);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (message.attachments.any((it) => !it.uploadState.isSuccess)) {
|
if (message.attachments.any((it) => !it.uploadState.isSuccess)) {
|
||||||
@@ -594,7 +594,7 @@ class Channel {
|
|||||||
ownReactions: message.ownReactions,
|
ownReactions: message.ownReactions,
|
||||||
);
|
);
|
||||||
|
|
||||||
state?.addMessage(m);
|
state?.updateMessage(m);
|
||||||
|
|
||||||
return response;
|
return response;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@@ -602,7 +602,7 @@ class Channel {
|
|||||||
if (e.isRetriable) {
|
if (e.isRetriable) {
|
||||||
state!._retryQueue.add([message]);
|
state!._retryQueue.add([message]);
|
||||||
} else {
|
} else {
|
||||||
state?.addMessage(originalMessage);
|
state?.updateMessage(originalMessage);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
rethrow;
|
rethrow;
|
||||||
@@ -630,7 +630,7 @@ class Channel {
|
|||||||
ownReactions: message.ownReactions,
|
ownReactions: message.ownReactions,
|
||||||
);
|
);
|
||||||
|
|
||||||
state?.addMessage(updatedMessage);
|
state?.updateMessage(updatedMessage);
|
||||||
|
|
||||||
return response;
|
return response;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@@ -643,13 +643,18 @@ class Channel {
|
|||||||
|
|
||||||
/// Deletes the [message] from the channel.
|
/// Deletes the [message] from the channel.
|
||||||
Future<EmptyResponse> deleteMessage(Message message, {bool? hard}) async {
|
Future<EmptyResponse> deleteMessage(Message message, {bool? hard}) async {
|
||||||
|
final hardDelete = hard ?? false;
|
||||||
|
|
||||||
// Directly deleting the local messages which are not yet sent to server
|
// Directly deleting the local messages which are not yet sent to server
|
||||||
if (message.status == MessageSendingStatus.sending ||
|
if (message.status == MessageSendingStatus.sending ||
|
||||||
message.status == MessageSendingStatus.failed) {
|
message.status == MessageSendingStatus.failed) {
|
||||||
state!.addMessage(message.copyWith(
|
state!.deleteMessage(
|
||||||
type: 'deleted',
|
message.copyWith(
|
||||||
status: MessageSendingStatus.sent,
|
type: 'deleted',
|
||||||
));
|
status: MessageSendingStatus.sent,
|
||||||
|
),
|
||||||
|
hardDelete: hardDelete,
|
||||||
|
);
|
||||||
|
|
||||||
// Removing the attachments upload completer to stop the `sendMessage`
|
// Removing the attachments upload completer to stop the `sendMessage`
|
||||||
// waiting for attachments to complete.
|
// waiting for attachments to complete.
|
||||||
@@ -667,11 +672,14 @@ class Channel {
|
|||||||
deletedAt: message.deletedAt ?? DateTime.now(),
|
deletedAt: message.deletedAt ?? DateTime.now(),
|
||||||
);
|
);
|
||||||
|
|
||||||
state?.addMessage(message);
|
state?.deleteMessage(message, hardDelete: hardDelete);
|
||||||
|
|
||||||
final response = await _client.deleteMessage(message.id, hard: hard);
|
final response = await _client.deleteMessage(message.id, hard: hard);
|
||||||
|
|
||||||
state?.addMessage(message.copyWith(status: MessageSendingStatus.sent));
|
state?.deleteMessage(
|
||||||
|
message.copyWith(status: MessageSendingStatus.sent),
|
||||||
|
hardDelete: hardDelete,
|
||||||
|
);
|
||||||
|
|
||||||
return response;
|
return response;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@@ -861,7 +869,7 @@ class Channel {
|
|||||||
ownReactions: ownReactions,
|
ownReactions: ownReactions,
|
||||||
);
|
);
|
||||||
|
|
||||||
state?.addMessage(newMessage);
|
state?.updateMessage(newMessage);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
final reactionResp = await _client.sendReaction(
|
final reactionResp = await _client.sendReaction(
|
||||||
@@ -874,7 +882,7 @@ class Channel {
|
|||||||
return reactionResp;
|
return reactionResp;
|
||||||
} catch (_) {
|
} catch (_) {
|
||||||
// Reset the message if the update fails
|
// Reset the message if the update fails
|
||||||
state?.addMessage(message);
|
state?.updateMessage(message);
|
||||||
rethrow;
|
rethrow;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -914,7 +922,7 @@ class Channel {
|
|||||||
ownReactions: ownReactions,
|
ownReactions: ownReactions,
|
||||||
);
|
);
|
||||||
|
|
||||||
state?.addMessage(newMessage);
|
state?.updateMessage(newMessage);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
final deleteResponse = await _client.deleteReaction(
|
final deleteResponse = await _client.deleteReaction(
|
||||||
@@ -924,7 +932,7 @@ class Channel {
|
|||||||
return deleteResponse;
|
return deleteResponse;
|
||||||
} catch (_) {
|
} catch (_) {
|
||||||
// Reset the message if the update fails
|
// Reset the message if the update fails
|
||||||
state?.addMessage(message);
|
state?.updateMessage(message);
|
||||||
rethrow;
|
rethrow;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1081,7 +1089,7 @@ class Channel {
|
|||||||
|
|
||||||
// update the passed message with response message
|
// update the passed message with response message
|
||||||
if (res.message != null) {
|
if (res.message != null) {
|
||||||
state!.addMessage(res.message!);
|
state!.updateMessage(res.message!);
|
||||||
} else {
|
} else {
|
||||||
// remove the passed message if response does
|
// remove the passed message if response does
|
||||||
// not contain message
|
// not contain message
|
||||||
@@ -1712,7 +1720,7 @@ class ChannelClientState {
|
|||||||
final message = event.message!.copyWith(
|
final message = event.message!.copyWith(
|
||||||
ownReactions: ownReactions,
|
ownReactions: ownReactions,
|
||||||
);
|
);
|
||||||
addMessage(message);
|
updateMessage(message);
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1725,7 +1733,7 @@ class ChannelClientState {
|
|||||||
final message = event.message!.copyWith(
|
final message = event.message!.copyWith(
|
||||||
ownReactions: oldMessage?.ownReactions,
|
ownReactions: oldMessage?.ownReactions,
|
||||||
);
|
);
|
||||||
addMessage(message);
|
updateMessage(message);
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1743,7 +1751,7 @@ class ChannelClientState {
|
|||||||
final message = event.message!.copyWith(
|
final message = event.message!.copyWith(
|
||||||
ownReactions: oldMessage?.ownReactions,
|
ownReactions: oldMessage?.ownReactions,
|
||||||
);
|
);
|
||||||
addMessage(message);
|
updateMessage(message);
|
||||||
|
|
||||||
if (message.pinned) {
|
if (message.pinned) {
|
||||||
_channelState = _channelState.copyWith(
|
_channelState = _channelState.copyWith(
|
||||||
@@ -1760,9 +1768,9 @@ class ChannelClientState {
|
|||||||
_subscriptions.add(_channel.on(EventType.messageDeleted).listen((event) {
|
_subscriptions.add(_channel.on(EventType.messageDeleted).listen((event) {
|
||||||
final message = event.message!;
|
final message = event.message!;
|
||||||
if (event.hardDelete == true) {
|
if (event.hardDelete == true) {
|
||||||
removeMessage(message, hardDelete: true);
|
removeMessage(message);
|
||||||
} else {
|
} else {
|
||||||
addMessage(message);
|
updateMessage(message);
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
@@ -1777,7 +1785,7 @@ class ChannelClientState {
|
|||||||
final message = event.message!;
|
final message = event.message!;
|
||||||
if (isUpToDate ||
|
if (isUpToDate ||
|
||||||
(message.parentId != null && message.showInChannel != true)) {
|
(message.parentId != null && message.showInChannel != true)) {
|
||||||
addMessage(message);
|
updateMessage(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_countMessageAsUnread(message)) {
|
if (_countMessageAsUnread(message)) {
|
||||||
@@ -1787,9 +1795,13 @@ class ChannelClientState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Add a [message] to this [channelState].
|
/// Add a [message] to this [channelState].
|
||||||
void addMessage(Message message) {
|
@Deprecated('Use updateMessage instead')
|
||||||
|
void addMessage(Message message) => updateMessage(message);
|
||||||
|
|
||||||
|
/// Updates the [message] in the state if it exists. Adds it otherwise.
|
||||||
|
void updateMessage(Message message) {
|
||||||
if (message.parentId == null || message.showInChannel == true) {
|
if (message.parentId == null || message.showInChannel == true) {
|
||||||
final newMessages = List<Message>.from(_channelState.messages);
|
final newMessages = [...messages];
|
||||||
final oldIndex = newMessages.indexWhere((m) => m.id == message.id);
|
final oldIndex = newMessages.indexWhere((m) => m.id == message.id);
|
||||||
if (oldIndex != -1) {
|
if (oldIndex != -1) {
|
||||||
Message? m;
|
Message? m;
|
||||||
@@ -1804,8 +1816,24 @@ class ChannelClientState {
|
|||||||
newMessages.add(message);
|
newMessages.add(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final newPinnedMessages = [...pinnedMessages];
|
||||||
|
final oldPinnedIndex =
|
||||||
|
newPinnedMessages.indexWhere((m) => m.id == message.id);
|
||||||
|
|
||||||
|
// Handle pinned messages
|
||||||
|
if (message.pinned) {
|
||||||
|
if (oldPinnedIndex != -1) {
|
||||||
|
newPinnedMessages[oldPinnedIndex] = message;
|
||||||
|
} else {
|
||||||
|
newPinnedMessages.add(message);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
newPinnedMessages.removeWhere((m) => m.id == message.id);
|
||||||
|
}
|
||||||
|
|
||||||
_channelState = _channelState.copyWith(
|
_channelState = _channelState.copyWith(
|
||||||
messages: newMessages..sort(_sortByCreatedAt),
|
messages: newMessages..sort(_sortByCreatedAt),
|
||||||
|
pinnedMessages: newPinnedMessages,
|
||||||
channel: _channelState.channel?.copyWith(
|
channel: _channelState.channel?.copyWith(
|
||||||
lastMessageAt: message.createdAt,
|
lastMessageAt: message.createdAt,
|
||||||
),
|
),
|
||||||
@@ -1818,41 +1846,35 @@ class ChannelClientState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Remove a [message] from this [channelState].
|
/// Remove a [message] from this [channelState].
|
||||||
void removeMessage(Message message, {bool hardDelete = false}) {
|
void removeMessage(Message message) {
|
||||||
final parentId = message.parentId;
|
final parentId = message.parentId;
|
||||||
// i.e. it's a thread message
|
// i.e. it's a thread message, Remove it
|
||||||
// 1. Remove the thread message
|
|
||||||
// 2. Reduce total reply count of parent message
|
|
||||||
if (parentId != null) {
|
if (parentId != null) {
|
||||||
final allMessages = [...messages];
|
final newThreads = {...threads};
|
||||||
final parentMessage = allMessages.firstWhereOrNull(
|
// Early return in case the thread is not available
|
||||||
(it) => it.id == parentId,
|
if (!newThreads.containsKey(parentId)) return;
|
||||||
);
|
|
||||||
|
|
||||||
// return if message not available in the memory
|
_threads = newThreads
|
||||||
if (parentMessage == null) return;
|
..update(
|
||||||
final replyCount = parentMessage.replyCount;
|
parentId,
|
||||||
// return if reply count is null or zero
|
(messages) => messages..removeWhere((e) => e.id == message.id),
|
||||||
if (replyCount == null || replyCount == 0) return;
|
);
|
||||||
|
|
||||||
addMessage(parentMessage.copyWith(replyCount: replyCount - 1));
|
// Early return if the thread message is not shown in channel.
|
||||||
updateThreadInfo(
|
if (message.showInChannel == false) return;
|
||||||
parentId,
|
|
||||||
threads[parentId]!
|
|
||||||
..removeWhere(
|
|
||||||
(e) => e.id == message.id,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
// Remove regular message
|
|
||||||
final allMessages = [...messages];
|
|
||||||
if (hardDelete) {
|
|
||||||
allMessages.removeWhere((e) => e.id == message.id);
|
|
||||||
_channelState = _channelState.copyWith(messages: allMessages);
|
|
||||||
} else if (allMessages.remove(message)) {
|
|
||||||
_channelState = _channelState.copyWith(messages: allMessages);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Remove regular message, thread message shown in channel
|
||||||
|
final allMessages = [...messages];
|
||||||
|
_channelState = _channelState.copyWith(
|
||||||
|
messages: allMessages..removeWhere((e) => e.id == message.id),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Removes/Updates the [message] based on the [hardDelete] value.
|
||||||
|
void deleteMessage(Message message, {bool hardDelete = false}) {
|
||||||
|
if (hardDelete) return removeMessage(message);
|
||||||
|
return updateMessage(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
void _listenReadEvents() {
|
void _listenReadEvents() {
|
||||||
@@ -1898,11 +1920,12 @@ class ChannelClientState {
|
|||||||
.distinct(const ListEquality().equals);
|
.distinct(const ListEquality().equals);
|
||||||
|
|
||||||
/// Channel pinned message list.
|
/// Channel pinned message list.
|
||||||
List<Message> get pinnedMessages => _channelState.pinnedMessages.toList();
|
List<Message> get pinnedMessages => _channelState.pinnedMessages;
|
||||||
|
|
||||||
/// Channel pinned message list as a stream.
|
/// Channel pinned message list as a stream.
|
||||||
Stream<List<Message>> get pinnedMessagesStream =>
|
Stream<List<Message>> get pinnedMessagesStream => channelStateStream
|
||||||
channelStateStream.map((cs) => cs.pinnedMessages.toList());
|
.map((cs) => cs.pinnedMessages)
|
||||||
|
.distinct(const ListEquality().equals);
|
||||||
|
|
||||||
/// Get channel last message.
|
/// Get channel last message.
|
||||||
Message? get lastMessage =>
|
Message? get lastMessage =>
|
||||||
@@ -2213,7 +2236,7 @@ class ChannelClientState {
|
|||||||
.toList();
|
.toList();
|
||||||
|
|
||||||
updateChannelState(_channelState.copyWith(
|
updateChannelState(_channelState.copyWith(
|
||||||
pinnedMessages: pinnedMessages.where(_pinIsValid()).toList(),
|
pinnedMessages: pinnedMessages.where(_pinIsValid).toList(),
|
||||||
messages: expiredMessages,
|
messages: expiredMessages,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
@@ -2250,7 +2273,7 @@ class ChannelClientState {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Function(Message) _pinIsValid() {
|
bool _pinIsValid(Message message) {
|
||||||
final now = DateTime.now();
|
final now = DateTime.now();
|
||||||
return (Message m) => m.pinExpires!.isAfter(now);
|
return message.pinExpires!.isAfter(now);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -158,7 +158,7 @@ class RetryQueue {
|
|||||||
: message.status == MessageSendingStatus.updating
|
: message.status == MessageSendingStatus.updating
|
||||||
? MessageSendingStatus.failed_update
|
? MessageSendingStatus.failed_update
|
||||||
: MessageSendingStatus.failed_delete;
|
: MessageSendingStatus.failed_delete;
|
||||||
channel.state?.addMessage(message.copyWith(status: newStatus));
|
channel.state?.updateMessage(message.copyWith(status: newStatus));
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _retryMessage(Message message) async {
|
Future<void> _retryMessage(Message message) async {
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ class ChannelState {
|
|||||||
ChannelModel? channel,
|
ChannelModel? channel,
|
||||||
List<Message>? messages,
|
List<Message>? messages,
|
||||||
List<Member>? members,
|
List<Member>? members,
|
||||||
List<Message>? pinnedMessages,
|
List<Message> pinnedMessages = _emptyPinnedMessages,
|
||||||
int? watcherCount,
|
int? watcherCount,
|
||||||
List<User>? watchers,
|
List<User>? watchers,
|
||||||
List<Read>? read,
|
List<Read>? read,
|
||||||
@@ -69,7 +69,7 @@ class ChannelState {
|
|||||||
// FIXME: Use non-nullable by default instead of empty list.
|
// FIXME: Use non-nullable by default instead of empty list.
|
||||||
pinnedMessages: pinnedMessages == _emptyPinnedMessages
|
pinnedMessages: pinnedMessages == _emptyPinnedMessages
|
||||||
? this.pinnedMessages
|
? this.pinnedMessages
|
||||||
: pinnedMessages ?? _emptyPinnedMessages,
|
: pinnedMessages,
|
||||||
watcherCount: watcherCount ?? this.watcherCount,
|
watcherCount: watcherCount ?? this.watcherCount,
|
||||||
watchers: watchers ?? this.watchers,
|
watchers: watchers ?? this.watchers,
|
||||||
read: read ?? this.read,
|
read: read ?? this.read,
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
- [[#888]](https://github.com/GetStream/stream-chat-flutter/issues/888) Fix `unban` command not working in `MessageInput`.
|
- [[#888]](https://github.com/GetStream/stream-chat-flutter/issues/888) Fix `unban` command not working in `MessageInput`.
|
||||||
- [[#805]](https://github.com/GetStream/stream-chat-flutter/issues/805) Updated chewie dependency version to 1.3.0
|
- [[#805]](https://github.com/GetStream/stream-chat-flutter/issues/805) Updated chewie dependency version to 1.3.0
|
||||||
- Fix `showScrollToBottom` in `MessageListView` not respecting false value.
|
- Fix `showScrollToBottom` in `MessageListView` not respecting false value.
|
||||||
|
- Fix default `Channel` route not opening from `ChannelListView` when `ChannelAvatar` is tapped
|
||||||
|
|
||||||
✅ Added
|
✅ Added
|
||||||
|
|
||||||
|
|||||||
@@ -599,7 +599,9 @@ class _ChannelListViewState extends State<ChannelListView> {
|
|||||||
child: ChannelPreview(
|
child: ChannelPreview(
|
||||||
onLongPress: widget.onChannelLongPress,
|
onLongPress: widget.onChannelLongPress,
|
||||||
channel: channel,
|
channel: channel,
|
||||||
onImageTap: () => widget.onImageTap?.call(channel),
|
onImageTap: widget.onImageTap != null
|
||||||
|
? () => widget.onImageTap!(channel)
|
||||||
|
: null,
|
||||||
onTap: (channel) => onTap(channel, widget.channelWidget),
|
onTap: (channel) => onTap(channel, widget.channelWidget),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -612,7 +614,7 @@ class _ChannelListViewState extends State<ChannelListView> {
|
|||||||
if (widget.onChannelTap != null) {
|
if (widget.onChannelTap != null) {
|
||||||
onTap = widget.onChannelTap!;
|
onTap = widget.onChannelTap!;
|
||||||
} else {
|
} else {
|
||||||
onTap = (client, _) {
|
onTap = (channel, _) {
|
||||||
if (widget.channelWidget == null) {
|
if (widget.channelWidget == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -620,7 +622,7 @@ class _ChannelListViewState extends State<ChannelListView> {
|
|||||||
context,
|
context,
|
||||||
MaterialPageRoute(
|
MaterialPageRoute(
|
||||||
builder: (context) => StreamChannel(
|
builder: (context) => StreamChannel(
|
||||||
channel: client,
|
channel: channel,
|
||||||
child: widget.channelWidget!,
|
child: widget.channelWidget!,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -65,24 +65,35 @@ class FullScreenMedia extends StatefulWidget {
|
|||||||
|
|
||||||
class _FullScreenMediaState extends State<FullScreenMedia>
|
class _FullScreenMediaState extends State<FullScreenMedia>
|
||||||
with SingleTickerProviderStateMixin {
|
with SingleTickerProviderStateMixin {
|
||||||
bool _optionsShown = true;
|
late final AnimationController _animationController;
|
||||||
|
|
||||||
late final AnimationController _controller;
|
|
||||||
late final PageController _pageController;
|
late final PageController _pageController;
|
||||||
|
|
||||||
late int _currentPage;
|
late final _curvedAnimation = CurvedAnimation(
|
||||||
|
parent: _animationController,
|
||||||
|
curve: Curves.easeOut,
|
||||||
|
reverseCurve: Curves.easeIn,
|
||||||
|
);
|
||||||
|
|
||||||
|
final _opacityTween = Tween<double>(begin: 1, end: 0);
|
||||||
|
late final _opacityAnimation = _opacityTween.animate(
|
||||||
|
CurvedAnimation(
|
||||||
|
parent: _animationController,
|
||||||
|
curve: const Interval(0, 0.6, curve: Curves.easeOut),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
late final ValueNotifier<int> _currentPage = ValueNotifier(widget.startIndex);
|
||||||
|
|
||||||
final videoPackages = <String, VideoPackage>{};
|
final videoPackages = <String, VideoPackage>{};
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
_controller = AnimationController(
|
_animationController = AnimationController(
|
||||||
vsync: this,
|
vsync: this,
|
||||||
duration: const Duration(milliseconds: 300),
|
duration: const Duration(milliseconds: 300),
|
||||||
);
|
);
|
||||||
_pageController = PageController(initialPage: widget.startIndex);
|
_pageController = PageController(initialPage: widget.startIndex);
|
||||||
_currentPage = widget.startIndex;
|
|
||||||
for (var i = 0; i < widget.mediaAttachments.length; i++) {
|
for (var i = 0; i < widget.mediaAttachments.length; i++) {
|
||||||
final attachment = widget.mediaAttachments[i];
|
final attachment = widget.mediaAttachments[i];
|
||||||
if (attachment.type != 'video') continue;
|
if (attachment.type != 'video') continue;
|
||||||
@@ -116,41 +127,38 @@ class _FullScreenMediaState extends State<FullScreenMedia>
|
|||||||
resizeToAvoidBottomInset: false,
|
resizeToAvoidBottomInset: false,
|
||||||
body: Stack(
|
body: Stack(
|
||||||
children: [
|
children: [
|
||||||
AnimatedBuilder(
|
PageView.builder(
|
||||||
animation: _controller,
|
controller: _pageController,
|
||||||
builder: (context, snapshot) => PageView.builder(
|
onPageChanged: (val) {
|
||||||
controller: _pageController,
|
_currentPage.value = val;
|
||||||
onPageChanged: (val) {
|
|
||||||
setState(() {
|
|
||||||
_currentPage = val;
|
|
||||||
});
|
|
||||||
|
|
||||||
if (videoPackages.isEmpty) {
|
if (videoPackages.isEmpty) {
|
||||||
return;
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final currentAttachment = widget.mediaAttachments[val];
|
||||||
|
|
||||||
|
for (final e in videoPackages.values) {
|
||||||
|
if (e._attachment != currentAttachment) {
|
||||||
|
e._chewieController?.pause();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
final currentAttachment = widget.mediaAttachments[val];
|
if (widget.autoplayVideos &&
|
||||||
|
currentAttachment.type == 'video') {
|
||||||
for (final e in videoPackages.values) {
|
final controller = videoPackages[currentAttachment.id]!;
|
||||||
if (e._attachment != currentAttachment) {
|
controller._chewieController?.play();
|
||||||
e._chewieController?.pause();
|
}
|
||||||
}
|
},
|
||||||
}
|
itemBuilder: (context, index) {
|
||||||
|
final attachment = widget.mediaAttachments[index];
|
||||||
if (widget.autoplayVideos &&
|
if (attachment.type == 'image' || attachment.type == 'giphy') {
|
||||||
currentAttachment.type == 'video') {
|
final imageUrl = attachment.imageUrl ??
|
||||||
final controller = videoPackages[currentAttachment.id]!;
|
attachment.assetUrl ??
|
||||||
controller._chewieController?.play();
|
attachment.thumbUrl;
|
||||||
}
|
return AnimatedBuilder(
|
||||||
},
|
animation: _curvedAnimation,
|
||||||
itemBuilder: (context, index) {
|
builder: (context, child) => PhotoView(
|
||||||
final attachment = widget.mediaAttachments[index];
|
|
||||||
if (attachment.type == 'image' ||
|
|
||||||
attachment.type == 'giphy') {
|
|
||||||
final imageUrl = attachment.imageUrl ??
|
|
||||||
attachment.assetUrl ??
|
|
||||||
attachment.thumbUrl;
|
|
||||||
return PhotoView(
|
|
||||||
loadingBuilder: (context, image) => const Offstage(),
|
loadingBuilder: (context, image) => const Offstage(),
|
||||||
imageProvider: (imageUrl == null &&
|
imageProvider: (imageUrl == null &&
|
||||||
attachment.localUri != null &&
|
attachment.localUri != null &&
|
||||||
@@ -166,97 +174,91 @@ class _FullScreenMediaState extends State<FullScreenMedia>
|
|||||||
color: ColorTween(
|
color: ColorTween(
|
||||||
begin: ChannelHeaderTheme.of(context).color,
|
begin: ChannelHeaderTheme.of(context).color,
|
||||||
end: Colors.black,
|
end: Colors.black,
|
||||||
).lerp(_controller.value),
|
).lerp(_curvedAnimation.value),
|
||||||
),
|
),
|
||||||
onTapUp: (a, b, c) {
|
onTapUp: (a, b, c) {
|
||||||
setState(() {
|
if (_animationController.isCompleted) {
|
||||||
_optionsShown = !_optionsShown;
|
_animationController.reverse();
|
||||||
});
|
|
||||||
if (_controller.isCompleted) {
|
|
||||||
_controller.reverse();
|
|
||||||
} else {
|
} else {
|
||||||
_controller.forward();
|
_animationController.forward();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
);
|
),
|
||||||
} else if (attachment.type == 'video') {
|
);
|
||||||
final controller = videoPackages[attachment.id]!;
|
} else if (attachment.type == 'video') {
|
||||||
if (!controller.initialized) {
|
final controller = videoPackages[attachment.id]!;
|
||||||
return const Center(
|
if (!controller.initialized) {
|
||||||
child: CircularProgressIndicator(),
|
return const Center(
|
||||||
);
|
child: CircularProgressIndicator(),
|
||||||
}
|
|
||||||
return InkWell(
|
|
||||||
onTap: () {
|
|
||||||
setState(() {
|
|
||||||
_optionsShown = !_optionsShown;
|
|
||||||
});
|
|
||||||
if (_controller.isCompleted) {
|
|
||||||
_controller.reverse();
|
|
||||||
} else {
|
|
||||||
_controller.forward();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
child: Padding(
|
|
||||||
padding: const EdgeInsets.symmetric(
|
|
||||||
vertical: 50,
|
|
||||||
),
|
|
||||||
child: Chewie(
|
|
||||||
controller: controller.chewieController!,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return Container();
|
return InkWell(
|
||||||
},
|
onTap: () {
|
||||||
itemCount: widget.mediaAttachments.length,
|
if (_animationController.isCompleted) {
|
||||||
),
|
_animationController.reverse();
|
||||||
),
|
} else {
|
||||||
AnimatedOpacity(
|
_animationController.forward();
|
||||||
opacity: _optionsShown ? 1.0 : 0.0,
|
}
|
||||||
duration: const Duration(milliseconds: 300),
|
},
|
||||||
child: Column(
|
child: Padding(
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
padding: const EdgeInsets.symmetric(
|
||||||
children: [
|
vertical: 50,
|
||||||
GalleryHeader(
|
),
|
||||||
userName: widget.userName,
|
child: Chewie(
|
||||||
sentAt: context.translations.sentAtText(
|
controller: controller.chewieController!,
|
||||||
date: widget.message.createdAt,
|
),
|
||||||
time: widget.message.createdAt,
|
|
||||||
),
|
),
|
||||||
onBackPressed: () {
|
);
|
||||||
Navigator.of(context).pop();
|
}
|
||||||
},
|
return const SizedBox();
|
||||||
message: widget.message,
|
},
|
||||||
currentIndex: _currentPage,
|
itemCount: widget.mediaAttachments.length,
|
||||||
onShowMessage: () {
|
),
|
||||||
widget.onShowMessage?.call(
|
FadeTransition(
|
||||||
widget.message,
|
opacity: _opacityAnimation,
|
||||||
StreamChannel.of(context).channel,
|
child: ValueListenableBuilder<int>(
|
||||||
);
|
valueListenable: _currentPage,
|
||||||
},
|
builder: (context, value, child) => Column(
|
||||||
attachmentActionsModalBuilder:
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
widget.attachmentActionsModalBuilder,
|
children: [
|
||||||
),
|
GalleryHeader(
|
||||||
if (!widget.message.isEphemeral)
|
userName: widget.userName,
|
||||||
GalleryFooter(
|
sentAt: context.translations.sentAtText(
|
||||||
currentPage: _currentPage,
|
date: widget.message.createdAt,
|
||||||
totalPages: widget.mediaAttachments.length,
|
time: widget.message.createdAt,
|
||||||
mediaAttachments: widget.mediaAttachments,
|
),
|
||||||
|
onBackPressed: () {
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
},
|
||||||
message: widget.message,
|
message: widget.message,
|
||||||
mediaSelectedCallBack: (val) {
|
currentIndex: value,
|
||||||
setState(() {
|
onShowMessage: () {
|
||||||
_currentPage = val;
|
widget.onShowMessage?.call(
|
||||||
|
widget.message,
|
||||||
|
StreamChannel.of(context).channel,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
attachmentActionsModalBuilder:
|
||||||
|
widget.attachmentActionsModalBuilder,
|
||||||
|
),
|
||||||
|
if (!widget.message.isEphemeral)
|
||||||
|
GalleryFooter(
|
||||||
|
currentPage: value,
|
||||||
|
totalPages: widget.mediaAttachments.length,
|
||||||
|
mediaAttachments: widget.mediaAttachments,
|
||||||
|
message: widget.message,
|
||||||
|
mediaSelectedCallBack: (val) {
|
||||||
|
_currentPage.value = val;
|
||||||
_pageController.animateToPage(
|
_pageController.animateToPage(
|
||||||
val,
|
val,
|
||||||
duration: const Duration(milliseconds: 300),
|
duration: const Duration(milliseconds: 300),
|
||||||
curve: Curves.easeInOut,
|
curve: Curves.easeInOut,
|
||||||
);
|
);
|
||||||
Navigator.pop(context);
|
Navigator.pop(context);
|
||||||
});
|
},
|
||||||
},
|
),
|
||||||
),
|
],
|
||||||
],
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@@ -264,9 +266,11 @@ class _FullScreenMediaState extends State<FullScreenMedia>
|
|||||||
);
|
);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() async {
|
void dispose() {
|
||||||
|
_animationController.dispose();
|
||||||
|
_pageController.dispose();
|
||||||
for (final package in videoPackages.values) {
|
for (final package in videoPackages.values) {
|
||||||
await package.dispose();
|
package.dispose();
|
||||||
}
|
}
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user