Merge remote-tracking branch 'origin/develop' into vale-linting

# Conflicts:
#	docusaurus/docs/Flutter/05-guides/05-push-notifications/adding_push_notifications_v2.mdx
#	packages/stream_chat_flutter/CHANGELOG.md
This commit is contained in:
xsahil03x
2023-04-07 02:01:22 +05:30
141 changed files with 1746 additions and 1233 deletions
+32
View File
@@ -1,3 +1,35 @@
## Upcoming
🐞 Fixed
- Fixed streamWatchers. Before it was always new, now it is possible to follow the watchers of a channel.
- Make `Message.i18n` field read-only.
🔄 Changed
- Cancelling a attachment upload now removes the attachment from the message.
- Updated `dio` and other dependencies to resolvable versions.
✅ Added
- Added `presence` property to `Channel::watch` method.
## 5.3.0
🔄 Changed
- Updated `rate_limiter` dependency to `^1.0.0`
## 5.2.0
✅ Added
- Added `Huawei` and `Xiaomi` PushProviders.
🐞 Fixed
- Fixed initializing last synced date.
## 5.1.0
✅ Added
@@ -26,7 +26,7 @@ apply plugin: 'kotlin-android'
apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
android {
compileSdkVersion 29
compileSdkVersion 33
sourceSets {
main.java.srcDirs += 'src/main/kotlin'
@@ -40,7 +40,7 @@ android {
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
applicationId "com.example.example"
minSdkVersion 16
targetSdkVersion 29
targetSdkVersion 33
versionCode flutterVersionCode.toInteger()
versionName flutterVersionName
}
@@ -6,15 +6,16 @@
additional functionality it is fine to subclass or reimplement
FlutterApplication and put your custom class here. -->
<application
android:name="io.flutter.app.FlutterApplication"
android:label="example"
android:icon="@mipmap/ic_launcher">
android:name="${applicationName}"
android:icon="@mipmap/ic_launcher"
android:label="example">
<activity
android:name=".MainActivity"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
android:exported="true"
android:hardwareAccelerated="true"
android:launchMode="singleTop"
android:theme="@style/LaunchTheme"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
android:hardwareAccelerated="true"
android:windowSoftInputMode="adjustResize">
<!-- Specifies an Android theme to apply to this Activity as soon as
the Android process has started. This theme is visible to the user
@@ -1,12 +1,12 @@
buildscript {
ext.kotlin_version = '1.3.50'
ext.kotlin_version = '1.7.21'
repositories {
google()
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:3.5.0'
classpath 'com.android.tools.build:gradle:7.3.1'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}
}
@@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-6.5-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-7.6-all.zip
@@ -6,6 +6,7 @@ version: 1.0.0+1
environment:
sdk: '>=2.17.0 <3.0.0'
flutter: ">=1.17.0"
dependencies:
cupertino_icons: ^1.0.0
@@ -463,12 +463,19 @@ class Channel {
client.logger.info('Found ${attachments.length} attachments');
void updateAttachment(Attachment attachment) {
void updateAttachment(Attachment attachment, {bool remove = false}) {
final index = message!.attachments.indexWhere(
(it) => it.id == attachment.id,
);
if (index != -1) {
final newAttachments = [...message!.attachments]..[index] = attachment;
// update or remove attachment from message.
final List<Attachment> newAttachments;
if (remove) {
newAttachments = [...message!.attachments]..removeAt(index);
} else {
newAttachments = [...message!.attachments]..[index] = attachment;
}
final updatedMessage = message!.copyWith(attachments: newAttachments);
state?.updateMessage(updatedMessage);
// updating original message for next iteration
@@ -533,6 +540,14 @@ class Channel {
);
}
}).catchError((e, stk) {
if (e is StreamChatNetworkError && e.isRequestCancelledError) {
client.logger.info('Attachment ${it.id} upload cancelled');
// remove attachment from message if cancelled.
updateAttachment(it, remove: true);
return;
}
client.logger.severe('error uploading the attachment', e, stk);
updateAttachment(
it.copyWith(uploadState: UploadState.failed(error: e.toString())),
@@ -1219,11 +1234,11 @@ class Channel {
}
/// Loads the initial channel state and watches for changes.
Future<ChannelState> watch() async {
Future<ChannelState> watch({bool presence = false}) async {
ChannelState response;
try {
response = await query(watch: true);
response = await query(watch: true, presence: presence);
} catch (error, stackTrace) {
if (!_initializedCompleter.isCompleted) {
_initializedCompleter.completeError(error, stackTrace);
@@ -1613,6 +1628,10 @@ class ChannelClientState {
_listenMemberUnbanned();
_listenUserStartWatching();
_listenUserStopWatching();
_startCleaningStaleTypingEvents();
_startCleaningStalePinnedMessages();
@@ -1754,6 +1773,39 @@ class ChannelClientState {
));
}
void _listenUserStartWatching() {
_subscriptions.add(
_channel.on(EventType.userWatchingStart).listen((event) {
final watcher = event.user;
if (watcher != null) {
final existingWatchers = channelState.watchers;
updateChannelState(channelState.copyWith(
watchers: [
...?existingWatchers,
watcher,
],
));
}
}),
);
}
void _listenUserStopWatching() {
_subscriptions.add(
_channel.on(EventType.userWatchingStop).listen((event) {
final watcher = event.user;
if (watcher != null) {
final existingWatchers = channelState.watchers;
updateChannelState(channelState.copyWith(
watchers: existingWatchers
?.where((user) => user.id != watcher.id)
.toList(growable: false),
));
}
}),
);
}
void _listenMemberUnbanned() {
_subscriptions.add(_channel
.on(EventType.userUnbanned)
@@ -2083,7 +2135,7 @@ class ChannelClientState {
channelStateStream.map((cs) => cs.watchers),
_channel.client.state.usersStream,
(watchers, users) => watchers!.map((e) => users[e.id] ?? e).toList(),
);
).distinct(const ListEquality().equals);
/// Channel member for the current user.
Member? get currentUserMember => members.firstWhereOrNull(
@@ -453,6 +453,15 @@ class StreamChatClient {
if (persistenceEnabled) {
await sync(cids: cids, lastSyncAt: _lastSyncedAt);
}
} else {
// channels are empty, assuming it's a fresh start
// and making sure `lastSyncAt` is initialized
if (persistenceEnabled) {
final lastSyncAt = await _chatPersistenceClient?.getLastSyncAt();
if (lastSyncAt == null) {
await _chatPersistenceClient?.updateLastSyncAt(DateTime.now());
}
}
}
handleEvent(Event(
type: EventType.connectionRecovered,
@@ -6,6 +6,12 @@ enum PushProvider {
/// Send notifications using Google's Firebase Cloud Messaging
firebase,
/// Send notifications using Huawei's Push Kit
huawei,
/// Send notifications using Xiaomi's Mi Push Service
xiaomi,
/// Send notifications using Apple's Push Notification service
apn,
}
@@ -40,7 +40,7 @@ class SortOption<T> {
final int direction;
/// Sorting field Comparator required for offline sorting
@JsonKey(ignore: true)
@JsonKey(includeToJson: false, includeFromJson: false)
final Comparator<T>? comparator;
/// Serialize model to json
@@ -74,6 +74,7 @@ class StreamChatNetworkError extends StreamChatError {
ChatErrorCode errorCode, {
int? statusCode,
this.data,
this.isRequestCancelledError = false,
}) : code = errorCode.code,
statusCode = statusCode ?? data?.statusCode,
super(errorCode.message);
@@ -84,6 +85,7 @@ class StreamChatNetworkError extends StreamChatError {
required String message,
this.statusCode,
this.data,
this.isRequestCancelledError = false,
}) : super(message);
///
@@ -96,10 +98,13 @@ class StreamChatNetworkError extends StreamChatError {
}
return StreamChatNetworkError.raw(
code: errorResponse?.code ?? -1,
message:
errorResponse?.message ?? response?.statusMessage ?? error.message,
message: errorResponse?.message ??
response?.statusMessage ??
error.message ??
'',
statusCode: errorResponse?.statusCode ?? response?.statusCode,
data: errorResponse,
isRequestCancelledError: error.type == DioErrorType.cancel,
)..stackTrace = error.stackTrace;
}
@@ -112,6 +117,9 @@ class StreamChatNetworkError extends StreamChatError {
/// Response body. please refer to [ErrorResponse].
final ErrorResponse? data;
/// True, in case the error is due to a cancelled network request.
final bool isRequestCancelledError;
StackTrace? _stackTrace;
///
@@ -87,8 +87,8 @@ class LoggingInterceptor extends Interceptor {
requestHeaders['contentType'] = options.contentType?.toString();
requestHeaders['responseType'] = options.responseType.toString();
requestHeaders['followRedirects'] = options.followRedirects;
requestHeaders['connectTimeout'] = options.connectTimeout;
requestHeaders['receiveTimeout'] = options.receiveTimeout;
requestHeaders['connectTimeout'] = options.connectTimeout?.toString();
requestHeaders['receiveTimeout'] = options.receiveTimeout?.toString();
_printMapAsTable(_logPrintRequest, requestHeaders, header: 'Headers');
_printMapAsTable(_logPrintRequest, options.extra, header: 'Extras');
}
@@ -101,7 +101,8 @@ class LoggingInterceptor extends Interceptor {
options.data as Map?,
header: 'Body',
);
} else if (data is FormData) {
}
if (data is FormData) {
final formDataMap = <String, dynamic>{}
..addEntries(data.fields)
..addEntries(data.files);
@@ -121,7 +122,7 @@ class LoggingInterceptor extends Interceptor {
@override
void onError(DioError err, ErrorInterceptorHandler handler) {
if (error) {
if (err.type == DioErrorType.response) {
if (err.type == DioErrorType.badResponse) {
final uri = err.response?.requestOptions.uri;
_printBoxed(
_logPrintError,
@@ -162,7 +163,7 @@ class LoggingInterceptor extends Interceptor {
_logPrintResponse('║');
_printResponse(_logPrintResponse, response);
_logPrintResponse('║');
_printLine(_logPrintResponse, '╚');
_logPrintResponse('╚');
}
super.onResponse(response, handler);
}
@@ -29,8 +29,8 @@ class StreamHttpClient {
httpClient = dio ?? Dio() {
httpClient
..options.baseUrl = _options.baseUrl
..options.receiveTimeout = _options.receiveTimeout.inMilliseconds
..options.connectTimeout = _options.connectTimeout.inMilliseconds
..options.receiveTimeout = _options.receiveTimeout
..options.connectTimeout = _options.connectTimeout
..options.queryParameters = {
'api_key': apiKey,
..._options.queryParameters,
@@ -135,7 +135,6 @@ class Attachment extends Equatable {
late final UploadState uploadState;
/// Map of custom channel extraData
@JsonKey(includeIfNull: false)
final Map<String, Object?> extraData;
/// The attachment ID.
@@ -146,13 +145,13 @@ class Attachment extends Equatable {
/// Shortcut for file size.
///
/// {@macro fileSize}
@JsonKey(ignore: true)
@JsonKey(includeToJson: false, includeFromJson: false)
int? get fileSize => extraData['file_size'] as int?;
/// Shortcut for file mimeType.
///
/// {@macro mimeType}
@JsonKey(ignore: true)
@JsonKey(includeToJson: false, includeFromJson: false)
String? get mimeType => extraData['mime_type'] as String?;
/// Known top level fields.
@@ -52,7 +52,7 @@ class AttachmentFile {
/// Byte data for this file. Particularly useful if you want to manipulate
/// its data or easily upload to somewhere else.
@JsonKey(ignore: true)
@JsonKey(includeToJson: false, includeFromJson: false)
final Uint8List? bytes;
/// The file size in bytes.
@@ -1,7 +1,7 @@
// coverage:ignore-file
// GENERATED CODE - DO NOT MODIFY BY HAND
// ignore_for_file: type=lint
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
part of 'attachment_file.dart';
@@ -43,10 +43,10 @@ mixin _$UploadState {
throw _privateConstructorUsedError;
@optionalTypeArgs
TResult? whenOrNull<TResult extends Object?>({
TResult Function()? preparing,
TResult Function(int uploaded, int total)? inProgress,
TResult Function()? success,
TResult Function(String error)? failed,
TResult? Function()? preparing,
TResult? Function(int uploaded, int total)? inProgress,
TResult? Function()? success,
TResult? Function(String error)? failed,
}) =>
throw _privateConstructorUsedError;
@optionalTypeArgs
@@ -68,10 +68,10 @@ mixin _$UploadState {
throw _privateConstructorUsedError;
@optionalTypeArgs
TResult? mapOrNull<TResult extends Object?>({
TResult Function(Preparing value)? preparing,
TResult Function(InProgress value)? inProgress,
TResult Function(Success value)? success,
TResult Function(Failed value)? failed,
TResult? Function(Preparing value)? preparing,
TResult? Function(InProgress value)? inProgress,
TResult? Function(Success value)? success,
TResult? Function(Failed value)? failed,
}) =>
throw _privateConstructorUsedError;
@optionalTypeArgs
@@ -90,16 +90,18 @@ mixin _$UploadState {
abstract class $UploadStateCopyWith<$Res> {
factory $UploadStateCopyWith(
UploadState value, $Res Function(UploadState) then) =
_$UploadStateCopyWithImpl<$Res>;
_$UploadStateCopyWithImpl<$Res, UploadState>;
}
/// @nodoc
class _$UploadStateCopyWithImpl<$Res> implements $UploadStateCopyWith<$Res> {
class _$UploadStateCopyWithImpl<$Res, $Val extends UploadState>
implements $UploadStateCopyWith<$Res> {
_$UploadStateCopyWithImpl(this._value, this._then);
final UploadState _value;
// ignore: unused_field
final $Res Function(UploadState) _then;
final $Val _value;
// ignore: unused_field
final $Res Function($Val) _then;
}
/// @nodoc
@@ -110,14 +112,12 @@ abstract class _$$PreparingCopyWith<$Res> {
}
/// @nodoc
class __$$PreparingCopyWithImpl<$Res> extends _$UploadStateCopyWithImpl<$Res>
class __$$PreparingCopyWithImpl<$Res>
extends _$UploadStateCopyWithImpl<$Res, _$Preparing>
implements _$$PreparingCopyWith<$Res> {
__$$PreparingCopyWithImpl(
_$Preparing _value, $Res Function(_$Preparing) _then)
: super(_value, (v) => _then(v as _$Preparing));
@override
_$Preparing get _value => super._value as _$Preparing;
: super(_value, _then);
}
/// @nodoc
@@ -162,10 +162,10 @@ class _$Preparing extends Preparing {
@override
@optionalTypeArgs
TResult? whenOrNull<TResult extends Object?>({
TResult Function()? preparing,
TResult Function(int uploaded, int total)? inProgress,
TResult Function()? success,
TResult Function(String error)? failed,
TResult? Function()? preparing,
TResult? Function(int uploaded, int total)? inProgress,
TResult? Function()? success,
TResult? Function(String error)? failed,
}) {
return preparing?.call();
}
@@ -199,10 +199,10 @@ class _$Preparing extends Preparing {
@override
@optionalTypeArgs
TResult? mapOrNull<TResult extends Object?>({
TResult Function(Preparing value)? preparing,
TResult Function(InProgress value)? inProgress,
TResult Function(Success value)? success,
TResult Function(Failed value)? failed,
TResult? Function(Preparing value)? preparing,
TResult? Function(InProgress value)? inProgress,
TResult? Function(Success value)? success,
TResult? Function(Failed value)? failed,
}) {
return preparing?.call(this);
}
@@ -242,30 +242,30 @@ abstract class _$$InProgressCopyWith<$Res> {
factory _$$InProgressCopyWith(
_$InProgress value, $Res Function(_$InProgress) then) =
__$$InProgressCopyWithImpl<$Res>;
@useResult
$Res call({int uploaded, int total});
}
/// @nodoc
class __$$InProgressCopyWithImpl<$Res> extends _$UploadStateCopyWithImpl<$Res>
class __$$InProgressCopyWithImpl<$Res>
extends _$UploadStateCopyWithImpl<$Res, _$InProgress>
implements _$$InProgressCopyWith<$Res> {
__$$InProgressCopyWithImpl(
_$InProgress _value, $Res Function(_$InProgress) _then)
: super(_value, (v) => _then(v as _$InProgress));
@override
_$InProgress get _value => super._value as _$InProgress;
: super(_value, _then);
@pragma('vm:prefer-inline')
@override
$Res call({
Object? uploaded = freezed,
Object? total = freezed,
Object? uploaded = null,
Object? total = null,
}) {
return _then(_$InProgress(
uploaded: uploaded == freezed
uploaded: null == uploaded
? _value.uploaded
: uploaded // ignore: cast_nullable_to_non_nullable
as int,
total: total == freezed
total: null == total
? _value.total
: total // ignore: cast_nullable_to_non_nullable
as int,
@@ -302,19 +302,18 @@ class _$InProgress extends InProgress {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _$InProgress &&
const DeepCollectionEquality().equals(other.uploaded, uploaded) &&
const DeepCollectionEquality().equals(other.total, total));
(identical(other.uploaded, uploaded) ||
other.uploaded == uploaded) &&
(identical(other.total, total) || other.total == total));
}
@JsonKey(ignore: true)
@override
int get hashCode => Object.hash(
runtimeType,
const DeepCollectionEquality().hash(uploaded),
const DeepCollectionEquality().hash(total));
int get hashCode => Object.hash(runtimeType, uploaded, total);
@JsonKey(ignore: true)
@override
@pragma('vm:prefer-inline')
_$$InProgressCopyWith<_$InProgress> get copyWith =>
__$$InProgressCopyWithImpl<_$InProgress>(this, _$identity);
@@ -332,10 +331,10 @@ class _$InProgress extends InProgress {
@override
@optionalTypeArgs
TResult? whenOrNull<TResult extends Object?>({
TResult Function()? preparing,
TResult Function(int uploaded, int total)? inProgress,
TResult Function()? success,
TResult Function(String error)? failed,
TResult? Function()? preparing,
TResult? Function(int uploaded, int total)? inProgress,
TResult? Function()? success,
TResult? Function(String error)? failed,
}) {
return inProgress?.call(uploaded, total);
}
@@ -369,10 +368,10 @@ class _$InProgress extends InProgress {
@override
@optionalTypeArgs
TResult? mapOrNull<TResult extends Object?>({
TResult Function(Preparing value)? preparing,
TResult Function(InProgress value)? inProgress,
TResult Function(Success value)? success,
TResult Function(Failed value)? failed,
TResult? Function(Preparing value)? preparing,
TResult? Function(InProgress value)? inProgress,
TResult? Function(Success value)? success,
TResult? Function(Failed value)? failed,
}) {
return inProgress?.call(this);
}
@@ -422,13 +421,11 @@ abstract class _$$SuccessCopyWith<$Res> {
}
/// @nodoc
class __$$SuccessCopyWithImpl<$Res> extends _$UploadStateCopyWithImpl<$Res>
class __$$SuccessCopyWithImpl<$Res>
extends _$UploadStateCopyWithImpl<$Res, _$Success>
implements _$$SuccessCopyWith<$Res> {
__$$SuccessCopyWithImpl(_$Success _value, $Res Function(_$Success) _then)
: super(_value, (v) => _then(v as _$Success));
@override
_$Success get _value => super._value as _$Success;
: super(_value, _then);
}
/// @nodoc
@@ -473,10 +470,10 @@ class _$Success extends Success {
@override
@optionalTypeArgs
TResult? whenOrNull<TResult extends Object?>({
TResult Function()? preparing,
TResult Function(int uploaded, int total)? inProgress,
TResult Function()? success,
TResult Function(String error)? failed,
TResult? Function()? preparing,
TResult? Function(int uploaded, int total)? inProgress,
TResult? Function()? success,
TResult? Function(String error)? failed,
}) {
return success?.call();
}
@@ -510,10 +507,10 @@ class _$Success extends Success {
@override
@optionalTypeArgs
TResult? mapOrNull<TResult extends Object?>({
TResult Function(Preparing value)? preparing,
TResult Function(InProgress value)? inProgress,
TResult Function(Success value)? success,
TResult Function(Failed value)? failed,
TResult? Function(Preparing value)? preparing,
TResult? Function(InProgress value)? inProgress,
TResult? Function(Success value)? success,
TResult? Function(Failed value)? failed,
}) {
return success?.call(this);
}
@@ -552,24 +549,24 @@ abstract class Success extends UploadState {
abstract class _$$FailedCopyWith<$Res> {
factory _$$FailedCopyWith(_$Failed value, $Res Function(_$Failed) then) =
__$$FailedCopyWithImpl<$Res>;
@useResult
$Res call({String error});
}
/// @nodoc
class __$$FailedCopyWithImpl<$Res> extends _$UploadStateCopyWithImpl<$Res>
class __$$FailedCopyWithImpl<$Res>
extends _$UploadStateCopyWithImpl<$Res, _$Failed>
implements _$$FailedCopyWith<$Res> {
__$$FailedCopyWithImpl(_$Failed _value, $Res Function(_$Failed) _then)
: super(_value, (v) => _then(v as _$Failed));
@override
_$Failed get _value => super._value as _$Failed;
: super(_value, _then);
@pragma('vm:prefer-inline')
@override
$Res call({
Object? error = freezed,
Object? error = null,
}) {
return _then(_$Failed(
error: error == freezed
error: null == error
? _value.error
: error // ignore: cast_nullable_to_non_nullable
as String,
@@ -603,16 +600,16 @@ class _$Failed extends Failed {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _$Failed &&
const DeepCollectionEquality().equals(other.error, error));
(identical(other.error, error) || other.error == error));
}
@JsonKey(ignore: true)
@override
int get hashCode =>
Object.hash(runtimeType, const DeepCollectionEquality().hash(error));
int get hashCode => Object.hash(runtimeType, error);
@JsonKey(ignore: true)
@override
@pragma('vm:prefer-inline')
_$$FailedCopyWith<_$Failed> get copyWith =>
__$$FailedCopyWithImpl<_$Failed>(this, _$identity);
@@ -630,10 +627,10 @@ class _$Failed extends Failed {
@override
@optionalTypeArgs
TResult? whenOrNull<TResult extends Object?>({
TResult Function()? preparing,
TResult Function(int uploaded, int total)? inProgress,
TResult Function()? success,
TResult Function(String error)? failed,
TResult? Function()? preparing,
TResult? Function(int uploaded, int total)? inProgress,
TResult? Function()? success,
TResult? Function(String error)? failed,
}) {
return failed?.call(error);
}
@@ -667,10 +664,10 @@ class _$Failed extends Failed {
@override
@optionalTypeArgs
TResult? mapOrNull<TResult extends Object?>({
TResult Function(Preparing value)? preparing,
TResult Function(InProgress value)? inProgress,
TResult Function(Success value)? success,
TResult Function(Failed value)? failed,
TResult? Function(Preparing value)? preparing,
TResult? Function(InProgress value)? inProgress,
TResult? Function(Success value)? success,
TResult? Function(Failed value)? failed,
}) {
return failed?.call(this);
}
@@ -62,19 +62,19 @@ class ChannelModel {
final String type;
/// The cid of this channel
@JsonKey(includeIfNull: false, toJson: Serializer.readOnly)
@JsonKey(includeToJson: false)
final String cid;
/// List of user permissions on this channel
@JsonKey(includeIfNull: false, toJson: Serializer.readOnly)
@JsonKey(includeToJson: false)
final List<String>? ownCapabilities;
/// The channel configuration data
@JsonKey(includeIfNull: false, toJson: Serializer.readOnly)
@JsonKey(includeToJson: false)
final ChannelConfig config;
/// The user that created this channel
@JsonKey(includeIfNull: false, toJson: Serializer.readOnly)
@JsonKey(includeToJson: false)
final User? createdBy;
/// True if this channel is frozen
@@ -82,23 +82,23 @@ class ChannelModel {
final bool frozen;
/// The date of the last message
@JsonKey(includeIfNull: false, toJson: Serializer.readOnly)
@JsonKey(includeToJson: false)
final DateTime? lastMessageAt;
/// The date of channel creation
@JsonKey(includeIfNull: false, toJson: Serializer.readOnly)
@JsonKey(includeToJson: false)
final DateTime createdAt;
/// The date of the last channel update
@JsonKey(includeIfNull: false, toJson: Serializer.readOnly)
@JsonKey(includeToJson: false)
final DateTime updatedAt;
/// The date of channel deletion
@JsonKey(includeIfNull: false, toJson: Serializer.readOnly)
@JsonKey(includeToJson: false)
final DateTime? deletedAt;
/// The count of this channel members
@JsonKey(includeIfNull: false, toJson: Serializer.readOnly)
@JsonKey(includeToJson: false)
final int memberCount;
/// The number of seconds in a cooldown
@@ -106,15 +106,15 @@ class ChannelModel {
final int cooldown;
/// True if the channel is disabled
@JsonKey(ignore: true)
@JsonKey(includeToJson: false, includeFromJson: false)
bool? get disabled => extraData['disabled'] as bool?;
/// True if the channel is hidden
@JsonKey(ignore: true)
@JsonKey(includeToJson: false, includeFromJson: false)
bool? get hidden => extraData['hidden'] as bool?;
/// The date of the last time channel got truncated
@JsonKey(ignore: true)
@JsonKey(includeToJson: false, includeFromJson: false)
DateTime? get truncatedAt {
final truncatedAt = extraData['truncated_at'] as String?;
if (truncatedAt == null) return null;
@@ -122,11 +122,10 @@ class ChannelModel {
}
/// Map of custom channel extraData
@JsonKey(includeIfNull: false)
final Map<String, Object?> extraData;
/// The team the channel belongs to
@JsonKey(includeIfNull: false, toJson: Serializer.readOnly)
@JsonKey(includeToJson: false)
final String? team;
/// Known top level fields.
@@ -38,30 +38,11 @@ ChannelModel _$ChannelModelFromJson(Map<String, dynamic> json) => ChannelModel(
cooldown: json['cooldown'] as int? ?? 0,
);
Map<String, dynamic> _$ChannelModelToJson(ChannelModel instance) {
final val = <String, dynamic>{
'id': instance.id,
'type': instance.type,
};
void writeNotNull(String key, dynamic value) {
if (value != null) {
val[key] = value;
}
}
writeNotNull('cid', readonly(instance.cid));
writeNotNull('own_capabilities', readonly(instance.ownCapabilities));
writeNotNull('config', readonly(instance.config));
writeNotNull('created_by', readonly(instance.createdBy));
val['frozen'] = instance.frozen;
writeNotNull('last_message_at', readonly(instance.lastMessageAt));
writeNotNull('created_at', readonly(instance.createdAt));
writeNotNull('updated_at', readonly(instance.updatedAt));
writeNotNull('deleted_at', readonly(instance.deletedAt));
writeNotNull('member_count', readonly(instance.memberCount));
val['cooldown'] = instance.cooldown;
val['extra_data'] = instance.extraData;
writeNotNull('team', readonly(instance.team));
return val;
}
Map<String, dynamic> _$ChannelModelToJson(ChannelModel instance) =>
<String, dynamic>{
'id': instance.id,
'type': instance.type,
'frozen': instance.frozen,
'cooldown': instance.cooldown,
'extra_data': instance.extraData,
};
@@ -95,14 +95,11 @@ class Message extends Equatable {
final String? text;
/// The status of a sending message.
@JsonKey(ignore: true)
@JsonKey(includeFromJson: false, includeToJson: false)
final MessageSendingStatus status;
/// The message type.
@JsonKey(
includeIfNull: false,
toJson: Serializer.readOnly,
)
@JsonKey(includeToJson: false)
final String type;
/// The list of attachments, either provided by the user or generated from a
@@ -115,26 +112,26 @@ class Message extends Equatable {
final List<User> mentionedUsers;
/// A map describing the count of number of every reaction.
@JsonKey(includeIfNull: false, toJson: Serializer.readOnly)
@JsonKey(includeToJson: false)
final Map<String, int>? reactionCounts;
/// A map describing the count of score of every reaction.
@JsonKey(includeIfNull: false, toJson: Serializer.readOnly)
@JsonKey(includeToJson: false)
final Map<String, int>? reactionScores;
/// The latest reactions to the message created by any user.
@JsonKey(includeIfNull: false, toJson: Serializer.readOnly)
@JsonKey(includeToJson: false)
final List<Reaction>? latestReactions;
/// The reactions added to the message by the current user.
@JsonKey(includeIfNull: false, toJson: Serializer.readOnly)
@JsonKey(includeToJson: false)
final List<Reaction>? ownReactions;
/// The ID of the parent message, if the message is a thread reply.
final String? parentId;
/// A quoted reply message.
@JsonKey(toJson: Serializer.readOnly)
@JsonKey(includeToJson: false)
final Message? quotedMessage;
final String? _quotedMessageId;
@@ -143,11 +140,11 @@ class Message extends Equatable {
String? get quotedMessageId => _quotedMessageId ?? quotedMessage?.id;
/// Reserved field indicating the number of replies for this message.
@JsonKey(includeIfNull: false, toJson: Serializer.readOnly)
@JsonKey(includeToJson: false)
final int? replyCount;
/// Reserved field indicating the thread participants for this message.
@JsonKey(includeIfNull: false, toJson: Serializer.readOnly)
@JsonKey(includeToJson: false)
final List<User>? threadParticipants;
/// Check if this message needs to show in the channel.
@@ -157,41 +154,38 @@ class Message extends Equatable {
final bool silent;
/// If true the message is shadowed.
@JsonKey(
includeIfNull: false,
toJson: Serializer.readOnly,
)
@JsonKey(includeToJson: false)
final bool shadowed;
/// A used command name.
@JsonKey(includeIfNull: false, toJson: Serializer.readOnly)
@JsonKey(includeToJson: false)
final String? command;
final DateTime? _createdAt;
/// Reserved field indicating when the message was deleted.
@JsonKey(includeIfNull: false, toJson: Serializer.readOnly)
@JsonKey(includeToJson: false)
final DateTime? deletedAt;
/// Reserved field indicating when the message was created.
@JsonKey(includeIfNull: false, toJson: Serializer.readOnly)
@JsonKey(includeToJson: false)
DateTime get createdAt => _createdAt ?? DateTime.now();
final DateTime? _updatedAt;
/// Reserved field indicating when the message was updated last time.
@JsonKey(includeIfNull: false, toJson: Serializer.readOnly)
@JsonKey(includeToJson: false)
DateTime get updatedAt => _updatedAt ?? DateTime.now();
/// User who sent the message.
@JsonKey(includeIfNull: false, toJson: Serializer.readOnly)
@JsonKey(includeToJson: false)
final User? user;
/// If true the message is pinned.
final bool pinned;
/// Reserved field indicating when the message was pinned.
@JsonKey(toJson: Serializer.readOnly)
@JsonKey(includeToJson: false)
final DateTime? pinnedAt;
/// Reserved field indicating when the message will expire.
@@ -200,11 +194,10 @@ class Message extends Equatable {
final DateTime? pinExpires;
/// Reserved field indicating who pinned the message.
@JsonKey(toJson: Serializer.readOnly)
@JsonKey(includeToJson: false)
final User? pinnedBy;
/// Message custom extraData.
@JsonKey(includeIfNull: false)
final Map<String, Object?> extraData;
/// True if the message is a system info.
@@ -217,7 +210,7 @@ class Message extends Equatable {
bool get isEphemeral => type == 'ephemeral';
/// A Map of translations.
@JsonKey(includeIfNull: false)
@JsonKey(includeToJson: false)
final Map<String, String>? i18n;
/// Known top level fields.
@@ -71,43 +71,16 @@ Message _$MessageFromJson(Map<String, dynamic> json) => Message(
),
);
Map<String, dynamic> _$MessageToJson(Message instance) {
final val = <String, dynamic>{
'id': instance.id,
'text': instance.text,
};
void writeNotNull(String key, dynamic value) {
if (value != null) {
val[key] = value;
}
}
writeNotNull('type', readonly(instance.type));
val['attachments'] = instance.attachments.map((e) => e.toJson()).toList();
val['mentioned_users'] = User.toIds(instance.mentionedUsers);
writeNotNull('reaction_counts', readonly(instance.reactionCounts));
writeNotNull('reaction_scores', readonly(instance.reactionScores));
writeNotNull('latest_reactions', readonly(instance.latestReactions));
writeNotNull('own_reactions', readonly(instance.ownReactions));
val['parent_id'] = instance.parentId;
val['quoted_message'] = readonly(instance.quotedMessage);
val['quoted_message_id'] = instance.quotedMessageId;
writeNotNull('reply_count', readonly(instance.replyCount));
writeNotNull('thread_participants', readonly(instance.threadParticipants));
val['show_in_channel'] = instance.showInChannel;
val['silent'] = instance.silent;
writeNotNull('shadowed', readonly(instance.shadowed));
writeNotNull('command', readonly(instance.command));
writeNotNull('deleted_at', readonly(instance.deletedAt));
writeNotNull('created_at', readonly(instance.createdAt));
writeNotNull('updated_at', readonly(instance.updatedAt));
writeNotNull('user', readonly(instance.user));
val['pinned'] = instance.pinned;
val['pinned_at'] = readonly(instance.pinnedAt);
val['pin_expires'] = instance.pinExpires?.toIso8601String();
val['pinned_by'] = readonly(instance.pinnedBy);
val['extra_data'] = instance.extraData;
writeNotNull('i18n', instance.i18n);
return val;
}
Map<String, dynamic> _$MessageToJson(Message instance) => <String, dynamic>{
'id': instance.id,
'text': instance.text,
'attachments': instance.attachments.map((e) => e.toJson()).toList(),
'mentioned_users': User.toIds(instance.mentionedUsers),
'parent_id': instance.parentId,
'quoted_message_id': instance.quotedMessageId,
'show_in_channel': instance.showInChannel,
'silent': instance.silent,
'pinned': instance.pinned,
'pin_expires': instance.pinExpires?.toIso8601String(),
'extra_data': instance.extraData,
};
@@ -33,22 +33,21 @@ class Reaction {
final String type;
/// The date of the reaction
@JsonKey(includeIfNull: false, toJson: Serializer.readOnly)
@JsonKey(includeToJson: false)
final DateTime createdAt;
/// The user that sent the reaction
@JsonKey(includeIfNull: false, toJson: Serializer.readOnly)
@JsonKey(includeToJson: false)
final User? user;
/// The score of the reaction (ie. number of reactions sent)
final int score;
/// The userId that sent the reaction
@JsonKey(includeIfNull: false, toJson: Serializer.readOnly)
@JsonKey(includeToJson: false)
final String? userId;
/// Reaction custom extraData
@JsonKey(includeIfNull: false)
final Map<String, Object?> extraData;
/// Map of custom user extraData
@@ -20,22 +20,9 @@ Reaction _$ReactionFromJson(Map<String, dynamic> json) => Reaction(
extraData: json['extra_data'] as Map<String, dynamic>? ?? const {},
);
Map<String, dynamic> _$ReactionToJson(Reaction instance) {
final val = <String, dynamic>{
'message_id': instance.messageId,
'type': instance.type,
};
void writeNotNull(String key, dynamic value) {
if (value != null) {
val[key] = value;
}
}
writeNotNull('created_at', readonly(instance.createdAt));
writeNotNull('user', readonly(instance.user));
val['score'] = instance.score;
writeNotNull('user_id', readonly(instance.userId));
val['extra_data'] = instance.extraData;
return val;
}
Map<String, dynamic> _$ReactionToJson(Reaction instance) => <String, dynamic>{
'message_id': instance.messageId,
'type': instance.type,
'score': instance.score,
'extra_data': instance.extraData,
};
@@ -79,7 +79,7 @@ class User extends Equatable {
/// Shortcut for user name.
///
/// {@macro name}
@JsonKey(ignore: true)
@JsonKey(includeToJson: false, includeFromJson: false)
String get name {
if (extraData.containsKey('name') && extraData['name'] != null) {
final name = extraData['name']! as String;
@@ -91,48 +91,39 @@ class User extends Equatable {
/// Shortcut for user image.
///
/// {@macro image}
@JsonKey(ignore: true)
@JsonKey(includeToJson: false, includeFromJson: false)
String? get image => extraData['image'] as String?;
/// User role.
@JsonKey(includeIfNull: false, toJson: Serializer.readOnly)
@JsonKey(includeToJson: false)
final String? role;
/// User teams
@JsonKey(
includeIfNull: false,
toJson: Serializer.readOnly,
)
@JsonKey(includeToJson: false)
final List<String> teams;
/// Date of user creation.
@JsonKey(includeIfNull: false, toJson: Serializer.readOnly)
@JsonKey(includeToJson: false)
final DateTime createdAt;
/// Date of last user update.
@JsonKey(includeIfNull: false, toJson: Serializer.readOnly)
@JsonKey(includeToJson: false)
final DateTime updatedAt;
/// Date of last user connection.
@JsonKey(includeIfNull: false, toJson: Serializer.readOnly)
@JsonKey(includeToJson: false)
final DateTime? lastActive;
/// True if user is online.
@JsonKey(
includeIfNull: false,
toJson: Serializer.readOnly,
)
@JsonKey(includeToJson: false)
final bool online;
/// True if user is banned from the chat.
@JsonKey(
includeIfNull: false,
toJson: Serializer.readOnly,
)
@JsonKey(includeToJson: false)
final bool banned;
/// The date at which the ban will expire.
@JsonKey(includeIfNull: false, toJson: Serializer.readOnly)
@JsonKey(includeToJson: false)
final DateTime? banExpires;
/// The language this user prefers.
@@ -140,7 +131,6 @@ class User extends Equatable {
final String? language;
/// Map of custom user extraData.
@JsonKey(includeIfNull: false)
final Map<String, Object?> extraData;
/// List of users to list of userIds.
@@ -41,14 +41,6 @@ Map<String, dynamic> _$UserToJson(User instance) {
}
}
writeNotNull('role', readonly(instance.role));
writeNotNull('teams', readonly(instance.teams));
writeNotNull('created_at', readonly(instance.createdAt));
writeNotNull('updated_at', readonly(instance.updatedAt));
writeNotNull('last_active', readonly(instance.lastActive));
writeNotNull('online', readonly(instance.online));
writeNotNull('banned', readonly(instance.banned));
writeNotNull('ban_expires', readonly(instance.banExpires));
writeNotNull('language', instance.language);
val['extra_data'] = instance.extraData;
return val;
@@ -1,12 +1,5 @@
/// Used to avoid to serialize properties to json
// ignore: prefer_void_to_null
Null readonly(_) => null;
/// Helper class for serialization to and from json
class Serializer {
/// Used to avoid to serialize properties to json
static const Function readOnly = readonly;
/// Takes unknown json keys and puts them in the `extra_data` key
static Map<String, dynamic> moveToExtraDataFromRoot(
Map<String, dynamic> json,
@@ -36,6 +36,12 @@ class EventType {
/// Event sent when updating a message
static const String messageUpdated = 'message.updated';
/// Event sent when a user starts watching a channel
static const String userWatchingStart = 'user.watching.start';
/// Event sent when a user stops watching a channel
static const String userWatchingStop = 'user.watching.stop';
/// Event sent when reading a message
static const String messageRead = 'message.read';
+3 -30
View File
@@ -10,41 +10,13 @@ export 'package:logging/logging.dart' show Logger, Level, LogRecord;
export 'package:rate_limiter/rate_limiter.dart';
export 'package:uuid/uuid.dart';
export './src/core/api/attachment_file_uploader.dart';
export './src/core/api/requests.dart';
export './src/core/api/requests.dart';
export './src/core/api/responses.dart';
export './src/core/api/stream_chat_api.dart' show PushProvider;
export './src/core/error/error.dart';
export './src/core/models/action.dart';
export './src/core/models/attachment.dart';
export './src/core/models/attachment_file.dart';
export './src/core/models/channel_config.dart';
export './src/core/models/channel_model.dart';
export './src/core/models/channel_state.dart';
export './src/core/models/command.dart';
export './src/core/models/device.dart';
export './src/core/models/event.dart';
export './src/core/models/filter.dart' show Filter;
export './src/core/models/member.dart';
export './src/core/models/message.dart';
export './src/core/models/mute.dart';
export './src/core/models/own_user.dart';
export './src/core/models/reaction.dart';
export './src/core/models/read.dart';
export './src/core/models/user.dart';
export './src/core/util/extension.dart';
export './src/db/chat_persistence_client.dart';
export './src/event_type.dart';
export './src/permission_type.dart';
export './src/ws/connection_status.dart';
export 'src/client/channel.dart';
export 'src/client/client.dart';
export 'src/client/key_stroke_handler.dart';
export 'src/core/api/attachment_file_uploader.dart' show AttachmentFileUploader;
export 'src/core/api/requests.dart';
export 'src/core/api/attachment_file_uploader.dart';
export 'src/core/api/requests.dart';
export 'src/core/api/responses.dart';
export 'src/core/api/stream_chat_api.dart' show PushProvider;
export 'src/core/api/stream_chat_api.dart';
export 'src/core/error/error.dart';
export 'src/core/models/action.dart';
@@ -68,4 +40,5 @@ export 'src/core/platform_detector/platform_detector.dart';
export 'src/core/util/extension.dart';
export 'src/db/chat_persistence_client.dart';
export 'src/event_type.dart';
export 'src/permission_type.dart';
export 'src/ws/connection_status.dart';
+1 -1
View File
@@ -3,4 +3,4 @@ import 'package:stream_chat/src/client/client.dart';
/// Current package version
/// Used in [StreamChatClient] to build the `x-stream-client` header
// ignore: constant_identifier_names
const PACKAGE_VERSION = '5.1.0';
const PACKAGE_VERSION = '5.3.0';
+21 -21
View File
@@ -1,7 +1,7 @@
name: stream_chat
homepage: https://getstream.io/
description: The official Dart client for Stream Chat, a service for building chat applications.
version: 5.1.0
version: 5.3.0
repository: https://github.com/GetStream/stream-chat-flutter
issue_tracker: https://github.com/GetStream/stream-chat-flutter/issues
@@ -9,26 +9,26 @@ environment:
sdk: '>=2.17.0 <3.0.0'
dependencies:
async: ^2.5.0
collection: ^1.15.0
dio: ^4.0.0
equatable: ^2.0.0
freezed_annotation: ^2.0.3
http_parser: ^4.0.0
jose: ^0.3.2
json_annotation: ^4.5.0
logging: ^1.0.1
meta: ^1.3.0
mime: ^1.0.0
rate_limiter: ^0.1.1
rxdart: ^0.27.0
uuid: ^3.0.4
web_socket_channel: ^2.0.0
async: ^2.10.0
collection: ^1.17.0
dio: ^5.1.1
equatable: ^2.0.5
freezed_annotation: ^2.2.0
http_parser: ^4.0.2
jose: ^0.3.3
json_annotation: ^4.8.0
logging: ^1.1.1
meta: ^1.8.0
mime: ^1.0.4
rate_limiter: ^1.0.0
rxdart: ^0.27.7
uuid: ^3.0.7
web_socket_channel: ^2.3.0
dev_dependencies:
build_runner: ^2.0.1
dart_code_metrics: ^4.4.0
freezed: ^2.0.3
json_serializable: ^6.2.0
build_runner: ^2.3.3
dart_code_metrics: ^5.7.0
freezed: ^2.3.2
json_serializable: ^6.6.1
mocktail: ^0.3.0
test: ^1.17.12
test: ^1.24.1
+25 -101
View File
@@ -1,4 +1,3 @@
{
"channel": {
"id": "dev",
@@ -18,400 +17,325 @@
"text": "fasdfa",
"attachments": [],
"parent_id": null,
"quoted_message": null,
"quoted_message_id": null,
"show_in_channel": null,
"mentioned_users": [],
"status": "SENT",
"silent": false,
"pinned": false,
"pinned_at": null,
"pin_expires": null,
"pinned_by": null
"pin_expires": null
},
{
"id": "dry-meadow-0-e8e74482-b4cd-48db-9d1e-30e6c191786f",
"text": "test message",
"attachments": [],
"parent_id": null,
"quoted_message": null,
"quoted_message_id": null,
"show_in_channel": null,
"mentioned_users": [],
"status": "SENT",
"silent": false,
"pinned": false,
"pinned_at": null,
"pin_expires": null,
"pinned_by": null
"pin_expires": null
},
{
"id": "dry-meadow-0-53e6299f-9b97-4a9c-a27e-7e2dde49b7e0",
"text": "test message",
"attachments": [],
"parent_id": null,
"quoted_message": null,
"quoted_message_id": null,
"show_in_channel": null,
"mentioned_users": [],
"status": "SENT",
"silent": false,
"pinned": false,
"pinned_at": null,
"pin_expires": null,
"pinned_by": null
"pin_expires": null
},
{
"id": "dry-meadow-0-80925be0-786e-40a5-b225-486518dafd35",
"text": "asdfadf",
"attachments": [],
"parent_id": null,
"quoted_message": null,
"quoted_message_id": null,
"show_in_channel": null,
"mentioned_users": [],
"status": "SENT",
"silent": false,
"pinned": false,
"pinned_at": null,
"pin_expires": null,
"pinned_by": null
"pin_expires": null
},
{
"id": "dry-meadow-0-64d7970f-ede8-4b31-9738-1bc1756d2bfe",
"text": "test",
"attachments": [],
"parent_id": null,
"quoted_message": null,
"quoted_message_id": null,
"show_in_channel": null,
"mentioned_users": [],
"status": "SENT",
"silent": false,
"pinned": false,
"pinned_at": null,
"pin_expires": null,
"pinned_by": null
"pin_expires": null
},
{
"id": "withered-cell-0-84cbd760-cf55-4f7e-9207-c5f66cccc6dc",
"text": "hi",
"attachments": [],
"parent_id": null,
"quoted_message": null,
"quoted_message_id": null,
"show_in_channel": null,
"mentioned_users": [],
"status": "SENT",
"silent": false,
"pinned": false,
"pinned_at": null,
"pin_expires": null,
"pinned_by": null
"pin_expires": null
},
{
"id": "dry-meadow-0-e9203588-43c3-40b1-91f7-f217fc42aa53",
"text": "fantastic",
"attachments": [],
"parent_id": null,
"quoted_message": null,
"quoted_message_id": null,
"show_in_channel": null,
"mentioned_users": [],
"status": "SENT",
"silent": false,
"pinned": false,
"pinned_at": null,
"pin_expires": null,
"pinned_by": null
"pin_expires": null
},
{
"id": "withered-cell-0-7e3552d7-7a0d-45f2-a856-e91b23a7e240",
"text": "nice to meet you",
"attachments": [],
"parent_id": null,
"quoted_message": null,
"quoted_message_id": null,
"show_in_channel": null,
"mentioned_users": [],
"status": "SENT",
"silent": false,
"pinned": false,
"pinned_at": null,
"pin_expires": null,
"pinned_by": null
"pin_expires": null
},
{
"id": "dry-meadow-0-1ffeafd4-e4fc-4c84-9394-9d7cb10fff42",
"text": "hey",
"attachments": [],
"parent_id": null,
"quoted_message": null,
"quoted_message_id": null,
"show_in_channel": null,
"mentioned_users": [],
"status": "SENT",
"silent": false,
"pinned": false,
"pinned_at": null,
"pin_expires": null,
"pinned_by": null
"pin_expires": null
},
{
"id": "dry-meadow-0-3f147324-12c8-4b41-9fb5-2db88d065efa",
"text": "hello, everyone",
"attachments": [],
"parent_id": null,
"quoted_message": null,
"quoted_message_id": null,
"show_in_channel": null,
"mentioned_users": [],
"status": "SENT",
"silent": false,
"pinned": false,
"pinned_at": null,
"pin_expires": null,
"pinned_by": null
"pin_expires": null
},
{
"id": "dry-meadow-0-51a348ae-0c0a-44de-a556-eac7891c0cf0",
"text": "who is there?",
"attachments": [],
"parent_id": null,
"quoted_message": null,
"quoted_message_id": null,
"show_in_channel": null,
"mentioned_users": [],
"status": "SENT",
"silent": false,
"pinned": false,
"pinned_at": null,
"pin_expires": null,
"pinned_by": null
"pin_expires": null
},
{
"id": "icy-recipe-7-a29e237b-8d81-4a97-9bc8-d42bca3f1356",
"text": "하이",
"attachments": [],
"parent_id": null,
"quoted_message": null,
"quoted_message_id": null,
"show_in_channel": null,
"mentioned_users": [],
"status": "SENT",
"silent": false,
"pinned": false,
"pinned_at": null,
"pin_expires": null,
"pinned_by": null
"pin_expires": null
},
{
"id": "icy-recipe-7-935c396e-ddf8-4a9a-951c-0a12fa5bf055",
"text": "what are you doing?",
"attachments": [],
"parent_id": null,
"quoted_message": null,
"quoted_message_id": null,
"show_in_channel": null,
"mentioned_users": [],
"status": "SENT",
"silent": false,
"pinned": false,
"pinned_at": null,
"pin_expires": null,
"pinned_by": null
"pin_expires": null
},
{
"id": "throbbing-boat-5-1e4d5730-5ff0-4d25-9948-9f34ffda43e4",
"text": "👍",
"attachments": [],
"parent_id": null,
"quoted_message": null,
"quoted_message_id": null,
"show_in_channel": null,
"mentioned_users": [],
"status": "SENT",
"silent": false,
"pinned": false,
"pinned_at": null,
"pin_expires": null,
"pinned_by": null
"pin_expires": null
},
{
"id": "snowy-credit-3-3e0c1a0d-d22f-42ee-b2a1-f9f49477bf21",
"text": "sdasas",
"attachments": [],
"parent_id": null,
"quoted_message": null,
"quoted_message_id": null,
"show_in_channel": null,
"mentioned_users": [],
"status": "SENT",
"silent": false,
"pinned": false,
"pinned_at": null,
"pin_expires": null,
"pinned_by": null
"pin_expires": null
},
{
"id": "snowy-credit-3-3319537e-2d0e-4876-8170-a54f046e4b7d",
"text": "cjshsa",
"attachments": [],
"parent_id": null,
"quoted_message": null,
"quoted_message_id": null,
"show_in_channel": null,
"mentioned_users": [],
"status": "SENT",
"silent": false,
"pinned": false,
"pinned_at": null,
"pin_expires": null,
"pinned_by": null
"pin_expires": null
},
{
"id": "snowy-credit-3-cfaf0b46-1daa-49c5-947c-b16d6697487d",
"text": "nhisagdhsadz",
"attachments": [],
"parent_id": null,
"quoted_message": null,
"quoted_message_id": null,
"show_in_channel": null,
"mentioned_users": [],
"status": "SENT",
"silent": false,
"pinned": false,
"pinned_at": null,
"pin_expires": null,
"pinned_by": null
"pin_expires": null
},
{
"id": "snowy-credit-3-cebe25a7-a3a3-49fc-9919-91c6725e81f3",
"text": "hvadhsahzd",
"attachments": [],
"parent_id": null,
"quoted_message": null,
"quoted_message_id": null,
"show_in_channel": null,
"mentioned_users": [],
"status": "SENT",
"silent": false,
"pinned": false,
"pinned_at": null,
"pin_expires": null,
"pinned_by": null
"pin_expires": null
},
{
"id": "divine-glade-9-0cea9262-5766-48e9-8b22-311870aed3bf",
"text": "hello",
"attachments": [],
"parent_id": null,
"quoted_message": null,
"quoted_message_id": null,
"show_in_channel": null,
"mentioned_users": [],
"status": "SENT",
"silent": false,
"pinned": false,
"pinned_at": null,
"pin_expires": null,
"pinned_by": null
"pin_expires": null
},
{
"id": "red-firefly-9-c4e9007b-bb7d-4238-ae08-5f8e3cd03d73",
"text": "hello",
"attachments": [],
"parent_id": null,
"quoted_message": null,
"quoted_message_id": null,
"show_in_channel": null,
"mentioned_users": [],
"status": "SENT",
"silent": false,
"pinned": false,
"pinned_at": null,
"pin_expires": null,
"pinned_by": null
"pin_expires": null
},
{
"id": "bitter-glade-2-02aee4eb-4093-4736-808b-2de75820e854",
"text": "hello",
"attachments": [],
"parent_id": null,
"quoted_message": null,
"quoted_message_id": null,
"show_in_channel": null,
"mentioned_users": [],
"status": "SENT",
"silent": false,
"pinned": false,
"pinned_at": null,
"pin_expires": null,
"pinned_by": null
"pin_expires": null
},
{
"id": "morning-sea-1-0c700bcb-46dd-4224-b590-e77bdbccc480",
"text": "http://jaeger.ui.gtstrm.com/",
"attachments": [],
"parent_id": null,
"quoted_message": null,
"quoted_message_id": null,
"show_in_channel": null,
"mentioned_users": [],
"status": "SENT",
"silent": false,
"pinned": false,
"pinned_at": null,
"pin_expires": null,
"pinned_by": null
"pin_expires": null
},
{
"id": "ancient-salad-0-53e8b4e6-5b7b-43ad-aeee-8bfb6a9ed0be",
"text": "hi",
"attachments": [],
"parent_id": null,
"quoted_message": null,
"quoted_message_id": null,
"show_in_channel": null,
"mentioned_users": [],
"status": "SENT",
"silent": false,
"pinned": false,
"pinned_at": null,
"pin_expires": null,
"pinned_by": null
"pin_expires": null
},
{
"id": "ancient-salad-0-8c225075-bd4c-42e2-8024-530aae13cd40",
"text": "hi",
"attachments": [],
"parent_id": null,
"quoted_message": null,
"quoted_message_id": null,
"show_in_channel": null,
"mentioned_users": [],
"status": "SENT",
"silent": false,
"pinned": false,
"pinned_at": null,
"pin_expires": null,
"pinned_by": null
"pin_expires": null
},
{
"id": "proud-sea-7-17802096-cbf8-4e3c-addd-4ee31f4c8b5c",
"text": "😃",
"attachments": [],
"parent_id": null,
"quoted_message": null,
"quoted_message_id": null,
"show_in_channel": null,
"mentioned_users": [],
"status": "SENT",
"silent": false,
"pinned": false,
"pinned_at": null,
"pin_expires": null,
"pinned_by": null
"pin_expires": null
}
],
"pinned_messages": [],
@@ -18,12 +18,9 @@
],
"mentioned_users": [],
"parent_id": "parentId",
"quoted_message": null,
"quoted_message_id": null,
"pinned": false,
"pinned_at": null,
"pin_expires": null,
"pinned_by": null,
"show_in_channel": true,
"hey": "test"
}
@@ -516,6 +516,9 @@ void main() {
});
setUp(() async {
when(() => persistence.updateLastSyncAt(any()))
.thenAnswer((_) => Future.value());
when(persistence.getLastSyncAt).thenAnswer((_) async => null);
client = StreamChatClient(apiKey, chatApi: api, ws: ws)
..chatPersistenceClient = persistence;
await client.connectUser(user, token);
@@ -532,9 +535,12 @@ void main() {
test(
'''should update persistence connectionInfo and lastSync when sync succeeds''',
() async {
// persistence.updateLastSyncAt might be called
// when connecting the user.
// Resetting the logs so we start counting invocations correctly.
reset(persistence);
const cids = ['test-cid-1', 'test-cid-2', 'test-cid-3'];
final lastSyncAt = DateTime.now();
when(() => api.general.sync(cids, lastSyncAt))
.thenAnswer((_) async => SyncResponse()
..events = [
@@ -567,6 +573,10 @@ void main() {
test(
'should work fine if persistence contains sync params',
() async {
// persistence.updateLastSyncAt might be called
// when connecting the user.
// Resetting the logs so we start counting invocations correctly.
reset(persistence);
const cids = ['test-cid-1', 'test-cid-2', 'test-cid-3'];
final lastSyncAt = DateTime.now();
@@ -22,26 +22,37 @@ void main() {
test('addDevice should work', () async {
const deviceId = 'test-device-id';
const pushProvider = PushProvider.firebase;
const pushProvidersMap = {
'apn': PushProvider.apn,
'firebase': PushProvider.firebase,
'huawei': PushProvider.huawei,
'xiaomi': PushProvider.xiaomi,
};
const path = '/devices';
when(() => client.post(
path,
data: {
'id': deviceId,
'push_provider': pushProvider.name,
},
))
.thenAnswer(
(_) async => successResponse(path, data: <String, dynamic>{}));
for (final pushProviderMapEntry in pushProvidersMap.entries) {
final data = {
'id': deviceId,
'push_provider': pushProviderMapEntry.key,
};
when(() {
return client.post(
path,
data: data,
);
}).thenAnswer(
(_) async => successResponse(path, data: <String, dynamic>{}));
final res = await deviceApi.addDevice(deviceId, pushProvider);
final res =
await deviceApi.addDevice(deviceId, pushProviderMapEntry.value);
expect(res, isNotNull);
expect(res, isNotNull);
verify(() => client.post(path, data: any(named: 'data'))).called(1);
verify(() => client.post(path, data: data)).called(1);
}
verifyNoMoreInteractions(client);
expect(pushProvidersMap.length, PushProvider.values.length,
reason: 'All PushProvider should be tested');
});
test('addDevice should work with pushProviderName', () async {
@@ -394,7 +394,12 @@ void main() {
path,
data: {'language': language},
)).thenAnswer((_) async => successResponse(path, data: {
'message': translatedMessage.toJson(),
'message': {
...translatedMessage.toJson(),
'i18n': {
language: translatedMessageText,
},
},
}));
final res = await messageApi.translateMessage(messageId, language);
@@ -129,7 +129,11 @@ void main() {
await client.get('path');
} on StreamChatNetworkError catch (e) {
expect(e, isA<StreamChatNetworkError>());
expect(e.message, "Dio can't establish new connection after closed.");
expect(
e.message,
"The connection errored: Dio can't establish a new connection"
' after it was closed.',
);
}
});
+40 -1
View File
@@ -1,14 +1,53 @@
## Upcoming
🐞 Fixed
- [#1462](https://github.com/GetStream/stream-chat-flutter/issues/1462) Fixed support for iPad in the share button for images.
- [#1475](https://github.com/GetStream/stream-chat-flutter/issues/1475) Fixed typo to fix compilation.
✅ Added
- 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 customize `AttachmentActionsModal`.
🔄 Changed
- Updated dependencies to resolvable versions.
## 5.3.0
🔄 Changed
- Updated `photo_manager` dependency to `^2.5.2`
🐞 Fixed
- [[#1424]](https://github.com/GetStream/stream-chat-flutter/issues/1424) Fixed a render issue when showing messages starting with 4 whitespaces.
- Fixed a bug where the `AttachmentPickerBottomSheet` was not able to identify the mobile browser.
- Fixed uploading files on Windows - fixed temp file path.
✅ Added
- New `noPhotoOrVideoLabel` displayed when there is no files to choose.
## 5.2.0
✅ Added
- Added a new `bottomRowBuilderWithDefaultWidget` parameter to `StreamMessageWidget` which contains a third parameter (default `BottomRow` widget with `copyWith` method available) to allow easier customization.
🔄 Changed
- Updated `lottie` dependency to `^2.0.0`
- Updated `desktop_drop` dependency to `^0.4.0`
- Updated `connectivity_plus` dependency to `^3.0.2`
- Updated `dart_vlc` dependency to `^0.4.0`
- Updated `file_picker` dependency to `^5.2.4`
- Deprecated `StreamMessageWidget.bottomRowBuilder` in favor of `StreamMessageWidget.bottomRowBuilderWithDefaultWidget`.
- Deprecated `StreamMessageWidget.deletedBottomRowBuilder` in favor of `StreamMessageWidget.bottomRowBuilderWithDefaultWidget`.
- Deprecated `StreamMessageWidget.usernameBuilder` in favor of `StreamMessageWidget.bottomRowBuilderWithDefaultWidget`.
🐞 Fixed
- [[#1379]](https://github.com/GetStream/stream-chat-flutter/issues/1379) Fixed "Issues with photo attachments on web", where the cached image attachment would not render while uploading.
- Fix render overflow issue with `MessageSearchListTileTitle`. It now uses `Text.rich` instead of `Row`. Better default behaviour and allows `TextOverflow`.
- [[1346]](https://github.com/GetStream/stream-chat-flutter/issues/1346) Fixed a render issue while uploading video on web.
- [[#1347]](https://github.com/GetStream/stream-chat-flutter/issues/1347) `onReply` not working in `AttachmentActionsModal` which is used by `StreamImageAttachment` and `StreamImageGroup`.
## 5.1.0
@@ -1317,4 +1356,4 @@ The property showVideoFullScreen was added mainly because of this issue brianega
## 0.0.1
- First release
- First release
+1
View File
@@ -48,6 +48,7 @@ You should then run `flutter packages get`
The package uses [`photo_manager`](https://pub.dev/packages/photo_manager) to access the device's photo library.
Follow [this wiki](https://pub.dev/packages/photo_manager#android-10-q-29) to fulfil the Android requirements.
You need to take additional steps if you are targeting Android 13. Read [this section](https://pub.dev/packages/photo_manager#android-13-api-33-extra-configs) for more information.
### iOS
@@ -26,7 +26,7 @@ apply plugin: 'kotlin-android'
apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
android {
compileSdkVersion 31
compileSdkVersion 33
sourceSets {
main.java.srcDirs += 'src/main/kotlin'
@@ -41,7 +41,7 @@ android {
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
applicationId "com.example.example"
minSdkVersion 23
targetSdkVersion 31
targetSdkVersion 33
versionCode flutterVersionCode.toInteger()
versionName flutterVersionName
}
@@ -9,17 +9,20 @@
<uses-permission android:name="android.permission.CAMERA"/>
<uses-permission android:name="android.permission.ACCESS_MEDIA_LOCATION"/>
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
<uses-permission android:name="android.permission.READ_MEDIA_AUDIO" />
<uses-permission android:name="android.permission.READ_MEDIA_VIDEO" />
<application
android:name=".Application"
android:label="example"
android:icon="@mipmap/ic_launcher">
android:icon="@mipmap/ic_launcher"
android:label="example">
<activity
android:name=".MainActivity"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
android:exported="true"
android:hardwareAccelerated="true"
android:launchMode="singleTop"
android:theme="@style/LaunchTheme"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
android:hardwareAccelerated="true"
android:exported="true"
android:windowSoftInputMode="adjustResize">
<!-- Specifies an Android theme to apply to this Activity as soon as
the Android process has started. This theme is visible to the user
@@ -1,12 +1,12 @@
buildscript {
ext.kotlin_version = '1.6.0'
ext.kotlin_version = '1.7.21'
repositories {
google()
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:4.2.2'
classpath 'com.android.tools.build:gradle:7.2.2'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}
}
@@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-6.9-all.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-7.6-all.zip
@@ -3,7 +3,7 @@
archiveVersion = 1;
classes = {
};
objectVersion = 51;
objectVersion = 54;
objects = {
/* Begin PBXBuildFile section */
@@ -199,6 +199,7 @@
/* Begin PBXShellScriptBuildPhase section */
3B06AD1E1E4923F5004D2608 /* Thin Binary */ = {
isa = PBXShellScriptBuildPhase;
alwaysOutOfDate = 1;
buildActionMask = 2147483647;
files = (
);
@@ -235,6 +236,7 @@
};
9740EEB61CF901F6004384FC /* Run Script */ = {
isa = PBXShellScriptBuildPhase;
alwaysOutOfDate = 1;
buildActionMask = 2147483647;
files = (
);
@@ -61,5 +61,7 @@
<string>Explain why your app uses the mic</string>
<key>CADisableMinimumFrameDurationOnPhone</key>
<true/>
<key>UIApplicationSupportsIndirectInputEvents</key>
<true/>
</dict>
</plist>
@@ -177,7 +177,7 @@ class _SplitViewState extends State<SplitView> {
: Center(
child: Text(
'Pick a channel to show the messages 💬',
style: Theme.of(context).textTheme.headline5,
style: Theme.of(context).textTheme.headlineSmall,
),
),
),
@@ -78,7 +78,7 @@ class _SplitViewState extends State<SplitView> {
: Center(
child: Text(
'Pick a channel to show the messages 💬',
style: Theme.of(context).textTheme.headline5,
style: Theme.of(context).textTheme.headlineSmall,
),
),
),
@@ -74,6 +74,9 @@ class MyApp extends StatelessWidget {
fit: BoxFit.cover,
),
),
ownMessageTheme: const StreamMessageThemeData(
urlAttachmentTitleMaxLine: 1,
),
otherMessageTheme: StreamMessageThemeData(
messageBackgroundColor: colorTheme.textHighEmphasis,
messageTextStyle: TextStyle(
@@ -82,6 +85,7 @@ class MyApp extends StatelessWidget {
avatarTheme: StreamAvatarThemeData(
borderRadius: BorderRadius.circular(8),
),
urlAttachmentTitleMaxLine: 1,
),
).merge(defaultTheme);
@@ -9,10 +9,8 @@
#include <dart_vlc/dart_vlc_plugin.h>
#include <desktop_drop/desktop_drop_plugin.h>
#include <file_selector_linux/file_selector_plugin.h>
#include <screen_retriever/screen_retriever_plugin.h>
#include <sqlite3_flutter_libs/sqlite3_flutter_libs_plugin.h>
#include <url_launcher_linux/url_launcher_plugin.h>
#include <window_manager/window_manager_plugin.h>
void fl_register_plugins(FlPluginRegistry* registry) {
g_autoptr(FlPluginRegistrar) dart_vlc_registrar =
@@ -24,16 +22,10 @@ void fl_register_plugins(FlPluginRegistry* registry) {
g_autoptr(FlPluginRegistrar) file_selector_linux_registrar =
fl_plugin_registry_get_registrar_for_plugin(registry, "FileSelectorPlugin");
file_selector_plugin_register_with_registrar(file_selector_linux_registrar);
g_autoptr(FlPluginRegistrar) screen_retriever_registrar =
fl_plugin_registry_get_registrar_for_plugin(registry, "ScreenRetrieverPlugin");
screen_retriever_plugin_register_with_registrar(screen_retriever_registrar);
g_autoptr(FlPluginRegistrar) sqlite3_flutter_libs_registrar =
fl_plugin_registry_get_registrar_for_plugin(registry, "Sqlite3FlutterLibsPlugin");
sqlite3_flutter_libs_plugin_register_with_registrar(sqlite3_flutter_libs_registrar);
g_autoptr(FlPluginRegistrar) url_launcher_linux_registrar =
fl_plugin_registry_get_registrar_for_plugin(registry, "UrlLauncherPlugin");
url_launcher_plugin_register_with_registrar(url_launcher_linux_registrar);
g_autoptr(FlPluginRegistrar) window_manager_registrar =
fl_plugin_registry_get_registrar_for_plugin(registry, "WindowManagerPlugin");
window_manager_plugin_register_with_registrar(window_manager_registrar);
}
@@ -6,10 +6,8 @@ list(APPEND FLUTTER_PLUGIN_LIST
dart_vlc
desktop_drop
file_selector_linux
screen_retriever
sqlite3_flutter_libs
url_launcher_linux
window_manager
)
list(APPEND FLUTTER_FFI_PLUGIN_LIST
@@ -3,7 +3,7 @@
archiveVersion = 1;
classes = {
};
objectVersion = 51;
objectVersion = 54;
objects = {
/* Begin PBXAggregateTarget section */
@@ -203,7 +203,7 @@
isa = PBXProject;
attributes = {
LastSwiftUpdateCheck = 0920;
LastUpgradeCheck = 0930;
LastUpgradeCheck = 1300;
ORGANIZATIONNAME = "";
TargetAttributes = {
33CC10EC2044A3C60003C045 = {
@@ -256,6 +256,7 @@
/* Begin PBXShellScriptBuildPhase section */
3399D490228B24CF009A79C7 /* ShellScript */ = {
isa = PBXShellScriptBuildPhase;
alwaysOutOfDate = 1;
buildActionMask = 2147483647;
files = (
);
@@ -404,7 +405,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
MACOSX_DEPLOYMENT_TARGET = 10.11;
MACOSX_DEPLOYMENT_TARGET = 10.14;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = macosx;
SWIFT_COMPILATION_MODE = wholemodule;
@@ -484,7 +485,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
MACOSX_DEPLOYMENT_TARGET = 10.11;
MACOSX_DEPLOYMENT_TARGET = 10.14;
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = macosx;
@@ -531,7 +532,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
MACOSX_DEPLOYMENT_TARGET = 10.11;
MACOSX_DEPLOYMENT_TARGET = 10.14;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = macosx;
SWIFT_COMPILATION_MODE = wholemodule;
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1000"
LastUpgradeVersion = "1300"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
@@ -19,6 +19,7 @@ version: 1.0.0+1
environment:
sdk: '>=2.17.0 <3.0.0'
flutter: ">=1.17.0"
dependencies:
# The following adds the Cupertino Icons font to your application.
@@ -27,7 +28,7 @@ dependencies:
cupertino_icons: ^1.0.4
flutter:
sdk: flutter
responsive_builder: ^0.4.2
responsive_builder: ^0.6.4
stream_chat_flutter:
path: ../
stream_chat_localizations:
@@ -6,16 +6,14 @@
#include "generated_plugin_registrant.h"
#include <connectivity_plus_windows/connectivity_plus_windows_plugin.h>
#include <connectivity_plus/connectivity_plus_windows_plugin.h>
#include <dart_vlc/dart_vlc_plugin.h>
#include <desktop_drop/desktop_drop_plugin.h>
#include <file_selector_windows/file_selector_windows.h>
#include <flutter_native_view/flutter_native_view_plugin.h>
#include <screen_retriever/screen_retriever_plugin.h>
#include <share_plus/share_plus_windows_plugin_c_api.h>
#include <sqlite3_flutter_libs/sqlite3_flutter_libs_plugin.h>
#include <thumblr_windows/thumblr_windows_plugin.h>
#include <url_launcher_windows/url_launcher_windows.h>
#include <window_manager/window_manager_plugin.h>
void RegisterPlugins(flutter::PluginRegistry* registry) {
ConnectivityPlusWindowsPluginRegisterWithRegistrar(
@@ -26,16 +24,12 @@ void RegisterPlugins(flutter::PluginRegistry* registry) {
registry->GetRegistrarForPlugin("DesktopDropPlugin"));
FileSelectorWindowsRegisterWithRegistrar(
registry->GetRegistrarForPlugin("FileSelectorWindows"));
FlutterNativeViewPluginRegisterWithRegistrar(
registry->GetRegistrarForPlugin("FlutterNativeViewPlugin"));
ScreenRetrieverPluginRegisterWithRegistrar(
registry->GetRegistrarForPlugin("ScreenRetrieverPlugin"));
SharePlusWindowsPluginCApiRegisterWithRegistrar(
registry->GetRegistrarForPlugin("SharePlusWindowsPluginCApi"));
Sqlite3FlutterLibsPluginRegisterWithRegistrar(
registry->GetRegistrarForPlugin("Sqlite3FlutterLibsPlugin"));
ThumblrWindowsPluginRegisterWithRegistrar(
registry->GetRegistrarForPlugin("ThumblrWindowsPlugin"));
UrlLauncherWindowsRegisterWithRegistrar(
registry->GetRegistrarForPlugin("UrlLauncherWindows"));
WindowManagerPluginRegisterWithRegistrar(
registry->GetRegistrarForPlugin("WindowManagerPlugin"));
}
@@ -3,16 +3,14 @@
#
list(APPEND FLUTTER_PLUGIN_LIST
connectivity_plus_windows
connectivity_plus
dart_vlc
desktop_drop
file_selector_windows
flutter_native_view
screen_retriever
share_plus
sqlite3_flutter_libs
thumblr_windows
url_launcher_windows
window_manager
)
list(APPEND FLUTTER_FFI_PLUGIN_LIST
@@ -1,5 +1,4 @@
export 'attachment_error.dart';
export 'attachment_error.dart';
export 'attachment_upload_state_builder.dart';
export 'attachment_widget.dart' show AttachmentSource;
export 'file_attachment.dart';
@@ -195,7 +195,7 @@ class _FileTypeImage extends StatelessWidget {
shape: _getDefaultShape(context),
child: source.when(
local: () => StreamVideoThumbnailImage(
video: attachment.file!.path!,
video: attachment.file!.path,
placeholderBuilder: (_) => const Center(
child: SizedBox(
width: 20,
@@ -205,7 +205,7 @@ class _FileTypeImage extends StatelessWidget {
),
),
network: () => StreamVideoThumbnailImage(
video: attachment.assetUrl!,
video: attachment.assetUrl,
placeholderBuilder: (_) => const Center(
child: SizedBox(
width: 20,
@@ -17,6 +17,7 @@ class StreamGiphyAttachment extends StreamAttachmentWidget {
this.onShowMessage,
this.onReplyMessage,
this.onAttachmentTap,
this.attachmentActionsModalBuilder,
});
/// {@macro showMessageCallback}
@@ -28,6 +29,9 @@ class StreamGiphyAttachment extends StreamAttachmentWidget {
/// {@macro onAttachmentTap}
final OnAttachmentTap? onAttachmentTap;
/// {@macro attachmentActionsBuilder}
final AttachmentActionsBuilder? attachmentActionsModalBuilder;
@override
Widget build(BuildContext context) {
final imageUrl =
@@ -260,6 +264,7 @@ class StreamGiphyAttachment extends StreamAttachmentWidget {
userName: message.user!.name,
onShowMessage: onShowMessage,
onReplyMessage: onReplyMessage,
attachmentActionsModalBuilder: attachmentActionsModalBuilder,
),
);
},
@@ -123,7 +123,9 @@ class StreamAttachmentHandler extends StreamAttachmentHandlerBase {
final tempDir = await getTemporaryDirectory();
final tempPath = Uri.file(tempDir.path, windows: CurrentPlatform.isWindows);
final tempFilePath = tempPath.resolve(fileName!).path;
final tempFilePath = tempPath
.resolve(fileName!)
.toFilePath(windows: CurrentPlatform.isWindows);
print(tempFilePath);
final attachmentFileBytes = attachmentFile.bytes;
@@ -22,6 +22,7 @@ class StreamImageAttachment extends StreamAttachmentWidget {
this.imageThumbnailSize = const Size(400, 400),
this.imageThumbnailResizeType = 'clip',
this.imageThumbnailCropType = 'center',
this.attachmentActionsModalBuilder,
});
/// The [StreamMessageThemeData] to use for the image title
@@ -52,6 +53,9 @@ class StreamImageAttachment extends StreamAttachmentWidget {
/// Defaults to [center]
final String /*center|top|bottom|left|right*/ imageThumbnailCropType;
/// {@macro attachmentActionsBuilder}
final AttachmentActionsBuilder? attachmentActionsModalBuilder;
@override
Widget build(BuildContext context) {
return source.when(
@@ -161,6 +165,8 @@ class StreamImageAttachment extends StreamAttachmentWidget {
userName: message.user!.name,
onShowMessage: onShowMessage,
onReplyMessage: onReplyMessage,
attachmentActionsModalBuilder:
attachmentActionsModalBuilder,
),
);
},
@@ -18,6 +18,7 @@ class StreamImageGroup extends StatelessWidget {
this.imageThumbnailSize = const Size(400, 400),
this.imageThumbnailResizeType = 'clip',
this.imageThumbnailCropType = 'center',
this.attachmentActionsModalBuilder,
});
/// List of attachments to show
@@ -54,6 +55,9 @@ class StreamImageGroup extends StatelessWidget {
/// Defaults to [center]
final String /*center|top|bottom|left|right*/ imageThumbnailCropType;
/// {@macro attachmentActionsBuilder}
final AttachmentActionsBuilder? attachmentActionsModalBuilder;
@override
Widget build(BuildContext context) {
return ConstrainedBox(
@@ -154,6 +158,7 @@ class StreamImageGroup extends StatelessWidget {
userName: message.user!.name,
onShowMessage: onShowMessage,
onReplyMessage: onReplyMessage,
attachmentActionsModalBuilder: attachmentActionsModalBuilder,
),
),
),
@@ -170,6 +175,7 @@ class StreamImageGroup extends StatelessWidget {
imageThumbnailSize: imageThumbnailSize,
imageThumbnailResizeType: imageThumbnailResizeType,
imageThumbnailCropType: imageThumbnailCropType,
attachmentActionsModalBuilder: attachmentActionsModalBuilder,
);
}
}
@@ -107,7 +107,7 @@ class StreamUrlAttachment extends StatelessWidget {
if (urlAttachment.title != null)
Text(
urlAttachment.title!.trim(),
maxLines: 1,
maxLines: messageTheme.urlAttachmentTitleMaxLine ?? 1,
overflow: TextOverflow.ellipsis,
style: chatThemeData.textTheme.body
.copyWith(fontWeight: FontWeight.w700),
@@ -17,6 +17,7 @@ class StreamVideoAttachment extends StreamAttachmentWidget {
this.onShowMessage,
this.onReplyMessage,
this.onAttachmentTap,
this.attachmentActionsModalBuilder,
});
/// The [StreamMessageThemeData] to use for the title
@@ -31,6 +32,9 @@ class StreamVideoAttachment extends StreamAttachmentWidget {
/// {@macro onAttachmentTap}
final OnAttachmentTap? onAttachmentTap;
/// {@macro attachmentActionsBuilder}
final AttachmentActionsBuilder? attachmentActionsModalBuilder;
@override
Widget build(BuildContext context) {
return source.when(
@@ -41,7 +45,7 @@ class StreamVideoAttachment extends StreamAttachmentWidget {
return _buildVideoAttachment(
context,
StreamVideoThumbnailImage(
video: attachment.file!.path!,
video: attachment.file!.path,
thumbUrl: attachment.thumbUrl,
constraints: constraints,
),
@@ -54,7 +58,7 @@ class StreamVideoAttachment extends StreamAttachmentWidget {
return _buildVideoAttachment(
context,
StreamVideoThumbnailImage(
video: attachment.assetUrl!,
video: attachment.assetUrl,
thumbUrl: attachment.thumbUrl,
constraints: constraints,
),
@@ -86,6 +90,8 @@ class StreamVideoAttachment extends StreamAttachmentWidget {
userName: message.user!.name,
onShowMessage: onShowMessage,
onReplyMessage: onReplyMessage,
attachmentActionsModalBuilder:
attachmentActionsModalBuilder,
),
),
),
@@ -162,8 +162,8 @@ class StreamChannelHeader extends StatelessWidget
showMessage: showConnectionStateTile && showStatus,
message: statusString,
child: AppBar(
toolbarTextStyle: theme.textTheme.bodyText2,
titleTextStyle: theme.textTheme.headline6,
toolbarTextStyle: theme.textTheme.bodyMedium,
titleTextStyle: theme.textTheme.titleLarge,
systemOverlayStyle: theme.brightness == Brightness.dark
? SystemUiOverlayStyle.light
: SystemUiOverlayStyle.dark,
@@ -131,8 +131,8 @@ class StreamChannelListHeader extends StatelessWidget
showMessage: showConnectionStateTile && showStatus,
message: statusString,
child: AppBar(
toolbarTextStyle: theme.textTheme.bodyText2,
titleTextStyle: theme.textTheme.headline6,
toolbarTextStyle: theme.textTheme.bodyMedium,
titleTextStyle: theme.textTheme.titleLarge,
systemOverlayStyle: theme.brightness == Brightness.dark
? SystemUiOverlayStyle.light
: SystemUiOverlayStyle.dark,
@@ -181,7 +181,10 @@ class StreamChannelListHeader extends StatelessWidget
package: 'stream_chat_flutter',
width: 24,
height: 24,
color: color,
colorFilter: ColorFilter.mode(
color,
BlendMode.srcIn,
),
);
},
),
@@ -1,7 +1,6 @@
import 'dart:io';
import 'package:cached_network_image/cached_network_image.dart';
import 'package:file_selector/file_selector.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:path_provider/path_provider.dart';
@@ -61,6 +60,7 @@ class StreamGalleryFooter extends StatefulWidget
class _StreamGalleryFooterState extends State<StreamGalleryFooter> {
final shareButtonKey = GlobalKey();
@override
Widget build(BuildContext context) {
const showShareButton = !kIsWeb;
@@ -108,12 +108,19 @@ class _StreamGalleryFooterState extends State<StreamGalleryFooter> {
await file.writeAsBytes(bytes);
final box =
shareButtonKey.currentContext?.findRenderObject();
final size = shareButtonKey.currentContext?.size;
final position =
(box! as RenderBox).localToGlobal(Offset.zero);
await Share.shareXFiles(
[XFile(filePath)],
sharePositionOrigin:
Rect.fromLTWH(position.dx, position.dy, 0, 0),
sharePositionOrigin: Rect.fromLTWH(
position.dx,
position.dy,
size?.width ?? 50,
(size?.height ?? 2) / 2,
),
);
},
),
@@ -219,8 +226,8 @@ class _StreamGalleryFooterState extends State<StreamGalleryFooter> {
child: AspectRatio(
aspectRatio: 1,
child: StreamVideoThumbnailImage(
video: (attachment.file?.path ??
attachment.assetUrl)!,
video:
attachment.file?.path ?? attachment.assetUrl,
),
),
),
@@ -80,8 +80,8 @@ class StreamGalleryHeader extends StatelessWidget
final galleryHeaderThemeData = StreamGalleryHeaderTheme.of(context);
final theme = Theme.of(context);
return AppBar(
toolbarTextStyle: theme.textTheme.bodyText2,
titleTextStyle: theme.textTheme.headline6,
toolbarTextStyle: theme.textTheme.bodyMedium,
titleTextStyle: theme.textTheme.titleLarge,
systemOverlayStyle: theme.brightness == Brightness.dark
? SystemUiOverlayStyle.light
: SystemUiOverlayStyle.dark,
@@ -18,6 +18,9 @@ abstract class Translations {
/// The label for showing no users
String get noUsersLabel;
/// The label for showing no photo or video
String get noPhotoOrVideoLabel;
/// The text for showing user is online
String get userOnlineText;
@@ -78,7 +81,10 @@ abstract class Translations {
/// The text for showing the unread messages count
/// in the [StreamMessageListView]
String unreadMessagesSeparatorText(int unreadCount);
String unreadMessagesSeparatorText(
@Deprecated('unreadCount is not used anymore and will be removed ')
int unreadCount,
);
/// The label for "connected" in [StreamConnectionStatusBuilder]
String get connectedLabel;
@@ -373,6 +379,9 @@ class DefaultTranslations implements Translations {
@override
String get noUsersLabel => 'There are no users currently';
@override
String get noPhotoOrVideoLabel => 'There is no photo or video';
@override
String get retryLabel => 'Retry';
@@ -791,12 +800,7 @@ Attachment limit exceeded: it's not possible to add more than $limit attachments
String get linkDisabledError => 'Links are disabled';
@override
String unreadMessagesSeparatorText(int unreadCount) {
if (unreadCount == 1) {
return '1 unread message';
}
return '$unreadCount unread messages';
}
String unreadMessagesSeparatorText(int unreadCount) => 'New messages';
@override
String get enableFileAccessMessage => 'Please enable access to files'
@@ -1,7 +1,7 @@
import 'dart:async';
import 'package:flutter/foundation.dart' show kIsWeb, defaultTargetPlatform;
import 'package:flutter/material.dart';
import 'package:stream_chat_flutter/platform_widget_builder/src/platform_widget.dart';
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
/// Shows a modal material design bottom sheet.
@@ -107,44 +107,34 @@ Future<T?> showStreamAttachmentPickerModalBottomSheet<T>({
controller: controller,
initialAttachments: initialAttachments,
builder: (context, controller, child) {
return PlatformWidget(
web: (context) {
return webOrDesktopAttachmentPickerBuilder.call(
context: context,
controller: controller,
customOptions: customOptions?.map(
WebOrDesktopAttachmentPickerOption.fromAttachmentPickerOption,
),
attachmentThumbnailSize: attachmentThumbnailSize,
attachmentThumbnailFormat: attachmentThumbnailFormat,
attachmentThumbnailQuality: attachmentThumbnailQuality,
attachmentThumbnailScale: attachmentThumbnailScale,
);
},
mobile: (context) {
return mobileAttachmentPickerBuilder.call(
context: context,
controller: controller,
customOptions: customOptions,
attachmentThumbnailSize: attachmentThumbnailSize,
attachmentThumbnailFormat: attachmentThumbnailFormat,
attachmentThumbnailQuality: attachmentThumbnailQuality,
attachmentThumbnailScale: attachmentThumbnailScale,
);
},
desktop: (context) {
return webOrDesktopAttachmentPickerBuilder.call(
context: context,
controller: controller,
customOptions: customOptions?.map(
WebOrDesktopAttachmentPickerOption.fromAttachmentPickerOption,
),
attachmentThumbnailSize: attachmentThumbnailSize,
attachmentThumbnailFormat: attachmentThumbnailFormat,
attachmentThumbnailQuality: attachmentThumbnailQuality,
attachmentThumbnailScale: attachmentThumbnailScale,
);
},
final currentPlatform = defaultTargetPlatform;
final isWebOrDesktop = kIsWeb ||
currentPlatform == TargetPlatform.macOS ||
currentPlatform == TargetPlatform.linux ||
currentPlatform == TargetPlatform.windows;
if (isWebOrDesktop) {
return webOrDesktopAttachmentPickerBuilder.call(
context: context,
controller: controller,
customOptions: customOptions?.map(
WebOrDesktopAttachmentPickerOption.fromAttachmentPickerOption,
),
attachmentThumbnailSize: attachmentThumbnailSize,
attachmentThumbnailFormat: attachmentThumbnailFormat,
attachmentThumbnailQuality: attachmentThumbnailQuality,
attachmentThumbnailScale: attachmentThumbnailScale,
);
}
return mobileAttachmentPickerBuilder.call(
context: context,
controller: controller,
customOptions: customOptions,
attachmentThumbnailSize: attachmentThumbnailSize,
attachmentThumbnailFormat: attachmentThumbnailFormat,
attachmentThumbnailQuality: attachmentThumbnailQuality,
attachmentThumbnailScale: attachmentThumbnailScale,
);
},
);
@@ -276,7 +276,7 @@ class _ParseAttachments extends StatelessWidget {
'video': (_, attachment) {
return StreamVideoThumbnailImage(
key: ValueKey(attachment.assetUrl),
video: attachment.file?.path ?? attachment.assetUrl!,
video: attachment.file?.path ?? attachment.assetUrl,
constraints: BoxConstraints.loose(const Size(32, 32)),
errorBuilder: (_, __) => AttachmentError(
constraints: BoxConstraints.loose(const Size(32, 32)),
@@ -1194,7 +1194,7 @@ class StreamMessageInputState extends State<StreamMessageInput>
104,
),
),
video: (attachment.file?.path ?? attachment.assetUrl)!,
video: attachment.file?.path ?? attachment.assetUrl,
),
Positioned(
left: 8,
@@ -80,7 +80,6 @@ class StreamMessageTextField extends StatefulWidget {
this.textAlignVertical,
this.textDirection,
this.readOnly = false,
ToolbarOptions? toolbarOptions,
this.showCursor,
this.autofocus = false,
this.obscuringCharacter = '•',
@@ -155,31 +154,7 @@ class StreamMessageTextField extends StatefulWidget {
keyboardType = keyboardType ??
(maxLines == 1 ? TextInputType.text : TextInputType.multiline),
enableInteractiveSelection =
enableInteractiveSelection ?? (!readOnly || !obscureText),
toolbarOptions = toolbarOptions ??
(obscureText
? (readOnly
// No point in even offering "Select All" in a read-only obscured
// field.
? const ToolbarOptions()
// Writable, but obscured.
: const ToolbarOptions(
selectAll: true,
paste: true,
))
: (readOnly
// Read-only, not obscured.
? const ToolbarOptions(
selectAll: true,
copy: true,
)
// Writable, not obscured.
: const ToolbarOptions(
copy: true,
cut: true,
selectAll: true,
paste: true,
)));
enableInteractiveSelection ?? (!readOnly || !obscureText);
/// Controls the message being edited.
///
@@ -304,13 +279,6 @@ class StreamMessageTextField extends StatefulWidget {
/// {@macro flutter.widgets.editableText.readOnly}
final bool readOnly;
/// Configuration of toolbar options.
///
/// If not set, select all and paste will default to be enabled. Copy and cut
/// will be disabled if [obscureText] is true. If [readOnly] is true,
/// paste and cut will be disabled regardless.
final ToolbarOptions toolbarOptions;
/// {@macro flutter.widgets.editableText.showCursor}
final bool? showCursor;
@@ -720,7 +688,6 @@ class _StreamMessageTextFieldState extends State<StreamMessageTextField>
textAlignVertical: widget.textAlignVertical,
textDirection: widget.textDirection,
readOnly: widget.readOnly,
toolbarOptions: widget.toolbarOptions,
showCursor: widget.showCursor,
autofocus: widget.autofocus,
obscuringCharacter: widget.obscuringCharacter,
@@ -24,9 +24,7 @@ class UnreadMessagesSeparator extends StatelessWidget {
child: Padding(
padding: const EdgeInsets.all(8),
child: Text(
context.translations.unreadMessagesSeparatorText(
unreadCount,
),
context.translations.unreadMessagesSeparatorText(unreadCount),
textAlign: TextAlign.center,
style: StreamChannelHeaderTheme.of(context).subtitleStyle,
),
@@ -32,6 +32,7 @@ class BottomRow extends StatelessWidget {
this.deletedBottomRowBuilder,
this.onThreadTap,
this.usernameBuilder,
this.sendingIndicatorBuilder,
});
/// {@macro messageIsDeleted}
@@ -88,6 +89,61 @@ class BottomRow extends StatelessWidget {
/// {@macro usernameBuilder}
final Widget Function(BuildContext, Message)? usernameBuilder;
/// {@macro sendingIndicatorBuilder}
final Widget Function(BuildContext, Message)? sendingIndicatorBuilder;
/// {@template copyWith}
/// Creates a copy of [BottomRow] with specified attributes
/// overridden.
/// {@endtemplate}
BottomRow copyWith({
Key? key,
bool? isDeleted,
Message? message,
bool? showThreadReplyIndicator,
bool? showInChannel,
bool? showTimeStamp,
bool? showUsername,
bool? reverse,
bool? showSendingIndicator,
bool? hasUrlAttachments,
bool? isGiphy,
bool? isOnlyEmoji,
StreamMessageThemeData? messageTheme,
StreamChatThemeData? streamChatTheme,
bool? hasNonUrlAttachments,
StreamChatState? streamChat,
Widget Function(BuildContext, Message)? deletedBottomRowBuilder,
void Function(Message)? onThreadTap,
Widget Function(BuildContext, Message)? usernameBuilder,
Widget Function(BuildContext, Message)? sendingIndicatorBuilder,
}) =>
BottomRow(
key: key ?? this.key,
isDeleted: isDeleted ?? this.isDeleted,
message: message ?? this.message,
showThreadReplyIndicator:
showThreadReplyIndicator ?? this.showThreadReplyIndicator,
showInChannel: showInChannel ?? this.showInChannel,
showTimeStamp: showTimeStamp ?? this.showTimeStamp,
showUsername: showUsername ?? this.showUsername,
reverse: reverse ?? this.reverse,
showSendingIndicator: showSendingIndicator ?? this.showSendingIndicator,
hasUrlAttachments: hasUrlAttachments ?? this.hasUrlAttachments,
isGiphy: isGiphy ?? this.isGiphy,
isOnlyEmoji: isOnlyEmoji ?? this.isOnlyEmoji,
messageTheme: messageTheme ?? this.messageTheme,
streamChatTheme: streamChatTheme ?? this.streamChatTheme,
hasNonUrlAttachments: hasNonUrlAttachments ?? this.hasNonUrlAttachments,
streamChat: streamChat ?? this.streamChat,
deletedBottomRowBuilder:
deletedBottomRowBuilder ?? this.deletedBottomRowBuilder,
onThreadTap: onThreadTap ?? this.onThreadTap,
usernameBuilder: usernameBuilder ?? this.usernameBuilder,
sendingIndicatorBuilder:
sendingIndicatorBuilder ?? this.sendingIndicatorBuilder,
);
@override
Widget build(BuildContext context) {
if (isDeleted) {
@@ -147,13 +203,14 @@ class BottomRow extends StatelessWidget {
),
if (showSendingIndicator)
WidgetSpan(
child: SendingIndicatorWrapper(
messageTheme: messageTheme,
message: message,
hasNonUrlAttachments: hasNonUrlAttachments,
streamChat: streamChat,
streamChatTheme: streamChatTheme,
),
child: sendingIndicatorBuilder?.call(context, message) ??
SendingIndicatorWrapper(
messageTheme: messageTheme,
message: message,
hasNonUrlAttachments: hasNonUrlAttachments,
streamChat: streamChat,
streamChatTheme: streamChatTheme,
),
),
]);
@@ -40,7 +40,8 @@ class StreamMessageText extends StatelessWidget {
.translate(language)
.replaceMentions()
.text
?.replaceAll('\n', '\n\n');
?.replaceAll('\n', '\n\n')
.trim();
final themeData = Theme.of(context);
return MarkdownBody(
data: messageText ?? '',
@@ -8,6 +8,7 @@ import 'package:stream_chat_flutter/src/context_menu_items/context_menu_reaction
import 'package:stream_chat_flutter/src/context_menu_items/stream_chat_context_menu_item.dart';
import 'package:stream_chat_flutter/src/dialogs/dialogs.dart';
import 'package:stream_chat_flutter/src/message_actions_modal/message_actions_modal.dart';
import 'package:stream_chat_flutter/src/message_widget/bottom_row.dart';
import 'package:stream_chat_flutter/src/message_widget/message_widget_content.dart';
import 'package:stream_chat_flutter/src/message_widget/reactions/message_reactions_modal.dart';
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
@@ -79,8 +80,15 @@ class StreamMessageWidget extends StatefulWidget {
this.userAvatarBuilder,
this.editMessageInputBuilder,
this.textBuilder,
this.bottomRowBuilder,
this.deletedBottomRowBuilder,
@Deprecated('''
Use [bottomRowBuilderWithDefaultWidget] instead.
Will be removed in the next major version.
''') this.bottomRowBuilder,
this.bottomRowBuilderWithDefaultWidget,
@Deprecated('''
Use [bottomRowBuilderWithDefaultWidget] instead.
Will be removed in the next major version.
''') this.deletedBottomRowBuilder,
this.customAttachmentBuilders,
this.padding,
this.textPadding = const EdgeInsets.symmetric(
@@ -92,11 +100,19 @@ class StreamMessageWidget extends StatefulWidget {
this.onQuotedMessageTap,
this.customActions = const [],
this.onAttachmentTap,
this.usernameBuilder,
@Deprecated('''
Use [bottomRowBuilderWithDefaultWidget] instead.
Will be removed in the next major version.
''') this.usernameBuilder,
this.imageAttachmentThumbnailSize = const Size(400, 400),
this.imageAttachmentThumbnailResizeType = 'clip',
this.imageAttachmentThumbnailCropType = 'center',
}) : attachmentBuilders = {
this.attachmentActionsModalBuilder,
}) : assert(
bottomRowBuilder == null || bottomRowBuilderWithDefaultWidget == null,
'You can only use one of the two bottom row builders',
),
attachmentBuilders = {
'image': (context, message, attachments) {
final border = RoundedRectangleBorder(
side: attachmentBorderSide ??
@@ -129,6 +145,8 @@ class StreamMessageWidget extends StatefulWidget {
imageThumbnailResizeType:
imageAttachmentThumbnailResizeType,
imageThumbnailCropType: imageAttachmentThumbnailCropType,
attachmentActionsModalBuilder:
attachmentActionsModalBuilder,
),
),
attachmentShape: border,
@@ -156,6 +174,7 @@ class StreamMessageWidget extends StatefulWidget {
imageThumbnailSize: imageAttachmentThumbnailSize,
imageThumbnailResizeType: imageAttachmentThumbnailResizeType,
imageThumbnailCropType: imageAttachmentThumbnailCropType,
attachmentActionsModalBuilder: attachmentActionsModalBuilder,
),
attachmentShape: border,
);
@@ -189,6 +208,8 @@ class StreamMessageWidget extends StatefulWidget {
onAttachmentTap(message, attachment);
}
: null,
attachmentActionsModalBuilder:
attachmentActionsModalBuilder,
);
}).toList(),
),
@@ -306,7 +327,13 @@ class StreamMessageWidget extends StatefulWidget {
/// {@template bottomRowBuilder}
/// Widget builder for building a bottom row below the message
/// {@endtemplate}
final Widget Function(BuildContext, Message)? bottomRowBuilder;
final BottomRowBuilder? bottomRowBuilder;
/// {@template bottomRowBuilderWithDefaultWidget}
/// Widget builder for building a bottom row below the message.
/// Also contains the default bottom row widget.
/// {@endtemplate}
final BottomRowBuilderWithDefaultWidget? bottomRowBuilderWithDefaultWidget;
/// {@template deletedBottomRowBuilder}
/// Widget builder for building a bottom row below a deleted message
@@ -512,6 +539,9 @@ class StreamMessageWidget extends StatefulWidget {
/// {@macro onMessageWidgetAttachmentTap}
final OnMessageWidgetAttachmentTap? onAttachmentTap;
/// {@macro attachmentActionsBuilder}
final AttachmentActionsBuilder? attachmentActionsModalBuilder;
/// Size of the image attachment thumbnail.
final Size imageAttachmentThumbnailSize;
@@ -537,9 +567,19 @@ class StreamMessageWidget extends StatefulWidget {
void Function(Message)? onReplyTap,
Widget Function(BuildContext, Message)? editMessageInputBuilder,
Widget Function(BuildContext, Message)? textBuilder,
Widget Function(BuildContext, Message)? usernameBuilder,
Widget Function(BuildContext, Message)? bottomRowBuilder,
Widget Function(BuildContext, Message)? deletedBottomRowBuilder,
@Deprecated('''
Use [bottomRowBuilderWithDefaultWidget] instead.
Will be removed in the next major version.
''') Widget Function(BuildContext, Message)? usernameBuilder,
@Deprecated('''
Use [bottomRowBuilderWithDefaultWidget] instead.
Will be removed in the next major version.
''') BottomRowBuilder? bottomRowBuilder,
BottomRowBuilderWithDefaultWidget? bottomRowBuilderWithDefaultWidget,
@Deprecated('''
Use [bottomRowBuilderWithDefaultWidget] instead.
Will be removed in the next major version.
''') Widget Function(BuildContext, Message)? deletedBottomRowBuilder,
void Function(BuildContext, Message)? onMessageActions,
Message? message,
StreamMessageThemeData? messageTheme,
@@ -586,7 +626,31 @@ class StreamMessageWidget extends StatefulWidget {
Size? imageAttachmentThumbnailSize,
String? imageAttachmentThumbnailResizeType,
String? imageAttachmentThumbnailCropType,
AttachmentActionsBuilder? attachmentActionsModalBuilder,
}) {
assert(
bottomRowBuilder == null || bottomRowBuilderWithDefaultWidget == null,
'You can only use one of the two bottom row builders',
);
var _bottomRowBuilderWithDefaultWidget =
bottomRowBuilderWithDefaultWidget ??
this.bottomRowBuilderWithDefaultWidget;
_bottomRowBuilderWithDefaultWidget ??= (context, message, defaultWidget) {
final _bottomRowBuilder = bottomRowBuilder ?? this.bottomRowBuilder;
if (_bottomRowBuilder != null) {
return _bottomRowBuilder(context, message);
}
return defaultWidget.copyWith(
onThreadTap: onThreadTap ?? this.onThreadTap,
usernameBuilder: usernameBuilder ?? this.usernameBuilder,
deletedBottomRowBuilder:
deletedBottomRowBuilder ?? this.deletedBottomRowBuilder,
);
};
return StreamMessageWidget(
key: key ?? this.key,
onMentionTap: onMentionTap ?? this.onMentionTap,
@@ -595,10 +659,7 @@ class StreamMessageWidget extends StatefulWidget {
editMessageInputBuilder:
editMessageInputBuilder ?? this.editMessageInputBuilder,
textBuilder: textBuilder ?? this.textBuilder,
usernameBuilder: usernameBuilder ?? this.usernameBuilder,
bottomRowBuilder: bottomRowBuilder ?? this.bottomRowBuilder,
deletedBottomRowBuilder:
deletedBottomRowBuilder ?? this.deletedBottomRowBuilder,
bottomRowBuilderWithDefaultWidget: _bottomRowBuilderWithDefaultWidget,
onMessageActions: onMessageActions ?? this.onMessageActions,
message: message ?? this.message,
messageTheme: messageTheme ?? this.messageTheme,
@@ -652,6 +713,8 @@ class StreamMessageWidget extends StatefulWidget {
this.imageAttachmentThumbnailResizeType,
imageAttachmentThumbnailCropType: imageAttachmentThumbnailCropType ??
this.imageAttachmentThumbnailCropType,
attachmentActionsModalBuilder:
attachmentActionsModalBuilder ?? this.attachmentActionsModalBuilder,
);
}
@@ -838,52 +901,69 @@ class _StreamMessageWidgetState extends State<StreamMessageWidget>
? Alignment.centerRight
: Alignment.centerLeft,
widthFactor: widget.widthFactor,
child: MessageWidgetContent(
streamChatTheme: _streamChatTheme,
showUsername: showUsername,
showTimeStamp: showTimeStamp,
showThreadReplyIndicator: showThreadReplyIndicator,
showSendingIndicator: showSendingIndicator,
showInChannel: showInChannel,
isGiphy: isGiphy,
isOnlyEmoji: isOnlyEmoji,
hasUrlAttachments: hasUrlAttachments,
messageTheme: widget.messageTheme,
reverse: widget.reverse,
message: widget.message,
hasNonUrlAttachments: hasNonUrlAttachments,
shouldShowReactions: shouldShowReactions,
hasQuotedMessage: hasQuotedMessage,
textPadding: widget.textPadding,
attachmentBuilders: widget.attachmentBuilders,
attachmentPadding: widget.attachmentPadding,
avatarWidth: avatarWidth,
bottomRowPadding: bottomRowPadding,
isFailedState: isFailedState,
isPinned: isPinned,
messageWidget: widget,
showBottomRow: showBottomRow,
showPinHighlight: widget.showPinHighlight,
showReactionPickerIndicator:
widget.showReactionPickerIndicator,
showReactions: showReactions,
showUserAvatar: widget.showUserAvatar,
streamChat: _streamChat,
translateUserAvatar: widget.translateUserAvatar,
deletedBottomRowBuilder: widget.deletedBottomRowBuilder,
onThreadTap: widget.onThreadTap,
shape: widget.shape,
borderSide: widget.borderSide,
borderRadiusGeometry: widget.borderRadiusGeometry,
textBuilder: widget.textBuilder,
onLinkTap: widget.onLinkTap,
onMentionTap: widget.onMentionTap,
onQuotedMessageTap: widget.onQuotedMessageTap,
bottomRowBuilder: widget.bottomRowBuilder,
onUserAvatarTap: widget.onUserAvatarTap,
userAvatarBuilder: widget.userAvatarBuilder,
usernameBuilder: widget.usernameBuilder,
),
child: Builder(builder: (context) {
var _bottomRowBuilderWithDefaultWidget =
widget.bottomRowBuilderWithDefaultWidget;
_bottomRowBuilderWithDefaultWidget ??=
(context, message, defaultWidget) {
final _bottomRowBuilder = widget.bottomRowBuilder;
if (_bottomRowBuilder != null) {
return _bottomRowBuilder(context, message);
}
return defaultWidget.copyWith(
onThreadTap: widget.onThreadTap,
usernameBuilder: widget.usernameBuilder,
deletedBottomRowBuilder: widget.deletedBottomRowBuilder,
);
};
return MessageWidgetContent(
streamChatTheme: _streamChatTheme,
showUsername: showUsername,
showTimeStamp: showTimeStamp,
showThreadReplyIndicator: showThreadReplyIndicator,
showSendingIndicator: showSendingIndicator,
showInChannel: showInChannel,
isGiphy: isGiphy,
isOnlyEmoji: isOnlyEmoji,
hasUrlAttachments: hasUrlAttachments,
messageTheme: widget.messageTheme,
reverse: widget.reverse,
message: widget.message,
hasNonUrlAttachments: hasNonUrlAttachments,
shouldShowReactions: shouldShowReactions,
hasQuotedMessage: hasQuotedMessage,
textPadding: widget.textPadding,
attachmentBuilders: widget.attachmentBuilders,
attachmentPadding: widget.attachmentPadding,
avatarWidth: avatarWidth,
bottomRowPadding: bottomRowPadding,
isFailedState: isFailedState,
isPinned: isPinned,
messageWidget: widget,
showBottomRow: showBottomRow,
showPinHighlight: widget.showPinHighlight,
showReactionPickerIndicator:
widget.showReactionPickerIndicator,
showReactions: showReactions,
showUserAvatar: widget.showUserAvatar,
streamChat: _streamChat,
translateUserAvatar: widget.translateUserAvatar,
shape: widget.shape,
borderSide: widget.borderSide,
borderRadiusGeometry: widget.borderRadiusGeometry,
textBuilder: widget.textBuilder,
onLinkTap: widget.onLinkTap,
onMentionTap: widget.onMentionTap,
onQuotedMessageTap: widget.onQuotedMessageTap,
bottomRowBuilderWithDefaultWidget:
_bottomRowBuilderWithDefaultWidget,
onUserAvatarTap: widget.onUserAvatarTap,
userAvatarBuilder: widget.userAvatarBuilder,
);
}),
),
),
),
@@ -4,6 +4,18 @@ import 'package:stream_chat_flutter/src/message_widget/message_widget_content_co
import 'package:stream_chat_flutter/src/message_widget/reactions/desktop_reactions_builder.dart';
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
/// Signature for the builder function that will be called when the message
/// bottom row is built. Includes the [Message].
typedef BottomRowBuilder = Widget Function(BuildContext, Message);
/// Signature for the builder function that will be called when the message
/// bottom row is built. Includes the [Message] and the default [BottomRow].
typedef BottomRowBuilderWithDefaultWidget = Widget Function(
BuildContext,
Message,
BottomRow,
);
/// {@template messageWidgetContent}
/// The main content of a [StreamMessageWidget].
///
@@ -51,12 +63,28 @@ class MessageWidgetContent extends StatelessWidget {
this.onMentionTap,
this.onLinkTap,
this.textBuilder,
this.bottomRowBuilder,
this.onThreadTap,
this.deletedBottomRowBuilder,
@Deprecated('''
Use [bottomRowBuilderWithDefaultWidget] instead.
Will be removed in the next major version.
''') this.bottomRowBuilder,
this.bottomRowBuilderWithDefaultWidget,
@Deprecated('''
Use [bottomRowBuilderWithDefaultWidget] instead.
Will be removed in the next major version.
''') this.onThreadTap,
@Deprecated('''
Use [bottomRowBuilderWithDefaultWidget] instead.
Will be removed in the next major version.
''') this.deletedBottomRowBuilder,
this.userAvatarBuilder,
this.usernameBuilder,
});
@Deprecated('''
Use [bottomRowBuilderWithDefaultWidget] instead.
Will be removed in the next major version.
''') this.usernameBuilder,
}) : assert(
bottomRowBuilder == null || bottomRowBuilderWithDefaultWidget == null,
'You can only use one of the two bottom row builders',
);
/// {@macro reverse}
final bool reverse;
@@ -152,7 +180,10 @@ class MessageWidgetContent extends StatelessWidget {
final double bottomRowPadding;
/// {@macro bottomRowBuilder}
final Widget Function(BuildContext, Message)? bottomRowBuilder;
final BottomRowBuilder? bottomRowBuilder;
/// {@macro bottomRowBuilderWithDefaultWidget}
final BottomRowBuilderWithDefaultWidget? bottomRowBuilderWithDefaultWidget;
/// {@macro showInChannelIndicator}
final bool showInChannel;
@@ -207,30 +238,7 @@ class MessageWidgetContent extends StatelessWidget {
right: reverse ? bottomRowPadding : 0,
bottom: isPinned && showPinHighlight ? 6.0 : 0.0,
),
child: bottomRowBuilder?.call(
context,
message,
) ??
BottomRow(
message: message,
reverse: reverse,
messageTheme: messageTheme,
hasUrlAttachments: hasUrlAttachments,
isOnlyEmoji: isOnlyEmoji,
isDeleted: message.isDeleted,
isGiphy: isGiphy,
showInChannel: showInChannel,
showSendingIndicator: showSendingIndicator,
showThreadReplyIndicator: showThreadReplyIndicator,
showTimeStamp: showTimeStamp,
showUsername: showUsername,
streamChatTheme: streamChatTheme,
onThreadTap: onThreadTap,
deletedBottomRowBuilder: deletedBottomRowBuilder,
streamChat: streamChat,
hasNonUrlAttachments: hasNonUrlAttachments,
usernameBuilder: usernameBuilder,
),
child: _buildBottomRow(context),
),
Padding(
padding: EdgeInsets.only(
@@ -457,4 +465,39 @@ class MessageWidgetContent extends StatelessWidget {
),
);
}
Widget _buildBottomRow(BuildContext context) {
final defaultWidget = BottomRow(
message: message,
reverse: reverse,
messageTheme: messageTheme,
hasUrlAttachments: hasUrlAttachments,
isOnlyEmoji: isOnlyEmoji,
isDeleted: message.isDeleted,
isGiphy: isGiphy,
showInChannel: showInChannel,
showSendingIndicator: showSendingIndicator,
showThreadReplyIndicator: showThreadReplyIndicator,
showTimeStamp: showTimeStamp,
showUsername: showUsername,
streamChatTheme: streamChatTheme,
onThreadTap: onThreadTap,
deletedBottomRowBuilder: deletedBottomRowBuilder,
streamChat: streamChat,
hasNonUrlAttachments: hasNonUrlAttachments,
usernameBuilder: usernameBuilder,
);
if (bottomRowBuilder != null) {
return bottomRowBuilder!(context, message);
} else if (bottomRowBuilderWithDefaultWidget != null) {
return bottomRowBuilderWithDefaultWidget!(
context,
message,
defaultWidget,
);
}
return defaultWidget;
}
}
@@ -1088,7 +1088,12 @@ class StreamSvgIcon extends StatelessWidget {
key: key,
width: width,
height: height,
color: color,
colorFilter: color != null
? ColorFilter.mode(
color!,
BlendMode.srcIn,
)
: null,
);
}
}
@@ -149,8 +149,8 @@ class StreamThreadHeader extends StatelessWidget
final theme = Theme.of(context);
return AppBar(
automaticallyImplyLeading: false,
toolbarTextStyle: theme.textTheme.bodyText2,
titleTextStyle: theme.textTheme.headline6,
toolbarTextStyle: theme.textTheme.bodyMedium,
titleTextStyle: theme.textTheme.titleLarge,
systemOverlayStyle: theme.brightness == Brightness.dark
? SystemUiOverlayStyle.light
: SystemUiOverlayStyle.dark,
@@ -409,7 +409,7 @@ class StreamChannelListErrorWidget extends StatelessWidget {
TextSpan(text: context.translations.loadingChannelsError),
],
),
style: Theme.of(context).textTheme.headline6,
style: Theme.of(context).textTheme.titleLarge,
),
TextButton(
onPressed: onPressed,
@@ -366,7 +366,7 @@ class StreamPhotoGallery extends StatelessWidget {
color: chatThemeData.colorTheme.disabled,
),
emptyTitle: Text(
context.translations.noUsersLabel,
context.translations.noPhotoOrVideoLabel,
style: chatThemeData.textTheme.headline,
),
),
@@ -377,7 +377,7 @@ class StreamPhotoGallery extends StatelessWidget {
return StreamScrollViewLoadMoreError.grid(
onTap: controller.retry,
error: Text(
context.translations.loadingUsersError,
context.translations.genericErrorText,
textAlign: TextAlign.center,
),
);
@@ -400,7 +400,7 @@ class StreamPhotoGallery extends StatelessWidget {
return errorBuilder?.call(context, error) ??
Center(
child: StreamScrollViewErrorWidget(
errorTitle: Text(context.translations.loadingUsersError),
errorTitle: Text(context.translations.genericErrorText),
onRetryPressed: controller.refresh,
),
);
@@ -21,6 +21,7 @@ class StreamMessageThemeData with Diagnosticable {
this.avatarTheme,
this.createdAtStyle,
this.linkBackgroundColor,
this.urlAttachmentTitleMaxLine,
});
/// Text style for message text
@@ -59,6 +60,9 @@ class StreamMessageThemeData with Diagnosticable {
/// Background color for messages with url attachments.
final Color? linkBackgroundColor;
/// Max number of lines in Url link title
final int? urlAttachmentTitleMaxLine;
/// Copy with a theme
StreamMessageThemeData copyWith({
TextStyle? messageTextStyle,
@@ -73,6 +77,7 @@ class StreamMessageThemeData with Diagnosticable {
Color? reactionsBorderColor,
Color? reactionsMaskColor,
Color? linkBackgroundColor,
int? urlAttachmentTitleMaxLine,
}) {
return StreamMessageThemeData(
messageTextStyle: messageTextStyle ?? this.messageTextStyle,
@@ -89,6 +94,8 @@ class StreamMessageThemeData with Diagnosticable {
reactionsBorderColor: reactionsBorderColor ?? this.reactionsBorderColor,
reactionsMaskColor: reactionsMaskColor ?? this.reactionsMaskColor,
linkBackgroundColor: linkBackgroundColor ?? this.linkBackgroundColor,
urlAttachmentTitleMaxLine:
urlAttachmentTitleMaxLine ?? this.urlAttachmentTitleMaxLine,
);
}
@@ -148,6 +155,7 @@ class StreamMessageThemeData with Diagnosticable {
reactionsBorderColor: other.reactionsBorderColor,
reactionsMaskColor: other.reactionsMaskColor,
linkBackgroundColor: other.linkBackgroundColor,
urlAttachmentTitleMaxLine: other.urlAttachmentTitleMaxLine,
);
}
@@ -167,7 +175,8 @@ class StreamMessageThemeData with Diagnosticable {
reactionsBorderColor == other.reactionsBorderColor &&
reactionsMaskColor == other.reactionsMaskColor &&
avatarTheme == other.avatarTheme &&
linkBackgroundColor == other.linkBackgroundColor;
linkBackgroundColor == other.linkBackgroundColor &&
urlAttachmentTitleMaxLine == other.urlAttachmentTitleMaxLine;
@override
int get hashCode =>
@@ -182,7 +191,8 @@ class StreamMessageThemeData with Diagnosticable {
reactionsBorderColor.hashCode ^
reactionsMaskColor.hashCode ^
avatarTheme.hashCode ^
linkBackgroundColor.hashCode;
linkBackgroundColor.hashCode ^
urlAttachmentTitleMaxLine.hashCode;
@override
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
@@ -199,6 +209,10 @@ class StreamMessageThemeData with Diagnosticable {
..add(ColorProperty('reactionsBackgroundColor', reactionsBackgroundColor))
..add(ColorProperty('reactionsBorderColor', reactionsBorderColor))
..add(ColorProperty('reactionsMaskColor', reactionsMaskColor))
..add(ColorProperty('linkBackgroundColor', linkBackgroundColor));
..add(ColorProperty('linkBackgroundColor', linkBackgroundColor))
..add(DiagnosticsProperty(
'urlAttachmentTitleMaxLine',
urlAttachmentTitleMaxLine,
));
}
}
@@ -21,6 +21,9 @@ class _IVideoService {
///
/// Thumbnails are not supported on Web at this time.
///
/// If no [video] path is supplied, or if a thumbnail cannot be generated,
/// returns [generatePlaceholderThumbnail]. A stock placeholder image.
///
/// For desktop, you can specify the position of the video to generate
/// the thumbnail.
///
@@ -29,14 +32,14 @@ class _IVideoService {
/// creates lower quality of the thumbnail image, but it gets ignored for
/// PNG format.
Future<Uint8List?> generateVideoThumbnail({
required String video,
String? video,
ImageFormat imageFormat = ImageFormat.PNG,
int maxHeight = 0,
int maxWidth = 0,
int timeMs = 0,
int quality = 10,
}) async {
if (kIsWeb) {
if (kIsWeb || video == null) {
final placeholder = await generatePlaceholderThumbnail();
return placeholder;
}
@@ -8,13 +8,23 @@ import 'package:stream_chat_flutter/stream_chat_flutter.dart';
import 'package:video_thumbnail/video_thumbnail.dart';
/// {@template streamVideoThumbnailImage}
/// Displays a video thumbnail for video attachments in a message.
/// Displays a video thumbnail for video attachments.
///
/// [thumbUrl] is used if provided.
///
/// Else [video] (path to local or remote video) is used to generate
/// a thumbnail from the video asset.
///
/// WARNING! a local path does not work on web.
///
/// If both [thumbUrl] and [video] are null, or if a thumbnail can't be
/// generated, a stock default image will be used.
/// {@endtemplate}
class StreamVideoThumbnailImage extends StatefulWidget {
/// {@macro streamVideoThumbnailImage}
const StreamVideoThumbnailImage({
super.key,
required this.video,
this.video,
this.thumbUrl,
this.constraints,
this.fit = BoxFit.cover,
@@ -24,7 +34,7 @@ class StreamVideoThumbnailImage extends StatefulWidget {
});
/// Video path or url
final String video;
final String? video;
/// Video thumbnail url
final String? thumbUrl;
@@ -1,6 +1,6 @@
import 'package:stream_chat_flutter/src/video/vlc/vlc_stub.dart'
if (dart.library.io) 'vlc_manager_desktop.dart'
if (dar.library.html) 'vlc_manager_web.dart';
if (dart.library.html) 'vlc_manager_web.dart';
/// {@template vlcManager}
/// An abstract class for the purpose of ensuring Flutter applications that
@@ -83,8 +83,6 @@ export 'src/scroll_view/photo_gallery/stream_photo_gallery_tile.dart';
export 'src/scroll_view/stream_scroll_view_empty_widget.dart';
export 'src/scroll_view/stream_scroll_view_indexed_widget_builder.dart';
export 'src/scroll_view/user_scroll_view/stream_user_grid_tile.dart';
export 'src/scroll_view/user_scroll_view/stream_user_grid_tile.dart';
export 'src/scroll_view/user_scroll_view/stream_user_grid_view.dart';
export 'src/scroll_view/user_scroll_view/stream_user_grid_view.dart';
export 'src/scroll_view/user_scroll_view/stream_user_list_tile.dart';
export 'src/scroll_view/user_scroll_view/stream_user_list_view.dart';
+15 -17
View File
@@ -1,50 +1,48 @@
name: stream_chat_flutter
homepage: https://github.com/GetStream/stream-chat-flutter
description: Stream Chat official Flutter SDK. Build your own chat experience using Dart and Flutter.
version: 5.1.0
version: 5.3.0
repository: https://github.com/GetStream/stream-chat-flutter
issue_tracker: https://github.com/GetStream/stream-chat-flutter/issues
environment:
sdk: '>=2.17.0 <3.0.0'
sdk: ">=2.17.0 <3.0.0"
flutter: ">=1.17.0"
dependencies:
cached_network_image: ^3.0.0
chewie: ^1.3.4
collection: ^1.15.0
collection: ^1.17.0
contextmenu: ^3.0.0
dart_vlc: ^0.3.0
dart_vlc: ^0.4.0
desktop_drop: ^0.4.0
diacritic: ^0.1.3
dio: ^4.0.6
ezanimation: ^0.6.0
file_picker: ^4.1.3
file_picker: ^5.2.4
file_selector: ^0.9.0
flutter:
sdk: flutter
flutter_markdown: ^0.6.1
flutter_portal: ^1.0.0
flutter_svg: ^1.0.1
http: ^0.13.4
flutter_svg: ^2.0.4
http_parser: ^4.0.0
image_gallery_saver: ^1.7.0
image_gallery_saver: ^1.7.1
image_picker: ^0.8.2
jiffy: ^5.0.0
lottie: ^2.0.0
meta: ^1.3.0
meta: ^1.8.0
path_provider: ^2.0.9
photo_manager: ^2.0.1
photo_manager: ^2.5.2
photo_view: ^0.14.0
rxdart: ^0.27.0
share_plus: ^4.5.0
share_plus: ^6.3.0
shimmer: ^2.0.0
stream_chat_flutter_core: ^5.1.0
stream_chat_flutter_core: ^5.3.0
synchronized: ^3.0.0
thumblr: ^0.0.4
url_launcher: ^6.1.0
video_player: ^2.4.5
video_player_macos: ^1.0.6
video_player_macos: ^2.0.1
video_thumbnail: ^0.5.0
flutter:
@@ -56,9 +54,9 @@ flutter:
uses-material-design: true
dev_dependencies:
dart_code_metrics: ^4.4.0
dart_code_metrics: ^5.7.0
flutter_test:
sdk: flutter
golden_toolkit: ^0.13.0
golden_toolkit: ^0.15.0
mocktail: ^0.3.0
path: ^1.8.0
path: ^1.8.2
@@ -163,7 +163,7 @@ void main() {
);
testWidgets(
'tapping on reply should pop',
'tapping on reply should invoke callback',
(WidgetTester tester) async {
final client = MockClient();
final clientState = MockClientState();
@@ -174,7 +174,7 @@ void main() {
final themeData = ThemeData();
final streamTheme = StreamChatThemeData.fromTheme(themeData);
final mockObserver = MockNavigatorObserver();
final mockCallback = MockVoidCallback();
final attachment = Attachment(
type: 'image',
@@ -192,7 +192,6 @@ void main() {
await tester.pumpWidget(
MaterialApp(
theme: themeData,
navigatorObservers: [mockObserver],
home: StreamChat(
streamChatThemeData: streamTheme,
client: client,
@@ -200,13 +199,14 @@ void main() {
child: AttachmentActionsModal(
message: message,
attachment: attachment,
onReply: mockCallback,
),
),
),
),
);
await tester.tap(find.text('Reply'));
verify(() => mockObserver.didPop(any(), any()));
verify(mockCallback.call);
},
);
@@ -1,4 +1,12 @@
## Upcomming
## Upcoming
- Updated dependencies to resolvable versions.
## 5.3.0
- Updated `stream_chat` dependency to [`5.3.0`](https://pub.dev/packages/stream_chat/changelog).
## 5.2.0
🔄 Changed
@@ -25,7 +25,7 @@ apply plugin: 'com.android.application'
apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
android {
compileSdkVersion 29
compileSdkVersion 33
lintOptions {
disable 'InvalidPackage'
@@ -35,7 +35,7 @@ android {
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
applicationId "example.example"
minSdkVersion 16
targetSdkVersion 29
targetSdkVersion 33
versionCode flutterVersionCode.toInteger()
versionName flutterVersionName
}
@@ -7,14 +7,15 @@
FlutterApplication and put your custom class here. -->
<application
android:name="${applicationName}"
android:label="example"
android:icon="@mipmap/ic_launcher">
android:icon="@mipmap/ic_launcher"
android:label="example">
<activity
android:name=".MainActivity"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
android:exported="true"
android:hardwareAccelerated="true"
android:launchMode="singleTop"
android:theme="@style/LaunchTheme"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
android:hardwareAccelerated="true"
android:windowSoftInputMode="adjustResize">
<!-- Specifies an Android theme to apply to this Activity as soon as
the Android process has started. This theme is visible to the user
@@ -1,11 +1,13 @@
buildscript {
ext.kotlin_version = '1.7.21'
repositories {
google()
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:3.5.0'
classpath 'com.android.tools.build:gradle:7.3.1'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}
}
@@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-6.5-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-7.6-all.zip
@@ -19,6 +19,7 @@ version: 1.0.0+1
environment:
sdk: '>=2.17.0 <3.0.0'
flutter: ">=1.17.0"
dependencies:
# The following adds the Cupertino Icons font to your application.
@@ -27,6 +28,7 @@ dependencies:
flutter:
sdk: flutter
stream_chat_flutter_core: ^5.1.0
dev_dependencies:
flutter_test:
sdk: flutter
@@ -6,7 +6,7 @@
#include "generated_plugin_registrant.h"
#include <connectivity_plus_windows/connectivity_plus_windows_plugin.h>
#include <connectivity_plus/connectivity_plus_windows_plugin.h>
void RegisterPlugins(flutter::PluginRegistry* registry) {
ConnectivityPlusWindowsPluginRegisterWithRegistrar(
@@ -3,7 +3,7 @@
#
list(APPEND FLUTTER_PLUGIN_LIST
connectivity_plus_windows
connectivity_plus
)
list(APPEND FLUTTER_FFI_PLUGIN_LIST
@@ -83,7 +83,7 @@ abstract class PagedValueNotifier<Key, Value>
/// Paged value that can be used with [PagedValueNotifier].
@freezed
abstract class PagedValue<Key, Value> with _$PagedValue<Key, Value> {
class PagedValue<Key, Value> with _$PagedValue<Key, Value> {
/// Represents the success state of the [PagedValue]
// @Assert(
// 'nextPageKey != null',
@@ -1,7 +1,7 @@
// coverage:ignore-file
// GENERATED CODE - DO NOT MODIFY BY HAND
// ignore_for_file: type=lint
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
part of 'paged_value_notifier.dart';
@@ -27,11 +27,11 @@ mixin _$PagedValue<Key, Value> {
throw _privateConstructorUsedError;
@optionalTypeArgs
TResult? whenOrNull<TResult extends Object?>(
TResult Function(
TResult? Function(
List<Value> items, Key? nextPageKey, StreamChatError? error)?
$default, {
TResult Function()? loading,
TResult Function(StreamChatError error)? error,
TResult? Function()? loading,
TResult? Function(StreamChatError error)? error,
}) =>
throw _privateConstructorUsedError;
@optionalTypeArgs
@@ -53,9 +53,9 @@ mixin _$PagedValue<Key, Value> {
throw _privateConstructorUsedError;
@optionalTypeArgs
TResult? mapOrNull<TResult extends Object?>(
TResult Function(Success<Key, Value> value)? $default, {
TResult Function(Loading<Key, Value> value)? loading,
TResult Function(Error<Key, Value> value)? error,
TResult? Function(Success<Key, Value> value)? $default, {
TResult? Function(Loading<Key, Value> value)? loading,
TResult? Function(Error<Key, Value> value)? error,
}) =>
throw _privateConstructorUsedError;
@optionalTypeArgs
@@ -72,17 +72,19 @@ mixin _$PagedValue<Key, Value> {
abstract class $PagedValueCopyWith<Key, Value, $Res> {
factory $PagedValueCopyWith(PagedValue<Key, Value> value,
$Res Function(PagedValue<Key, Value>) then) =
_$PagedValueCopyWithImpl<Key, Value, $Res>;
_$PagedValueCopyWithImpl<Key, Value, $Res, PagedValue<Key, Value>>;
}
/// @nodoc
class _$PagedValueCopyWithImpl<Key, Value, $Res>
class _$PagedValueCopyWithImpl<Key, Value, $Res,
$Val extends PagedValue<Key, Value>>
implements $PagedValueCopyWith<Key, Value, $Res> {
_$PagedValueCopyWithImpl(this._value, this._then);
final PagedValue<Key, Value> _value;
// ignore: unused_field
final $Res Function(PagedValue<Key, Value>) _then;
final $Val _value;
// ignore: unused_field
final $Res Function($Val) _then;
}
/// @nodoc
@@ -90,36 +92,35 @@ abstract class _$$SuccessCopyWith<Key, Value, $Res> {
factory _$$SuccessCopyWith(_$Success<Key, Value> value,
$Res Function(_$Success<Key, Value>) then) =
__$$SuccessCopyWithImpl<Key, Value, $Res>;
@useResult
$Res call({List<Value> items, Key? nextPageKey, StreamChatError? error});
}
/// @nodoc
class __$$SuccessCopyWithImpl<Key, Value, $Res>
extends _$PagedValueCopyWithImpl<Key, Value, $Res>
extends _$PagedValueCopyWithImpl<Key, Value, $Res, _$Success<Key, Value>>
implements _$$SuccessCopyWith<Key, Value, $Res> {
__$$SuccessCopyWithImpl(
_$Success<Key, Value> _value, $Res Function(_$Success<Key, Value>) _then)
: super(_value, (v) => _then(v as _$Success<Key, Value>));
@override
_$Success<Key, Value> get _value => super._value as _$Success<Key, Value>;
: super(_value, _then);
@pragma('vm:prefer-inline')
@override
$Res call({
Object? items = freezed,
Object? items = null,
Object? nextPageKey = freezed,
Object? error = freezed,
}) {
return _then(_$Success<Key, Value>(
items: items == freezed
items: null == items
? _value._items
: items // ignore: cast_nullable_to_non_nullable
as List<Value>,
nextPageKey: nextPageKey == freezed
nextPageKey: freezed == nextPageKey
? _value.nextPageKey
: nextPageKey // ignore: cast_nullable_to_non_nullable
as Key?,
error: error == freezed
error: freezed == error
? _value.error
: error // ignore: cast_nullable_to_non_nullable
as StreamChatError?,
@@ -142,6 +143,7 @@ class _$Success<Key, Value> extends Success<Key, Value>
/// List with all items loaded so far.
@override
List<Value> get items {
if (_items is EqualUnmodifiableListView) return _items;
// ignore: implicit_dynamic_type
return EqualUnmodifiableListView(_items);
}
@@ -177,7 +179,7 @@ class _$Success<Key, Value> extends Success<Key, Value>
const DeepCollectionEquality().equals(other._items, _items) &&
const DeepCollectionEquality()
.equals(other.nextPageKey, nextPageKey) &&
const DeepCollectionEquality().equals(other.error, error));
(identical(other.error, error) || other.error == error));
}
@override
@@ -185,10 +187,11 @@ class _$Success<Key, Value> extends Success<Key, Value>
runtimeType,
const DeepCollectionEquality().hash(_items),
const DeepCollectionEquality().hash(nextPageKey),
const DeepCollectionEquality().hash(error));
error);
@JsonKey(ignore: true)
@override
@pragma('vm:prefer-inline')
_$$SuccessCopyWith<Key, Value, _$Success<Key, Value>> get copyWith =>
__$$SuccessCopyWithImpl<Key, Value, _$Success<Key, Value>>(
this, _$identity);
@@ -208,11 +211,11 @@ class _$Success<Key, Value> extends Success<Key, Value>
@override
@optionalTypeArgs
TResult? whenOrNull<TResult extends Object?>(
TResult Function(
TResult? Function(
List<Value> items, Key? nextPageKey, StreamChatError? error)?
$default, {
TResult Function()? loading,
TResult Function(StreamChatError error)? error,
TResult? Function()? loading,
TResult? Function(StreamChatError error)? error,
}) {
return $default?.call(items, nextPageKey, this.error);
}
@@ -246,9 +249,9 @@ class _$Success<Key, Value> extends Success<Key, Value>
@override
@optionalTypeArgs
TResult? mapOrNull<TResult extends Object?>(
TResult Function(Success<Key, Value> value)? $default, {
TResult Function(Loading<Key, Value> value)? loading,
TResult Function(Error<Key, Value> value)? error,
TResult? Function(Success<Key, Value> value)? $default, {
TResult? Function(Loading<Key, Value> value)? loading,
TResult? Function(Error<Key, Value> value)? error,
}) {
return $default?.call(this);
}
@@ -276,13 +279,13 @@ abstract class Success<Key, Value> extends PagedValue<Key, Value> {
const Success._() : super._();
/// List with all items loaded so far.
List<Value> get items => throw _privateConstructorUsedError;
List<Value> get items;
/// The key for the next page to be fetched.
Key? get nextPageKey => throw _privateConstructorUsedError;
Key? get nextPageKey;
/// The current error, if any.
StreamChatError? get error => throw _privateConstructorUsedError;
StreamChatError? get error;
@JsonKey(ignore: true)
_$$SuccessCopyWith<Key, Value, _$Success<Key, Value>> get copyWith =>
throw _privateConstructorUsedError;
@@ -297,14 +300,11 @@ abstract class _$$LoadingCopyWith<Key, Value, $Res> {
/// @nodoc
class __$$LoadingCopyWithImpl<Key, Value, $Res>
extends _$PagedValueCopyWithImpl<Key, Value, $Res>
extends _$PagedValueCopyWithImpl<Key, Value, $Res, _$Loading<Key, Value>>
implements _$$LoadingCopyWith<Key, Value, $Res> {
__$$LoadingCopyWithImpl(
_$Loading<Key, Value> _value, $Res Function(_$Loading<Key, Value>) _then)
: super(_value, (v) => _then(v as _$Loading<Key, Value>));
@override
_$Loading<Key, Value> get _value => super._value as _$Loading<Key, Value>;
: super(_value, _then);
}
/// @nodoc
@@ -349,11 +349,11 @@ class _$Loading<Key, Value> extends Loading<Key, Value>
@override
@optionalTypeArgs
TResult? whenOrNull<TResult extends Object?>(
TResult Function(
TResult? Function(
List<Value> items, Key? nextPageKey, StreamChatError? error)?
$default, {
TResult Function()? loading,
TResult Function(StreamChatError error)? error,
TResult? Function()? loading,
TResult? Function(StreamChatError error)? error,
}) {
return loading?.call();
}
@@ -387,9 +387,9 @@ class _$Loading<Key, Value> extends Loading<Key, Value>
@override
@optionalTypeArgs
TResult? mapOrNull<TResult extends Object?>(
TResult Function(Success<Key, Value> value)? $default, {
TResult Function(Loading<Key, Value> value)? loading,
TResult Function(Error<Key, Value> value)? error,
TResult? Function(Success<Key, Value> value)? $default, {
TResult? Function(Loading<Key, Value> value)? loading,
TResult? Function(Error<Key, Value> value)? error,
}) {
return loading?.call(this);
}
@@ -419,26 +419,25 @@ abstract class _$$ErrorCopyWith<Key, Value, $Res> {
factory _$$ErrorCopyWith(
_$Error<Key, Value> value, $Res Function(_$Error<Key, Value>) then) =
__$$ErrorCopyWithImpl<Key, Value, $Res>;
@useResult
$Res call({StreamChatError error});
}
/// @nodoc
class __$$ErrorCopyWithImpl<Key, Value, $Res>
extends _$PagedValueCopyWithImpl<Key, Value, $Res>
extends _$PagedValueCopyWithImpl<Key, Value, $Res, _$Error<Key, Value>>
implements _$$ErrorCopyWith<Key, Value, $Res> {
__$$ErrorCopyWithImpl(
_$Error<Key, Value> _value, $Res Function(_$Error<Key, Value>) _then)
: super(_value, (v) => _then(v as _$Error<Key, Value>));
@override
_$Error<Key, Value> get _value => super._value as _$Error<Key, Value>;
: super(_value, _then);
@pragma('vm:prefer-inline')
@override
$Res call({
Object? error = freezed,
Object? error = null,
}) {
return _then(_$Error<Key, Value>(
error == freezed
null == error
? _value.error
: error // ignore: cast_nullable_to_non_nullable
as StreamChatError,
@@ -473,15 +472,15 @@ class _$Error<Key, Value> extends Error<Key, Value>
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _$Error<Key, Value> &&
const DeepCollectionEquality().equals(other.error, error));
(identical(other.error, error) || other.error == error));
}
@override
int get hashCode =>
Object.hash(runtimeType, const DeepCollectionEquality().hash(error));
int get hashCode => Object.hash(runtimeType, error);
@JsonKey(ignore: true)
@override
@pragma('vm:prefer-inline')
_$$ErrorCopyWith<Key, Value, _$Error<Key, Value>> get copyWith =>
__$$ErrorCopyWithImpl<Key, Value, _$Error<Key, Value>>(this, _$identity);
@@ -500,11 +499,11 @@ class _$Error<Key, Value> extends Error<Key, Value>
@override
@optionalTypeArgs
TResult? whenOrNull<TResult extends Object?>(
TResult Function(
TResult? Function(
List<Value> items, Key? nextPageKey, StreamChatError? error)?
$default, {
TResult Function()? loading,
TResult Function(StreamChatError error)? error,
TResult? Function()? loading,
TResult? Function(StreamChatError error)? error,
}) {
return error?.call(this.error);
}
@@ -538,9 +537,9 @@ class _$Error<Key, Value> extends Error<Key, Value>
@override
@optionalTypeArgs
TResult? mapOrNull<TResult extends Object?>(
TResult Function(Success<Key, Value> value)? $default, {
TResult Function(Loading<Key, Value> value)? loading,
TResult Function(Error<Key, Value> value)? error,
TResult? Function(Success<Key, Value> value)? $default, {
TResult? Function(Loading<Key, Value> value)? loading,
TResult? Function(Error<Key, Value> value)? error,
}) {
return error?.call(this);
}
@@ -564,7 +563,7 @@ abstract class Error<Key, Value> extends PagedValue<Key, Value> {
const factory Error(final StreamChatError error) = _$Error<Key, Value>;
const Error._() : super._();
StreamChatError get error => throw _privateConstructorUsedError;
StreamChatError get error;
@JsonKey(ignore: true)
_$$ErrorCopyWith<Key, Value, _$Error<Key, Value>> get copyWith =>
throw _privateConstructorUsedError;
@@ -425,8 +425,8 @@ class StreamChannelState extends State<StreamChannel> {
var message = snapshot.error.toString();
if (snapshot.error is DioError) {
final dioError = snapshot.error as DioError?;
if (dioError?.type == DioErrorType.response) {
message = dioError!.message;
if (dioError?.type == DioErrorType.badResponse) {
message = dioError!.message ?? 'Bad response';
} else {
message = 'Check your connection and retry';
}
@@ -244,10 +244,11 @@ class StreamChannelListController extends PagedValueNotifier<int, Channel> {
_unsubscribeFromChannelListEvents();
}
_channelEventSubscription =
client.on().skip(1) // Skipping the last emitted event.
// We only need to handle the latest events.
.listen((event) {
_channelEventSubscription = client
.on()
.skip(1) // Skipping the last emitted event.
// We only need to handle the latest events.
.listen((event) {
// Only handle the event if the value is in success state.
if (value.isNotSuccess) return;
@@ -1,7 +1,7 @@
name: stream_chat_flutter_core
homepage: https://github.com/GetStream/stream-chat-flutter
description: Stream Chat official Flutter SDK Core. Build your own chat experience using Dart and Flutter.
version: 5.1.0
version: 5.3.0
repository: https://github.com/GetStream/stream-chat-flutter
issue_tracker: https://github.com/GetStream/stream-chat-flutter/issues
@@ -10,20 +10,21 @@ environment:
flutter: ">=1.17.0"
dependencies:
collection: ^1.15.0
collection: ^1.17.0
connectivity_plus: ^3.0.2
flutter:
sdk: flutter
freezed_annotation: ^2.0.3
meta: ^1.3.0
meta: ^1.8.0
rxdart: ^0.27.0
stream_chat: ^5.1.0
stream_chat: ^5.3.0
dev_dependencies:
build_runner: ^2.0.1
dart_code_metrics: ^4.4.0
build_runner: ^2.3.3
dart_code_metrics: ^5.7.0
fake_async: ^1.2.0
flutter_test:
sdk: flutter
freezed: ^2.0.3
freezed: ^2.3.2
mocktail: ^0.3.0
@@ -1,48 +0,0 @@
import 'package:flutter_test/flutter_test.dart';
import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart';
Matcher isSameChannelAs(Channel targetChannel) =>
_IsSameChannelAs(targetChannel: targetChannel);
class _IsSameChannelAs extends Matcher {
const _IsSameChannelAs({
required this.targetChannel,
});
final Channel targetChannel;
@override
bool matches(covariant Channel channel, Map matchState) =>
channel.cid == targetChannel.cid;
@override
Description describe(Description description) =>
description.add('is same channel as $targetChannel');
}
Matcher isSameChannelListAs(List<Channel> targetChannelList) =>
_IsSameChannelListAs(targetChannelList: targetChannelList);
class _IsSameChannelListAs extends Matcher {
const _IsSameChannelListAs({
required this.targetChannelList,
});
final List<Channel> targetChannelList;
@override
bool matches(covariant List<Channel> channelList, Map matchState) {
var matches = true;
for (var i = 0; i < channelList.length; i++) {
final channel = channelList[i];
final targetChannel = targetChannelList[i];
matches = isSameChannelAs(targetChannel).matches(channel, matchState);
if (!matches) break;
}
return matches;
}
@override
Description describe(Description description) =>
description.add('is same channelList as $targetChannelList');
}

Some files were not shown because too many files have changed in this diff Show More