Merge branch 'develop' of https://github.com/GetStream/stream-chat-flutter into fix/indicator
Conflicts: packages/stream_chat/CHANGELOG.md
This commit is contained in:
@@ -33,7 +33,7 @@ jobs:
|
|||||||
flutter-version: ${{ env.flutter_version }}
|
flutter-version: ${{ env.flutter_version }}
|
||||||
|
|
||||||
- name: "Install Tools"
|
- name: "Install Tools"
|
||||||
run: flutter pub global activate melos 1.0.0-dev.6
|
run: flutter pub global activate melos 1.0.0-dev.10
|
||||||
- name: "Bootstrap Workspace"
|
- name: "Bootstrap Workspace"
|
||||||
run: melos bootstrap
|
run: melos bootstrap
|
||||||
|
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ jobs:
|
|||||||
flutter-version: ${{ env.flutter_version }}
|
flutter-version: ${{ env.flutter_version }}
|
||||||
- name: "Install Tools"
|
- name: "Install Tools"
|
||||||
run: |
|
run: |
|
||||||
flutter pub global activate melos 1.0.0-dev.6
|
flutter pub global activate melos 1.0.0-dev.10
|
||||||
- name: "Bootstrap Workspace"
|
- name: "Bootstrap Workspace"
|
||||||
run: melos bootstrap
|
run: melos bootstrap
|
||||||
- name: "Dart Analyze"
|
- name: "Dart Analyze"
|
||||||
|
|||||||
@@ -2,8 +2,15 @@
|
|||||||
|
|
||||||
🐞 Fixed
|
🐞 Fixed
|
||||||
|
|
||||||
|
- `closeConnection()` now uses `normalClosure` status when closing websocket.
|
||||||
- Fixed unread count indicator
|
- Fixed unread count indicator
|
||||||
|
|
||||||
|
## Upcoming
|
||||||
|
|
||||||
|
✅ Added
|
||||||
|
|
||||||
|
- Extra properties added to `PaginationParams` to aid in fetching messages.
|
||||||
|
|
||||||
## 3.2.0
|
## 3.2.0
|
||||||
|
|
||||||
🐞 Fixed
|
🐞 Fixed
|
||||||
@@ -726,4 +733,4 @@
|
|||||||
|
|
||||||
## 0.0.2
|
## 0.0.2
|
||||||
|
|
||||||
- first beta version
|
- first beta version
|
||||||
@@ -60,8 +60,11 @@ class PaginationParams extends Equatable {
|
|||||||
/// ```
|
/// ```
|
||||||
const PaginationParams({
|
const PaginationParams({
|
||||||
this.limit = 10,
|
this.limit = 10,
|
||||||
|
this.before = 10,
|
||||||
|
this.after = 10,
|
||||||
this.offset,
|
this.offset,
|
||||||
this.next,
|
this.next,
|
||||||
|
this.idAround,
|
||||||
this.greaterThan,
|
this.greaterThan,
|
||||||
this.greaterThanOrEqual,
|
this.greaterThanOrEqual,
|
||||||
this.lessThan,
|
this.lessThan,
|
||||||
@@ -78,12 +81,22 @@ class PaginationParams extends Equatable {
|
|||||||
/// The amount of items requested from the APIs.
|
/// The amount of items requested from the APIs.
|
||||||
final int limit;
|
final int limit;
|
||||||
|
|
||||||
|
/// The amount of items requested before message ID from the APIs.
|
||||||
|
final int before;
|
||||||
|
|
||||||
|
/// The amount of items requested after message ID from the APIs.
|
||||||
|
final int after;
|
||||||
|
|
||||||
/// The offset of requesting items.
|
/// The offset of requesting items.
|
||||||
final int? offset;
|
final int? offset;
|
||||||
|
|
||||||
/// A key used to paginate.
|
/// A key used to paginate.
|
||||||
final String? next;
|
final String? next;
|
||||||
|
|
||||||
|
/// Message ID to fetch messages around
|
||||||
|
@JsonKey(name: 'id_around')
|
||||||
|
final String? idAround;
|
||||||
|
|
||||||
/// Filter on ids greater than the given value.
|
/// Filter on ids greater than the given value.
|
||||||
@JsonKey(name: 'id_gt')
|
@JsonKey(name: 'id_gt')
|
||||||
final String? greaterThan;
|
final String? greaterThan;
|
||||||
@@ -106,7 +119,10 @@ class PaginationParams extends Equatable {
|
|||||||
/// Creates a copy of [PaginationParams] with specified attributes overridden.
|
/// Creates a copy of [PaginationParams] with specified attributes overridden.
|
||||||
PaginationParams copyWith({
|
PaginationParams copyWith({
|
||||||
int? limit,
|
int? limit,
|
||||||
|
int? before,
|
||||||
|
int? after,
|
||||||
int? offset,
|
int? offset,
|
||||||
|
String? idAround,
|
||||||
String? next,
|
String? next,
|
||||||
String? greaterThan,
|
String? greaterThan,
|
||||||
String? greaterThanOrEqual,
|
String? greaterThanOrEqual,
|
||||||
@@ -115,7 +131,10 @@ class PaginationParams extends Equatable {
|
|||||||
}) =>
|
}) =>
|
||||||
PaginationParams(
|
PaginationParams(
|
||||||
limit: limit ?? this.limit,
|
limit: limit ?? this.limit,
|
||||||
|
before: before ?? this.before,
|
||||||
|
after: limit ?? this.after,
|
||||||
offset: offset ?? this.offset,
|
offset: offset ?? this.offset,
|
||||||
|
idAround: idAround ?? this.idAround,
|
||||||
next: next ?? this.next,
|
next: next ?? this.next,
|
||||||
greaterThan: greaterThan ?? this.greaterThan,
|
greaterThan: greaterThan ?? this.greaterThan,
|
||||||
greaterThanOrEqual: greaterThanOrEqual ?? this.greaterThanOrEqual,
|
greaterThanOrEqual: greaterThanOrEqual ?? this.greaterThanOrEqual,
|
||||||
@@ -126,8 +145,11 @@ class PaginationParams extends Equatable {
|
|||||||
@override
|
@override
|
||||||
List<Object?> get props => [
|
List<Object?> get props => [
|
||||||
limit,
|
limit,
|
||||||
|
before,
|
||||||
|
after,
|
||||||
offset,
|
offset,
|
||||||
next,
|
next,
|
||||||
|
idAround,
|
||||||
greaterThan,
|
greaterThan,
|
||||||
greaterThanOrEqual,
|
greaterThanOrEqual,
|
||||||
lessThan,
|
lessThan,
|
||||||
|
|||||||
@@ -21,8 +21,11 @@ Map<String, dynamic> _$SortOptionToJson<T>(SortOption<T> instance) =>
|
|||||||
PaginationParams _$PaginationParamsFromJson(Map<String, dynamic> json) =>
|
PaginationParams _$PaginationParamsFromJson(Map<String, dynamic> json) =>
|
||||||
PaginationParams(
|
PaginationParams(
|
||||||
limit: json['limit'] as int? ?? 10,
|
limit: json['limit'] as int? ?? 10,
|
||||||
|
before: json['before'] as int? ?? 10,
|
||||||
|
after: json['after'] as int? ?? 10,
|
||||||
offset: json['offset'] as int?,
|
offset: json['offset'] as int?,
|
||||||
next: json['next'] as String?,
|
next: json['next'] as String?,
|
||||||
|
idAround: json['id_around'] as String?,
|
||||||
greaterThan: json['id_gt'] as String?,
|
greaterThan: json['id_gt'] as String?,
|
||||||
greaterThanOrEqual: json['id_gte'] as String?,
|
greaterThanOrEqual: json['id_gte'] as String?,
|
||||||
lessThan: json['id_lt'] as String?,
|
lessThan: json['id_lt'] as String?,
|
||||||
@@ -32,6 +35,8 @@ PaginationParams _$PaginationParamsFromJson(Map<String, dynamic> json) =>
|
|||||||
Map<String, dynamic> _$PaginationParamsToJson(PaginationParams instance) {
|
Map<String, dynamic> _$PaginationParamsToJson(PaginationParams instance) {
|
||||||
final val = <String, dynamic>{
|
final val = <String, dynamic>{
|
||||||
'limit': instance.limit,
|
'limit': instance.limit,
|
||||||
|
'before': instance.before,
|
||||||
|
'after': instance.after,
|
||||||
};
|
};
|
||||||
|
|
||||||
void writeNotNull(String key, dynamic value) {
|
void writeNotNull(String key, dynamic value) {
|
||||||
@@ -42,6 +47,7 @@ Map<String, dynamic> _$PaginationParamsToJson(PaginationParams instance) {
|
|||||||
|
|
||||||
writeNotNull('offset', instance.offset);
|
writeNotNull('offset', instance.offset);
|
||||||
writeNotNull('next', instance.next);
|
writeNotNull('next', instance.next);
|
||||||
|
writeNotNull('id_around', instance.idAround);
|
||||||
writeNotNull('id_gt', instance.greaterThan);
|
writeNotNull('id_gt', instance.greaterThan);
|
||||||
writeNotNull('id_gte', instance.greaterThanOrEqual);
|
writeNotNull('id_gte', instance.greaterThanOrEqual);
|
||||||
writeNotNull('id_lt', instance.lessThan);
|
writeNotNull('id_lt', instance.lessThan);
|
||||||
|
|||||||
@@ -121,7 +121,8 @@ class WebSocket with TimerHelper {
|
|||||||
_logger?.info('Closing connection with $baseUrl');
|
_logger?.info('Closing connection with $baseUrl');
|
||||||
if (_webSocketChannel != null) {
|
if (_webSocketChannel != null) {
|
||||||
_unsubscribeFromWebSocketChannel();
|
_unsubscribeFromWebSocketChannel();
|
||||||
_webSocketChannel?.sink.close(status.goingAway);
|
_webSocketChannel?.sink
|
||||||
|
.close(_manuallyClosed ? status.normalClosure : status.goingAway);
|
||||||
_webSocketChannel = null;
|
_webSocketChannel = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,15 @@
|
|||||||
## Upcoming
|
## Upcoming
|
||||||
|
|
||||||
|
✅ Added
|
||||||
|
|
||||||
|
- `MessageListView` now allows more better control over spacing after messages using `spacingWidgetBuilder`.
|
||||||
|
- `StreamChannel` can now fetch messages around a message ID with the `queryAroundMessage` call.
|
||||||
|
|
||||||
🐞 Fixed
|
🐞 Fixed
|
||||||
|
|
||||||
- [[#766]]`AttachmentActionsModal` now has customisation options for actions.
|
- [[#766]]`AttachmentActionsModal` now has customisation options for actions.
|
||||||
|
- Fixed `MessageWidget` null errors associated with `channel.memberCount`.
|
||||||
|
- Fixed adding attachments on web.
|
||||||
|
|
||||||
## 3.2.0
|
## 3.2.0
|
||||||
|
|
||||||
@@ -866,4 +873,4 @@ The property showVideoFullScreen was added mainly because of this issue brianega
|
|||||||
|
|
||||||
## 0.0.1
|
## 0.0.1
|
||||||
|
|
||||||
- First release
|
- First release
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
import 'package:characters/characters.dart';
|
import 'package:characters/characters.dart';
|
||||||
import 'package:diacritic/diacritic.dart';
|
import 'package:diacritic/diacritic.dart';
|
||||||
import 'package:file_picker/file_picker.dart';
|
import 'package:file_picker/file_picker.dart';
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:stream_chat_flutter/src/emoji/emoji.dart';
|
import 'package:stream_chat_flutter/src/emoji/emoji.dart';
|
||||||
import 'package:stream_chat_flutter/src/localization/translations.dart';
|
import 'package:stream_chat_flutter/src/localization/translations.dart';
|
||||||
@@ -46,7 +47,7 @@ extension IterableX<T> on Iterable<T> {
|
|||||||
extension PlatformFileX on PlatformFile {
|
extension PlatformFileX on PlatformFile {
|
||||||
/// Converts the [PlatformFile] into [AttachmentFile]
|
/// Converts the [PlatformFile] into [AttachmentFile]
|
||||||
AttachmentFile get toAttachmentFile => AttachmentFile(
|
AttachmentFile get toAttachmentFile => AttachmentFile(
|
||||||
path: path,
|
path: kIsWeb ? null : path,
|
||||||
name: name,
|
name: name,
|
||||||
bytes: bytes,
|
bytes: bytes,
|
||||||
size: size,
|
size: size,
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:cached_network_image/cached_network_image.dart';
|
import 'package:cached_network_image/cached_network_image.dart';
|
||||||
import 'package:dio/dio.dart';
|
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:path_provider/path_provider.dart';
|
import 'package:path_provider/path_provider.dart';
|
||||||
|
|||||||
@@ -58,6 +58,46 @@ typedef OnMessageTap = void Function(Message);
|
|||||||
/// Callback on reply tapped
|
/// Callback on reply tapped
|
||||||
typedef ReplyTapCallback = void Function(Message);
|
typedef ReplyTapCallback = void Function(Message);
|
||||||
|
|
||||||
|
/// Spacing Types (These are properties of a message to help inform the decision
|
||||||
|
/// of how much space / which widget to build after it)
|
||||||
|
enum SpacingType {
|
||||||
|
/// Message is a thread
|
||||||
|
thread,
|
||||||
|
|
||||||
|
/// There is a >1s time diff between current and last message
|
||||||
|
timeDiff,
|
||||||
|
|
||||||
|
/// Next message is by a different user
|
||||||
|
otherUser,
|
||||||
|
|
||||||
|
/// Message is deleted
|
||||||
|
deleted,
|
||||||
|
|
||||||
|
/// No other conditions are valid, default spacing (This will likely be the
|
||||||
|
/// only rule in the list provided)
|
||||||
|
defaultSpacing,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Builder for building certain spacing after widgets.
|
||||||
|
/// This spacing can be in form of any widgets you like.
|
||||||
|
/// A List of [SpacingType] is provided to help inform the decision of
|
||||||
|
/// what to build after the message.
|
||||||
|
///
|
||||||
|
/// As an example:
|
||||||
|
/// MessageListView(
|
||||||
|
/// spacingWidgetBuilder: (context, list) {
|
||||||
|
/// if(list.contains(SpacingType.defaultSpacing)) {
|
||||||
|
/// return SizedBox(height: 2.0,);
|
||||||
|
/// } else {
|
||||||
|
/// return SizedBox(height: 8.0,);
|
||||||
|
/// }
|
||||||
|
/// },
|
||||||
|
/// ),
|
||||||
|
typedef SpacingWidgetBuilder = Widget Function(
|
||||||
|
BuildContext context,
|
||||||
|
List<SpacingType> spacingTypes,
|
||||||
|
);
|
||||||
|
|
||||||
/// Class for message details
|
/// Class for message details
|
||||||
// ignore: prefer-match-file-name
|
// ignore: prefer-match-file-name
|
||||||
class MessageDetails {
|
class MessageDetails {
|
||||||
@@ -171,6 +211,7 @@ class MessageListView extends StatefulWidget {
|
|||||||
this.reverse = true,
|
this.reverse = true,
|
||||||
this.paginationLimit = 20,
|
this.paginationLimit = 20,
|
||||||
this.paginationLoadingIndicatorBuilder,
|
this.paginationLoadingIndicatorBuilder,
|
||||||
|
this.spacingWidgetBuilder,
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
/// Function used to build a custom message widget
|
/// Function used to build a custom message widget
|
||||||
@@ -289,6 +330,12 @@ class MessageListView extends StatefulWidget {
|
|||||||
/// Builder used to build the loading indicator shown while paginating.
|
/// Builder used to build the loading indicator shown while paginating.
|
||||||
final WidgetBuilder? paginationLoadingIndicatorBuilder;
|
final WidgetBuilder? paginationLoadingIndicatorBuilder;
|
||||||
|
|
||||||
|
/// This allows a user to customise the space after a message
|
||||||
|
/// A List of [SpacingType] is provided to provide more data about the
|
||||||
|
/// type of message (thread, difference in time between current and last
|
||||||
|
/// message, default spacing, etc)
|
||||||
|
final SpacingWidgetBuilder? spacingWidgetBuilder;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
_MessageListViewState createState() => _MessageListViewState();
|
_MessageListViewState createState() => _MessageListViewState();
|
||||||
}
|
}
|
||||||
@@ -564,17 +611,38 @@ class _MessageListViewState extends State<MessageListView> {
|
|||||||
Units.MINUTE,
|
Units.MINUTE,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
final spacingRules = <SpacingType>[];
|
||||||
|
|
||||||
final isNextUserSame =
|
final isNextUserSame =
|
||||||
message.user!.id == nextMessage.user?.id;
|
message.user!.id == nextMessage.user?.id;
|
||||||
final isThread = message.replyCount! > 0;
|
final isThread = message.replyCount! > 0;
|
||||||
final isDeleted = message.isDeleted;
|
final isDeleted = message.isDeleted;
|
||||||
if (timeDiff >= 1 ||
|
final hasTimeDiff = timeDiff >= 1;
|
||||||
!isNextUserSame ||
|
|
||||||
isThread ||
|
if (hasTimeDiff) {
|
||||||
isDeleted) {
|
spacingRules.add(SpacingType.timeDiff);
|
||||||
return const SizedBox(height: 8);
|
|
||||||
}
|
}
|
||||||
return const SizedBox(height: 2);
|
|
||||||
|
if (!isNextUserSame) {
|
||||||
|
spacingRules.add(SpacingType.otherUser);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isThread) {
|
||||||
|
spacingRules.add(SpacingType.thread);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isDeleted) {
|
||||||
|
spacingRules.add(SpacingType.deleted);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (spacingRules.isNotEmpty) {
|
||||||
|
return widget.spacingWidgetBuilder
|
||||||
|
?.call(context, spacingRules) ??
|
||||||
|
const SizedBox(height: 8);
|
||||||
|
}
|
||||||
|
return widget.spacingWidgetBuilder
|
||||||
|
?.call(context, [SpacingType.defaultSpacing]) ??
|
||||||
|
const SizedBox(height: 2);
|
||||||
},
|
},
|
||||||
itemBuilder: (context, i) {
|
itemBuilder: (context, i) {
|
||||||
if (i == itemCount - 1) {
|
if (i == itemCount - 1) {
|
||||||
|
|||||||
@@ -1230,6 +1230,7 @@ class _MessageWidgetState extends State<MessageWidget>
|
|||||||
Widget _buildSendingIndicator() {
|
Widget _buildSendingIndicator() {
|
||||||
final style = widget.messageTheme.createdAtStyle;
|
final style = widget.messageTheme.createdAtStyle;
|
||||||
final message = widget.message;
|
final message = widget.message;
|
||||||
|
final memberCount = StreamChannel.of(context).channel.memberCount ?? 0;
|
||||||
|
|
||||||
if (hasNonUrlAttachments &&
|
if (hasNonUrlAttachments &&
|
||||||
(message.status == MessageSendingStatus.sending ||
|
(message.status == MessageSendingStatus.sending ||
|
||||||
@@ -1260,7 +1261,7 @@ class _MessageWidgetState extends State<MessageWidget>
|
|||||||
if (isMessageRead) {
|
if (isMessageRead) {
|
||||||
child = Row(
|
child = Row(
|
||||||
children: [
|
children: [
|
||||||
if (StreamChannel.of(context).channel.memberCount! > 2)
|
if (memberCount > 2)
|
||||||
Text(
|
Text(
|
||||||
widget.readList!.length.toString(),
|
widget.readList!.length.toString(),
|
||||||
style: style.copyWith(
|
style: style.copyWith(
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ export 'package:jiffy/jiffy.dart';
|
|||||||
export 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart';
|
export 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart';
|
||||||
|
|
||||||
export 'src/attachment/attachment.dart';
|
export 'src/attachment/attachment.dart';
|
||||||
|
export 'src/attachment_actions_modal.dart';
|
||||||
export 'src/back_button.dart';
|
export 'src/back_button.dart';
|
||||||
export 'src/channel_avatar.dart';
|
export 'src/channel_avatar.dart';
|
||||||
export 'src/channel_header.dart';
|
export 'src/channel_header.dart';
|
||||||
|
|||||||
@@ -227,13 +227,13 @@ class StreamChannelState extends State<StreamChannel> {
|
|||||||
preferOffline: preferOffline,
|
preferOffline: preferOffline,
|
||||||
);
|
);
|
||||||
|
|
||||||
Future<List<ChannelState>> _queryAtMessage({
|
Future<ChannelState?> _queryAtMessage({
|
||||||
String? messageId,
|
String? messageId,
|
||||||
int before = 20,
|
int before = 20,
|
||||||
int after = 20,
|
int after = 20,
|
||||||
bool preferOffline = false,
|
bool preferOffline = false,
|
||||||
}) async {
|
}) async {
|
||||||
if (channel.state == null) return [];
|
if (channel.state == null) return null;
|
||||||
channel.state!.isUpToDate = false;
|
channel.state!.isUpToDate = false;
|
||||||
channel.state!.truncate();
|
channel.state!.truncate();
|
||||||
|
|
||||||
@@ -245,23 +245,33 @@ class StreamChannelState extends State<StreamChannel> {
|
|||||||
preferOffline: preferOffline,
|
preferOffline: preferOffline,
|
||||||
);
|
);
|
||||||
channel.state!.isUpToDate = true;
|
channel.state!.isUpToDate = true;
|
||||||
return [];
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return Future.wait([
|
return queryAroundMessage(
|
||||||
queryBeforeMessage(
|
messageId,
|
||||||
messageId,
|
before: before,
|
||||||
limit: before,
|
after: after,
|
||||||
preferOffline: preferOffline,
|
preferOffline: preferOffline,
|
||||||
),
|
);
|
||||||
queryAfterMessage(
|
|
||||||
messageId,
|
|
||||||
limit: after,
|
|
||||||
preferOffline: preferOffline,
|
|
||||||
),
|
|
||||||
]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
///
|
||||||
|
Future<ChannelState> queryAroundMessage(
|
||||||
|
String messageId, {
|
||||||
|
int before = 20,
|
||||||
|
int after = 20,
|
||||||
|
bool preferOffline = false,
|
||||||
|
}) =>
|
||||||
|
channel.query(
|
||||||
|
messagesPagination: PaginationParams(
|
||||||
|
idAround: messageId,
|
||||||
|
before: before,
|
||||||
|
after: after,
|
||||||
|
),
|
||||||
|
preferOffline: preferOffline,
|
||||||
|
);
|
||||||
|
|
||||||
///
|
///
|
||||||
Future<ChannelState> queryBeforeMessage(
|
Future<ChannelState> queryBeforeMessage(
|
||||||
String messageId, {
|
String messageId, {
|
||||||
|
|||||||
Reference in New Issue
Block a user