Merge branch 'develop' of https://github.com/GetStream/stream-chat-flutter into mark-read-fix

This commit is contained in:
Deven Joshi
2021-10-08 17:45:06 +05:30
89 changed files with 1032 additions and 859 deletions
+2 -3
View File
@@ -1,4 +1,4 @@
name: example
name: stream_chat_example
description: A new Flutter project.
publish_to: "none"
@@ -11,8 +11,7 @@ dependencies:
cupertino_icons: ^1.0.0
flutter:
sdk: flutter
stream_chat:
path: ../
stream_chat: ^2.2.1
dev_dependencies:
flutter_test:
@@ -521,7 +521,7 @@ class Channel {
state!.addMessage(message);
try {
if (message.attachments.any((it) => !it.uploadState.isSuccess) == true) {
if (message.attachments.any((it) => !it.uploadState.isSuccess)) {
final attachmentsUploadCompleter = Completer<Message>();
_messageAttachmentsUploadCompleter[message.id] =
attachmentsUploadCompleter;
@@ -849,12 +849,12 @@ class Channel {
..update(type, (value) {
if (enforceUnique) return value;
return value + 1;
}, ifAbsent: () => 1),
}, ifAbsent: () => 1), // ignore: prefer-trailing-comma
reactionScores: {...message.reactionScores ?? <String, int>{}}
..update(type, (value) {
if (enforceUnique) return value;
return value + 1;
}, ifAbsent: () => 1),
}, ifAbsent: () => 1), // ignore: prefer-trailing-comma
latestReactions: latestReactions,
ownReactions: ownReactions,
);
@@ -878,7 +878,9 @@ class Channel {
/// Delete a reaction from this channel.
Future<EmptyResponse> deleteReaction(
Message message, Reaction reaction) async {
Message message,
Reaction reaction,
) async {
final type = reaction.type;
final user = _client.state.currentUser;
@@ -1336,7 +1338,7 @@ class Channel {
type,
clearHistory: clearHistory,
);
if (clearHistory == true) {
if (clearHistory) {
state!.truncate();
final cid = _cid;
if (cid != null) {
@@ -1501,28 +1503,27 @@ class ChannelClientState {
final expiredAttachmentMessagesId = channelState.messages
.where((m) =>
!_updatedMessagesIds.contains(m.id) &&
m.attachments.isNotEmpty == true &&
m.attachments.isNotEmpty &&
m.attachments.any((e) {
final url = e.imageUrl ?? e.assetUrl;
if (url == null || !url.contains('')) {
return false;
}
final uri = Uri.parse(url);
if (!uri.host.endsWith('stream-io-cdn.com') ||
uri.queryParameters['Expires'] == null) {
return false;
}
final secondsFromEpoch =
int.parse(uri.queryParameters['Expires']!);
final expiration = DateTime.fromMillisecondsSinceEpoch(
secondsFromEpoch * 1000);
return expiration.isBefore(DateTime.now());
}) ==
true)
final url = e.imageUrl ?? e.assetUrl;
if (url == null || !url.contains('')) {
return false;
}
final uri = Uri.parse(url);
if (!uri.host.endsWith('stream-io-cdn.com') ||
uri.queryParameters['Expires'] == null) {
return false;
}
final secondsFromEpoch =
int.parse(uri.queryParameters['Expires']!);
final expiration =
DateTime.fromMillisecondsSinceEpoch(secondsFromEpoch * 1000);
return expiration.isBefore(DateTime.now());
}))
.map((e) => e.id)
.toList();
if (expiredAttachmentMessagesId.isNotEmpty == true) {
if (expiredAttachmentMessagesId.isNotEmpty) {
await _channel._initializedCompleter.future;
_updatedMessagesIds.addAll(expiredAttachmentMessagesId);
_channel.getMessagesById(expiredAttachmentMessagesId);
@@ -1546,7 +1547,8 @@ class ChannelClientState {
final user = e.user;
updateChannelState(channelState.copyWith(
members: List.from(
channelState.members..removeWhere((m) => m.userId == user!.id)),
channelState.members..removeWhere((m) => m.userId == user!.id),
),
));
}));
}
@@ -1647,7 +1649,7 @@ class ChannelClientState {
);
addMessage(message);
if (message.pinned == true) {
if (message.pinned) {
_channelState = _channelState.copyWith(
pinnedMessages: [
..._channelState.pinnedMessages,
@@ -1794,9 +1796,8 @@ class ChannelClientState {
channelStateStream.map((cs) => cs.pinnedMessages.toList());
/// Get channel last message.
Message? get lastMessage => _channelState.messages.isNotEmpty == true
? _channelState.messages.last
: null;
Message? get lastMessage =>
_channelState.messages.isNotEmpty ? _channelState.messages.last : null;
/// Get channel last message.
Stream<Message?> get lastMessageStream =>
@@ -1859,8 +1860,8 @@ class ChannelClientState {
(m) => m.user.id == message.user?.id,
) !=
null;
return message.silent != true &&
message.shadowed != true &&
return !message.silent &&
!message.shadowed &&
message.user?.id != userId &&
!userIsMuted;
}
@@ -1898,9 +1899,7 @@ class ChannelClientState {
...updatedState.messages,
..._channelState.messages
.where((m) =>
updatedState.messages
.any((newMessage) => newMessage.id == m.id) !=
true)
!updatedState.messages.any((newMessage) => newMessage.id == m.id))
.toList(),
]..sort(_sortByCreatedAt);
@@ -1908,9 +1907,7 @@ class ChannelClientState {
...updatedState.watchers,
..._channelState.watchers
.where((w) =>
updatedState.watchers
.any((newWatcher) => newWatcher.id == w.id) !=
true)
!updatedState.watchers.any((newWatcher) => newWatcher.id == w.id))
.toList(),
];
@@ -1922,9 +1919,7 @@ class ChannelClientState {
...updatedState.read,
..._channelState.read
.where((r) =>
updatedState.read
.any((newRead) => newRead.user.id == r.user.id) !=
true)
!updatedState.read.any((newRead) => newRead.user.id == r.user.id))
.toList(),
];
@@ -2031,7 +2026,7 @@ class ChannelClientState {
.on()
.where((event) =>
event.user != null &&
members.any((m) => m.userId == event.user!.id) == true)
members.any((m) => m.userId == event.user!.id))
.listen(
(event) {
final newMembers = List<Member>.from(members);
@@ -75,7 +75,7 @@ class ChannelApi {
if (messageLimit != null) 'message_limit': messageLimit,
// pagination
...paginationParams.toJson()
...paginationParams.toJson(),
}),
},
);
@@ -7,7 +7,7 @@ enum PushProvider {
firebase,
/// Send notifications using Apple's Push Notification service
apn
apn,
}
/// Helper extension for [PushProvider]
@@ -90,7 +90,7 @@ enum ChatErrorCode {
internalSystemError,
/// No access to requested channels
noAccessToChannels
noAccessToChannels,
}
const _errorCodeWithDescription = {
@@ -98,14 +98,20 @@ const _errorCodeWithDescription = {
MapEntry(1000, 'Unauthorised, token not defined'),
ChatErrorCode.inputError:
MapEntry(4, 'Wrong data/parameter is sent to the API'),
ChatErrorCode.duplicateUsername: MapEntry(6,
'Duplicate username is sent while enforce_unique_usernames is enabled'),
ChatErrorCode.duplicateUsername: MapEntry(
6,
'Duplicate username is sent while enforce_unique_usernames is enabled',
),
ChatErrorCode.messageTooLong: MapEntry(20, 'Message is too long'),
ChatErrorCode.eventNotSupported: MapEntry(18, 'Event is not supported'),
ChatErrorCode.channelFeatureNotSupported: MapEntry(19,
'The feature is currently disabled on the dashboard (i.e. Reactions & Replies)'),
ChatErrorCode.multipleNestling: MapEntry(21,
'Multiple Levels Reply is not supported - the API only supports 1 level deep reply threads'),
ChatErrorCode.channelFeatureNotSupported: MapEntry(
19,
'The feature is currently disabled on the dashboard (i.e. Reactions & Replies)',
),
ChatErrorCode.multipleNestling: MapEntry(
21,
'Multiple Levels Reply is not supported - the API only supports 1 level deep reply threads',
),
ChatErrorCode.customCommandEndpointCall:
MapEntry(45, 'Custom Command handler returned an error'),
ChatErrorCode.customCommandEndpointMissing:
@@ -35,7 +35,8 @@ class StreamWebSocketError extends StreamChatError {
///
factory StreamWebSocketError.fromWebSocketChannelError(
WebSocketChannelException error) {
WebSocketChannelException error,
) {
final message = error.message ?? '';
return StreamWebSocketError(message);
}
@@ -105,8 +105,11 @@ class LoggingInterceptor extends Interceptor {
final formDataMap = <String, dynamic>{}
..addEntries(data.fields)
..addEntries(data.files);
_printMapAsTable(_logPrintRequest, formDataMap,
header: 'Form data | ${data.boundary}');
_printMapAsTable(
_logPrintRequest,
formDataMap,
header: 'Form data | ${data.boundary}',
);
} else {
_printBlock(_logPrintRequest, data.toString());
}
@@ -201,14 +204,19 @@ class LoggingInterceptor extends Interceptor {
}
void _printRequestHeader(
void Function(Object) logPrint, RequestOptions options) {
void Function(Object) logPrint,
RequestOptions options,
) {
final uri = options.uri;
final method = options.method;
_printBoxed(logPrint, header: 'Request ║ $method ', text: uri.toString());
}
void _printLine(void Function(Object) logPrint,
[String pre = '', String suf = '']) =>
void _printLine(
void Function(Object) logPrint, [
String pre = '',
String suf = '',
]) =>
logPrint('$pre${'' * maxWidth}$suf');
void _printKV(void Function(Object) logPrint, String? key, Object? v) {
@@ -227,8 +235,10 @@ class LoggingInterceptor extends Interceptor {
final lines = (msg.length / maxWidth).ceil();
for (var i = 0; i < lines; ++i) {
logPrint((i >= 0 ? '' : '') +
msg.substring(i * maxWidth,
math.min<int>(i * maxWidth + maxWidth, msg.length)));
msg.substring(
i * maxWidth,
math.min<int>(i * maxWidth + maxWidth, msg.length),
));
}
}
@@ -301,8 +311,13 @@ class LoggingInterceptor extends Interceptor {
if (compact) {
logPrint('${_indent(tabs)} $e${!isLast ? ',' : ''}');
} else {
_printPrettyMap(logPrint, e,
tabs: tabs + 1, isListItem: true, isLast: isLast);
_printPrettyMap(
logPrint,
e,
tabs: tabs + 1,
isListItem: true,
isLast: isLast,
);
}
} else {
logPrint('${_indent(tabs + 2)} $e${isLast ? '' : ','}');
@@ -56,12 +56,15 @@ class Attachment extends Equatable {
/// Create a new instance from a json
factory Attachment.fromJson(Map<String, dynamic> json) =>
_$AttachmentFromJson(
Serializer.moveToExtraDataFromRoot(json, topLevelFields));
Serializer.moveToExtraDataFromRoot(json, topLevelFields),
);
/// Create a new instance from a db data
factory Attachment.fromData(Map<String, dynamic> json) =>
_$AttachmentFromJson(Serializer.moveToExtraDataFromRoot(
json, topLevelFields + dbSpecificTopLevelFields));
json,
topLevelFields + dbSpecificTopLevelFields,
));
///The attachment type based on the URL resource. This can be: audio,
///image or video
@@ -11,54 +11,6 @@ part 'attachment_file.freezed.dart';
part 'attachment_file.g.dart';
/// Union class to hold various [UploadState] of a attachment.
@freezed
class UploadState with _$UploadState {
/// Preparing state of the union
const factory UploadState.preparing() = Preparing;
/// InProgress state of the union
const factory UploadState.inProgress({
required int uploaded,
required int total,
}) = InProgress;
/// Success state of the union
const factory UploadState.success() = Success;
/// Failed state of the union
const factory UploadState.failed({required String error}) = Failed;
/// Creates a new instance from a json
factory UploadState.fromJson(Map<String, dynamic> json) =>
_$UploadStateFromJson(json);
}
/// Helper extension for UploadState
extension UploadStateX on UploadState? {
/// Returns true if state is [Preparing]
bool get isPreparing => this is Preparing;
/// Returns true if state is [InProgress]
bool get isInProgress => this is InProgress;
/// Returns true if state is [Success]
bool get isSuccess => this is Success;
/// Returns true if state is [Failed]
bool get isFailed => this is Failed;
}
Uint8List? _fromString(String? bytes) {
if (bytes == null) return null;
return Uint8List.fromList(bytes.codeUnits);
}
String? _toString(Uint8List? bytes) {
if (bytes == null) return null;
return String.fromCharCodes(bytes);
}
/// The class that contains the information about an attachment file
@JsonSerializable()
class AttachmentFile {
@@ -135,3 +87,51 @@ class AttachmentFile {
return multiPartFile;
}
}
/// Union class to hold various [UploadState] of a attachment.
@freezed
class UploadState with _$UploadState {
/// Preparing state of the union
const factory UploadState.preparing() = Preparing;
/// InProgress state of the union
const factory UploadState.inProgress({
required int uploaded,
required int total,
}) = InProgress;
/// Success state of the union
const factory UploadState.success() = Success;
/// Failed state of the union
const factory UploadState.failed({required String error}) = Failed;
/// Creates a new instance from a json
factory UploadState.fromJson(Map<String, dynamic> json) =>
_$UploadStateFromJson(json);
}
/// Helper extension for UploadState
extension UploadStateX on UploadState? {
/// Returns true if state is [Preparing]
bool get isPreparing => this is Preparing;
/// Returns true if state is [InProgress]
bool get isInProgress => this is InProgress;
/// Returns true if state is [Success]
bool get isSuccess => this is Success;
/// Returns true if state is [Failed]
bool get isFailed => this is Failed;
}
Uint8List? _fromString(String? bytes) {
if (bytes == null) return null;
return Uint8List.fromList(bytes.codeUnits);
}
String? _toString(Uint8List? bytes) {
if (bytes == null) return null;
return String.fromCharCodes(bytes);
}
@@ -38,7 +38,8 @@ class ChannelModel {
/// Create a new instance from a json
factory ChannelModel.fromJson(Map<String, dynamic> json) =>
_$ChannelModelFromJson(
Serializer.moveToExtraDataFromRoot(json, topLevelFields));
Serializer.moveToExtraDataFromRoot(json, topLevelFields),
);
/// The id of this channel
final String id;
@@ -81,7 +81,8 @@ class Message extends Equatable {
/// Create a new instance from a json
factory Message.fromJson(Map<String, dynamic> json) => _$MessageFromJson(
Serializer.moveToExtraDataFromRoot(json, topLevelFields));
Serializer.moveToExtraDataFromRoot(json, topLevelFields),
);
/// The message ID. This is either created by Stream or set client side when
/// the message is added.
@@ -48,7 +48,8 @@ class OwnUser extends User {
/// Create a new instance from json.
factory OwnUser.fromJson(Map<String, dynamic> json) => _$OwnUserFromJson(
Serializer.moveToExtraDataFromRoot(json, topLevelFields));
Serializer.moveToExtraDataFromRoot(json, topLevelFields),
);
/// Create a new instance from [User] object.
factory OwnUser.fromUser(User user) => OwnUser(
+2 -1
View File
@@ -27,7 +27,8 @@ dependencies:
dev_dependencies:
build_runner: ^2.0.1
dart_code_metrics: ^4.4.0
freezed: ^0.14.1+3
json_serializable: ^5.0.2
mocktail: ^0.1.1
test: ^1.18.2
test: ^1.17.12
@@ -291,7 +291,7 @@ void main() {
(index) => Attachment(
id: 'test-attachment-id-$index',
type: index.isEven ? 'image' : 'file',
file: AttachmentFile(size: 33 * index, path: 'test-file-path'),
file: AttachmentFile(size: index * 33, path: 'test-file-path'),
),
);
@@ -498,7 +498,7 @@ void main() {
(index) => Attachment(
id: 'test-attachment-id-$index',
type: index.isEven ? 'image' : 'file',
file: AttachmentFile(size: 33 * index, path: 'test-file-path'),
file: AttachmentFile(size: index * 33, path: 'test-file-path'),
),
);