Merge branch 'v4' of https://github.com/GetStream/stream-chat-flutter into live-event-improvements
This commit is contained in:
@@ -35,7 +35,7 @@ jobs:
|
||||
run: |
|
||||
flutter pub global activate melos ${{ env.melos_version }}
|
||||
- name: "Bootstrap Workspace"
|
||||
run: melos bootstrap
|
||||
run: melos bootstrap --verbose
|
||||
- name: "Dart Analyze"
|
||||
run: |
|
||||
melos run analyze
|
||||
|
||||
@@ -11,7 +11,8 @@ dependencies:
|
||||
cupertino_icons: ^1.0.0
|
||||
flutter:
|
||||
sdk: flutter
|
||||
stream_chat: ^2.2.1
|
||||
stream_chat:
|
||||
path: ../
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
|
||||
@@ -294,6 +294,18 @@ class Channel {
|
||||
return data;
|
||||
}
|
||||
|
||||
/// List of user permissions on this channel
|
||||
List<String> get ownCapabilities =>
|
||||
state?._channelState.channel?.ownCapabilities ?? [];
|
||||
|
||||
/// List of user permissions on this channel
|
||||
Stream<List<String>> get ownCapabilitiesStream {
|
||||
_checkInitialized();
|
||||
return state!.channelStateStream
|
||||
.map((cs) => cs.channel?.ownCapabilities ?? [])
|
||||
.distinct();
|
||||
}
|
||||
|
||||
/// Channel extra data as a stream.
|
||||
Stream<Map<String, Object?>> get extraDataStream {
|
||||
_checkInitialized();
|
||||
@@ -487,6 +499,7 @@ class Channel {
|
||||
Future<SendMessageResponse> sendMessage(
|
||||
Message message, {
|
||||
bool skipPush = false,
|
||||
bool skipEnrichUrl = false,
|
||||
}) async {
|
||||
_checkInitialized();
|
||||
// Cancelling previous completer in case it's called again in the process
|
||||
@@ -534,6 +547,7 @@ class Channel {
|
||||
id!,
|
||||
type,
|
||||
skipPush: skipPush,
|
||||
skipEnrichUrl: skipEnrichUrl,
|
||||
);
|
||||
state!.updateMessage(response.message);
|
||||
if (cooldown > 0) cooldownStartedAt = DateTime.now();
|
||||
@@ -550,7 +564,10 @@ class Channel {
|
||||
///
|
||||
/// Waits for a [_messageAttachmentsUploadCompleter] to complete
|
||||
/// before actually updating the message.
|
||||
Future<UpdateMessageResponse> updateMessage(Message message) async {
|
||||
Future<UpdateMessageResponse> updateMessage(
|
||||
Message message, {
|
||||
bool skipEnrichUrl = false,
|
||||
}) async {
|
||||
final originalMessage = message;
|
||||
|
||||
// Cancelling previous completer in case it's called again in the process
|
||||
@@ -588,7 +605,10 @@ class Channel {
|
||||
message = await attachmentsUploadCompleter.future;
|
||||
}
|
||||
|
||||
final response = await _client.updateMessage(message);
|
||||
final response = await _client.updateMessage(
|
||||
message,
|
||||
skipEnrichUrl: skipEnrichUrl,
|
||||
);
|
||||
|
||||
final m = response.message.copyWith(
|
||||
ownReactions: message.ownReactions,
|
||||
@@ -618,12 +638,14 @@ class Channel {
|
||||
Message message, {
|
||||
Map<String, Object?>? set,
|
||||
List<String>? unset,
|
||||
bool skipEnrichUrl = false,
|
||||
}) async {
|
||||
try {
|
||||
final response = await _client.partialUpdateMessage(
|
||||
message.id,
|
||||
set: set,
|
||||
unset: unset,
|
||||
skipEnrichUrl: skipEnrichUrl,
|
||||
);
|
||||
|
||||
final updatedMessage = response.message.copyWith(
|
||||
@@ -1598,7 +1620,9 @@ class ChannelClientState {
|
||||
_subscriptions.add(_channel.on(EventType.channelUpdated).listen((Event e) {
|
||||
final channel = e.channel!;
|
||||
updateChannelState(channelState.copyWith(
|
||||
channel: channel,
|
||||
channel: channel.copyWith(
|
||||
ownCapabilities: channelState.channel?.ownCapabilities,
|
||||
),
|
||||
members: channel.members,
|
||||
));
|
||||
}));
|
||||
@@ -1968,6 +1992,14 @@ class ChannelClientState {
|
||||
(watchers, users) => watchers!.map((e) => users[e.id] ?? e).toList(),
|
||||
);
|
||||
|
||||
/// Channel member for the current user.
|
||||
Member? get currentUserMember => members.firstWhereOrNull(
|
||||
(m) => m.user?.id == _channel.client.state.currentUser?.id,
|
||||
);
|
||||
|
||||
/// User role for the current user.
|
||||
String? get currentUserRole => currentUserMember?.role;
|
||||
|
||||
/// Channel read list.
|
||||
List<Read> get read => _channelState.read;
|
||||
|
||||
|
||||
@@ -1212,12 +1212,14 @@ class StreamChatClient {
|
||||
String channelId,
|
||||
String channelType, {
|
||||
bool skipPush = false,
|
||||
bool skipEnrichUrl = false,
|
||||
}) =>
|
||||
_chatApi.message.sendMessage(
|
||||
channelId,
|
||||
channelType,
|
||||
message,
|
||||
skipPush: skipPush,
|
||||
skipEnrichUrl: skipEnrichUrl,
|
||||
);
|
||||
|
||||
/// Lists all the message replies for the [parentId]
|
||||
@@ -1241,8 +1243,14 @@ class StreamChatClient {
|
||||
);
|
||||
|
||||
/// Update the given message
|
||||
Future<UpdateMessageResponse> updateMessage(Message message) =>
|
||||
_chatApi.message.updateMessage(message);
|
||||
Future<UpdateMessageResponse> updateMessage(
|
||||
Message message, {
|
||||
bool skipEnrichUrl = false,
|
||||
}) =>
|
||||
_chatApi.message.updateMessage(
|
||||
message,
|
||||
skipEnrichUrl: skipEnrichUrl,
|
||||
);
|
||||
|
||||
/// Partially update the given [messageId]
|
||||
/// Use [set] to define values to be set
|
||||
@@ -1251,11 +1259,13 @@ class StreamChatClient {
|
||||
String messageId, {
|
||||
Map<String, Object?>? set,
|
||||
List<String>? unset,
|
||||
bool skipEnrichUrl = false,
|
||||
}) =>
|
||||
_chatApi.message.partialUpdateMessage(
|
||||
messageId,
|
||||
set: set,
|
||||
unset: unset,
|
||||
skipEnrichUrl: skipEnrichUrl,
|
||||
);
|
||||
|
||||
/// Deletes the given message
|
||||
|
||||
@@ -5,9 +5,9 @@ import 'package:rxdart/rxdart.dart';
|
||||
import 'package:stream_chat/src/client/retry_policy.dart';
|
||||
import 'package:stream_chat/stream_chat.dart';
|
||||
|
||||
/// The retry queue associated to a channel
|
||||
/// The retry queue associated to a channel.
|
||||
class RetryQueue {
|
||||
/// Instantiate a new RetryQueue object
|
||||
/// Instantiate a new RetryQueue object.
|
||||
RetryQueue({
|
||||
required this.channel,
|
||||
this.logger,
|
||||
@@ -17,13 +17,13 @@ class RetryQueue {
|
||||
_listenFailedEvents();
|
||||
}
|
||||
|
||||
/// The channel of this queue
|
||||
/// The channel of this queue.
|
||||
final Channel channel;
|
||||
|
||||
/// The client associated with this [channel]
|
||||
/// The client associated with this [channel].
|
||||
final StreamChatClient client;
|
||||
|
||||
/// The logger associated to this queue
|
||||
/// The logger associated to this queue.
|
||||
final Logger? logger;
|
||||
|
||||
late final RetryPolicy _retryPolicy;
|
||||
@@ -63,7 +63,7 @@ class RetryQueue {
|
||||
}).addTo(_compositeSubscription);
|
||||
}
|
||||
|
||||
/// Add a list of messages
|
||||
/// Add a list of messages.
|
||||
void add(List<Message> messages) {
|
||||
if (messages.isEmpty) return;
|
||||
if (!_messageQueue.containsAllMessage(messages)) {
|
||||
@@ -113,6 +113,7 @@ class RetryQueue {
|
||||
} catch (e) {
|
||||
if (e is! StreamChatNetworkError || !e.isRetriable) {
|
||||
_messageQueue.removeMessage(message);
|
||||
_sendFailedEvent(message);
|
||||
return true;
|
||||
}
|
||||
// retry logic
|
||||
@@ -174,10 +175,10 @@ class RetryQueue {
|
||||
}
|
||||
}
|
||||
|
||||
/// Whether our [_messageQueue] has messages or not
|
||||
/// Whether our [_messageQueue] has messages or not.
|
||||
bool get hasMessages => _messageQueue.isNotEmpty;
|
||||
|
||||
/// Call this method to dispose this object
|
||||
/// Call this method to dispose this object.
|
||||
void dispose() {
|
||||
_messageQueue.clear();
|
||||
_compositeSubscription.dispose();
|
||||
|
||||
@@ -269,6 +269,7 @@ class ChannelApi {
|
||||
) async {
|
||||
final response = await _client.post(
|
||||
'${_getChannelUrl(channelId, channelType)}/truncate',
|
||||
data: {},
|
||||
);
|
||||
return EmptyResponse.fromJson(response.data);
|
||||
}
|
||||
|
||||
@@ -16,12 +16,14 @@ class MessageApi {
|
||||
String channelType,
|
||||
Message message, {
|
||||
bool skipPush = false,
|
||||
bool skipEnrichUrl = false,
|
||||
}) async {
|
||||
final response = await _client.post(
|
||||
'/channels/$channelType/$channelId/message',
|
||||
data: {
|
||||
'message': message,
|
||||
'skip_push': skipPush,
|
||||
'skip_enrich_url': skipEnrichUrl,
|
||||
},
|
||||
);
|
||||
return SendMessageResponse.fromJson(response.data);
|
||||
@@ -51,11 +53,15 @@ class MessageApi {
|
||||
|
||||
/// Updates the given [message]
|
||||
Future<UpdateMessageResponse> updateMessage(
|
||||
Message message,
|
||||
) async {
|
||||
Message message, {
|
||||
bool skipEnrichUrl = false,
|
||||
}) async {
|
||||
final response = await _client.post(
|
||||
'/messages/${message.id}',
|
||||
data: {'message': message},
|
||||
data: {
|
||||
'message': message,
|
||||
'skip_enrich_url': skipEnrichUrl,
|
||||
},
|
||||
);
|
||||
return UpdateMessageResponse.fromJson(response.data);
|
||||
}
|
||||
@@ -67,12 +73,14 @@ class MessageApi {
|
||||
String messageId, {
|
||||
Map<String, Object?>? set,
|
||||
List<String>? unset,
|
||||
bool skipEnrichUrl = false,
|
||||
}) async {
|
||||
final response = await _client.put(
|
||||
'/messages/$messageId',
|
||||
data: {
|
||||
if (set != null) 'set': set,
|
||||
if (unset != null) 'unset': unset,
|
||||
'skip_enrich_url': skipEnrichUrl,
|
||||
},
|
||||
);
|
||||
return UpdateMessageResponse.fromJson(response.data);
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
import 'package:equatable/equatable.dart';
|
||||
import 'package:json_annotation/json_annotation.dart';
|
||||
import 'package:stream_chat/src/core/api/responses.dart';
|
||||
import 'package:stream_chat/src/core/models/action.dart';
|
||||
import 'package:stream_chat/src/core/models/attachment_file.dart';
|
||||
import 'package:stream_chat/src/core/util/serializer.dart';
|
||||
@@ -66,6 +67,21 @@ class Attachment extends Equatable {
|
||||
topLevelFields + dbSpecificTopLevelFields,
|
||||
));
|
||||
|
||||
factory Attachment.fromOGAttachment(OGAttachmentResponse ogAttachment) =>
|
||||
Attachment(
|
||||
type: ogAttachment.type,
|
||||
title: ogAttachment.title,
|
||||
titleLink: ogAttachment.titleLink,
|
||||
text: ogAttachment.text,
|
||||
imageUrl: ogAttachment.imageUrl,
|
||||
thumbUrl: ogAttachment.thumbUrl,
|
||||
authorName: ogAttachment.authorName,
|
||||
authorLink: ogAttachment.authorLink,
|
||||
assetUrl: ogAttachment.assetUrl,
|
||||
ogScrapeUrl: ogAttachment.ogScrapeUrl,
|
||||
uploadState: const UploadState.success(),
|
||||
);
|
||||
|
||||
///The attachment type based on the URL resource. This can be: audio,
|
||||
///image or video
|
||||
final String? type;
|
||||
@@ -229,6 +245,33 @@ class Attachment extends Equatable {
|
||||
extraData: extraData ?? this.extraData,
|
||||
);
|
||||
|
||||
Attachment merge(Attachment? other) {
|
||||
if (other == null) return this;
|
||||
return copyWith(
|
||||
type: other.type,
|
||||
titleLink: other.titleLink,
|
||||
title: other.title,
|
||||
thumbUrl: other.thumbUrl,
|
||||
text: other.text,
|
||||
pretext: other.pretext,
|
||||
ogScrapeUrl: other.ogScrapeUrl,
|
||||
imageUrl: other.imageUrl,
|
||||
footerIcon: other.footerIcon,
|
||||
footer: other.footer,
|
||||
fields: other.fields,
|
||||
fallback: other.fallback,
|
||||
color: other.color,
|
||||
authorName: other.authorName,
|
||||
authorLink: other.authorLink,
|
||||
authorIcon: other.authorIcon,
|
||||
assetUrl: other.assetUrl,
|
||||
actions: other.actions,
|
||||
file: other.file,
|
||||
uploadState: other.uploadState,
|
||||
extraData: other.extraData,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
List<Object?> get props => [
|
||||
id,
|
||||
|
||||
@@ -13,6 +13,7 @@ class ChannelModel {
|
||||
String? id,
|
||||
String? type,
|
||||
String? cid,
|
||||
this.ownCapabilities = const [],
|
||||
ChannelConfig? config,
|
||||
this.createdBy,
|
||||
this.frozen = false,
|
||||
@@ -51,6 +52,10 @@ class ChannelModel {
|
||||
@JsonKey(includeIfNull: false, toJson: Serializer.readOnly)
|
||||
final String cid;
|
||||
|
||||
/// List of user permissions on this channel
|
||||
@JsonKey(includeIfNull: false, toJson: Serializer.readOnly)
|
||||
final List<String> ownCapabilities;
|
||||
|
||||
/// The channel configuration data
|
||||
@JsonKey(includeIfNull: false, toJson: Serializer.readOnly)
|
||||
final ChannelConfig config;
|
||||
@@ -101,6 +106,7 @@ class ChannelModel {
|
||||
'id',
|
||||
'type',
|
||||
'cid',
|
||||
'own_capabilities',
|
||||
'config',
|
||||
'created_by',
|
||||
'frozen',
|
||||
@@ -127,6 +133,7 @@ class ChannelModel {
|
||||
String? id,
|
||||
String? type,
|
||||
String? cid,
|
||||
List<String>? ownCapabilities,
|
||||
ChannelConfig? config,
|
||||
User? createdBy,
|
||||
bool? frozen,
|
||||
@@ -143,6 +150,7 @@ class ChannelModel {
|
||||
id: id ?? this.id,
|
||||
type: type ?? this.type,
|
||||
cid: cid ?? this.cid,
|
||||
ownCapabilities: ownCapabilities ?? this.ownCapabilities,
|
||||
config: config ?? this.config,
|
||||
createdBy: createdBy ?? this.createdBy,
|
||||
frozen: frozen ?? this.frozen,
|
||||
@@ -164,6 +172,7 @@ class ChannelModel {
|
||||
id: other.id,
|
||||
type: other.type,
|
||||
cid: other.cid,
|
||||
ownCapabilities: other.ownCapabilities,
|
||||
config: other.config,
|
||||
createdBy: other.createdBy,
|
||||
frozen: other.frozen,
|
||||
|
||||
@@ -10,6 +10,10 @@ ChannelModel _$ChannelModelFromJson(Map<String, dynamic> json) => ChannelModel(
|
||||
id: json['id'] as String?,
|
||||
type: json['type'] as String?,
|
||||
cid: json['cid'] as String?,
|
||||
ownCapabilities: (json['own_capabilities'] as List<dynamic>?)
|
||||
?.map((e) => e as String)
|
||||
.toList() ??
|
||||
const [],
|
||||
config: json['config'] == null
|
||||
? null
|
||||
: ChannelConfig.fromJson(json['config'] as Map<String, dynamic>),
|
||||
@@ -48,6 +52,7 @@ Map<String, dynamic> _$ChannelModelToJson(ChannelModel instance) {
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
@@ -8,13 +8,13 @@ import 'package:uuid/uuid.dart';
|
||||
|
||||
part 'message.g.dart';
|
||||
|
||||
class _PinExpires {
|
||||
const _PinExpires();
|
||||
class _NullConst {
|
||||
const _NullConst();
|
||||
}
|
||||
|
||||
const _pinExpires = _PinExpires();
|
||||
const _nullConst = _NullConst();
|
||||
|
||||
/// Enum defining the status of a sending message
|
||||
/// Enum defining the status of a sending message.
|
||||
enum MessageSendingStatus {
|
||||
/// Message is being sent
|
||||
sending,
|
||||
@@ -40,10 +40,10 @@ enum MessageSendingStatus {
|
||||
sent,
|
||||
}
|
||||
|
||||
/// The class that contains the information about a message
|
||||
/// The class that contains the information about a message.
|
||||
@JsonSerializable()
|
||||
class Message extends Equatable {
|
||||
/// Constructor used for json serialization
|
||||
/// Constructor used for json serialization.
|
||||
Message({
|
||||
String? id,
|
||||
this.text,
|
||||
@@ -58,44 +58,47 @@ class Message extends Equatable {
|
||||
this.ownReactions,
|
||||
this.parentId,
|
||||
this.quotedMessage,
|
||||
this.quotedMessageId,
|
||||
String? quotedMessageId,
|
||||
this.replyCount = 0,
|
||||
this.threadParticipants,
|
||||
this.showInChannel,
|
||||
this.command,
|
||||
DateTime? createdAt,
|
||||
DateTime? updatedAt,
|
||||
this.deletedAt,
|
||||
this.user,
|
||||
this.pinned = false,
|
||||
this.pinnedAt,
|
||||
DateTime? pinExpires,
|
||||
this.pinnedBy,
|
||||
this.extraData = const {},
|
||||
this.deletedAt,
|
||||
this.status = MessageSendingStatus.sent,
|
||||
this.status = MessageSendingStatus.sending,
|
||||
this.i18n,
|
||||
}) : id = id ?? const Uuid().v4(),
|
||||
pinExpires = pinExpires?.toUtc(),
|
||||
createdAt = createdAt ?? DateTime.now(),
|
||||
updatedAt = updatedAt ?? DateTime.now();
|
||||
_createdAt = createdAt,
|
||||
_updatedAt = updatedAt,
|
||||
_quotedMessageId = quotedMessageId;
|
||||
|
||||
/// Create a new instance from a json
|
||||
/// Create a new instance from JSON.
|
||||
factory Message.fromJson(Map<String, dynamic> json) => _$MessageFromJson(
|
||||
Serializer.moveToExtraDataFromRoot(json, topLevelFields),
|
||||
).copyWith(
|
||||
status: MessageSendingStatus.sent,
|
||||
);
|
||||
|
||||
/// The message ID. This is either created by Stream or set client side when
|
||||
/// the message is added.
|
||||
final String id;
|
||||
|
||||
/// The text of this message
|
||||
/// The text of this message.
|
||||
final String? text;
|
||||
|
||||
/// The status of a sending message
|
||||
/// The status of a sending message.
|
||||
@JsonKey(ignore: true)
|
||||
final MessageSendingStatus status;
|
||||
|
||||
/// The message type
|
||||
/// The message type.
|
||||
@JsonKey(
|
||||
includeIfNull: false,
|
||||
toJson: Serializer.readOnly,
|
||||
@@ -107,15 +110,15 @@ class Message extends Equatable {
|
||||
@JsonKey(includeIfNull: false)
|
||||
final List<Attachment> attachments;
|
||||
|
||||
/// The list of user mentioned in the message
|
||||
/// The list of user mentioned in the message.
|
||||
@JsonKey(toJson: User.toIds)
|
||||
final List<User> mentionedUsers;
|
||||
|
||||
/// A map describing the count of number of every reaction
|
||||
/// A map describing the count of number of every reaction.
|
||||
@JsonKey(includeIfNull: false, toJson: Serializer.readOnly)
|
||||
final Map<String, int>? reactionCounts;
|
||||
|
||||
/// A map describing the count of score of every reaction
|
||||
/// A map describing the count of score of every reaction.
|
||||
@JsonKey(includeIfNull: false, toJson: Serializer.readOnly)
|
||||
final Map<String, int>? reactionScores;
|
||||
|
||||
@@ -130,12 +133,14 @@ class Message extends Equatable {
|
||||
/// The ID of the parent message, if the message is a thread reply.
|
||||
final String? parentId;
|
||||
|
||||
/// A quoted reply message
|
||||
/// A quoted reply message.
|
||||
@JsonKey(toJson: Serializer.readOnly)
|
||||
final Message? quotedMessage;
|
||||
|
||||
final String? _quotedMessageId;
|
||||
|
||||
/// The ID of the quoted message, if the message is a quoted reply.
|
||||
final String? quotedMessageId;
|
||||
String? get quotedMessageId => _quotedMessageId ?? quotedMessage?.id;
|
||||
|
||||
/// Reserved field indicating the number of replies for this message.
|
||||
@JsonKey(includeIfNull: false, toJson: Serializer.readOnly)
|
||||
@@ -148,10 +153,10 @@ class Message extends Equatable {
|
||||
/// Check if this message needs to show in the channel.
|
||||
final bool? showInChannel;
|
||||
|
||||
/// If true the message is silent
|
||||
/// If true the message is silent.
|
||||
final bool silent;
|
||||
|
||||
/// If true the message is shadowed
|
||||
/// If true the message is shadowed.
|
||||
@JsonKey(
|
||||
includeIfNull: false,
|
||||
toJson: Serializer.readOnly,
|
||||
@@ -162,56 +167,61 @@ class Message extends Equatable {
|
||||
@JsonKey(includeIfNull: false, toJson: Serializer.readOnly)
|
||||
final String? command;
|
||||
|
||||
/// Reserved field indicating when the message was created.
|
||||
@JsonKey(includeIfNull: false, toJson: Serializer.readOnly)
|
||||
final DateTime createdAt;
|
||||
|
||||
/// Reserved field indicating when the message was updated last time.
|
||||
@JsonKey(includeIfNull: false, toJson: Serializer.readOnly)
|
||||
final DateTime updatedAt;
|
||||
|
||||
/// User who sent the message
|
||||
@JsonKey(includeIfNull: false, toJson: Serializer.readOnly)
|
||||
final User? user;
|
||||
|
||||
/// If true the message is pinned
|
||||
final bool pinned;
|
||||
|
||||
/// Reserved field indicating when the message was pinned
|
||||
@JsonKey(toJson: Serializer.readOnly)
|
||||
final DateTime? pinnedAt;
|
||||
|
||||
/// Reserved field indicating when the message will expire
|
||||
///
|
||||
/// if `null` message has no expiry
|
||||
final DateTime? pinExpires;
|
||||
|
||||
/// Reserved field indicating who pinned the message
|
||||
@JsonKey(toJson: Serializer.readOnly)
|
||||
final User? pinnedBy;
|
||||
|
||||
/// Message custom extraData
|
||||
@JsonKey(includeIfNull: false)
|
||||
final Map<String, Object?> extraData;
|
||||
|
||||
/// True if the message is a system info
|
||||
bool get isSystem => type == 'system';
|
||||
|
||||
/// True if the message has been deleted
|
||||
bool get isDeleted => type == 'deleted';
|
||||
|
||||
/// True if the message is ephemeral
|
||||
bool get isEphemeral => type == 'ephemeral';
|
||||
final DateTime? _createdAt;
|
||||
|
||||
/// Reserved field indicating when the message was deleted.
|
||||
@JsonKey(includeIfNull: false, toJson: Serializer.readOnly)
|
||||
final DateTime? deletedAt;
|
||||
|
||||
/// Reserved field indicating when the message was created.
|
||||
@JsonKey(includeIfNull: false, toJson: Serializer.readOnly)
|
||||
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)
|
||||
DateTime get updatedAt => _updatedAt ?? DateTime.now();
|
||||
|
||||
/// User who sent the message.
|
||||
@JsonKey(includeIfNull: false, toJson: Serializer.readOnly)
|
||||
final User? user;
|
||||
|
||||
/// If true the message is pinned.
|
||||
final bool pinned;
|
||||
|
||||
/// Reserved field indicating when the message was pinned.
|
||||
@JsonKey(toJson: Serializer.readOnly)
|
||||
final DateTime? pinnedAt;
|
||||
|
||||
/// Reserved field indicating when the message will expire.
|
||||
///
|
||||
/// If `null` message has no expiry.
|
||||
final DateTime? pinExpires;
|
||||
|
||||
/// Reserved field indicating who pinned the message.
|
||||
@JsonKey(toJson: Serializer.readOnly)
|
||||
final User? pinnedBy;
|
||||
|
||||
/// Message custom extraData.
|
||||
@JsonKey(includeIfNull: false)
|
||||
final Map<String, Object?> extraData;
|
||||
|
||||
/// True if the message is a system info.
|
||||
bool get isSystem => type == 'system';
|
||||
|
||||
/// True if the message has been deleted.
|
||||
bool get isDeleted => type == 'deleted';
|
||||
|
||||
/// True if the message is ephemeral.
|
||||
bool get isEphemeral => type == 'ephemeral';
|
||||
|
||||
/// A Map of translations.
|
||||
@JsonKey(includeIfNull: false)
|
||||
final Map<String, String>? i18n;
|
||||
|
||||
/// Known top level fields.
|
||||
///
|
||||
/// Useful for [Serializer] methods.
|
||||
static const topLevelFields = [
|
||||
'id',
|
||||
@@ -244,7 +254,7 @@ class Message extends Equatable {
|
||||
'i18n',
|
||||
];
|
||||
|
||||
/// Serialize to json
|
||||
/// Serialize to json.
|
||||
Map<String, dynamic> toJson() => Serializer.moveFromExtraDataToRoot(
|
||||
_$MessageToJson(this),
|
||||
);
|
||||
@@ -256,18 +266,18 @@ class Message extends Equatable {
|
||||
String? type,
|
||||
List<Attachment>? attachments,
|
||||
List<User>? mentionedUsers,
|
||||
bool? silent,
|
||||
bool? shadowed,
|
||||
Map<String, int>? reactionCounts,
|
||||
Map<String, int>? reactionScores,
|
||||
List<Reaction>? latestReactions,
|
||||
List<Reaction>? ownReactions,
|
||||
String? parentId,
|
||||
Message? quotedMessage,
|
||||
String? quotedMessageId,
|
||||
Object? quotedMessage = _nullConst,
|
||||
Object? quotedMessageId = _nullConst,
|
||||
int? replyCount,
|
||||
List<User>? threadParticipants,
|
||||
bool? showInChannel,
|
||||
bool? shadowed,
|
||||
bool? silent,
|
||||
String? command,
|
||||
DateTime? createdAt,
|
||||
DateTime? updatedAt,
|
||||
@@ -275,7 +285,7 @@ class Message extends Equatable {
|
||||
User? user,
|
||||
bool? pinned,
|
||||
DateTime? pinnedAt,
|
||||
Object? pinExpires = _pinExpires,
|
||||
Object? pinExpires = _nullConst,
|
||||
User? pinnedBy,
|
||||
Map<String, Object?>? extraData,
|
||||
MessageSendingStatus? status,
|
||||
@@ -284,41 +294,68 @@ class Message extends Equatable {
|
||||
assert(() {
|
||||
if (pinExpires is! DateTime &&
|
||||
pinExpires != null &&
|
||||
pinExpires is! _PinExpires) {
|
||||
pinExpires is! _NullConst) {
|
||||
throw ArgumentError('`pinExpires` can only be set as DateTime or null');
|
||||
}
|
||||
return true;
|
||||
}(), 'Validate type for pinExpires');
|
||||
|
||||
assert(() {
|
||||
if (quotedMessage is! Message &&
|
||||
quotedMessage != null &&
|
||||
quotedMessage is! _NullConst) {
|
||||
throw ArgumentError(
|
||||
'`quotedMessage` can only be set as Message or null',
|
||||
);
|
||||
}
|
||||
return true;
|
||||
}(), 'Validate type for quotedMessage');
|
||||
|
||||
assert(() {
|
||||
if (quotedMessageId is! String &&
|
||||
quotedMessageId != null &&
|
||||
quotedMessageId is! _NullConst) {
|
||||
throw ArgumentError(
|
||||
'`quotedMessage` can only be set as String or null',
|
||||
);
|
||||
}
|
||||
return true;
|
||||
}(), 'Validate type for quotedMessage');
|
||||
|
||||
return Message(
|
||||
id: id ?? this.id,
|
||||
text: text ?? this.text,
|
||||
type: type ?? this.type,
|
||||
attachments: attachments ?? this.attachments,
|
||||
mentionedUsers: mentionedUsers ?? this.mentionedUsers,
|
||||
silent: silent ?? this.silent,
|
||||
shadowed: shadowed ?? this.shadowed,
|
||||
reactionCounts: reactionCounts ?? this.reactionCounts,
|
||||
reactionScores: reactionScores ?? this.reactionScores,
|
||||
latestReactions: latestReactions ?? this.latestReactions,
|
||||
ownReactions: ownReactions ?? this.ownReactions,
|
||||
parentId: parentId ?? this.parentId,
|
||||
quotedMessage: quotedMessage ?? this.quotedMessage,
|
||||
quotedMessageId: quotedMessageId ?? this.quotedMessageId,
|
||||
quotedMessage: quotedMessage == _nullConst
|
||||
? this.quotedMessage
|
||||
: quotedMessage as Message?,
|
||||
quotedMessageId: quotedMessageId == _nullConst
|
||||
? _quotedMessageId
|
||||
: quotedMessageId as String?,
|
||||
replyCount: replyCount ?? this.replyCount,
|
||||
threadParticipants: threadParticipants ?? this.threadParticipants,
|
||||
showInChannel: showInChannel ?? this.showInChannel,
|
||||
command: command ?? this.command,
|
||||
createdAt: createdAt ?? this.createdAt,
|
||||
silent: silent ?? this.silent,
|
||||
extraData: extraData ?? this.extraData,
|
||||
user: user ?? this.user,
|
||||
shadowed: shadowed ?? this.shadowed,
|
||||
updatedAt: updatedAt ?? this.updatedAt,
|
||||
createdAt: createdAt ?? _createdAt,
|
||||
updatedAt: updatedAt ?? _updatedAt,
|
||||
deletedAt: deletedAt ?? this.deletedAt,
|
||||
status: status ?? this.status,
|
||||
user: user ?? this.user,
|
||||
pinned: pinned ?? this.pinned,
|
||||
pinnedAt: pinnedAt ?? this.pinnedAt,
|
||||
pinnedBy: pinnedBy ?? this.pinnedBy,
|
||||
pinExpires:
|
||||
pinExpires == _pinExpires ? this.pinExpires : pinExpires as DateTime?,
|
||||
pinExpires == _nullConst ? this.pinExpires : pinExpires as DateTime?,
|
||||
pinnedBy: pinnedBy ?? this.pinnedBy,
|
||||
extraData: extraData ?? this.extraData,
|
||||
status: status ?? this.status,
|
||||
i18n: i18n ?? this.i18n,
|
||||
);
|
||||
}
|
||||
@@ -331,6 +368,8 @@ class Message extends Equatable {
|
||||
type: other.type,
|
||||
attachments: other.attachments,
|
||||
mentionedUsers: other.mentionedUsers,
|
||||
silent: other.silent,
|
||||
shadowed: other.shadowed,
|
||||
reactionCounts: other.reactionCounts,
|
||||
reactionScores: other.reactionScores,
|
||||
latestReactions: other.latestReactions,
|
||||
@@ -343,17 +382,15 @@ class Message extends Equatable {
|
||||
showInChannel: other.showInChannel,
|
||||
command: other.command,
|
||||
createdAt: other.createdAt,
|
||||
silent: other.silent,
|
||||
extraData: other.extraData,
|
||||
user: other.user,
|
||||
shadowed: other.shadowed,
|
||||
updatedAt: other.updatedAt,
|
||||
deletedAt: other.deletedAt,
|
||||
status: other.status,
|
||||
user: other.user,
|
||||
pinned: other.pinned,
|
||||
pinnedAt: other.pinnedAt,
|
||||
pinExpires: other.pinExpires,
|
||||
pinnedBy: other.pinnedBy,
|
||||
extraData: other.extraData,
|
||||
status: other.status,
|
||||
i18n: other.i18n,
|
||||
);
|
||||
|
||||
@@ -377,8 +414,8 @@ class Message extends Equatable {
|
||||
shadowed,
|
||||
silent,
|
||||
command,
|
||||
createdAt,
|
||||
updatedAt,
|
||||
_createdAt,
|
||||
_updatedAt,
|
||||
deletedAt,
|
||||
user,
|
||||
pinned,
|
||||
|
||||
@@ -0,0 +1,96 @@
|
||||
/// Describes capabilities of a user vis-a-vis a channel
|
||||
class PermissionType {
|
||||
/// Capability required to send a message in the channel
|
||||
/// Channel is not frozen (or user has UseFrozenChannel permission)
|
||||
/// and user has CreateMessage permission.
|
||||
static const String sendMessage = 'send-message';
|
||||
|
||||
/// Capability required to receive connect events in the channel
|
||||
static const String connectEvents = 'connect-events';
|
||||
|
||||
/// Capability required to send a message
|
||||
/// Reactions are enabled for the channel, channel is not frozen
|
||||
/// (or user has UseFrozenChannel permission) and user has
|
||||
/// CreateReaction permission
|
||||
static const String sendReaction = 'send-reaction';
|
||||
|
||||
/// Capability required to send links in a channel
|
||||
/// send-message + user has AddLinks permission
|
||||
static const String sendLinks = 'send-links';
|
||||
|
||||
/// Capability required to send thread reply
|
||||
/// send-message + channel has replies enabled
|
||||
static const String sendReply = 'send-reply';
|
||||
|
||||
/// Capability to freeze a channel
|
||||
/// User has UpdateChannelFrozen permission.
|
||||
/// The name implies freezing,
|
||||
/// but unfreezing is also allowed when this capability is present
|
||||
static const String freezeChannel = 'freeze-channel';
|
||||
|
||||
/// User has UpdateChannelCooldown permission.
|
||||
/// Allows to enable/disable slow mode in the channel
|
||||
static const String setChannelCooldown = 'set-channel-cooldown';
|
||||
|
||||
/// User has RemoveOwnChannelMembership or UpdateChannelMembers permission
|
||||
static const String leaveChannel = 'leave-channel';
|
||||
|
||||
/// User can mute channel
|
||||
static const String muteChannel = 'mute-channel';
|
||||
|
||||
/// Ability to receive read events
|
||||
static const String readEvents = 'read-events';
|
||||
|
||||
/// Capability required to pin a message in a channel
|
||||
/// Corresponds to PinMessage permission
|
||||
static const String pinMessage = 'pin-message';
|
||||
|
||||
/// Capability required to quote a message in a channel
|
||||
static const String quoteMessage = 'quote-message';
|
||||
|
||||
/// Capability required to flag a message in a channel
|
||||
static const String flagMessage = 'flag-message';
|
||||
|
||||
/// User has ability to delete any message in the channel
|
||||
/// User has DeleteMessage permission
|
||||
/// which applies to any message in the channel
|
||||
static const String deleteAnyMessage = 'delete-any-message';
|
||||
|
||||
/// User has ability to delete their own message in the channel
|
||||
/// User has DeleteMessage permission which applies only to owned messages
|
||||
static const String deleteOwnMessage = 'delete-own-message';
|
||||
|
||||
/// User has ability to update/edit any message in the channel
|
||||
/// User has UpdateMessage permission which
|
||||
/// applies to any message in the channel
|
||||
static const String updateAnyMessage = 'update-any-message';
|
||||
|
||||
/// User has ability to update/edit their own message in the channel
|
||||
/// User has UpdateMessage permission which applies only to owned messages
|
||||
static const String updateOwnMessage = 'update-own-message';
|
||||
|
||||
/// User can search for message in a channel
|
||||
/// Search feature is enabled (it will also have
|
||||
/// permission check in the future)
|
||||
static const String searchMessages = 'search-messages';
|
||||
|
||||
/// Capability required to send typing events in a channel
|
||||
/// (Typing events are enabled)
|
||||
static const String sendTypingEvents = 'send-typing-events';
|
||||
|
||||
/// Capability required to upload a file in a channel
|
||||
/// Uploads are enabled and user has UploadAttachment
|
||||
static const String uploadFile = 'upload-file';
|
||||
|
||||
/// Capability required to delete channel
|
||||
/// User has DeleteChannel permission
|
||||
static const String deleteChannel = 'delete-channel';
|
||||
|
||||
/// Capability required update/edit channel info
|
||||
/// User has UpdateChannel permission
|
||||
static const String updateChannel = 'update-channel';
|
||||
|
||||
/// Capability required to update/edit channel members
|
||||
/// Channel is not distinct and user has UpdateChannelMembers permission
|
||||
static const String updateChannelMembers = 'update-channel-members';
|
||||
}
|
||||
@@ -7,6 +7,7 @@ export 'package:dio/src/options.dart';
|
||||
export 'package:dio/src/options.dart' show ProgressCallback;
|
||||
export 'package:logging/logging.dart' show Logger, Level;
|
||||
export 'package:rate_limiter/rate_limiter.dart';
|
||||
export 'package:uuid/uuid.dart';
|
||||
|
||||
export './src/core/api/attachment_file_uploader.dart'
|
||||
show AttachmentFileUploader;
|
||||
@@ -36,6 +37,7 @@ export './src/core/util/extension.dart';
|
||||
export './src/db/chat_persistence_client.dart';
|
||||
export './src/event_type.dart';
|
||||
export './src/location.dart';
|
||||
export './src/permission_type.dart';
|
||||
export './src/ws/connection_status.dart';
|
||||
export 'src/client/channel.dart';
|
||||
export 'src/client/client.dart';
|
||||
|
||||
@@ -244,9 +244,13 @@ void main() {
|
||||
|
||||
group('`.sendMessage`', () {
|
||||
test('should work fine', () async {
|
||||
final message = Message(id: 'test-message-id');
|
||||
final message = Message(
|
||||
id: 'test-message-id',
|
||||
user: client.state.currentUser,
|
||||
);
|
||||
|
||||
final sendMessageResponse = SendMessageResponse()..message = message;
|
||||
final sendMessageResponse = SendMessageResponse()
|
||||
..message = message.copyWith(status: MessageSendingStatus.sent);
|
||||
|
||||
when(() => client.sendMessage(
|
||||
any(that: isSameMessageAs(message)),
|
||||
@@ -329,6 +333,7 @@ void main() {
|
||||
.map((it) =>
|
||||
it.copyWith(uploadState: const UploadState.success()))
|
||||
.toList(growable: false),
|
||||
status: MessageSendingStatus.sent,
|
||||
));
|
||||
|
||||
expectLater(
|
||||
@@ -455,7 +460,10 @@ void main() {
|
||||
|
||||
group('`.updateMessage`', () {
|
||||
test('should work fine', () async {
|
||||
final message = Message(id: 'test-message-id');
|
||||
final message = Message(
|
||||
id: 'test-message-id',
|
||||
status: MessageSendingStatus.sent,
|
||||
);
|
||||
|
||||
final updateMessageResponse = UpdateMessageResponse()
|
||||
..message = message;
|
||||
@@ -530,6 +538,7 @@ void main() {
|
||||
any(that: isSameMessageAs(message)),
|
||||
)).thenAnswer((_) async => UpdateMessageResponse()
|
||||
..message = message.copyWith(
|
||||
status: MessageSendingStatus.sent,
|
||||
attachments: attachments
|
||||
.map((it) =>
|
||||
it.copyWith(uploadState: const UploadState.success()))
|
||||
@@ -678,7 +687,7 @@ void main() {
|
||||
[
|
||||
isSameMessageAs(
|
||||
updateMessageResponse.message.copyWith(
|
||||
status: MessageSendingStatus.sent,
|
||||
status: MessageSendingStatus.sending,
|
||||
),
|
||||
matchText: true,
|
||||
matchSendingStatus: true,
|
||||
@@ -707,7 +716,10 @@ void main() {
|
||||
group('`.deleteMessage`', () {
|
||||
test('should work fine', () async {
|
||||
const messageId = 'test-message-id';
|
||||
final message = Message(id: messageId);
|
||||
final message = Message(
|
||||
id: messageId,
|
||||
status: MessageSendingStatus.sent,
|
||||
);
|
||||
|
||||
when(() => client.deleteMessage(messageId))
|
||||
.thenAnswer((_) async => EmptyResponse());
|
||||
@@ -744,7 +756,6 @@ void main() {
|
||||
const messageId = 'test-message-id';
|
||||
final message = Message(
|
||||
id: messageId,
|
||||
status: MessageSendingStatus.sending,
|
||||
);
|
||||
|
||||
expectLater(
|
||||
@@ -1077,7 +1088,10 @@ void main() {
|
||||
group('`.sendReaction`', () {
|
||||
test('should work fine', () async {
|
||||
const type = 'test-reaction-type';
|
||||
final message = Message(id: 'test-message-id');
|
||||
final message = Message(
|
||||
id: 'test-message-id',
|
||||
status: MessageSendingStatus.sent,
|
||||
);
|
||||
|
||||
final reaction = Reaction(type: type, messageId: message.id);
|
||||
|
||||
@@ -1118,7 +1132,10 @@ void main() {
|
||||
|
||||
test('should work fine with score passed explicitly', () async {
|
||||
const type = 'test-reaction-type';
|
||||
final message = Message(id: 'test-message-id');
|
||||
final message = Message(
|
||||
id: 'test-message-id',
|
||||
status: MessageSendingStatus.sent,
|
||||
);
|
||||
|
||||
const score = 5;
|
||||
final reaction = Reaction(
|
||||
@@ -1178,7 +1195,10 @@ void main() {
|
||||
test('should work fine with score passed explicitly and in extraData',
|
||||
() async {
|
||||
const type = 'test-reaction-type';
|
||||
final message = Message(id: 'test-message-id');
|
||||
final message = Message(
|
||||
id: 'test-message-id',
|
||||
status: MessageSendingStatus.sent,
|
||||
);
|
||||
|
||||
const score = 5;
|
||||
const extraDataScore = 3;
|
||||
@@ -1249,7 +1269,10 @@ void main() {
|
||||
'should restore previous message if `client.sendReaction` throws',
|
||||
() async {
|
||||
const type = 'test-reaction-type';
|
||||
final message = Message(id: 'test-message-id');
|
||||
final message = Message(
|
||||
id: 'test-message-id',
|
||||
status: MessageSendingStatus.sent,
|
||||
);
|
||||
|
||||
final reaction = Reaction(type: type, messageId: message.id);
|
||||
|
||||
@@ -1310,6 +1333,7 @@ void main() {
|
||||
latestReactions: [prevReaction],
|
||||
reactionScores: const {prevType: 1},
|
||||
reactionCounts: const {prevType: 1},
|
||||
status: MessageSendingStatus.sent,
|
||||
);
|
||||
|
||||
const type = 'test-reaction-type-2';
|
||||
@@ -1341,7 +1365,7 @@ void main() {
|
||||
emitsInOrder([
|
||||
[
|
||||
isSameMessageAs(
|
||||
newMessage.copyWith(status: MessageSendingStatus.sent),
|
||||
newMessage,
|
||||
matchReactions: true,
|
||||
matchSendingStatus: true,
|
||||
),
|
||||
@@ -1374,6 +1398,7 @@ void main() {
|
||||
final message = Message(
|
||||
id: 'test-message-id',
|
||||
parentId: 'test-parent-id', // is thread message
|
||||
status: MessageSendingStatus.sent,
|
||||
);
|
||||
|
||||
final reaction = Reaction(type: type, messageId: message.id);
|
||||
@@ -1423,6 +1448,7 @@ void main() {
|
||||
final message = Message(
|
||||
id: 'test-message-id',
|
||||
parentId: 'test-parent-id', // is thread message
|
||||
status: MessageSendingStatus.sent,
|
||||
);
|
||||
|
||||
final reaction = Reaction(type: type, messageId: message.id);
|
||||
@@ -1490,6 +1516,7 @@ void main() {
|
||||
latestReactions: [prevReaction],
|
||||
reactionScores: const {prevType: 1},
|
||||
reactionCounts: const {prevType: 1},
|
||||
status: MessageSendingStatus.sent,
|
||||
);
|
||||
|
||||
const type = 'test-reaction-type-2';
|
||||
@@ -1567,6 +1594,7 @@ void main() {
|
||||
latestReactions: [reaction],
|
||||
reactionScores: const {type: 1},
|
||||
reactionCounts: const {type: 1},
|
||||
status: MessageSendingStatus.sent,
|
||||
);
|
||||
|
||||
when(() => client.deleteReaction(messageId, type))
|
||||
@@ -1614,6 +1642,7 @@ void main() {
|
||||
latestReactions: [reaction],
|
||||
reactionScores: const {type: 1},
|
||||
reactionCounts: const {type: 1},
|
||||
status: MessageSendingStatus.sent,
|
||||
);
|
||||
|
||||
when(() => client.deleteReaction(messageId, type))
|
||||
@@ -1668,11 +1697,13 @@ void main() {
|
||||
);
|
||||
final message = Message(
|
||||
id: messageId,
|
||||
parentId: parentId, // is thread
|
||||
parentId: parentId,
|
||||
// is thread
|
||||
ownReactions: [reaction],
|
||||
latestReactions: [reaction],
|
||||
reactionScores: const {type: 1},
|
||||
reactionCounts: const {type: 1},
|
||||
status: MessageSendingStatus.sent,
|
||||
);
|
||||
|
||||
when(() => client.deleteReaction(messageId, type))
|
||||
@@ -1725,6 +1756,7 @@ void main() {
|
||||
latestReactions: [reaction],
|
||||
reactionScores: const {type: 1},
|
||||
reactionCounts: const {type: 1},
|
||||
status: MessageSendingStatus.sent,
|
||||
);
|
||||
|
||||
when(() => client.deleteReaction(messageId, type))
|
||||
|
||||
@@ -481,14 +481,21 @@ void main() {
|
||||
|
||||
final path = '${_getChannelUrl(channelId, channelType)}/truncate';
|
||||
|
||||
when(() => client.post(path)).thenAnswer(
|
||||
(_) async => successResponse(path, data: <String, dynamic>{}));
|
||||
when(() => client.post(
|
||||
path,
|
||||
data: {},
|
||||
))
|
||||
.thenAnswer(
|
||||
(_) async => successResponse(path, data: <String, dynamic>{}));
|
||||
|
||||
final res = await channelApi.truncateChannel(channelId, channelType);
|
||||
|
||||
expect(res, isNotNull);
|
||||
|
||||
verify(() => client.post(path)).called(1);
|
||||
verify(() => client.post(
|
||||
path,
|
||||
data: {},
|
||||
)).called(1);
|
||||
verifyNoMoreInteractions(client);
|
||||
});
|
||||
|
||||
|
||||
@@ -32,6 +32,7 @@ void main() {
|
||||
data: {
|
||||
'message': message,
|
||||
'skip_push': false,
|
||||
'skip_enrich_url': false,
|
||||
},
|
||||
)).thenAnswer((_) async => successResponse(path, data: {
|
||||
'message': message.toJson(),
|
||||
@@ -58,6 +59,7 @@ void main() {
|
||||
data: {
|
||||
'message': message,
|
||||
'skip_push': true,
|
||||
'skip_enrich_url': false,
|
||||
},
|
||||
)).thenAnswer((_) async => successResponse(path, data: {
|
||||
'message': message.toJson(),
|
||||
@@ -137,7 +139,10 @@ void main() {
|
||||
|
||||
when(() => client.post(
|
||||
path,
|
||||
data: {'message': message},
|
||||
data: {
|
||||
'message': message,
|
||||
'skip_enrich_url': false,
|
||||
},
|
||||
)).thenAnswer(
|
||||
(_) async => successResponse(path, data: {'message': message.toJson()}),
|
||||
);
|
||||
@@ -162,7 +167,11 @@ void main() {
|
||||
|
||||
when(() => client.put(
|
||||
path,
|
||||
data: {'set': set, 'unset': unset},
|
||||
data: {
|
||||
'set': set,
|
||||
'unset': unset,
|
||||
'skip_enrich_url': false,
|
||||
},
|
||||
)).thenAnswer(
|
||||
(_) async => successResponse(path, data: {'message': message.toJson()}),
|
||||
);
|
||||
@@ -180,7 +189,11 @@ void main() {
|
||||
|
||||
verify(() => client.put(
|
||||
path,
|
||||
data: {'set': set, 'unset': unset},
|
||||
data: {
|
||||
'set': set,
|
||||
'unset': unset,
|
||||
'skip_enrich_url': false,
|
||||
},
|
||||
)).called(1);
|
||||
verifyNoMoreInteractions(client);
|
||||
});
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
## 3.5.1
|
||||
|
||||
🛑️ Breaking Changes
|
||||
|
||||
- `pinPermissions` is no longer needed in `MessageListView`.
|
||||
- `MessageInput` now works with a `MessageInputController` instead of a `TextEditingController`
|
||||
|
||||
🐞 Fixed
|
||||
|
||||
- Mentions overlay now doesn't overflow when there is not enough height available
|
||||
@@ -31,7 +36,12 @@
|
||||
|
||||
✅ Added
|
||||
|
||||
- Videos can now be auto-played in `FullScreenMedia`, by setting the `autoplayVideos` argument to true.
|
||||
- Videos can now be auto-played in `FullScreenMedia`
|
||||
- Extra customisation options for `MessageInput`
|
||||
|
||||
🔄 Changed
|
||||
|
||||
- Add `didUpdateWidget` override in `MessageInput` widget to handle changes to `focusNode`.
|
||||
|
||||
## 3.3.2
|
||||
|
||||
|
||||
@@ -87,7 +87,7 @@ class MyApp extends StatelessWidget {
|
||||
|
||||
/// A list of messages sent in the current channel.
|
||||
///
|
||||
/// This is implemented using [MessageListView], a widget that provides query
|
||||
/// This is implemented using [StreamMessageListView], a widget that provides query
|
||||
/// functionalities fetching the messages from the api and showing them in a
|
||||
/// listView.
|
||||
class ChannelPage extends StatelessWidget {
|
||||
@@ -98,13 +98,13 @@ class ChannelPage extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) => Scaffold(
|
||||
appBar: const ChannelHeader(),
|
||||
appBar: const StreamChannelHeader(),
|
||||
body: Column(
|
||||
children: const <Widget>[
|
||||
Expanded(
|
||||
child: MessageListView(),
|
||||
child: StreamMessageListView(),
|
||||
),
|
||||
MessageInput(attachmentLimit: 3),
|
||||
StreamMessageInput(attachmentLimit: 3),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
@@ -84,7 +84,7 @@ class _SplitViewState extends State<SplitView> {
|
||||
);
|
||||
}
|
||||
|
||||
class ChannelListPage extends StatelessWidget {
|
||||
class ChannelListPage extends StatefulWidget {
|
||||
const ChannelListPage({
|
||||
Key? key,
|
||||
this.onTap,
|
||||
@@ -92,22 +92,26 @@ class ChannelListPage extends StatelessWidget {
|
||||
|
||||
final void Function(Channel)? onTap;
|
||||
|
||||
@override
|
||||
State<ChannelListPage> createState() => _ChannelListPageState();
|
||||
}
|
||||
|
||||
class _ChannelListPageState extends State<ChannelListPage> {
|
||||
late final _listController = StreamChannelListController(
|
||||
client: StreamChat.of(context).client,
|
||||
filter: Filter.in_(
|
||||
'members',
|
||||
[StreamChat.of(context).currentUser!.id],
|
||||
),
|
||||
sort: const [SortOption('last_message_at')],
|
||||
limit: 20,
|
||||
);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) => Scaffold(
|
||||
body: ChannelsBloc(
|
||||
child: ChannelListView(
|
||||
onChannelTap: onTap != null
|
||||
? (channel, _) {
|
||||
onTap!(channel);
|
||||
}
|
||||
: null,
|
||||
filter: Filter.in_(
|
||||
'members',
|
||||
[StreamChat.of(context).currentUser!.id],
|
||||
),
|
||||
sort: const [SortOption('last_message_at')],
|
||||
limit: 20,
|
||||
),
|
||||
body: StreamChannelListView(
|
||||
onChannelTap: widget.onTap,
|
||||
controller: _listController,
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -121,15 +125,15 @@ class ChannelPage extends StatelessWidget {
|
||||
Widget build(BuildContext context) => Navigator(
|
||||
onGenerateRoute: (settings) => MaterialPageRoute(
|
||||
builder: (context) => Scaffold(
|
||||
appBar: const ChannelHeader(
|
||||
appBar: const StreamChannelHeader(
|
||||
showBackButton: false,
|
||||
),
|
||||
body: Column(
|
||||
children: const <Widget>[
|
||||
Expanded(
|
||||
child: MessageListView(),
|
||||
child: StreamMessageListView(),
|
||||
),
|
||||
MessageInput(),
|
||||
StreamMessageInput(),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
@@ -27,7 +27,7 @@ import 'package:stream_chat_flutter/stream_chat_flutter.dart';
|
||||
/// - We make [StreamChat] the root Widget of our application
|
||||
///
|
||||
/// - We create a single [ChannelPage] widget under [StreamChat] with three
|
||||
/// widgets: [ChannelHeader], [MessageListView] and [MessageInput]
|
||||
/// widgets: [StreamChannelHeader], [StreamMessageListView] and [StreamMessageInput]
|
||||
///
|
||||
/// If you now run the simulator you will see a single channel UI.
|
||||
void main() async {
|
||||
@@ -90,18 +90,15 @@ class ChannelPage extends StatelessWidget {
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
// ignore: prefer_expression_function_bodies
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: const ChannelHeader(),
|
||||
body: Column(
|
||||
children: const <Widget>[
|
||||
Expanded(
|
||||
child: MessageListView(),
|
||||
),
|
||||
MessageInput(),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
Widget build(BuildContext context) => Scaffold(
|
||||
appBar: const StreamChannelHeader(),
|
||||
body: Column(
|
||||
children: const <Widget>[
|
||||
Expanded(
|
||||
child: StreamMessageListView(),
|
||||
),
|
||||
StreamMessageInput(),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@ import 'package:stream_chat_flutter/stream_chat_flutter.dart';
|
||||
/// The [ChannelListPage] widget retrieves the list of channels based on a
|
||||
/// custom query and ordering. In this case we are showing the list of
|
||||
/// channels in which the current user is a member and we order them based
|
||||
/// on the time they had a new message. [ChannelListView] handles pagination
|
||||
/// on the time they had a new message. [StreamChannelListView] handles pagination
|
||||
/// and updates automatically when new channels are created or when a new
|
||||
/// message is added to a channel.
|
||||
void main() async {
|
||||
@@ -62,33 +62,53 @@ class MyApp extends StatelessWidget {
|
||||
client: client,
|
||||
child: child,
|
||||
),
|
||||
home: const ChannelListPage(),
|
||||
home: ChannelListPage(
|
||||
client: client,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class ChannelListPage extends StatelessWidget {
|
||||
class ChannelListPage extends StatefulWidget {
|
||||
const ChannelListPage({
|
||||
Key? key,
|
||||
required this.client,
|
||||
}) : super(key: key);
|
||||
|
||||
final StreamChatClient client;
|
||||
|
||||
@override
|
||||
// ignore: prefer_expression_function_bodies
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
body: ChannelsBloc(
|
||||
child: ChannelListView(
|
||||
filter: Filter.in_(
|
||||
'members',
|
||||
[StreamChat.of(context).currentUser!.id],
|
||||
State<ChannelListPage> createState() => _ChannelListPageState();
|
||||
}
|
||||
|
||||
class _ChannelListPageState extends State<ChannelListPage> {
|
||||
late final _controller = StreamChannelListController(
|
||||
client: widget.client,
|
||||
filter: Filter.in_(
|
||||
'members',
|
||||
[StreamChat.of(context).currentUser!.id],
|
||||
),
|
||||
sort: const [SortOption('last_message_at')],
|
||||
);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) => Scaffold(
|
||||
body: RefreshIndicator(
|
||||
onRefresh: _controller.refresh,
|
||||
child: StreamChannelListView(
|
||||
controller: _controller,
|
||||
onChannelTap: (channel) => Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (_) => StreamChannel(
|
||||
channel: channel,
|
||||
child: const ChannelPage(),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
sort: const [SortOption('last_message_at')],
|
||||
limit: 20,
|
||||
channelWidget: const ChannelPage(),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
class ChannelPage extends StatelessWidget {
|
||||
@@ -97,18 +117,15 @@ class ChannelPage extends StatelessWidget {
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
// ignore: prefer_expression_function_bodies
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: const ChannelHeader(),
|
||||
body: Column(
|
||||
children: const <Widget>[
|
||||
Expanded(
|
||||
child: MessageListView(),
|
||||
),
|
||||
MessageInput(),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
Widget build(BuildContext context) => Scaffold(
|
||||
appBar: const StreamChannelHeader(),
|
||||
body: Column(
|
||||
children: const <Widget>[
|
||||
Expanded(
|
||||
child: StreamMessageListView(),
|
||||
),
|
||||
StreamMessageInput(),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -15,8 +15,8 @@ import 'package:stream_chat_flutter/stream_chat_flutter.dart';
|
||||
/// We start by changing how channel previews are shown in the channel list
|
||||
/// and include the number of unread messages for each.
|
||||
///
|
||||
/// We're passing a custom widget to [ChannelListView.channelPreviewBuilder];
|
||||
/// this will override the default [ChannelPreview] and allows you to create
|
||||
/// We're passing a custom widget to [StreamChannelListView.channelPreviewBuilder];
|
||||
/// this will override the default [StreamChannelPreview] and allows you to create
|
||||
/// one yourself.
|
||||
///
|
||||
/// There are a couple interesting things we do in this widget:
|
||||
@@ -68,31 +68,49 @@ class MyApp extends StatelessWidget {
|
||||
}
|
||||
}
|
||||
|
||||
class ChannelListPage extends StatelessWidget {
|
||||
class ChannelListPage extends StatefulWidget {
|
||||
const ChannelListPage({
|
||||
Key? key,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
// ignore: prefer_expression_function_bodies
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
body: ChannelsBloc(
|
||||
child: ChannelListView(
|
||||
filter: Filter.in_(
|
||||
'members',
|
||||
[StreamChat.of(context).currentUser!.id],
|
||||
),
|
||||
channelPreviewBuilder: _channelPreviewBuilder,
|
||||
// sort: [SortOption('last_message_at')],
|
||||
limit: 20,
|
||||
channelWidget: const ChannelPage(),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
State<ChannelListPage> createState() => _ChannelListPageState();
|
||||
}
|
||||
|
||||
Widget _channelPreviewBuilder(BuildContext context, Channel channel) {
|
||||
class _ChannelListPageState extends State<ChannelListPage> {
|
||||
late final _listController = StreamChannelListController(
|
||||
client: StreamChat.of(context).client,
|
||||
filter: Filter.in_(
|
||||
'members',
|
||||
[StreamChat.of(context).currentUser!.id],
|
||||
),
|
||||
sort: const [SortOption('last_message_at')],
|
||||
limit: 20,
|
||||
);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) => Scaffold(
|
||||
body: StreamChannelListView(
|
||||
controller: _listController,
|
||||
itemBuilder: _channelPreviewBuilder,
|
||||
onChannelTap: (channel) {
|
||||
Navigator.of(context).push(
|
||||
MaterialPageRoute(
|
||||
builder: (context) => StreamChannel(
|
||||
channel: channel,
|
||||
child: const ChannelPage(),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
Widget _channelPreviewBuilder(
|
||||
BuildContext context,
|
||||
Channel channel,
|
||||
StreamChannelListTile defaultTile,
|
||||
) {
|
||||
final lastMessage = channel.state?.messages.reversed.firstWhereOrNull(
|
||||
(message) => !message.isDeleted,
|
||||
);
|
||||
@@ -112,16 +130,17 @@ class ChannelListPage extends StatelessWidget {
|
||||
),
|
||||
);
|
||||
},
|
||||
leading: ChannelAvatar(
|
||||
leading: StreamChannelAvatar(
|
||||
channel: channel,
|
||||
),
|
||||
title: ChannelName(
|
||||
textStyle: ChannelPreviewTheme.of(context).titleStyle!.copyWith(
|
||||
title: StreamChannelName(
|
||||
textStyle: StreamChannelPreviewTheme.of(context).titleStyle!.copyWith(
|
||||
color: StreamChatTheme.of(context)
|
||||
.colorTheme
|
||||
.textHighEmphasis
|
||||
.withOpacity(opacity),
|
||||
),
|
||||
channel: channel,
|
||||
),
|
||||
subtitle: Text(subtitle),
|
||||
trailing: channel.state!.unreadCount > 0
|
||||
@@ -143,13 +162,13 @@ class ChannelPage extends StatelessWidget {
|
||||
// ignore: prefer_expression_function_bodies
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: const ChannelHeader(),
|
||||
appBar: const StreamChannelHeader(),
|
||||
body: Column(
|
||||
children: const <Widget>[
|
||||
Expanded(
|
||||
child: MessageListView(),
|
||||
child: StreamMessageListView(),
|
||||
),
|
||||
MessageInput(),
|
||||
StreamMessageInput(),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
@@ -8,8 +8,8 @@ import 'package:stream_chat_flutter/stream_chat_flutter.dart';
|
||||
/// to create sub-conversations inside the same channel.
|
||||
///
|
||||
/// Using threaded conversations is very simple and mostly a matter of
|
||||
/// plugging the [MessageListView] to another widget that renders the widget.
|
||||
/// To make this simple, such a widget only needs to build [MessageListView]
|
||||
/// plugging the [StreamMessageListView] to another widget that renders the widget.
|
||||
/// To make this simple, such a widget only needs to build [StreamMessageListView]
|
||||
/// with the parent attribute set to the thread’s root message.
|
||||
///
|
||||
/// Now we can open threads and create new ones as well. If you long-press a
|
||||
@@ -53,28 +53,42 @@ class MyApp extends StatelessWidget {
|
||||
}
|
||||
}
|
||||
|
||||
class ChannelListPage extends StatelessWidget {
|
||||
class ChannelListPage extends StatefulWidget {
|
||||
const ChannelListPage({
|
||||
Key? key,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
// ignore: prefer_expression_function_bodies
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
body: ChannelsBloc(
|
||||
child: ChannelListView(
|
||||
filter: Filter.in_(
|
||||
'members',
|
||||
[StreamChat.of(context).currentUser!.id],
|
||||
),
|
||||
sort: const [SortOption('last_message_at')],
|
||||
limit: 20,
|
||||
channelWidget: const ChannelPage(),
|
||||
State<ChannelListPage> createState() => _ChannelListPageState();
|
||||
}
|
||||
|
||||
class _ChannelListPageState extends State<ChannelListPage> {
|
||||
late final _listController = StreamChannelListController(
|
||||
client: StreamChat.of(context).client,
|
||||
filter: Filter.in_(
|
||||
'members',
|
||||
[StreamChat.of(context).currentUser!.id],
|
||||
),
|
||||
sort: const [SortOption('last_message_at')],
|
||||
limit: 20,
|
||||
);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) => Scaffold(
|
||||
body: StreamChannelListView(
|
||||
controller: _listController,
|
||||
onChannelTap: (channel) {
|
||||
Navigator.of(context).push(
|
||||
MaterialPageRoute(
|
||||
builder: (context) => StreamChannel(
|
||||
channel: channel,
|
||||
child: const ChannelPage(),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
class ChannelPage extends StatelessWidget {
|
||||
@@ -83,24 +97,21 @@ class ChannelPage extends StatelessWidget {
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
// ignore: prefer_expression_function_bodies
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: const ChannelHeader(),
|
||||
body: Column(
|
||||
children: <Widget>[
|
||||
Expanded(
|
||||
child: MessageListView(
|
||||
threadBuilder: (_, parentMessage) => ThreadPage(
|
||||
parent: parentMessage,
|
||||
Widget build(BuildContext context) => Scaffold(
|
||||
appBar: const StreamChannelHeader(),
|
||||
body: Column(
|
||||
children: <Widget>[
|
||||
Expanded(
|
||||
child: StreamMessageListView(
|
||||
threadBuilder: (_, parentMessage) => ThreadPage(
|
||||
parent: parentMessage,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
const MessageInput(),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
const StreamMessageInput(),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
class ThreadPage extends StatelessWidget {
|
||||
@@ -115,18 +126,20 @@ class ThreadPage extends StatelessWidget {
|
||||
// ignore: prefer_expression_function_bodies
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: ThreadHeader(
|
||||
appBar: StreamThreadHeader(
|
||||
parent: parent!,
|
||||
),
|
||||
body: Column(
|
||||
children: <Widget>[
|
||||
Expanded(
|
||||
child: MessageListView(
|
||||
child: StreamMessageListView(
|
||||
parentMessage: parent,
|
||||
),
|
||||
),
|
||||
MessageInput(
|
||||
parentMessage: parent,
|
||||
StreamMessageInput(
|
||||
messageInputController: MessageInputController(
|
||||
message: Message(parentId: parent!.id),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
@@ -8,7 +8,7 @@ import 'package:stream_chat_flutter/stream_chat_flutter.dart';
|
||||
/// the SDK supports easily.
|
||||
///
|
||||
/// Replacing the built-in message component with your own is done by passing
|
||||
/// it as a builder function to the [MessageListView] widget.
|
||||
/// it as a builder function to the [StreamMessageListView] widget.
|
||||
///
|
||||
/// The message builder function will get the usual [BuildContext] argument
|
||||
/// as well as the [Message] object and its position inside the list.
|
||||
@@ -59,28 +59,42 @@ class MyApp extends StatelessWidget {
|
||||
}
|
||||
}
|
||||
|
||||
class ChannelListPage extends StatelessWidget {
|
||||
class ChannelListPage extends StatefulWidget {
|
||||
const ChannelListPage({
|
||||
Key? key,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
// ignore: prefer_expression_function_bodies
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
body: ChannelsBloc(
|
||||
child: ChannelListView(
|
||||
filter: Filter.in_(
|
||||
'members',
|
||||
[StreamChat.of(context).currentUser!.id],
|
||||
),
|
||||
sort: const [SortOption('last_message_at')],
|
||||
limit: 20,
|
||||
channelWidget: const ChannelPage(),
|
||||
State<ChannelListPage> createState() => _ChannelListPageState();
|
||||
}
|
||||
|
||||
class _ChannelListPageState extends State<ChannelListPage> {
|
||||
late final _listController = StreamChannelListController(
|
||||
client: StreamChat.of(context).client,
|
||||
filter: Filter.in_(
|
||||
'members',
|
||||
[StreamChat.of(context).currentUser!.id],
|
||||
),
|
||||
sort: const [SortOption('last_message_at')],
|
||||
limit: 20,
|
||||
);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) => Scaffold(
|
||||
body: StreamChannelListView(
|
||||
controller: _listController,
|
||||
onChannelTap: (channel) {
|
||||
Navigator.of(context).push(
|
||||
MaterialPageRoute(
|
||||
builder: (context) => StreamChannel(
|
||||
channel: channel,
|
||||
child: const ChannelPage(),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
class ChannelPage extends StatelessWidget {
|
||||
@@ -92,15 +106,15 @@ class ChannelPage extends StatelessWidget {
|
||||
// ignore: prefer_expression_function_bodies
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: const ChannelHeader(),
|
||||
appBar: const StreamChannelHeader(),
|
||||
body: Column(
|
||||
children: <Widget>[
|
||||
Expanded(
|
||||
child: MessageListView(
|
||||
child: StreamMessageListView(
|
||||
messageBuilder: _messageBuilder,
|
||||
),
|
||||
),
|
||||
const MessageInput(),
|
||||
const StreamMessageInput(),
|
||||
],
|
||||
),
|
||||
);
|
||||
@@ -110,7 +124,7 @@ class ChannelPage extends StatelessWidget {
|
||||
BuildContext context,
|
||||
MessageDetails details,
|
||||
List<Message> messages,
|
||||
MessageWidget _,
|
||||
StreamMessageWidget _,
|
||||
) {
|
||||
final message = details.message;
|
||||
final isCurrentUser =
|
||||
|
||||
@@ -58,24 +58,24 @@ class MyApp extends StatelessWidget {
|
||||
final defaultTheme = StreamChatThemeData.fromTheme(themeData);
|
||||
final colorTheme = defaultTheme.colorTheme;
|
||||
final customTheme = defaultTheme.merge(StreamChatThemeData(
|
||||
channelPreviewTheme: ChannelPreviewThemeData(
|
||||
avatarTheme: AvatarThemeData(
|
||||
channelPreviewTheme: StreamChannelPreviewThemeData(
|
||||
avatarTheme: StreamAvatarThemeData(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
),
|
||||
messageListViewTheme: const MessageListViewThemeData(
|
||||
messageListViewTheme: const StreamMessageListViewThemeData(
|
||||
backgroundColor: Colors.grey,
|
||||
backgroundImage: DecorationImage(
|
||||
image: AssetImage('assets/background_doodle.png'),
|
||||
fit: BoxFit.cover,
|
||||
),
|
||||
),
|
||||
otherMessageTheme: MessageThemeData(
|
||||
otherMessageTheme: StreamMessageThemeData(
|
||||
messageBackgroundColor: colorTheme.textHighEmphasis,
|
||||
messageTextStyle: TextStyle(
|
||||
color: colorTheme.barsBg,
|
||||
),
|
||||
avatarTheme: AvatarThemeData(
|
||||
avatarTheme: StreamAvatarThemeData(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
),
|
||||
@@ -93,28 +93,41 @@ class MyApp extends StatelessWidget {
|
||||
}
|
||||
}
|
||||
|
||||
class ChannelListPage extends StatelessWidget {
|
||||
class ChannelListPage extends StatefulWidget {
|
||||
const ChannelListPage({
|
||||
Key? key,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
// ignore: prefer_expression_function_bodies
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
body: ChannelsBloc(
|
||||
child: ChannelListView(
|
||||
filter: Filter.in_(
|
||||
'members',
|
||||
[StreamChat.of(context).currentUser!.id],
|
||||
),
|
||||
sort: const [SortOption('last_message_at')],
|
||||
limit: 20,
|
||||
channelWidget: const ChannelPage(),
|
||||
State<ChannelListPage> createState() => _ChannelListPageState();
|
||||
}
|
||||
|
||||
class _ChannelListPageState extends State<ChannelListPage> {
|
||||
late final _listController = StreamChannelListController(
|
||||
client: StreamChat.of(context).client,
|
||||
filter: Filter.in_(
|
||||
'members',
|
||||
[StreamChat.of(context).currentUser!.id],
|
||||
),
|
||||
sort: const [SortOption('last_message_at')],
|
||||
limit: 20,
|
||||
);
|
||||
@override
|
||||
Widget build(BuildContext context) => Scaffold(
|
||||
body: StreamChannelListView(
|
||||
controller: _listController,
|
||||
onChannelTap: (channel) {
|
||||
Navigator.of(context).push(
|
||||
MaterialPageRoute(
|
||||
builder: (context) => StreamChannel(
|
||||
channel: channel,
|
||||
child: const ChannelPage(),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
class ChannelPage extends StatelessWidget {
|
||||
@@ -123,24 +136,21 @@ class ChannelPage extends StatelessWidget {
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
// ignore: prefer_expression_function_bodies
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: const ChannelHeader(),
|
||||
body: Column(
|
||||
children: <Widget>[
|
||||
Expanded(
|
||||
child: MessageListView(
|
||||
threadBuilder: (_, parentMessage) => ThreadPage(
|
||||
parent: parentMessage,
|
||||
Widget build(BuildContext context) => Scaffold(
|
||||
appBar: const StreamChannelHeader(),
|
||||
body: Column(
|
||||
children: <Widget>[
|
||||
Expanded(
|
||||
child: StreamMessageListView(
|
||||
threadBuilder: (_, parentMessage) => ThreadPage(
|
||||
parent: parentMessage,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
const MessageInput(),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
const StreamMessageInput(),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
class ThreadPage extends StatelessWidget {
|
||||
@@ -155,18 +165,20 @@ class ThreadPage extends StatelessWidget {
|
||||
// ignore: prefer_expression_function_bodies
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: ThreadHeader(
|
||||
appBar: StreamThreadHeader(
|
||||
parent: parent!,
|
||||
),
|
||||
body: Column(
|
||||
children: <Widget>[
|
||||
Expanded(
|
||||
child: MessageListView(
|
||||
child: StreamMessageListView(
|
||||
parentMessage: parent,
|
||||
),
|
||||
),
|
||||
MessageInput(
|
||||
parentMessage: parent,
|
||||
StreamMessageInput(
|
||||
messageInputController: MessageInputController(
|
||||
message: Message(parentId: parent!.id),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
@@ -1,17 +1,23 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
|
||||
|
||||
/// {@macro attachment_title}
|
||||
@Deprecated("Use 'StreamAttachmentTitle' instead")
|
||||
typedef AttachmentTitle = StreamAttachmentTitle;
|
||||
|
||||
/// {@template attachment_title}
|
||||
/// Title for attachments
|
||||
class AttachmentTitle extends StatelessWidget {
|
||||
/// {@endtemplate}
|
||||
class StreamAttachmentTitle extends StatelessWidget {
|
||||
/// Supply attachment and theme for constructing title
|
||||
const AttachmentTitle({
|
||||
const StreamAttachmentTitle({
|
||||
Key? key,
|
||||
required this.attachment,
|
||||
required this.messageTheme,
|
||||
}) : super(key: key);
|
||||
|
||||
/// Theme to apply to text
|
||||
final MessageThemeData messageTheme;
|
||||
final StreamMessageThemeData messageTheme;
|
||||
|
||||
/// Attachment data to display
|
||||
final Attachment attachment;
|
||||
|
||||
+11
-5
@@ -9,10 +9,16 @@ typedef InProgressBuilder = Widget Function(BuildContext, int, int);
|
||||
/// Widget to build on failure
|
||||
typedef FailedBuilder = Widget Function(BuildContext, String);
|
||||
|
||||
/// {@macro attachment_upload_state_builder}
|
||||
@Deprecated("Use 'StreamAttachmentsUploadStateBuilder' instead")
|
||||
typedef AttachmentUploadStateBuilder = StreamAttachmentUploadStateBuilder;
|
||||
|
||||
/// {@template attachment_upload_state_builder}
|
||||
/// Widget to display attachment upload state
|
||||
class AttachmentUploadStateBuilder extends StatelessWidget {
|
||||
/// Constructor for creating an [AttachmentUploadStateBuilder] widget
|
||||
const AttachmentUploadStateBuilder({
|
||||
/// {@endtemplate}
|
||||
class StreamAttachmentUploadStateBuilder extends StatelessWidget {
|
||||
/// Constructor for creating an [StreamAttachmentUploadStateBuilder] widget
|
||||
const StreamAttachmentUploadStateBuilder({
|
||||
Key? key,
|
||||
required this.message,
|
||||
required this.attachment,
|
||||
@@ -137,7 +143,7 @@ class _PreparingState extends StatelessWidget {
|
||||
),
|
||||
Align(
|
||||
alignment: Alignment.topRight,
|
||||
child: UploadProgressIndicator(
|
||||
child: StreamUploadProgressIndicator(
|
||||
uploaded: 0,
|
||||
total: double.maxFinite.toInt(),
|
||||
),
|
||||
@@ -177,7 +183,7 @@ class _InProgressState extends StatelessWidget {
|
||||
),
|
||||
Align(
|
||||
alignment: Alignment.topRight,
|
||||
child: UploadProgressIndicator(
|
||||
child: StreamUploadProgressIndicator(
|
||||
uploaded: sent,
|
||||
total: total,
|
||||
),
|
||||
|
||||
@@ -28,10 +28,16 @@ extension AttachmentSourceX on AttachmentSource {
|
||||
}
|
||||
}
|
||||
|
||||
/// {@macro attachment_widget}
|
||||
@Deprecated("Use 'StreamAttachmentWidget' instead")
|
||||
typedef AttachmentWidget = StreamAttachmentWidget;
|
||||
|
||||
/// {@template attachment_widget}
|
||||
/// Abstract class for deriving attachment types
|
||||
abstract class AttachmentWidget extends StatelessWidget {
|
||||
/// {@endtemplate}
|
||||
abstract class StreamAttachmentWidget extends StatelessWidget {
|
||||
/// Constructor for creating attachment widget
|
||||
const AttachmentWidget({
|
||||
const StreamAttachmentWidget({
|
||||
Key? key,
|
||||
required this.message,
|
||||
required this.attachment,
|
||||
|
||||
@@ -10,10 +10,16 @@ import 'package:stream_chat_flutter/src/utils.dart';
|
||||
import 'package:stream_chat_flutter/src/video_thumbnail_image.dart';
|
||||
import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart';
|
||||
|
||||
/// {@macro file_attachment}
|
||||
@Deprecated("Use 'StreamFileAttachment' instead")
|
||||
typedef FileAttachment = StreamFileAttachment;
|
||||
|
||||
/// {@template file_attachment}
|
||||
/// Widget for displaying file attachments
|
||||
class FileAttachment extends AttachmentWidget {
|
||||
/// {@endtemplate}
|
||||
class StreamFileAttachment extends StreamAttachmentWidget {
|
||||
/// Constructor for creating a widget when attachment is of type 'file'
|
||||
const FileAttachment({
|
||||
const StreamFileAttachment({
|
||||
Key? key,
|
||||
required Message message,
|
||||
required Attachment attachment,
|
||||
@@ -157,7 +163,7 @@ class FileAttachment extends AttachmentWidget {
|
||||
type: MaterialType.transparency,
|
||||
shape: _getDefaultShape(context),
|
||||
child: source.when(
|
||||
local: () => VideoThumbnailImage(
|
||||
local: () => StreamVideoThumbnailImage(
|
||||
fit: BoxFit.cover,
|
||||
video: attachment.file!.path!,
|
||||
placeholderBuilder: (_) => const Center(
|
||||
@@ -168,7 +174,7 @@ class FileAttachment extends AttachmentWidget {
|
||||
),
|
||||
),
|
||||
),
|
||||
network: () => VideoThumbnailImage(
|
||||
network: () => StreamVideoThumbnailImage(
|
||||
fit: BoxFit.cover,
|
||||
video: attachment.assetUrl!,
|
||||
placeholderBuilder: (_) => const Center(
|
||||
@@ -278,7 +284,7 @@ class FileAttachment extends AttachmentWidget {
|
||||
);
|
||||
return attachment.uploadState.when(
|
||||
preparing: () => Text(fileSize(size), style: textStyle),
|
||||
inProgress: (sent, total) => UploadProgressIndicator(
|
||||
inProgress: (sent, total) => StreamUploadProgressIndicator(
|
||||
uploaded: sent,
|
||||
total: total,
|
||||
showBackground: false,
|
||||
|
||||
@@ -5,10 +5,16 @@ import 'package:stream_chat_flutter/src/attachment/attachment_widget.dart';
|
||||
import 'package:stream_chat_flutter/src/extension.dart';
|
||||
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
|
||||
|
||||
/// {@macro giphy_attachment}
|
||||
@Deprecated("Use 'StreamGiphyAttachment' instead")
|
||||
typedef GiphyAttachment = StreamGiphyAttachment;
|
||||
|
||||
/// {@template giphy_attachment}
|
||||
/// Widget for showing a GIF attachment
|
||||
class GiphyAttachment extends AttachmentWidget {
|
||||
/// Constructor for creating a [GiphyAttachment] widget
|
||||
const GiphyAttachment({
|
||||
/// {@endtemplate}
|
||||
class StreamGiphyAttachment extends StreamAttachmentWidget {
|
||||
/// Constructor for creating a [StreamGiphyAttachment] widget
|
||||
const StreamGiphyAttachment({
|
||||
Key? key,
|
||||
required Message message,
|
||||
required Attachment attachment,
|
||||
@@ -228,7 +234,7 @@ class GiphyAttachment extends AttachmentWidget {
|
||||
alignment: Alignment.centerRight,
|
||||
child: Padding(
|
||||
padding: EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
||||
child: VisibleFootnote(),
|
||||
child: StreamVisibleFootnote(),
|
||||
),
|
||||
),
|
||||
],
|
||||
@@ -243,7 +249,7 @@ class GiphyAttachment extends AttachmentWidget {
|
||||
final channel = StreamChannel.of(context).channel;
|
||||
return StreamChannel(
|
||||
channel: channel,
|
||||
child: FullScreenMedia(
|
||||
child: StreamFullScreenMedia(
|
||||
mediaAttachments: message.attachments,
|
||||
startIndex: message.attachments.indexOf(attachment),
|
||||
userName: message.user?.name,
|
||||
|
||||
@@ -5,10 +5,16 @@ import 'package:stream_chat_flutter/src/attachment/attachment_title.dart';
|
||||
import 'package:stream_chat_flutter/src/attachment/attachment_widget.dart';
|
||||
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
|
||||
|
||||
/// {@macro image_attachment}
|
||||
@Deprecated("use 'StreamImageAttachment' instead")
|
||||
typedef ImageAttachment = StreamImageAttachment;
|
||||
|
||||
/// {@template image_attachment}
|
||||
/// Widget for showing an image attachment
|
||||
class ImageAttachment extends AttachmentWidget {
|
||||
/// Constructor for creating a [ImageAttachment] widget
|
||||
const ImageAttachment({
|
||||
/// {@endtemplate}
|
||||
class StreamImageAttachment extends StreamAttachmentWidget {
|
||||
/// Constructor for creating a [StreamImageAttachment] widget
|
||||
const StreamImageAttachment({
|
||||
Key? key,
|
||||
required Message message,
|
||||
required Attachment attachment,
|
||||
@@ -25,8 +31,8 @@ class ImageAttachment extends AttachmentWidget {
|
||||
size: size,
|
||||
);
|
||||
|
||||
/// [MessageThemeData] for showing image title
|
||||
final MessageThemeData messageTheme;
|
||||
/// [StreamMessageThemeData] for showing image title
|
||||
final StreamMessageThemeData messageTheme;
|
||||
|
||||
/// Flag for showing title
|
||||
final bool showTitle;
|
||||
@@ -137,7 +143,7 @@ class ImageAttachment extends AttachmentWidget {
|
||||
StreamChannel.of(context).channel;
|
||||
return StreamChannel(
|
||||
channel: channel,
|
||||
child: FullScreenMedia(
|
||||
child: StreamFullScreenMedia(
|
||||
mediaAttachments: message.attachments,
|
||||
startIndex:
|
||||
message.attachments.indexOf(attachment),
|
||||
@@ -155,7 +161,7 @@ class ImageAttachment extends AttachmentWidget {
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(8),
|
||||
child: AttachmentUploadStateBuilder(
|
||||
child: StreamAttachmentUploadStateBuilder(
|
||||
message: message,
|
||||
attachment: attachment,
|
||||
),
|
||||
@@ -166,7 +172,7 @@ class ImageAttachment extends AttachmentWidget {
|
||||
if (showTitle && attachment.title != null)
|
||||
Material(
|
||||
color: messageTheme.messageBackgroundColor,
|
||||
child: AttachmentTitle(
|
||||
child: StreamAttachmentTitle(
|
||||
messageTheme: messageTheme,
|
||||
attachment: attachment,
|
||||
),
|
||||
|
||||
@@ -2,10 +2,16 @@ import 'package:cached_network_image/cached_network_image.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
|
||||
|
||||
/// {@macro url_attachment}
|
||||
@Deprecated("Use 'StreamUrlAttachment' instead")
|
||||
typedef UrlAttachment = StreamUrlAttachment;
|
||||
|
||||
/// {@template url_attachment}
|
||||
/// Widget to display URL attachment
|
||||
class UrlAttachment extends StatelessWidget {
|
||||
/// Constructor for creating a [UrlAttachment]
|
||||
const UrlAttachment({
|
||||
/// {@endtemplate}
|
||||
class StreamUrlAttachment extends StatelessWidget {
|
||||
/// Constructor for creating a [StreamUrlAttachment]
|
||||
const StreamUrlAttachment({
|
||||
Key? key,
|
||||
required this.urlAttachment,
|
||||
required this.hostDisplayName,
|
||||
@@ -26,8 +32,8 @@ class UrlAttachment extends StatelessWidget {
|
||||
/// Padding for text
|
||||
final EdgeInsets textPadding;
|
||||
|
||||
/// [MessageThemeData] for showing image title
|
||||
final MessageThemeData messageTheme;
|
||||
/// [StreamMessageThemeData] for showing image title
|
||||
final StreamMessageThemeData messageTheme;
|
||||
|
||||
/// The function called when tapping on a link
|
||||
final void Function(String)? onLinkTap;
|
||||
|
||||
@@ -4,10 +4,16 @@ import 'package:stream_chat_flutter/src/attachment/attachment_widget.dart';
|
||||
import 'package:stream_chat_flutter/src/video_thumbnail_image.dart';
|
||||
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
|
||||
|
||||
/// {@macro video_attachment}
|
||||
@Deprecated("Use 'StreamVideoAttachment' instead")
|
||||
typedef VideoAttachment = StreamVideoAttachment;
|
||||
|
||||
/// {@template video_attachment}
|
||||
/// Widget for showing a video attachment
|
||||
class VideoAttachment extends AttachmentWidget {
|
||||
/// Constructor for creating a [VideoAttachment] widget
|
||||
const VideoAttachment({
|
||||
/// {@endtemplate}
|
||||
class StreamVideoAttachment extends StreamAttachmentWidget {
|
||||
/// Constructor for creating a [StreamVideoAttachment] widget
|
||||
const StreamVideoAttachment({
|
||||
Key? key,
|
||||
required Message message,
|
||||
required Attachment attachment,
|
||||
@@ -23,8 +29,8 @@ class VideoAttachment extends AttachmentWidget {
|
||||
size: size,
|
||||
);
|
||||
|
||||
/// [MessageThemeData] for showing title
|
||||
final MessageThemeData messageTheme;
|
||||
/// [StreamMessageThemeData] for showing title
|
||||
final StreamMessageThemeData messageTheme;
|
||||
|
||||
/// Callback when show message is tapped
|
||||
final ShowMessageCallback? onShowMessage;
|
||||
@@ -43,7 +49,7 @@ class VideoAttachment extends AttachmentWidget {
|
||||
}
|
||||
return _buildVideoAttachment(
|
||||
context,
|
||||
VideoThumbnailImage(
|
||||
StreamVideoThumbnailImage(
|
||||
video: attachment.file!.path!,
|
||||
height: size?.height,
|
||||
width: size?.width,
|
||||
@@ -58,7 +64,7 @@ class VideoAttachment extends AttachmentWidget {
|
||||
}
|
||||
return _buildVideoAttachment(
|
||||
context,
|
||||
VideoThumbnailImage(
|
||||
StreamVideoThumbnailImage(
|
||||
video: attachment.assetUrl!,
|
||||
height: size?.height,
|
||||
width: size?.width,
|
||||
@@ -84,7 +90,7 @@ class VideoAttachment extends AttachmentWidget {
|
||||
MaterialPageRoute(
|
||||
builder: (_) => StreamChannel(
|
||||
channel: channel,
|
||||
child: FullScreenMedia(
|
||||
child: StreamFullScreenMedia(
|
||||
mediaAttachments: message.attachments,
|
||||
startIndex:
|
||||
message.attachments.indexOf(attachment),
|
||||
@@ -111,7 +117,7 @@ class VideoAttachment extends AttachmentWidget {
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(8),
|
||||
child: AttachmentUploadStateBuilder(
|
||||
child: StreamAttachmentUploadStateBuilder(
|
||||
message: message,
|
||||
attachment: attachment,
|
||||
),
|
||||
@@ -123,7 +129,7 @@ class VideoAttachment extends AttachmentWidget {
|
||||
if (attachment.title != null)
|
||||
Material(
|
||||
color: messageTheme.messageBackgroundColor,
|
||||
child: AttachmentTitle(
|
||||
child: StreamAttachmentTitle(
|
||||
messageTheme: messageTheme,
|
||||
attachment: attachment,
|
||||
),
|
||||
|
||||
@@ -58,7 +58,7 @@ class AttachmentActionsModal extends StatelessWidget {
|
||||
/// List of custom actions
|
||||
final List<AttachmentAction> customActions;
|
||||
|
||||
/// Creates a copy of [MessageWidget] with specified attributes overridden.
|
||||
/// Creates a copy of [StreamMessageWidget] with specified attributes overridden.
|
||||
AttachmentActionsModal copyWith({
|
||||
Key? key,
|
||||
int? currentIndex,
|
||||
|
||||
@@ -50,7 +50,7 @@ class StreamBackButton extends StatelessWidget {
|
||||
Positioned(
|
||||
top: 7,
|
||||
right: 7,
|
||||
child: UnreadIndicator(
|
||||
child: StreamUnreadIndicator(
|
||||
cid: cid,
|
||||
),
|
||||
),
|
||||
|
||||
@@ -44,6 +44,11 @@ import 'package:stream_chat_flutter/stream_chat_flutter.dart';
|
||||
/// The widget renders the ui based on the first ancestor of type
|
||||
/// [StreamChatTheme].
|
||||
/// Modify it to change the widget appearance.
|
||||
|
||||
@Deprecated(
|
||||
"'ChannelAvatar' is deprecated and shouldn't be used. "
|
||||
"Please use 'StreamChannelAvatar' instead.",
|
||||
)
|
||||
class ChannelAvatar extends StatelessWidget {
|
||||
/// Instantiate a new ChannelImage
|
||||
const ChannelAvatar({
|
||||
@@ -147,7 +152,7 @@ class ChannelAvatar extends StatelessWidget {
|
||||
return BetterStreamBuilder<User>(
|
||||
stream: streamChat.client.state.currentUserStream.map((it) => it!),
|
||||
initialData: currentUser,
|
||||
builder: (context, user) => UserAvatar(
|
||||
builder: (context, user) => StreamUserAvatar(
|
||||
borderRadius: borderRadius ?? previewTheme?.borderRadius,
|
||||
user: user,
|
||||
constraints: constraints ?? previewTheme?.constraints,
|
||||
@@ -170,7 +175,7 @@ class ChannelAvatar extends StatelessWidget {
|
||||
),
|
||||
),
|
||||
initialData: member,
|
||||
builder: (context, member) => UserAvatar(
|
||||
builder: (context, member) => StreamUserAvatar(
|
||||
borderRadius: borderRadius ?? previewTheme?.borderRadius,
|
||||
user: member.user!,
|
||||
constraints: constraints ?? previewTheme?.constraints,
|
||||
@@ -183,7 +188,7 @@ class ChannelAvatar extends StatelessWidget {
|
||||
}
|
||||
|
||||
// Group conversation
|
||||
return GroupAvatar(
|
||||
return StreamGroupAvatar(
|
||||
members: otherMembers,
|
||||
borderRadius: borderRadius ?? previewTheme?.borderRadius,
|
||||
constraints: constraints ?? previewTheme?.constraints,
|
||||
|
||||
@@ -4,6 +4,7 @@ import 'package:stream_chat_flutter/src/extension.dart';
|
||||
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
|
||||
|
||||
/// Bottom Sheet with options
|
||||
@Deprecated("Use 'StreamChannelInfoBottomSheet' instead")
|
||||
class ChannelBottomSheet extends StatefulWidget {
|
||||
/// Constructor for creating bottom sheet
|
||||
const ChannelBottomSheet({Key? key, this.onViewInfoTap}) : super(key: key);
|
||||
@@ -15,11 +16,12 @@ class ChannelBottomSheet extends StatefulWidget {
|
||||
_ChannelBottomSheetState createState() => _ChannelBottomSheetState();
|
||||
}
|
||||
|
||||
// ignore: deprecated_member_use_from_same_package
|
||||
class _ChannelBottomSheetState extends State<ChannelBottomSheet> {
|
||||
bool _showActions = true;
|
||||
|
||||
late StreamChannelState _streamChannelState;
|
||||
late ChannelPreviewThemeData _channelPreviewThemeData;
|
||||
late StreamChannelPreviewThemeData _channelPreviewThemeData;
|
||||
late StreamChatThemeData _streamChatThemeData;
|
||||
late StreamChatState _streamChatState;
|
||||
|
||||
@@ -53,7 +55,8 @@ class _ChannelBottomSheetState extends State<ChannelBottomSheet> {
|
||||
Center(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
child: ChannelName(
|
||||
child: StreamChannelName(
|
||||
channel: channel,
|
||||
textStyle: _streamChatThemeData.textTheme.headlineBold,
|
||||
),
|
||||
),
|
||||
@@ -62,7 +65,7 @@ class _ChannelBottomSheetState extends State<ChannelBottomSheet> {
|
||||
height: 5,
|
||||
),
|
||||
Center(
|
||||
child: ChannelInfo(
|
||||
child: StreamChannelInfo(
|
||||
showTypingIndicator: false,
|
||||
channel: _streamChannelState.channel,
|
||||
textStyle: _channelPreviewThemeData.subtitleStyle,
|
||||
@@ -74,7 +77,7 @@ class _ChannelBottomSheetState extends State<ChannelBottomSheet> {
|
||||
if (channel.isDistinct && channel.memberCount == 2)
|
||||
Column(
|
||||
children: [
|
||||
UserAvatar(
|
||||
StreamUserAvatar(
|
||||
user: members
|
||||
.firstWhere(
|
||||
(e) => e.user?.id != userAsMember.user?.id,
|
||||
@@ -117,7 +120,7 @@ class _ChannelBottomSheetState extends State<ChannelBottomSheet> {
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8),
|
||||
child: Column(
|
||||
children: [
|
||||
UserAvatar(
|
||||
StreamUserAvatar(
|
||||
user: members[index].user!,
|
||||
constraints: const BoxConstraints.tightFor(
|
||||
height: 64,
|
||||
@@ -145,7 +148,7 @@ class _ChannelBottomSheetState extends State<ChannelBottomSheet> {
|
||||
const SizedBox(
|
||||
height: 24,
|
||||
),
|
||||
OptionListTile(
|
||||
StreamOptionListTile(
|
||||
leading: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
child: StreamSvgIcon.user(
|
||||
@@ -155,8 +158,10 @@ class _ChannelBottomSheetState extends State<ChannelBottomSheet> {
|
||||
title: context.translations.viewInfoLabel,
|
||||
onTap: widget.onViewInfoTap,
|
||||
),
|
||||
if (!channel.isDistinct)
|
||||
OptionListTile(
|
||||
if (!channel.isDistinct &&
|
||||
channel.ownCapabilities
|
||||
.contains(PermissionType.leaveChannel))
|
||||
StreamOptionListTile(
|
||||
leading: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
child: StreamSvgIcon.userRemove(
|
||||
@@ -174,8 +179,10 @@ class _ChannelBottomSheetState extends State<ChannelBottomSheet> {
|
||||
});
|
||||
},
|
||||
),
|
||||
if (isOwner)
|
||||
OptionListTile(
|
||||
if (isOwner &&
|
||||
channel.ownCapabilities
|
||||
.contains(PermissionType.deleteChannel))
|
||||
StreamOptionListTile(
|
||||
leading: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
child: StreamSvgIcon.delete(
|
||||
@@ -194,7 +201,7 @@ class _ChannelBottomSheetState extends State<ChannelBottomSheet> {
|
||||
});
|
||||
},
|
||||
),
|
||||
OptionListTile(
|
||||
StreamOptionListTile(
|
||||
leading: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
child: StreamSvgIcon.closeSmall(
|
||||
@@ -215,7 +222,7 @@ class _ChannelBottomSheetState extends State<ChannelBottomSheet> {
|
||||
void didChangeDependencies() {
|
||||
_streamChannelState = StreamChannel.of(context);
|
||||
_streamChatThemeData = StreamChatTheme.of(context);
|
||||
_channelPreviewThemeData = ChannelPreviewTheme.of(context);
|
||||
_channelPreviewThemeData = StreamChannelPreviewTheme.of(context);
|
||||
_streamChatState = StreamChat.of(context);
|
||||
super.didChangeDependencies();
|
||||
}
|
||||
|
||||
@@ -4,6 +4,11 @@ import 'package:stream_chat_flutter/src/channel_info.dart';
|
||||
import 'package:stream_chat_flutter/src/extension.dart';
|
||||
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
|
||||
|
||||
///{@macro template_name}
|
||||
@Deprecated("Use 'StreamChannelHeader' instead")
|
||||
typedef ChannelHeader = StreamChannelHeader;
|
||||
|
||||
/// {@template channel_header}
|
||||
/// 
|
||||
/// 
|
||||
///
|
||||
@@ -49,9 +54,11 @@ import 'package:stream_chat_flutter/stream_chat_flutter.dart';
|
||||
/// The widget components render the ui based on the first ancestor of type
|
||||
/// [StreamChatTheme] and on its [ChannelTheme.channelHeaderTheme] property.
|
||||
/// Modify it to change the widget appearance.
|
||||
class ChannelHeader extends StatelessWidget implements PreferredSizeWidget {
|
||||
/// {@endtemplate}
|
||||
class StreamChannelHeader extends StatelessWidget
|
||||
implements PreferredSizeWidget {
|
||||
/// Creates a channel header
|
||||
const ChannelHeader({
|
||||
const StreamChannelHeader({
|
||||
Key? key,
|
||||
this.showBackButton = true,
|
||||
this.onBackPressed,
|
||||
@@ -96,16 +103,16 @@ class ChannelHeader extends StatelessWidget implements PreferredSizeWidget {
|
||||
final Widget? leading;
|
||||
|
||||
/// AppBar actions
|
||||
/// By default it shows the [ChannelAvatar]
|
||||
/// By default it shows the [StreamChannelAvatar]
|
||||
final List<Widget>? actions;
|
||||
|
||||
/// The background color for this [ChannelHeader].
|
||||
/// The background color for this [StreamChannelHeader].
|
||||
final Color? backgroundColor;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final channel = StreamChannel.of(context).channel;
|
||||
final channelHeaderTheme = ChannelHeaderTheme.of(context);
|
||||
final channelHeaderTheme = StreamChannelHeaderTheme.of(context);
|
||||
|
||||
final leadingWidget = leading ??
|
||||
(showBackButton
|
||||
@@ -115,7 +122,7 @@ class ChannelHeader extends StatelessWidget implements PreferredSizeWidget {
|
||||
)
|
||||
: const SizedBox());
|
||||
|
||||
return ConnectionStatusBuilder(
|
||||
return StreamConnectionStatusBuilder(
|
||||
statusBuilder: (context, status) {
|
||||
var statusString = '';
|
||||
var showStatus = true;
|
||||
@@ -135,7 +142,7 @@ class ChannelHeader extends StatelessWidget implements PreferredSizeWidget {
|
||||
|
||||
final theme = Theme.of(context);
|
||||
|
||||
return InfoTile(
|
||||
return StreamInfoTile(
|
||||
showMessage: showConnectionStateTile && showStatus,
|
||||
message: statusString,
|
||||
child: AppBar(
|
||||
@@ -152,7 +159,8 @@ class ChannelHeader extends StatelessWidget implements PreferredSizeWidget {
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(right: 10),
|
||||
child: Center(
|
||||
child: ChannelAvatar(
|
||||
child: StreamChannelAvatar(
|
||||
channel: channel,
|
||||
borderRadius:
|
||||
channelHeaderTheme.avatarTheme?.borderRadius,
|
||||
constraints:
|
||||
@@ -172,12 +180,13 @@ class ChannelHeader extends StatelessWidget implements PreferredSizeWidget {
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: <Widget>[
|
||||
title ??
|
||||
ChannelName(
|
||||
StreamChannelName(
|
||||
channel: channel,
|
||||
textStyle: channelHeaderTheme.titleStyle,
|
||||
),
|
||||
const SizedBox(height: 2),
|
||||
subtitle ??
|
||||
ChannelInfo(
|
||||
StreamChannelInfo(
|
||||
showTypingIndicator: showTypingIndicator,
|
||||
channel: channel,
|
||||
textStyle: channelHeaderTheme.subtitleStyle,
|
||||
|
||||
@@ -3,10 +3,16 @@ import 'package:flutter/material.dart';
|
||||
import 'package:stream_chat_flutter/src/extension.dart';
|
||||
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
|
||||
|
||||
/// {@macro channel_info}
|
||||
@Deprecated("Use 'StreamChannelInfo' instead")
|
||||
typedef ChannelInfo = StreamChannelInfo;
|
||||
|
||||
/// {@template channel_info}
|
||||
/// Widget which shows channel info
|
||||
class ChannelInfo extends StatelessWidget {
|
||||
/// Constructor which creates a [ChannelInfo] widget
|
||||
const ChannelInfo({
|
||||
/// {@endtemplate}
|
||||
class StreamChannelInfo extends StatelessWidget {
|
||||
/// Constructor which creates a [StreamChannelInfo] widget
|
||||
const StreamChannelInfo({
|
||||
Key? key,
|
||||
required this.channel,
|
||||
this.textStyle,
|
||||
@@ -32,7 +38,7 @@ class ChannelInfo extends StatelessWidget {
|
||||
return BetterStreamBuilder<List<Member>>(
|
||||
stream: channel.state!.membersStream,
|
||||
initialData: channel.state!.members,
|
||||
builder: (context, data) => ConnectionStatusBuilder(
|
||||
builder: (context, data) => StreamConnectionStatusBuilder(
|
||||
statusBuilder: (context, status) {
|
||||
switch (status) {
|
||||
case ConnectionStatus.connected:
|
||||
@@ -60,12 +66,13 @@ class ChannelInfo extends StatelessWidget {
|
||||
var text = context.translations.membersCountText(memberCount);
|
||||
final onlineCount =
|
||||
members?.where((m) => m.user?.online == true).length ?? 0;
|
||||
if (onlineCount > 0) {
|
||||
if (channel.ownCapabilities.contains(PermissionType.connectEvents) &&
|
||||
onlineCount > 0) {
|
||||
text += ', ${context.translations.watchersCountText(onlineCount)}';
|
||||
}
|
||||
alternativeWidget = Text(
|
||||
text,
|
||||
style: ChannelHeaderTheme.of(context).subtitleStyle,
|
||||
style: StreamChannelHeaderTheme.of(context).subtitleStyle,
|
||||
);
|
||||
} else {
|
||||
final userId = StreamChat.of(context).currentUser?.id;
|
||||
@@ -93,11 +100,12 @@ class ChannelInfo extends StatelessWidget {
|
||||
return alternativeWidget ?? const Offstage();
|
||||
}
|
||||
|
||||
return TypingIndicator(
|
||||
parentId: parentId,
|
||||
alignment: Alignment.center,
|
||||
alternativeWidget: alternativeWidget,
|
||||
style: textStyle,
|
||||
return Align(
|
||||
child: StreamTypingIndicator(
|
||||
parentId: parentId,
|
||||
style: textStyle,
|
||||
alternativeWidget: alternativeWidget,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -11,7 +11,11 @@ typedef TitleBuilder = Widget Function(
|
||||
StreamChatClient client,
|
||||
);
|
||||
|
||||
///
|
||||
/// {@macro channel_list_header}
|
||||
@Deprecated("Use 'StreamChannelListHeader' instead")
|
||||
typedef ChannelListHeader = StreamChannelListHeader;
|
||||
|
||||
/// {@template channel_list_header}
|
||||
/// It shows the current [StreamChatClient] status.
|
||||
///
|
||||
/// ```dart
|
||||
@@ -43,11 +47,13 @@ typedef TitleBuilder = Widget Function(
|
||||
/// if you don't have it in the widget tree.
|
||||
///
|
||||
/// The widget components render the ui based on the first ancestor of type
|
||||
/// [StreamChatTheme] and on its [ChannelListHeaderThemeData] property.
|
||||
/// [StreamChatTheme] and on its [StreamChannelListHeaderThemeData] property.
|
||||
/// Modify it to change the widget appearance.
|
||||
class ChannelListHeader extends StatelessWidget implements PreferredSizeWidget {
|
||||
/// {@endtemplate}
|
||||
class StreamChannelListHeader extends StatelessWidget
|
||||
implements PreferredSizeWidget {
|
||||
/// Instantiates a ChannelListHeader
|
||||
const ChannelListHeader({
|
||||
const StreamChannelListHeader({
|
||||
Key? key,
|
||||
this.client,
|
||||
this.titleBuilder,
|
||||
@@ -91,14 +97,14 @@ class ChannelListHeader extends StatelessWidget implements PreferredSizeWidget {
|
||||
/// By default it shows the new chat button
|
||||
final List<Widget>? actions;
|
||||
|
||||
/// The background color for this [ChannelListHeader].
|
||||
/// The background color for this [StreamChannelListHeader].
|
||||
final Color? backgroundColor;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final _client = client ?? StreamChat.of(context).client;
|
||||
final user = _client.state.currentUser;
|
||||
return ConnectionStatusBuilder(
|
||||
return StreamConnectionStatusBuilder(
|
||||
statusBuilder: (context, status) {
|
||||
var statusString = '';
|
||||
var showStatus = true;
|
||||
@@ -117,9 +123,10 @@ class ChannelListHeader extends StatelessWidget implements PreferredSizeWidget {
|
||||
}
|
||||
|
||||
final chatThemeData = StreamChatTheme.of(context);
|
||||
final channelListHeaderThemeData = ChannelListHeaderTheme.of(context);
|
||||
final channelListHeaderThemeData =
|
||||
StreamChannelListHeaderTheme.of(context);
|
||||
final theme = Theme.of(context);
|
||||
return InfoTile(
|
||||
return StreamInfoTile(
|
||||
showMessage: showConnectionStateTile && showStatus,
|
||||
message: statusString,
|
||||
child: AppBar(
|
||||
@@ -135,7 +142,7 @@ class ChannelListHeader extends StatelessWidget implements PreferredSizeWidget {
|
||||
leading: leading ??
|
||||
Center(
|
||||
child: user != null
|
||||
? UserAvatar(
|
||||
? StreamUserAvatar(
|
||||
user: user,
|
||||
showOnlineStatus: false,
|
||||
onTap: onUserAvatarTap ??
|
||||
@@ -156,7 +163,7 @@ class ChannelListHeader extends StatelessWidget implements PreferredSizeWidget {
|
||||
[
|
||||
StreamNeumorphicButton(
|
||||
child: IconButton(
|
||||
icon: ConnectionStatusBuilder(
|
||||
icon: StreamConnectionStatusBuilder(
|
||||
statusBuilder: (context, status) {
|
||||
Color? color;
|
||||
switch (status) {
|
||||
@@ -234,10 +241,11 @@ class ChannelListHeader extends StatelessWidget implements PreferredSizeWidget {
|
||||
const SizedBox(width: 10),
|
||||
Text(
|
||||
context.translations.searchingForNetworkText,
|
||||
style: ChannelListHeaderTheme.of(context).titleStyle?.copyWith(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
style:
|
||||
StreamChannelListHeaderTheme.of(context).titleStyle?.copyWith(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
@@ -247,7 +255,7 @@ class ChannelListHeader extends StatelessWidget implements PreferredSizeWidget {
|
||||
StreamChatClient client,
|
||||
) {
|
||||
final chatThemeData = StreamChatTheme.of(context);
|
||||
final channelListHeaderTheme = ChannelListHeaderTheme.of(context);
|
||||
final channelListHeaderTheme = StreamChannelListHeaderTheme.of(context);
|
||||
return Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_slidable/flutter_slidable.dart';
|
||||
import 'package:shimmer/shimmer.dart';
|
||||
import 'package:stream_chat_flutter/src/channel_bottom_sheet.dart';
|
||||
import 'package:stream_chat_flutter/src/extension.dart';
|
||||
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
|
||||
|
||||
@@ -12,7 +10,7 @@ typedef ChannelTapCallback = void Function(Channel, Widget?);
|
||||
/// Callback called when tapping on a channel
|
||||
typedef ChannelInfoCallback = void Function(Channel);
|
||||
|
||||
/// Builder used to create a custom [ChannelPreview] from a [Channel]
|
||||
/// Builder used to create a custom [StreamChannelPreview] from a [Channel]
|
||||
typedef ChannelPreviewBuilder = Widget Function(BuildContext, Channel);
|
||||
|
||||
/// Callback for when 'View Info' is tapped
|
||||
@@ -53,6 +51,7 @@ typedef ViewInfoCallback = void Function(Channel);
|
||||
/// The widget components render the ui based on the first ancestor of
|
||||
/// type [StreamChatTheme].
|
||||
/// Modify it to change the widget appearance.
|
||||
@Deprecated("Use 'StreamChannelListView' instead")
|
||||
class ChannelListView extends StatefulWidget {
|
||||
/// Instantiate a new ChannelListView
|
||||
ChannelListView({
|
||||
@@ -245,7 +244,8 @@ class _ChannelListViewState extends State<ChannelListView> {
|
||||
child: child,
|
||||
);
|
||||
|
||||
final backgroundColor = ChannelListViewTheme.of(context).backgroundColor;
|
||||
final backgroundColor =
|
||||
StreamChannelListViewTheme.of(context).backgroundColor;
|
||||
|
||||
if (backgroundColor != null) {
|
||||
return ColoredBox(
|
||||
@@ -547,7 +547,8 @@ class _ChannelListViewState extends State<ChannelListView> {
|
||||
context: context,
|
||||
builder: (context) => StreamChannel(
|
||||
channel: channel,
|
||||
child: ChannelBottomSheet(
|
||||
child: StreamChannelInfoBottomSheet(
|
||||
channel: channel,
|
||||
onViewInfoTap: () {
|
||||
widget.onViewInfoTap?.call(channel);
|
||||
},
|
||||
@@ -556,14 +557,8 @@ class _ChannelListViewState extends State<ChannelListView> {
|
||||
);
|
||||
},
|
||||
),
|
||||
if ([
|
||||
'admin',
|
||||
'owner',
|
||||
].contains(channel.state!.members
|
||||
.firstWhereOrNull(
|
||||
(m) => m.userId == channel.client.state.currentUser?.id,
|
||||
)
|
||||
?.role))
|
||||
if (channel.ownCapabilities
|
||||
.contains(PermissionType.deleteChannel))
|
||||
IconSlideAction(
|
||||
color: backgroundColor,
|
||||
iconWidget: StreamSvgIcon.delete(
|
||||
@@ -642,7 +637,7 @@ class _ChannelListViewState extends State<ChannelListView> {
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
ChannelAvatar(
|
||||
StreamChannelAvatar(
|
||||
channel: channel,
|
||||
borderRadius: BorderRadius.circular(32),
|
||||
selected: selected,
|
||||
@@ -657,8 +652,9 @@ class _ChannelListViewState extends State<ChannelListView> {
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8),
|
||||
child: StreamChannel(
|
||||
channel: channel,
|
||||
child: const ChannelName(
|
||||
textStyle: TextStyle(
|
||||
child: StreamChannelName(
|
||||
channel: channel,
|
||||
textStyle: const TextStyle(
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
|
||||
@@ -6,6 +6,7 @@ import 'package:stream_chat_flutter/stream_chat_flutter.dart';
|
||||
///
|
||||
/// The widget uses a [StreamBuilder] to render the channel information
|
||||
/// image as soon as it updates.
|
||||
@Deprecated("Use 'StreamChannelName' instead")
|
||||
class ChannelName extends StatelessWidget {
|
||||
/// Instantiate a new ChannelName
|
||||
const ChannelName({
|
||||
|
||||
@@ -4,6 +4,7 @@ import 'package:flutter/material.dart';
|
||||
import 'package:stream_chat_flutter/src/extension.dart';
|
||||
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
|
||||
|
||||
/// {@template channel_preview}
|
||||
/// 
|
||||
/// 
|
||||
///
|
||||
@@ -13,11 +14,13 @@ import 'package:stream_chat_flutter/stream_chat_flutter.dart';
|
||||
/// image as soon as it updates.
|
||||
///
|
||||
/// Usually you don't use this widget as it's the default channel preview
|
||||
/// used by [ChannelListView].
|
||||
/// used by [StreamChannelListView].
|
||||
///
|
||||
/// The widget renders the ui based on the first ancestor of type
|
||||
/// [StreamChatTheme].
|
||||
/// Modify it to change the widget appearance.
|
||||
/// {@endtemplate}
|
||||
@Deprecated("Use 'StreamChannelListTile' instead")
|
||||
class ChannelPreview extends StatelessWidget {
|
||||
/// Constructor for creating [ChannelPreview]
|
||||
const ChannelPreview({
|
||||
@@ -52,7 +55,7 @@ class ChannelPreview extends StatelessWidget {
|
||||
final Widget? subtitle;
|
||||
|
||||
/// Widget rendering the leading element, by default
|
||||
/// it shows the [ChannelAvatar]
|
||||
/// it shows the [StreamChannelAvatar]
|
||||
final Widget? leading;
|
||||
|
||||
/// Widget rendering the trailing element,
|
||||
@@ -60,7 +63,7 @@ class ChannelPreview extends StatelessWidget {
|
||||
final Widget? trailing;
|
||||
|
||||
/// Widget rendering the sending indicator,
|
||||
/// by default it uses the [SendingIndicator] widget
|
||||
/// by default it uses the [StreamSendingIndicator] widget
|
||||
final Widget? sendingIndicator;
|
||||
|
||||
@override
|
||||
@@ -80,13 +83,18 @@ class ChannelPreview extends StatelessWidget {
|
||||
),
|
||||
onTap: () => onTap?.call(channel),
|
||||
onLongPress: () => onLongPress?.call(channel),
|
||||
leading: leading ?? ChannelAvatar(onTap: onImageTap),
|
||||
leading: leading ??
|
||||
StreamChannelAvatar(
|
||||
channel: channel,
|
||||
onTap: onImageTap,
|
||||
),
|
||||
title: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: <Widget>[
|
||||
Flexible(
|
||||
child: title ??
|
||||
ChannelName(
|
||||
StreamChannelName(
|
||||
channel: channel,
|
||||
textStyle: channelPreviewTheme.titleStyle,
|
||||
),
|
||||
),
|
||||
@@ -100,7 +108,7 @@ class ChannelPreview extends StatelessWidget {
|
||||
e.user!.id == channel.client.state.currentUser?.id)) {
|
||||
return const SizedBox();
|
||||
}
|
||||
return UnreadIndicator(
|
||||
return StreamUnreadIndicator(
|
||||
cid: channel.cid,
|
||||
);
|
||||
},
|
||||
@@ -136,7 +144,7 @@ class ChannelPreview extends StatelessWidget {
|
||||
)));
|
||||
final isMessageRead = readList.length >=
|
||||
(channel.memberCount ?? 0) - 1;
|
||||
return SendingIndicator(
|
||||
return StreamSendingIndicator(
|
||||
message: lastMessage!,
|
||||
size: channelPreviewTheme.indicatorIconSize,
|
||||
isMessageRead: isMessageRead,
|
||||
@@ -204,7 +212,7 @@ class ChannelPreview extends StatelessWidget {
|
||||
],
|
||||
);
|
||||
}
|
||||
return TypingIndicator(
|
||||
return StreamTypingIndicator(
|
||||
channel: channel,
|
||||
alternativeWidget: _buildLastMessage(context),
|
||||
style: channelPreviewTheme.subtitleStyle,
|
||||
|
||||
@@ -2,10 +2,17 @@ import 'package:flutter/material.dart';
|
||||
import 'package:stream_chat_flutter/src/extension.dart';
|
||||
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
|
||||
|
||||
/// {@macro commands_overlay}
|
||||
@Deprecated("Use 'StreamCommandsOverlay' instead")
|
||||
typedef CommandsOverlay = StreamCommandsOverlay;
|
||||
|
||||
/// {@template commands_overlay}
|
||||
/// Overlay for displaying commands that can be used
|
||||
class CommandsOverlay extends StatelessWidget {
|
||||
/// Constructor for creating a [CommandsOverlay]
|
||||
const CommandsOverlay({
|
||||
/// to interact with the channel.
|
||||
/// {@endtemplate}
|
||||
class StreamCommandsOverlay extends StatelessWidget {
|
||||
/// Constructor for creating a [StreamCommandsOverlay]
|
||||
const StreamCommandsOverlay({
|
||||
required this.text,
|
||||
required this.onCommandResult,
|
||||
required this.size,
|
||||
|
||||
@@ -1,14 +1,20 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
|
||||
|
||||
/// {@macro connection_status_builder}
|
||||
@Deprecated("Use 'StreamConnectionStatusBuilder' instead")
|
||||
typedef ConnectionStatusBuilder = StreamConnectionStatusBuilder;
|
||||
|
||||
/// {@template connection_status_builder}
|
||||
/// Widget that builds itself based on the latest snapshot of interaction with
|
||||
/// a [Stream] of type [ConnectionStatus].
|
||||
///
|
||||
/// The widget will use the closest [StreamChatClient.wsConnectionStatusStream]
|
||||
/// in case no stream is provided.
|
||||
class ConnectionStatusBuilder extends StatelessWidget {
|
||||
/// {@endtemplate}
|
||||
class StreamConnectionStatusBuilder extends StatelessWidget {
|
||||
/// Creates a new ConnectionStatusBuilder
|
||||
const ConnectionStatusBuilder({
|
||||
const StreamConnectionStatusBuilder({
|
||||
Key? key,
|
||||
required this.statusBuilder,
|
||||
this.connectionStatusStream,
|
||||
|
||||
@@ -3,10 +3,16 @@ import 'package:jiffy/jiffy.dart';
|
||||
import 'package:stream_chat_flutter/src/extension.dart';
|
||||
import 'package:stream_chat_flutter/src/stream_chat_theme.dart';
|
||||
|
||||
/// {@macro date_divider}
|
||||
@Deprecated("Use 'StreamDateDivider' instead")
|
||||
typedef DateDivider = StreamDateDivider;
|
||||
|
||||
/// {@template date_divider}
|
||||
/// It shows a date divider depending on the date difference
|
||||
class DateDivider extends StatelessWidget {
|
||||
/// Constructor for creating a [DateDivider]
|
||||
const DateDivider({
|
||||
/// {@endtemplate}
|
||||
class StreamDateDivider extends StatelessWidget {
|
||||
/// Constructor for creating a [StreamDateDivider]
|
||||
const StreamDateDivider({
|
||||
Key? key,
|
||||
required this.dateTime,
|
||||
this.uppercase = false,
|
||||
|
||||
@@ -3,10 +3,16 @@ import 'package:stream_chat_flutter/src/extension.dart';
|
||||
import 'package:stream_chat_flutter/src/stream_chat_theme.dart';
|
||||
import 'package:stream_chat_flutter/src/theme/themes.dart';
|
||||
|
||||
/// Widget to display deleted message
|
||||
class DeletedMessage extends StatelessWidget {
|
||||
/// Constructor to create [DeletedMessage]
|
||||
const DeletedMessage({
|
||||
/// {@macro deleted_message}
|
||||
@Deprecated("Use 'StreamDeletedMessage' instead")
|
||||
typedef DeletedMessage = StreamDeletedMessage;
|
||||
|
||||
/// {@template deleted_message}
|
||||
/// Widget to display deleted message.
|
||||
/// {@endtemplate}
|
||||
class StreamDeletedMessage extends StatelessWidget {
|
||||
/// Constructor to create [StreamDeletedMessage]
|
||||
const StreamDeletedMessage({
|
||||
Key? key,
|
||||
required this.messageTheme,
|
||||
this.borderRadiusGeometry,
|
||||
@@ -16,7 +22,7 @@ class DeletedMessage extends StatelessWidget {
|
||||
}) : super(key: key);
|
||||
|
||||
/// The theme of the message
|
||||
final MessageThemeData messageTheme;
|
||||
final StreamMessageThemeData messageTheme;
|
||||
|
||||
/// The border radius of the message text
|
||||
final BorderRadiusGeometry? borderRadiusGeometry;
|
||||
|
||||
@@ -4,10 +4,16 @@ import 'package:stream_chat_flutter/src/extension.dart';
|
||||
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
|
||||
import 'package:substring_highlight/substring_highlight.dart';
|
||||
|
||||
/// {@macro emoji_overlay}
|
||||
@Deprecated("Use 'StreamEmojiOverlay' instead")
|
||||
typedef EmojiOverlay = StreamEmojiOverlay;
|
||||
|
||||
/// {@template emoji_overlay}
|
||||
/// Overlay for displaying emoji that can be used
|
||||
class EmojiOverlay extends StatelessWidget {
|
||||
/// Constructor for creating a [EmojiOverlay]
|
||||
const EmojiOverlay({
|
||||
/// {@endtemplate}
|
||||
class StreamEmojiOverlay extends StatelessWidget {
|
||||
/// Constructor for creating a [StreamEmojiOverlay]
|
||||
const StreamEmojiOverlay({
|
||||
required this.query,
|
||||
required this.onEmojiResult,
|
||||
required this.size,
|
||||
|
||||
@@ -46,7 +46,7 @@ extension IterableX<T> on Iterable<T> {
|
||||
extension PlatformFileX on PlatformFile {
|
||||
/// Converts the [PlatformFile] into [AttachmentFile]
|
||||
AttachmentFile get toAttachmentFile => AttachmentFile(
|
||||
//ignore: avoid_redundant_argument_values
|
||||
// ignore: avoid_redundant_argument_values
|
||||
path: kIsWeb ? null : path,
|
||||
name: name,
|
||||
bytes: bytes,
|
||||
@@ -225,3 +225,50 @@ extension UserListX on List<User> {
|
||||
return entries.map((e) => e.key).toList(growable: false);
|
||||
}
|
||||
}
|
||||
|
||||
/// Extensions on Message
|
||||
extension MessageX on Message {
|
||||
/// It replaces the user mentions with the actual user names.
|
||||
Message replaceMentions({bool linkify = true}) {
|
||||
var messageTextToRender = text;
|
||||
for (final user in mentionedUsers.toSet()) {
|
||||
final userId = user.id;
|
||||
final userName = user.name;
|
||||
if (linkify) {
|
||||
messageTextToRender = messageTextToRender?.replaceAll(
|
||||
'@$userId',
|
||||
'[@$userName](@${userName.replaceAll(' ', '')})',
|
||||
);
|
||||
} else {
|
||||
messageTextToRender = messageTextToRender?.replaceAll(
|
||||
'@$userId',
|
||||
'@$userName',
|
||||
);
|
||||
}
|
||||
}
|
||||
return copyWith(text: messageTextToRender);
|
||||
}
|
||||
|
||||
/// It returns the message with the translated text if available locally
|
||||
Message translate(String language) =>
|
||||
copyWith(text: i18n?['${language}_text'] ?? text);
|
||||
|
||||
/// It returns the message replacing the mentioned user names with
|
||||
/// the respective user ids
|
||||
Message replaceMentionsWithId() {
|
||||
if (mentionedUsers.isEmpty) return this;
|
||||
|
||||
var messageTextToSend = text;
|
||||
if (messageTextToSend == null) return this;
|
||||
|
||||
for (final user in mentionedUsers.toSet()) {
|
||||
final userName = user.name;
|
||||
messageTextToSend = messageTextToSend!.replaceAll(
|
||||
'@$userName',
|
||||
'@${user.id}',
|
||||
);
|
||||
}
|
||||
|
||||
return copyWith(text: messageTextToSend);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,10 +21,16 @@ enum ReturnActionType {
|
||||
/// Callback when show message is tapped
|
||||
typedef ShowMessageCallback = void Function(Message message, Channel channel);
|
||||
|
||||
/// {@macro full_screen_media}
|
||||
@Deprecated("Use 'StreamFullScreenMedia' instead")
|
||||
typedef FullScreenMedia = StreamFullScreenMedia;
|
||||
|
||||
/// {@template full_screen_media}
|
||||
/// A full screen image widget
|
||||
class FullScreenMedia extends StatefulWidget {
|
||||
/// {@endtemplate}
|
||||
class StreamFullScreenMedia extends StatefulWidget {
|
||||
/// Instantiate a new FullScreenImage
|
||||
const FullScreenMedia({
|
||||
const StreamFullScreenMedia({
|
||||
Key? key,
|
||||
required this.mediaAttachments,
|
||||
required this.message,
|
||||
@@ -60,10 +66,10 @@ class FullScreenMedia extends StatefulWidget {
|
||||
final bool autoplayVideos;
|
||||
|
||||
@override
|
||||
_FullScreenMediaState createState() => _FullScreenMediaState();
|
||||
_StreamFullScreenMediaState createState() => _StreamFullScreenMediaState();
|
||||
}
|
||||
|
||||
class _FullScreenMediaState extends State<FullScreenMedia>
|
||||
class _StreamFullScreenMediaState extends State<StreamFullScreenMedia>
|
||||
with SingleTickerProviderStateMixin {
|
||||
late final AnimationController _animationController;
|
||||
late final PageController _pageController;
|
||||
@@ -172,7 +178,7 @@ class _FullScreenMediaState extends State<FullScreenMedia>
|
||||
),
|
||||
backgroundDecoration: BoxDecoration(
|
||||
color: ColorTween(
|
||||
begin: ChannelHeaderTheme.of(context).color,
|
||||
begin: StreamChannelHeaderTheme.of(context).color,
|
||||
end: Colors.black,
|
||||
).lerp(_curvedAnimation.value),
|
||||
),
|
||||
@@ -221,7 +227,7 @@ class _FullScreenMediaState extends State<FullScreenMedia>
|
||||
builder: (context, value, child) => Column(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
GalleryHeader(
|
||||
StreamGalleryHeader(
|
||||
userName: widget.userName,
|
||||
sentAt: context.translations.sentAtText(
|
||||
date: widget.message.createdAt,
|
||||
@@ -242,7 +248,7 @@ class _FullScreenMediaState extends State<FullScreenMedia>
|
||||
widget.attachmentActionsModalBuilder,
|
||||
),
|
||||
if (!widget.message.isEphemeral)
|
||||
GalleryFooter(
|
||||
StreamGalleryFooter(
|
||||
currentPage: value,
|
||||
totalPages: widget.mediaAttachments.length,
|
||||
mediaAttachments: widget.mediaAttachments,
|
||||
|
||||
@@ -9,10 +9,17 @@ import 'package:stream_chat_flutter/src/extension.dart';
|
||||
import 'package:stream_chat_flutter/src/video_thumbnail_image.dart';
|
||||
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
|
||||
|
||||
/// {@macro gallery_footer}
|
||||
@Deprecated("Use 'StreamGalleryFooter' instead")
|
||||
typedef GalleryFooter = StreamGalleryFooter;
|
||||
|
||||
/// {@template gallery_footer}
|
||||
/// Footer widget for media display
|
||||
class GalleryFooter extends StatefulWidget implements PreferredSizeWidget {
|
||||
/// {@endtemplate}
|
||||
class StreamGalleryFooter extends StatefulWidget
|
||||
implements PreferredSizeWidget {
|
||||
/// Creates a channel header
|
||||
const GalleryFooter({
|
||||
const StreamGalleryFooter({
|
||||
Key? key,
|
||||
required this.message,
|
||||
this.onBackPressed,
|
||||
@@ -51,22 +58,22 @@ class GalleryFooter extends StatefulWidget implements PreferredSizeWidget {
|
||||
/// Callback when media is selected
|
||||
final ValueChanged<int>? mediaSelectedCallBack;
|
||||
|
||||
/// The background color of this [GalleryFooter].
|
||||
/// The background color of this [StreamGalleryFooter].
|
||||
final Color? backgroundColor;
|
||||
|
||||
@override
|
||||
_GalleryFooterState createState() => _GalleryFooterState();
|
||||
_StreamGalleryFooterState createState() => _StreamGalleryFooterState();
|
||||
|
||||
@override
|
||||
final Size preferredSize;
|
||||
}
|
||||
|
||||
class _GalleryFooterState extends State<GalleryFooter> {
|
||||
class _StreamGalleryFooterState extends State<StreamGalleryFooter> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
const showShareButton = !kIsWeb;
|
||||
final mediaQueryData = MediaQuery.of(context);
|
||||
final galleryFooterThemeData = GalleryFooterTheme.of(context);
|
||||
final galleryFooterThemeData = StreamGalleryFooterTheme.of(context);
|
||||
return SizedBox.fromSize(
|
||||
size: Size(
|
||||
mediaQueryData.size.width,
|
||||
@@ -149,7 +156,7 @@ class _GalleryFooterState extends State<GalleryFooter> {
|
||||
|
||||
void _showPhotosModal(context) {
|
||||
final chatThemeData = StreamChatTheme.of(context);
|
||||
final galleryFooterThemeData = GalleryFooterTheme.of(context);
|
||||
final galleryFooterThemeData = StreamGalleryFooterTheme.of(context);
|
||||
showModalBottomSheet(
|
||||
context: context,
|
||||
barrierColor: galleryFooterThemeData.bottomSheetBarrierColor,
|
||||
@@ -222,7 +229,7 @@ class _GalleryFooterState extends State<GalleryFooter> {
|
||||
onTap: () => widget.mediaSelectedCallBack!(index),
|
||||
child: FittedBox(
|
||||
fit: BoxFit.cover,
|
||||
child: VideoThumbnailImage(
|
||||
child: StreamVideoThumbnailImage(
|
||||
video: (attachment.file?.path ??
|
||||
attachment.assetUrl)!,
|
||||
),
|
||||
@@ -264,7 +271,7 @@ class _GalleryFooterState extends State<GalleryFooter> {
|
||||
),
|
||||
],
|
||||
),
|
||||
child: UserAvatar(
|
||||
child: StreamUserAvatar(
|
||||
user: widget.message.user!,
|
||||
constraints:
|
||||
BoxConstraints.tight(const Size(24, 24)),
|
||||
|
||||
@@ -15,10 +15,17 @@ typedef AttachmentActionsBuilder = Widget Function(
|
||||
AttachmentActionsModal defaultActionsModal,
|
||||
);
|
||||
|
||||
/// {@macro gallery_header}
|
||||
@Deprecated("Use 'StreamGalleryHeader' instead")
|
||||
typedef GalleryHeader = StreamGalleryHeader;
|
||||
|
||||
/// {@template gallery_header}
|
||||
/// Header/AppBar widget for media display screen
|
||||
class GalleryHeader extends StatelessWidget implements PreferredSizeWidget {
|
||||
/// {@endtemplate}
|
||||
class StreamGalleryHeader extends StatelessWidget
|
||||
implements PreferredSizeWidget {
|
||||
/// Creates a channel header
|
||||
const GalleryHeader({
|
||||
const StreamGalleryHeader({
|
||||
Key? key,
|
||||
required this.message,
|
||||
this.currentIndex = 0,
|
||||
@@ -62,7 +69,7 @@ class GalleryHeader extends StatelessWidget implements PreferredSizeWidget {
|
||||
/// Stores the current index of media shown
|
||||
final int currentIndex;
|
||||
|
||||
/// The background color of this [GalleryHeader].
|
||||
/// The background color of this [StreamGalleryHeader].
|
||||
final Color? backgroundColor;
|
||||
|
||||
/// Widget builder for attachment actions modal
|
||||
@@ -72,7 +79,7 @@ class GalleryHeader extends StatelessWidget implements PreferredSizeWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final galleryHeaderThemeData = GalleryHeaderTheme.of(context);
|
||||
final galleryHeaderThemeData = StreamGalleryHeaderTheme.of(context);
|
||||
final theme = Theme.of(context);
|
||||
return AppBar(
|
||||
toolbarTextStyle: theme.textTheme.bodyText2,
|
||||
|
||||
@@ -3,10 +3,16 @@ import 'dart:ui' as ui;
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
/// {@macro gradient_avatar}
|
||||
@Deprecated("Use 'StreamGradientAvatar' instead")
|
||||
typedef GradientAvatar = StreamGradientAvatar;
|
||||
|
||||
/// {@template gradient_avatar}
|
||||
/// Fallback user avatar with a polygon gradient overlayed with text
|
||||
class GradientAvatar extends StatefulWidget {
|
||||
/// Constructor for [GradientAvatar]
|
||||
const GradientAvatar({
|
||||
/// {@endtemplate}
|
||||
class StreamGradientAvatar extends StatefulWidget {
|
||||
/// Constructor for [StreamGradientAvatar]
|
||||
const StreamGradientAvatar({
|
||||
Key? key,
|
||||
required this.name,
|
||||
required this.userId,
|
||||
@@ -19,10 +25,10 @@ class GradientAvatar extends StatefulWidget {
|
||||
final String userId;
|
||||
|
||||
@override
|
||||
_GradientAvatarState createState() => _GradientAvatarState();
|
||||
_StreamGradientAvatarState createState() => _StreamGradientAvatarState();
|
||||
}
|
||||
|
||||
class _GradientAvatarState extends State<GradientAvatar> {
|
||||
class _StreamGradientAvatarState extends State<StreamGradientAvatar> {
|
||||
@override
|
||||
Widget build(BuildContext context) => Center(
|
||||
child: RepaintBoundary(
|
||||
|
||||
@@ -1,11 +1,18 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
|
||||
|
||||
/// {@macro group_avatar}
|
||||
@Deprecated("Use 'StreamGroupAvatar' instead")
|
||||
typedef GroupAvatar = StreamGroupAvatar;
|
||||
|
||||
/// {@template group_avatar}
|
||||
/// Widget for constructing a group of images
|
||||
class GroupAvatar extends StatelessWidget {
|
||||
/// Constructor for creating a [GroupAvatar]
|
||||
const GroupAvatar({
|
||||
/// {@endtemplate}
|
||||
class StreamGroupAvatar extends StatelessWidget {
|
||||
/// Constructor for creating a [StreamGroupAvatar]
|
||||
const StreamGroupAvatar({
|
||||
Key? key,
|
||||
this.channel,
|
||||
required this.members,
|
||||
this.constraints,
|
||||
this.onTap,
|
||||
@@ -15,6 +22,9 @@ class GroupAvatar extends StatelessWidget {
|
||||
this.selectionThickness = 4,
|
||||
}) : super(key: key);
|
||||
|
||||
/// The channel of the avatar
|
||||
final Channel? channel;
|
||||
|
||||
/// List of images to display
|
||||
final List<Member> members;
|
||||
|
||||
@@ -38,7 +48,7 @@ class GroupAvatar extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final channel = StreamChannel.of(context).channel;
|
||||
final channel = this.channel ?? StreamChannel.of(context).channel;
|
||||
|
||||
assert(channel.state != null, 'Channel ${channel.id} is not initialized');
|
||||
|
||||
@@ -80,7 +90,7 @@ class GroupAvatar extends StatelessWidget {
|
||||
),
|
||||
),
|
||||
initialData: member,
|
||||
builder: (context, member) => UserAvatar(
|
||||
builder: (context, member) => StreamUserAvatar(
|
||||
showOnlineStatus: false,
|
||||
user: member.user!,
|
||||
borderRadius: BorderRadius.zero,
|
||||
@@ -118,7 +128,8 @@ class GroupAvatar extends StatelessWidget {
|
||||
),
|
||||
),
|
||||
initialData: member,
|
||||
builder: (context, member) => UserAvatar(
|
||||
builder: (context, member) =>
|
||||
StreamUserAvatar(
|
||||
showOnlineStatus: false,
|
||||
user: member.user!,
|
||||
borderRadius: BorderRadius.zero,
|
||||
|
||||
@@ -1,10 +1,16 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
|
||||
|
||||
/// {@macro image_group}
|
||||
@Deprecated("Use 'StreamImageGroup' instead")
|
||||
typedef ImageGroup = StreamImageGroup;
|
||||
|
||||
/// {@template image_group}
|
||||
/// Widget for constructing a group of images in message
|
||||
class ImageGroup extends StatelessWidget {
|
||||
/// Constructor for creating [ImageGroup] widget
|
||||
const ImageGroup({
|
||||
/// {@endtemplate}
|
||||
class StreamImageGroup extends StatelessWidget {
|
||||
/// Constructor for creating [StreamImageGroup] widget
|
||||
const StreamImageGroup({
|
||||
Key? key,
|
||||
required this.images,
|
||||
required this.message,
|
||||
@@ -27,8 +33,8 @@ class ImageGroup extends StatelessWidget {
|
||||
/// Message which images are attached to
|
||||
final Message message;
|
||||
|
||||
/// [MessageThemeData] to apply to message
|
||||
final MessageThemeData messageTheme;
|
||||
/// [StreamMessageThemeData] to apply to message
|
||||
final StreamMessageThemeData messageTheme;
|
||||
|
||||
/// Size of iamges
|
||||
final Size size;
|
||||
@@ -129,7 +135,7 @@ class ImageGroup extends StatelessWidget {
|
||||
MaterialPageRoute(
|
||||
builder: (context) => StreamChannel(
|
||||
channel: channel,
|
||||
child: FullScreenMedia(
|
||||
child: StreamFullScreenMedia(
|
||||
mediaAttachments: images,
|
||||
startIndex: index,
|
||||
userName: message.user?.name,
|
||||
@@ -142,7 +148,7 @@ class ImageGroup extends StatelessWidget {
|
||||
if (res != null) onReturnAction?.call(res);
|
||||
}
|
||||
|
||||
Widget _buildImage(BuildContext context, int index) => ImageAttachment(
|
||||
Widget _buildImage(BuildContext context, int index) => StreamImageAttachment(
|
||||
attachment: images[index],
|
||||
size: size,
|
||||
message: message,
|
||||
|
||||
@@ -2,10 +2,16 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter_portal/flutter_portal.dart';
|
||||
import 'package:stream_chat_flutter/src/stream_chat_theme.dart';
|
||||
|
||||
/// {@macro info_tile}
|
||||
@Deprecated("Use 'StreamInfoTile' instead")
|
||||
typedef InfoTile = StreamInfoTile;
|
||||
|
||||
/// {@template info_tile}
|
||||
/// Tile to display a message, used in stream chat to display connection status
|
||||
class InfoTile extends StatelessWidget {
|
||||
/// Constructor for creating an [InfoTile] widget
|
||||
const InfoTile({
|
||||
/// {@endtemplate}
|
||||
class StreamInfoTile extends StatelessWidget {
|
||||
/// Constructor for creating an [StreamInfoTile] widget
|
||||
const StreamInfoTile({
|
||||
Key? key,
|
||||
required this.message,
|
||||
required this.child,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import 'package:jiffy/jiffy.dart';
|
||||
import 'package:stream_chat_flutter/src/connection_status_builder.dart';
|
||||
import 'package:stream_chat_flutter/src/message_input.dart';
|
||||
import 'package:stream_chat_flutter/src/message_input/message_input.dart';
|
||||
import 'package:stream_chat_flutter/src/message_list_view.dart';
|
||||
import 'package:stream_chat_flutter/src/message_search_list_view.dart';
|
||||
import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'
|
||||
@@ -59,7 +59,7 @@ abstract class Translations {
|
||||
/// The error shown when loading messages fails
|
||||
String get loadingMessagesError;
|
||||
|
||||
/// The text for showing the result count in [MessageSearchListView]
|
||||
/// The text for showing the result count in [StreamMessageSearchListView]
|
||||
String resultCountText(int count);
|
||||
|
||||
/// The text for showing the message is deleted
|
||||
@@ -74,44 +74,47 @@ abstract class Translations {
|
||||
/// The text for showing there are no chats
|
||||
String get emptyChatMessagesText;
|
||||
|
||||
/// The text for showing the thread separator in case [MessageListView]
|
||||
/// The text for showing the thread separator in case [StreamMessageListView]
|
||||
/// contains a parent message
|
||||
String threadSeparatorText(int replyCount);
|
||||
|
||||
/// The label for "connected" in [ConnectionStatusBuilder]
|
||||
/// The label for "connected" in [StreamConnectionStatusBuilder]
|
||||
String get connectedLabel;
|
||||
|
||||
/// The label for "disconnected" in [ConnectionStatusBuilder]
|
||||
/// The label for "disconnected" in [StreamConnectionStatusBuilder]
|
||||
String get disconnectedLabel;
|
||||
|
||||
/// The label for "reconnecting" in [ConnectionStatusBuilder]
|
||||
/// The label for "reconnecting" in [StreamConnectionStatusBuilder]
|
||||
String get reconnectingLabel;
|
||||
|
||||
/// The label for also send as direct message "checkbox"" in [MessageInput]
|
||||
/// The label for also send as direct message "checkbox"" in [StreamMessageInput]
|
||||
String get alsoSendAsDirectMessageLabel;
|
||||
|
||||
/// The label for search Gif
|
||||
String get searchGifLabel;
|
||||
|
||||
/// The label for the MessageInput hint when permission denied on sendMessage
|
||||
String get sendMessagePermissionError;
|
||||
|
||||
/// The label for add a comment or send in case of
|
||||
/// attachments inside [MessageInput]
|
||||
/// attachments inside [StreamMessageInput]
|
||||
String get addACommentOrSendLabel;
|
||||
|
||||
/// The label for write a message in [MessageInput]
|
||||
/// The label for write a message in [StreamMessageInput]
|
||||
String get writeAMessageLabel;
|
||||
|
||||
/// The label for slow mode enabled in [MessageInput]
|
||||
/// The label for slow mode enabled in [StreamMessageInput]
|
||||
String get slowModeOnLabel;
|
||||
|
||||
/// The label for instant commands in [MessageInput]
|
||||
/// The label for instant commands in [StreamMessageInput]
|
||||
String get instantCommandsLabel;
|
||||
|
||||
/// The error shown in case the fi"le is too large even after compression
|
||||
/// while uploading via [MessageInput]
|
||||
/// while uploading via [StreamMessageInput]
|
||||
String fileTooLargeAfterCompressionError(double limitInMB);
|
||||
|
||||
/// The error shown in case the file is too large
|
||||
/// while uploading via [MessageInput]
|
||||
/// while uploading via [StreamMessageInput]
|
||||
String fileTooLargeError(double limitInMB);
|
||||
|
||||
/// The text for showing the query while searching for emojis
|
||||
@@ -141,6 +144,12 @@ abstract class Translations {
|
||||
/// The label for "OK"
|
||||
String get okLabel;
|
||||
|
||||
/// The label for a link disabled error
|
||||
String get linkDisabledError;
|
||||
|
||||
/// The additional info on a link disabled error
|
||||
String get linkDisabledDetails;
|
||||
|
||||
/// The label for "add more files"
|
||||
String get addMoreFilesLabel;
|
||||
|
||||
@@ -377,6 +386,10 @@ class DefaultTranslations implements Translations {
|
||||
return 'Pinned by ${pinnedBy.name}';
|
||||
}
|
||||
|
||||
@override
|
||||
String get sendMessagePermissionError =>
|
||||
'You don\'t have permission to send messages';
|
||||
|
||||
@override
|
||||
String get emptyMessagesText => 'There are no messages currently';
|
||||
|
||||
@@ -685,4 +698,11 @@ class DefaultTranslations implements Translations {
|
||||
@override
|
||||
String attachmentLimitExceedError(int limit) => """
|
||||
Attachment limit exceeded: it's not possible to add more than $limit attachments""";
|
||||
|
||||
@override
|
||||
String get linkDisabledDetails =>
|
||||
'Sending links is not allowed in this conversation.';
|
||||
|
||||
@override
|
||||
String get linkDisabledError => 'Links are disabled';
|
||||
}
|
||||
|
||||
@@ -7,21 +7,16 @@ import 'package:flutter_svg/flutter_svg.dart';
|
||||
import 'package:photo_manager/photo_manager.dart';
|
||||
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
|
||||
|
||||
extension on Duration {
|
||||
String format() {
|
||||
final s = '$this'.split('.')[0].padLeft(8, '0');
|
||||
if (s.startsWith('00:')) {
|
||||
return s.replaceFirst('00:', '');
|
||||
}
|
||||
|
||||
return s;
|
||||
}
|
||||
}
|
||||
/// {@macro media_list_view}
|
||||
@Deprecated("Use 'StreamMediaListView' instead")
|
||||
typedef MediaListView = StreamMediaListView;
|
||||
|
||||
/// {@template media_list_view}
|
||||
/// Constructs a list of media
|
||||
class MediaListView extends StatefulWidget {
|
||||
/// Constructor for creating a [MediaListView] widget
|
||||
const MediaListView({
|
||||
/// {@endtemplate}
|
||||
class StreamMediaListView extends StatefulWidget {
|
||||
/// Constructor for creating a [StreamMediaListView] widget
|
||||
const StreamMediaListView({
|
||||
Key? key,
|
||||
this.selectedIds = const [],
|
||||
this.onSelect,
|
||||
@@ -34,10 +29,10 @@ class MediaListView extends StatefulWidget {
|
||||
final void Function(AssetEntity media)? onSelect;
|
||||
|
||||
@override
|
||||
_MediaListViewState createState() => _MediaListViewState();
|
||||
_StreamMediaListViewState createState() => _StreamMediaListViewState();
|
||||
}
|
||||
|
||||
class _MediaListViewState extends State<MediaListView> {
|
||||
class _StreamMediaListViewState extends State<StreamMediaListView> {
|
||||
final _media = <AssetEntity>[];
|
||||
final ScrollController _scrollController = ScrollController();
|
||||
int _currentPage = 0;
|
||||
@@ -222,3 +217,14 @@ class MediaThumbnailProvider extends ImageProvider<MediaThumbnailProvider> {
|
||||
@override
|
||||
String toString() => '$runtimeType("${media.id}")';
|
||||
}
|
||||
|
||||
extension on Duration {
|
||||
String format() {
|
||||
final s = '$this'.split('.')[0].padLeft(8, '0');
|
||||
if (s.startsWith('00:')) {
|
||||
return s.replaceFirst('00:', '');
|
||||
}
|
||||
|
||||
return s;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,101 +0,0 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
|
||||
|
||||
/// This widget is used for showing user tiles for mentions
|
||||
/// Use [title], [subtitle], [leading], [trailing] for
|
||||
/// substituting widgets in respective positions
|
||||
@Deprecated('Use `UserMentionTile` instead. Will be removed in future release')
|
||||
class MentionTile extends StatelessWidget {
|
||||
/// Constructor for creating a [MentionTile] widget
|
||||
const MentionTile(
|
||||
this.member, {
|
||||
Key? key,
|
||||
this.title,
|
||||
this.subtitle,
|
||||
this.leading,
|
||||
this.trailing,
|
||||
}) : super(key: key);
|
||||
|
||||
/// Member to display in the tile
|
||||
final Member member;
|
||||
|
||||
/// Widget to display as title
|
||||
final Widget? title;
|
||||
|
||||
/// Widget to display below [title]
|
||||
final Widget? subtitle;
|
||||
|
||||
/// Widget at the start of the tile
|
||||
final Widget? leading;
|
||||
|
||||
/// Widget at the end of tile
|
||||
final Widget? trailing;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final chatThemeData = StreamChatTheme.of(context);
|
||||
return SizedBox(
|
||||
height: 56,
|
||||
child: Row(
|
||||
children: [
|
||||
const SizedBox(
|
||||
width: 16,
|
||||
),
|
||||
leading ??
|
||||
UserAvatar(
|
||||
constraints: BoxConstraints.tight(
|
||||
const Size(
|
||||
40,
|
||||
40,
|
||||
),
|
||||
),
|
||||
user: member.user!,
|
||||
),
|
||||
const SizedBox(
|
||||
width: 8,
|
||||
),
|
||||
Expanded(
|
||||
child: Align(
|
||||
alignment: Alignment.centerLeft,
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
title ??
|
||||
Text(
|
||||
member.user!.name,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: chatThemeData.textTheme.bodyBold,
|
||||
),
|
||||
const SizedBox(
|
||||
height: 2,
|
||||
),
|
||||
subtitle ??
|
||||
Text(
|
||||
'@${member.userId}',
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: chatThemeData.textTheme.footnoteBold.copyWith(
|
||||
color: chatThemeData.colorTheme.textLowEmphasis,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
trailing ??
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
right: 18,
|
||||
left: 8,
|
||||
),
|
||||
child: StreamSvgIcon.mentions(
|
||||
color: chatThemeData.colorTheme.accentPrimary,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,16 @@
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
|
||||
|
||||
/// {@macro message_action}
|
||||
@Deprecated("Use 'StreamMessageActions' instead")
|
||||
typedef MessageAction = StreamMessageAction;
|
||||
|
||||
/// {@template message_action}
|
||||
/// Class describing a message action
|
||||
class MessageAction {
|
||||
/// returns a new instance of a [MessageAction]
|
||||
MessageAction({
|
||||
/// {@endtemplate}
|
||||
class StreamMessageAction {
|
||||
/// returns a new instance of a [StreamMessageAction]
|
||||
StreamMessageAction({
|
||||
this.leading,
|
||||
this.title,
|
||||
this.onTap,
|
||||
|
||||
@@ -3,25 +3,31 @@ import 'package:flutter/material.dart';
|
||||
import 'package:stream_chat_flutter/src/extension.dart';
|
||||
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
|
||||
|
||||
/// {@macro message_actions_modal}
|
||||
@Deprecated("Use 'StreamMessageActionsModal' instead")
|
||||
typedef MessageActionsModal = StreamMessageActionsModal;
|
||||
|
||||
/// {@template message_actions_modal}
|
||||
/// Constructs a modal with actions for a message
|
||||
class MessageActionsModal extends StatefulWidget {
|
||||
/// Constructor for creating a [MessageActionsModal] widget
|
||||
const MessageActionsModal({
|
||||
/// {@endtemplate}
|
||||
class StreamMessageActionsModal extends StatefulWidget {
|
||||
/// Constructor for creating a [StreamMessageActionsModal] widget
|
||||
const StreamMessageActionsModal({
|
||||
Key? key,
|
||||
required this.message,
|
||||
required this.messageWidget,
|
||||
required this.messageTheme,
|
||||
this.showReactions = true,
|
||||
this.showDeleteMessage = true,
|
||||
this.showEditMessage = true,
|
||||
this.showReactions,
|
||||
this.showDeleteMessage,
|
||||
this.showEditMessage,
|
||||
this.onReplyTap,
|
||||
this.onThreadReplyTap,
|
||||
this.showCopyMessage = true,
|
||||
this.showReplyMessage = true,
|
||||
this.showResendMessage = true,
|
||||
this.showThreadReplyMessage = true,
|
||||
this.showFlagButton = true,
|
||||
this.showPinButton = true,
|
||||
this.showThreadReplyMessage,
|
||||
this.showFlagButton,
|
||||
this.showPinButton,
|
||||
this.editMessageInputBuilder,
|
||||
this.reverse = false,
|
||||
this.customActions = const [],
|
||||
@@ -43,51 +49,54 @@ class MessageActionsModal extends StatefulWidget {
|
||||
/// Message in focus for actions
|
||||
final Message message;
|
||||
|
||||
/// [MessageThemeData] for message
|
||||
final MessageThemeData messageTheme;
|
||||
/// [StreamMessageThemeData] for message
|
||||
final StreamMessageThemeData messageTheme;
|
||||
|
||||
/// Flag for showing reactions
|
||||
final bool showReactions;
|
||||
final bool? showReactions;
|
||||
|
||||
/// Callback when copy is tapped
|
||||
final OnMessageTap? onCopyTap;
|
||||
|
||||
/// Callback when delete is tapped
|
||||
final bool showDeleteMessage;
|
||||
final bool? showDeleteMessage;
|
||||
|
||||
/// Flag for showing copy action
|
||||
final bool showCopyMessage;
|
||||
|
||||
/// Flag for showing edit action
|
||||
final bool showEditMessage;
|
||||
final bool? showEditMessage;
|
||||
|
||||
/// Flag for showing resend action
|
||||
final bool showResendMessage;
|
||||
|
||||
/// Flag for showing reply action
|
||||
final bool showReplyMessage;
|
||||
final bool? showReplyMessage;
|
||||
|
||||
/// Flag for showing thread reply action
|
||||
final bool showThreadReplyMessage;
|
||||
final bool? showThreadReplyMessage;
|
||||
|
||||
/// Flag for showing flag action
|
||||
final bool showFlagButton;
|
||||
final bool? showFlagButton;
|
||||
|
||||
/// Flag for showing pin action
|
||||
final bool showPinButton;
|
||||
final bool? showPinButton;
|
||||
|
||||
/// Flag for reversing message
|
||||
final bool reverse;
|
||||
|
||||
/// List of custom actions
|
||||
final List<MessageAction> customActions;
|
||||
final List<StreamMessageAction> customActions;
|
||||
|
||||
@override
|
||||
_MessageActionsModalState createState() => _MessageActionsModalState();
|
||||
_StreamMessageActionsModalState createState() =>
|
||||
_StreamMessageActionsModalState();
|
||||
}
|
||||
|
||||
class _MessageActionsModalState extends State<MessageActionsModal> {
|
||||
class _StreamMessageActionsModalState extends State<StreamMessageActionsModal> {
|
||||
bool _showActions = true;
|
||||
late List<String> _userPermissions;
|
||||
late bool _isMyMessage;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) => _showMessageOptionsModal();
|
||||
@@ -122,6 +131,19 @@ class _MessageActionsModalState extends State<MessageActionsModal> {
|
||||
final shiftFactor =
|
||||
numberOfReactions < 5 ? (5 - numberOfReactions) * 0.1 : 0.0;
|
||||
|
||||
final hasEditPermission = _userPermissions.contains(
|
||||
PermissionType.updateAnyMessage,
|
||||
) ||
|
||||
_userPermissions.contains(PermissionType.updateOwnMessage);
|
||||
|
||||
final hasDeletePermission = _userPermissions.contains(
|
||||
PermissionType.deleteAnyMessage,
|
||||
) ||
|
||||
_userPermissions.contains(PermissionType.deleteOwnMessage);
|
||||
|
||||
final hasReactionPermission =
|
||||
_userPermissions.contains(PermissionType.sendReaction);
|
||||
|
||||
final child = Center(
|
||||
child: SingleChildScrollView(
|
||||
child: Padding(
|
||||
@@ -131,7 +153,7 @@ class _MessageActionsModalState extends State<MessageActionsModal> {
|
||||
? CrossAxisAlignment.end
|
||||
: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
if (widget.showReactions &&
|
||||
if ((widget.showReactions ?? hasReactionPermission) &&
|
||||
(widget.message.status == MessageSendingStatus.sent))
|
||||
Align(
|
||||
alignment: Alignment(
|
||||
@@ -144,7 +166,7 @@ class _MessageActionsModalState extends State<MessageActionsModal> {
|
||||
: -(1.2 - divFactor)),
|
||||
0,
|
||||
),
|
||||
child: ReactionPicker(
|
||||
child: StreamReactionPicker(
|
||||
message: widget.message,
|
||||
),
|
||||
),
|
||||
@@ -168,21 +190,35 @@ class _MessageActionsModalState extends State<MessageActionsModal> {
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
if (widget.showReplyMessage &&
|
||||
widget.message.status == MessageSendingStatus.sent)
|
||||
if (widget.showReplyMessage ??
|
||||
(_userPermissions
|
||||
.contains(PermissionType.quoteMessage) &&
|
||||
widget.message.status ==
|
||||
MessageSendingStatus.sent))
|
||||
_buildReplyButton(context),
|
||||
if (widget.showThreadReplyMessage &&
|
||||
(widget.message.status ==
|
||||
MessageSendingStatus.sent) &&
|
||||
widget.message.parentId == null)
|
||||
if (widget.showThreadReplyMessage ??
|
||||
_userPermissions
|
||||
.contains(PermissionType.sendReply) &&
|
||||
(widget.message.status ==
|
||||
MessageSendingStatus.sent) &&
|
||||
widget.message.parentId == null)
|
||||
_buildThreadReplyButton(context),
|
||||
if (widget.showResendMessage)
|
||||
_buildResendMessage(context),
|
||||
if (widget.showEditMessage) _buildEditMessage(context),
|
||||
if (widget.showEditMessage ??
|
||||
_isMyMessage && hasEditPermission)
|
||||
_buildEditMessage(context),
|
||||
if (widget.showCopyMessage) _buildCopyButton(context),
|
||||
if (widget.showFlagButton) _buildFlagButton(context),
|
||||
if (widget.showPinButton) _buildPinButton(context),
|
||||
if (widget.showDeleteMessage)
|
||||
if (widget.showFlagButton ??
|
||||
_userPermissions
|
||||
.contains(PermissionType.flagMessage))
|
||||
_buildFlagButton(context),
|
||||
if (widget.showPinButton ??
|
||||
_userPermissions
|
||||
.contains(PermissionType.pinMessage))
|
||||
_buildPinButton(context),
|
||||
if (widget.showDeleteMessage ??
|
||||
(_isMyMessage && hasDeletePermission))
|
||||
_buildDeleteButton(context),
|
||||
...widget.customActions
|
||||
.map((action) => _buildCustomAction(
|
||||
@@ -239,7 +275,7 @@ class _MessageActionsModalState extends State<MessageActionsModal> {
|
||||
|
||||
InkWell _buildCustomAction(
|
||||
BuildContext context,
|
||||
MessageAction messageAction,
|
||||
StreamMessageAction messageAction,
|
||||
) =>
|
||||
InkWell(
|
||||
onTap: () {
|
||||
@@ -560,7 +596,7 @@ class _MessageActionsModalState extends State<MessageActionsModal> {
|
||||
elevation: 2,
|
||||
clipBehavior: Clip.hardEdge,
|
||||
isScrollControlled: true,
|
||||
backgroundColor: MessageInputTheme.of(context).inputBackgroundColor,
|
||||
backgroundColor: StreamMessageInputTheme.of(context).inputBackgroundColor,
|
||||
shape: const RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.only(
|
||||
topLeft: Radius.circular(16),
|
||||
@@ -602,8 +638,10 @@ class _MessageActionsModalState extends State<MessageActionsModal> {
|
||||
if (widget.editMessageInputBuilder != null)
|
||||
widget.editMessageInputBuilder!(context, widget.message)
|
||||
else
|
||||
MessageInput(
|
||||
editMessage: widget.message,
|
||||
StreamMessageInput(
|
||||
messageInputController: MessageInputController(
|
||||
message: widget.message,
|
||||
),
|
||||
preMessageSending: (m) {
|
||||
FocusScope.of(context).unfocus();
|
||||
Navigator.pop(context);
|
||||
@@ -643,4 +681,13 @@ class _MessageActionsModalState extends State<MessageActionsModal> {
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void didChangeDependencies() {
|
||||
final newStreamChannel = StreamChannel.of(context);
|
||||
_userPermissions = newStreamChannel.channel.ownCapabilities;
|
||||
_isMyMessage =
|
||||
widget.message.user?.id == StreamChat.of(context).currentUser?.id;
|
||||
super.didChangeDependencies();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,12 +18,8 @@ import 'package:stream_chat_flutter/src/media_list_view.dart';
|
||||
import 'package:stream_chat_flutter/src/multi_overlay.dart';
|
||||
import 'package:stream_chat_flutter/src/quoted_message_widget.dart';
|
||||
import 'package:stream_chat_flutter/src/user_mentions_overlay.dart';
|
||||
import 'package:stream_chat_flutter/src/video_service.dart';
|
||||
import 'package:stream_chat_flutter/src/video_thumbnail_image.dart';
|
||||
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
|
||||
import 'package:video_compress/video_compress.dart';
|
||||
|
||||
export 'package:video_compress/video_compress.dart' show VideoQuality;
|
||||
|
||||
/// A callback that can be passed to [MessageInput.onError].
|
||||
///
|
||||
@@ -59,7 +55,7 @@ typedef MentionTileBuilder = Widget Function(
|
||||
|
||||
/// Builder function for building a user mention tile.
|
||||
///
|
||||
/// Use [UserMentionTile] for the default implementation.
|
||||
/// Use [StreamUserMentionTile] for the default implementation.
|
||||
typedef UserMentionTileBuilder = Widget Function(
|
||||
BuildContext context,
|
||||
User user,
|
||||
@@ -155,12 +151,13 @@ const _kDefaultMaxAttachmentSize = 20971520; // 20MB in Bytes
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// You usually put this widget in the same page of a [MessageListView]
|
||||
/// You usually put this widget in the same page of a [StreamMessageListView]
|
||||
/// as the bottom widget.
|
||||
///
|
||||
/// The widget renders the ui based on the first ancestor of
|
||||
/// type [StreamChatTheme].
|
||||
/// Modify it to change the widget appearance.
|
||||
@Deprecated("Use 'StreamMessageInput' instead")
|
||||
class MessageInput extends StatefulWidget {
|
||||
/// Instantiate a new MessageInput
|
||||
const MessageInput({
|
||||
@@ -190,8 +187,6 @@ class MessageInput extends StatefulWidget {
|
||||
this.mentionsTileBuilder,
|
||||
this.userMentionsTileBuilder,
|
||||
this.maxAttachmentSize = _kDefaultMaxAttachmentSize,
|
||||
this.compressedVideoQuality = VideoQuality.DefaultQuality,
|
||||
this.compressedVideoFrameRate = 30,
|
||||
this.onError,
|
||||
this.attachmentLimit = 10,
|
||||
this.onAttachmentLimitExceed,
|
||||
@@ -213,12 +208,6 @@ class MessageInput extends StatefulWidget {
|
||||
/// Message to edit
|
||||
final Message? editMessage;
|
||||
|
||||
/// Video quality to use when compressing the videos
|
||||
final VideoQuality compressedVideoQuality;
|
||||
|
||||
/// Frame rate to use when compressing the videos
|
||||
final int compressedVideoFrameRate;
|
||||
|
||||
/// Max attachment size in bytes
|
||||
/// Defaults to 20 MB
|
||||
/// do not set it if you're using our default CDN
|
||||
@@ -341,6 +330,7 @@ class MessageInput extends StatefulWidget {
|
||||
}
|
||||
|
||||
/// State of [MessageInput]
|
||||
@Deprecated("Use 'StreamMessageInput' instead")
|
||||
class MessageInputState extends State<MessageInput> {
|
||||
final _attachments = <String, Attachment>{};
|
||||
final List<User> _mentionedUsers = [];
|
||||
@@ -364,7 +354,7 @@ class MessageInputState extends State<MessageInput> {
|
||||
widget.textEditingController ?? TextEditingController();
|
||||
|
||||
late StreamChatThemeData _streamChatTheme;
|
||||
late MessageInputThemeData _messageInputTheme;
|
||||
late StreamMessageInputThemeData _messageInputTheme;
|
||||
|
||||
bool get _hasQuotedMessage => widget.quotedMessage != null;
|
||||
|
||||
@@ -487,7 +477,7 @@ class MessageInputState extends State<MessageInput> {
|
||||
);
|
||||
}
|
||||
|
||||
return MultiOverlay(
|
||||
return StreamMultiOverlay(
|
||||
childAnchor: Alignment.topCenter,
|
||||
overlayAnchor: Alignment.bottomCenter,
|
||||
overlayOptions: [
|
||||
@@ -932,7 +922,7 @@ class MessageInputState extends State<MessageInput> {
|
||||
if (renderObject == null) {
|
||||
return const Offstage();
|
||||
}
|
||||
return CommandsOverlay(
|
||||
return StreamCommandsOverlay(
|
||||
channel: StreamChannel.of(context).channel,
|
||||
size: Size(renderObject.size.width - 16, 400),
|
||||
text: text,
|
||||
@@ -1121,41 +1111,18 @@ class MessageInputState extends State<MessageInput> {
|
||||
|
||||
if (mediaFile == null) return;
|
||||
|
||||
var file = AttachmentFile(
|
||||
final file = AttachmentFile(
|
||||
path: mediaFile.path,
|
||||
size: await mediaFile.length(),
|
||||
bytes: mediaFile.readAsBytesSync(),
|
||||
);
|
||||
|
||||
if (file.size! > widget.maxAttachmentSize) {
|
||||
if (medium.type == AssetType.video && file.path != null) {
|
||||
final mediaInfo = await VideoService.compressVideo(
|
||||
file.path!,
|
||||
frameRate: widget.compressedVideoFrameRate,
|
||||
quality: widget.compressedVideoQuality,
|
||||
);
|
||||
|
||||
if (mediaInfo == null ||
|
||||
mediaInfo.filesize! > widget.maxAttachmentSize) {
|
||||
_showErrorAlert(
|
||||
context.translations.fileTooLargeAfterCompressionError(
|
||||
widget.maxAttachmentSize / (1024 * 1024),
|
||||
),
|
||||
);
|
||||
return;
|
||||
}
|
||||
file = AttachmentFile(
|
||||
name: file.name,
|
||||
size: mediaInfo.filesize,
|
||||
bytes: await mediaInfo.file?.readAsBytes(),
|
||||
path: mediaInfo.path,
|
||||
);
|
||||
} else {
|
||||
_showErrorAlert(context.translations.fileTooLargeError(
|
||||
return _showErrorAlert(
|
||||
context.translations.fileTooLargeError(
|
||||
widget.maxAttachmentSize / (1024 * 1024),
|
||||
));
|
||||
return;
|
||||
}
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
setState(() {
|
||||
@@ -1197,7 +1164,7 @@ class MessageInputState extends State<MessageInput> {
|
||||
}
|
||||
|
||||
return LayoutBuilder(
|
||||
builder: (context, snapshot) => UserMentionsOverlay(
|
||||
builder: (context, snapshot) => StreamUserMentionsOverlay(
|
||||
query: query,
|
||||
mentionAllAppUsers: widget.mentionAllAppUsers,
|
||||
client: StreamChat.of(context).client,
|
||||
@@ -1242,7 +1209,7 @@ class MessageInputState extends State<MessageInput> {
|
||||
// ignore: cast_nullable_to_non_nullable
|
||||
final renderObject = context.findRenderObject() as RenderBox;
|
||||
|
||||
return EmojiOverlay(
|
||||
return StreamEmojiOverlay(
|
||||
size: Size(renderObject.size.width - 16, 200),
|
||||
query: query,
|
||||
onEmojiResult: (emoji) {
|
||||
@@ -1277,7 +1244,7 @@ class MessageInputState extends State<MessageInput> {
|
||||
if (!_hasQuotedMessage) return const Offstage();
|
||||
final containsUrl = widget.quotedMessage!.attachments
|
||||
.any((element) => element.titleLink != null);
|
||||
return QuotedMessageWidget(
|
||||
return StreamQuotedMessageWidget(
|
||||
reverse: true,
|
||||
showBorder: !containsUrl,
|
||||
message: widget.quotedMessage!,
|
||||
@@ -1308,10 +1275,8 @@ class MessageInputState extends State<MessageInput> {
|
||||
.map<Widget>(
|
||||
(e) => ClipRRect(
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
child: FileAttachment(
|
||||
message: Message(
|
||||
status: MessageSendingStatus.sending,
|
||||
), // dummy message
|
||||
child: StreamFileAttachment(
|
||||
message: Message(), // dummy message
|
||||
attachment: e,
|
||||
size: Size(
|
||||
MediaQuery.of(context).size.width * 0.65,
|
||||
@@ -1432,7 +1397,7 @@ class MessageInputState extends State<MessageInput> {
|
||||
case 'video':
|
||||
return Stack(
|
||||
children: [
|
||||
VideoThumbnailImage(
|
||||
StreamVideoThumbnailImage(
|
||||
height: 104,
|
||||
width: 104,
|
||||
video: (attachment.file?.path ?? attachment.assetUrl)!,
|
||||
@@ -1682,33 +1647,11 @@ class MessageInputState extends State<MessageInput> {
|
||||
);
|
||||
|
||||
if (file.size! > widget.maxAttachmentSize) {
|
||||
if (attachmentType == 'video' && file.path != null) {
|
||||
final mediaInfo = await (VideoService.compressVideo(
|
||||
file.path!,
|
||||
frameRate: widget.compressedVideoFrameRate,
|
||||
quality: widget.compressedVideoQuality,
|
||||
) as FutureOr<MediaInfo>);
|
||||
|
||||
if (mediaInfo.filesize! > widget.maxAttachmentSize) {
|
||||
_showErrorAlert(
|
||||
context.translations.fileTooLargeAfterCompressionError(
|
||||
widget.maxAttachmentSize / (1024 * 1024),
|
||||
),
|
||||
);
|
||||
return;
|
||||
}
|
||||
file = AttachmentFile(
|
||||
name: file.name,
|
||||
size: mediaInfo.filesize,
|
||||
bytes: await mediaInfo.file!.readAsBytes(),
|
||||
path: mediaInfo.path,
|
||||
);
|
||||
} else {
|
||||
_showErrorAlert(context.translations.fileTooLargeError(
|
||||
return _showErrorAlert(
|
||||
context.translations.fileTooLargeError(
|
||||
widget.maxAttachmentSize / (1024 * 1024),
|
||||
));
|
||||
return;
|
||||
}
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
setState(() {
|
||||
@@ -1947,7 +1890,7 @@ class MessageInputState extends State<MessageInput> {
|
||||
@override
|
||||
void didChangeDependencies() {
|
||||
_streamChatTheme = StreamChatTheme.of(context);
|
||||
_messageInputTheme = MessageInputTheme.of(context);
|
||||
_messageInputTheme = StreamMessageInputTheme.of(context);
|
||||
if (widget.editMessage == null) _startSlowMode();
|
||||
|
||||
if ((widget.editMessage != null || widget.initialMessage != null) &&
|
||||
@@ -2023,7 +1966,7 @@ class _PickerWidgetState extends State<_PickerWidget> {
|
||||
),
|
||||
);
|
||||
}
|
||||
return MediaListView(
|
||||
return StreamMediaListView(
|
||||
selectedIds: widget.selectedMedias,
|
||||
onSelect: widget.onMediaSelected,
|
||||
);
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
|
||||
|
||||
/// Button for showing visual component of slow mode.
|
||||
class StreamCountdownButton extends StatelessWidget {
|
||||
/// Constructor for creating [StreamCountdownButton].
|
||||
const StreamCountdownButton({
|
||||
Key? key,
|
||||
required this.count,
|
||||
}) : super(key: key);
|
||||
|
||||
/// Count of time remaining to show to the user.
|
||||
final int count;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) => Padding(
|
||||
padding: const EdgeInsets.all(8),
|
||||
child: DecoratedBox(
|
||||
decoration: BoxDecoration(
|
||||
color: StreamChatTheme.of(context).colorTheme.disabled,
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
child: SizedBox(
|
||||
height: 24,
|
||||
width: 24,
|
||||
child: Center(
|
||||
child: Text('$count'),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,26 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
/// A [SafeArea] with an enabled toggle
|
||||
class SimpleSafeArea extends StatelessWidget {
|
||||
/// Constructor for [SimpleSafeArea]
|
||||
const SimpleSafeArea({
|
||||
Key? key,
|
||||
this.enabled = true,
|
||||
required this.child,
|
||||
}) : super(key: key);
|
||||
|
||||
/// Wrap [child] with [SafeArea]
|
||||
final bool enabled;
|
||||
|
||||
/// Child widget to wrap
|
||||
final Widget child;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) => SafeArea(
|
||||
left: enabled,
|
||||
top: enabled,
|
||||
right: enabled,
|
||||
bottom: enabled,
|
||||
child: child,
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,537 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_svg/flutter_svg.dart';
|
||||
import 'package:photo_manager/photo_manager.dart';
|
||||
import 'package:stream_chat_flutter/src/extension.dart';
|
||||
import 'package:stream_chat_flutter/src/media_list_view.dart';
|
||||
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
|
||||
|
||||
/// Callback for when a file has to be picked.
|
||||
typedef FilePickerCallback = void Function(
|
||||
DefaultAttachmentTypes fileType, {
|
||||
bool camera,
|
||||
});
|
||||
|
||||
/// Callback for building an icon for a custom attachment type.
|
||||
typedef CustomAttachmentIconBuilder = Widget Function(
|
||||
BuildContext context,
|
||||
bool active,
|
||||
);
|
||||
|
||||
/// A widget that allows to pick an attachment.
|
||||
class StreamAttachmentPicker extends StatefulWidget {
|
||||
/// Default constructor for [StreamAttachmentPicker] which creates the Stream
|
||||
/// attachment picker widget.
|
||||
const StreamAttachmentPicker({
|
||||
Key? key,
|
||||
required this.messageInputController,
|
||||
required this.onFilePicked,
|
||||
this.isOpen = false,
|
||||
this.pickerSize = 360.0,
|
||||
this.attachmentLimit = 10,
|
||||
this.onAttachmentLimitExceeded,
|
||||
this.maxAttachmentSize = 20971520,
|
||||
this.onError,
|
||||
this.allowedAttachmentTypes = const [
|
||||
DefaultAttachmentTypes.image,
|
||||
DefaultAttachmentTypes.file,
|
||||
DefaultAttachmentTypes.video,
|
||||
],
|
||||
this.customAttachmentTypes = const [],
|
||||
}) : super(key: key);
|
||||
|
||||
/// True if the picker is open.
|
||||
final bool isOpen;
|
||||
|
||||
/// The picker size in height.
|
||||
final double pickerSize;
|
||||
|
||||
/// The [MessageInputController] linked to this picker.
|
||||
final MessageInputController messageInputController;
|
||||
|
||||
/// The limit of attachments that can be picked.
|
||||
final int attachmentLimit;
|
||||
|
||||
/// The callback for when the attachment limit is exceeded.
|
||||
final AttachmentLimitExceedListener? onAttachmentLimitExceeded;
|
||||
|
||||
/// Callback for when an error occurs in the attachment picker.
|
||||
final ValueChanged<String>? onError;
|
||||
|
||||
/// Callback for when file is picked.
|
||||
final FilePickerCallback onFilePicked;
|
||||
|
||||
/// Max attachment size in bytes:
|
||||
/// - Defaults to 20 MB
|
||||
/// - Do not set it if you're using our default CDN
|
||||
final int maxAttachmentSize;
|
||||
|
||||
/// The list of attachment types that can be picked.
|
||||
final List<DefaultAttachmentTypes> allowedAttachmentTypes;
|
||||
|
||||
/// The list of custom attachment types that can be picked.
|
||||
final List<CustomAttachmentType> customAttachmentTypes;
|
||||
|
||||
/// Used to create a new copy of [StreamAttachmentPicker] with modified
|
||||
/// properties.
|
||||
StreamAttachmentPicker copyWith({
|
||||
Key? key,
|
||||
MessageInputController? messageInputController,
|
||||
FilePickerCallback? onFilePicked,
|
||||
bool? isOpen,
|
||||
double? pickerSize,
|
||||
int? attachmentLimit,
|
||||
AttachmentLimitExceedListener? onAttachmentLimitExceeded,
|
||||
int? maxAttachmentSize,
|
||||
ValueChanged<bool>? onChangeInputState,
|
||||
ValueChanged<String>? onError,
|
||||
List<DefaultAttachmentTypes>? allowedAttachmentTypes,
|
||||
List<CustomAttachmentType>? customAttachmentTypes = const [],
|
||||
}) =>
|
||||
StreamAttachmentPicker(
|
||||
key: key ?? this.key,
|
||||
messageInputController:
|
||||
messageInputController ?? this.messageInputController,
|
||||
onFilePicked: onFilePicked ?? this.onFilePicked,
|
||||
isOpen: isOpen ?? this.isOpen,
|
||||
pickerSize: pickerSize ?? this.pickerSize,
|
||||
attachmentLimit: attachmentLimit ?? this.attachmentLimit,
|
||||
onAttachmentLimitExceeded:
|
||||
onAttachmentLimitExceeded ?? this.onAttachmentLimitExceeded,
|
||||
maxAttachmentSize: maxAttachmentSize ?? this.maxAttachmentSize,
|
||||
onError: onError ?? this.onError,
|
||||
allowedAttachmentTypes:
|
||||
allowedAttachmentTypes ?? this.allowedAttachmentTypes,
|
||||
customAttachmentTypes:
|
||||
customAttachmentTypes ?? this.customAttachmentTypes,
|
||||
);
|
||||
|
||||
@override
|
||||
State<StreamAttachmentPicker> createState() => _StreamAttachmentPickerState();
|
||||
}
|
||||
|
||||
class _StreamAttachmentPickerState extends State<StreamAttachmentPicker> {
|
||||
int _filePickerIndex = 0;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final _streamChatTheme = StreamChatTheme.of(context);
|
||||
final messageInputController = widget.messageInputController;
|
||||
|
||||
final _attachmentContainsImage =
|
||||
messageInputController.attachments.any((it) => it.type == 'image');
|
||||
|
||||
final _attachmentContainsFile =
|
||||
messageInputController.attachments.any((it) => it.type == 'file');
|
||||
|
||||
final _attachmentContainsVideo =
|
||||
messageInputController.attachments.any((it) => it.type == 'video');
|
||||
|
||||
final attachmentLimitCrossed =
|
||||
messageInputController.attachments.length >= widget.attachmentLimit;
|
||||
|
||||
Color _getIconColor(int index) {
|
||||
final streamChatThemeData = _streamChatTheme;
|
||||
switch (index) {
|
||||
case 0:
|
||||
return _filePickerIndex == 0 || _attachmentContainsImage
|
||||
? streamChatThemeData.colorTheme.accentPrimary
|
||||
: (_attachmentContainsImage
|
||||
? streamChatThemeData.colorTheme.accentPrimary
|
||||
: streamChatThemeData.colorTheme.textHighEmphasis.withOpacity(
|
||||
messageInputController.attachments.isEmpty ? 0.5 : 0.2,
|
||||
));
|
||||
case 1:
|
||||
return _attachmentContainsFile
|
||||
? streamChatThemeData.colorTheme.accentPrimary
|
||||
: (messageInputController.attachments.isEmpty
|
||||
? streamChatThemeData.colorTheme.textHighEmphasis
|
||||
.withOpacity(0.5)
|
||||
: streamChatThemeData.colorTheme.textHighEmphasis
|
||||
.withOpacity(0.2));
|
||||
case 2:
|
||||
return widget.messageInputController.attachments.isNotEmpty &&
|
||||
(!_attachmentContainsImage || attachmentLimitCrossed)
|
||||
? streamChatThemeData.colorTheme.textHighEmphasis.withOpacity(0.2)
|
||||
: _attachmentContainsFile &&
|
||||
messageInputController.attachments.isNotEmpty
|
||||
? streamChatThemeData.colorTheme.textHighEmphasis
|
||||
.withOpacity(0.2)
|
||||
: streamChatThemeData.colorTheme.textHighEmphasis
|
||||
.withOpacity(0.5);
|
||||
case 3:
|
||||
return widget.messageInputController.attachments.isNotEmpty &&
|
||||
(!_attachmentContainsVideo || attachmentLimitCrossed)
|
||||
? streamChatThemeData.colorTheme.textHighEmphasis.withOpacity(0.2)
|
||||
: _attachmentContainsFile &&
|
||||
messageInputController.attachments.isNotEmpty
|
||||
? streamChatThemeData.colorTheme.textHighEmphasis
|
||||
.withOpacity(0.2)
|
||||
: streamChatThemeData.colorTheme.textHighEmphasis
|
||||
.withOpacity(0.5);
|
||||
default:
|
||||
return Colors.black;
|
||||
}
|
||||
}
|
||||
|
||||
return AnimatedContainer(
|
||||
duration:
|
||||
widget.isOpen ? const Duration(milliseconds: 300) : const Duration(),
|
||||
curve: Curves.easeOut,
|
||||
height: widget.isOpen ? widget.pickerSize : 0,
|
||||
child: SingleChildScrollView(
|
||||
child: SizedBox(
|
||||
height: widget.pickerSize,
|
||||
child: Material(
|
||||
color: _streamChatTheme.colorTheme.inputBg,
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
if (widget.allowedAttachmentTypes
|
||||
.contains(DefaultAttachmentTypes.image))
|
||||
IconButton(
|
||||
icon: StreamSvgIcon.pictures(
|
||||
color: _getIconColor(0),
|
||||
),
|
||||
onPressed:
|
||||
messageInputController.attachments.isNotEmpty &&
|
||||
!_attachmentContainsImage
|
||||
? null
|
||||
: () {
|
||||
setState(() {
|
||||
_filePickerIndex = 0;
|
||||
});
|
||||
},
|
||||
),
|
||||
if (widget.allowedAttachmentTypes
|
||||
.contains(DefaultAttachmentTypes.file))
|
||||
IconButton(
|
||||
iconSize: 32,
|
||||
icon: StreamSvgIcon.files(
|
||||
color: _getIconColor(1),
|
||||
),
|
||||
onPressed: messageInputController
|
||||
.attachments.isNotEmpty &&
|
||||
!_attachmentContainsFile
|
||||
? null
|
||||
: () {
|
||||
widget
|
||||
.onFilePicked(DefaultAttachmentTypes.file);
|
||||
},
|
||||
),
|
||||
if (widget.allowedAttachmentTypes
|
||||
.contains(DefaultAttachmentTypes.image))
|
||||
IconButton(
|
||||
icon: StreamSvgIcon.camera(
|
||||
color: _getIconColor(2),
|
||||
),
|
||||
onPressed: attachmentLimitCrossed ||
|
||||
(messageInputController
|
||||
.attachments.isNotEmpty &&
|
||||
!_attachmentContainsVideo)
|
||||
? null
|
||||
: () {
|
||||
widget.onFilePicked(
|
||||
DefaultAttachmentTypes.image,
|
||||
camera: true,
|
||||
);
|
||||
},
|
||||
),
|
||||
if (widget.allowedAttachmentTypes
|
||||
.contains(DefaultAttachmentTypes.video))
|
||||
IconButton(
|
||||
padding: const EdgeInsets.all(0),
|
||||
icon: StreamSvgIcon.record(
|
||||
color: _getIconColor(3),
|
||||
),
|
||||
onPressed: attachmentLimitCrossed ||
|
||||
(messageInputController
|
||||
.attachments.isNotEmpty &&
|
||||
!_attachmentContainsVideo)
|
||||
? null
|
||||
: () {
|
||||
widget.onFilePicked(
|
||||
DefaultAttachmentTypes.video,
|
||||
camera: true,
|
||||
);
|
||||
},
|
||||
),
|
||||
for (int i = 0;
|
||||
i < widget.customAttachmentTypes.length;
|
||||
i++)
|
||||
IconButton(
|
||||
onPressed: () {
|
||||
if (messageInputController.attachments.isNotEmpty) {
|
||||
if (!messageInputController.attachments.any((e) =>
|
||||
e.type ==
|
||||
widget.customAttachmentTypes[i].type)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
setState(() {
|
||||
_filePickerIndex = i + 1;
|
||||
});
|
||||
},
|
||||
icon: widget.customAttachmentTypes[i]
|
||||
.iconBuilder(context, _filePickerIndex == i + 1),
|
||||
),
|
||||
],
|
||||
),
|
||||
DecoratedBox(
|
||||
decoration: BoxDecoration(
|
||||
color: _streamChatTheme.colorTheme.barsBg,
|
||||
borderRadius: const BorderRadius.only(
|
||||
topLeft: Radius.circular(16),
|
||||
topRight: Radius.circular(16),
|
||||
),
|
||||
),
|
||||
child: Center(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(8),
|
||||
child: Container(
|
||||
width: 40,
|
||||
height: 4,
|
||||
decoration: BoxDecoration(
|
||||
color: _streamChatTheme.colorTheme.inputBg,
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
if (widget.isOpen &&
|
||||
(widget.allowedAttachmentTypes
|
||||
.contains(DefaultAttachmentTypes.image) ||
|
||||
(widget.allowedAttachmentTypes
|
||||
.contains(DefaultAttachmentTypes.file))))
|
||||
Expanded(
|
||||
child: DecoratedBox(
|
||||
decoration: BoxDecoration(
|
||||
color: _streamChatTheme.colorTheme.barsBg,
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
child: _PickerWidget(
|
||||
filePickerIndex: _filePickerIndex,
|
||||
streamChatTheme: _streamChatTheme,
|
||||
containsFile: _attachmentContainsFile,
|
||||
selectedMedias: messageInputController.attachments
|
||||
.map((e) => e.id)
|
||||
.toList(),
|
||||
onAddMoreFilesClick: widget.onFilePicked,
|
||||
onMediaSelected: (media) {
|
||||
if (messageInputController.attachments
|
||||
.any((e) => e.id == media.id)) {
|
||||
messageInputController
|
||||
.removeAttachmentById(media.id);
|
||||
} else {
|
||||
_addAssetAttachment(media);
|
||||
}
|
||||
},
|
||||
allowedAttachmentTypes: widget.allowedAttachmentTypes,
|
||||
customAttachmentTypes: widget.customAttachmentTypes,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _addAssetAttachment(AssetEntity medium) async {
|
||||
final mediaFile = await medium.originFile.timeout(
|
||||
const Duration(seconds: 5),
|
||||
onTimeout: () => medium.originFile,
|
||||
);
|
||||
|
||||
if (mediaFile == null) return;
|
||||
|
||||
var file = AttachmentFile(
|
||||
path: mediaFile.path,
|
||||
size: await mediaFile.length(),
|
||||
bytes: mediaFile.readAsBytesSync(),
|
||||
);
|
||||
|
||||
if (file.size! > widget.maxAttachmentSize) {
|
||||
return widget.onError?.call(
|
||||
context.translations.fileTooLargeError(
|
||||
widget.maxAttachmentSize / (1024 * 1024),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
setState(() {
|
||||
final attachment = Attachment(
|
||||
id: medium.id,
|
||||
file: file,
|
||||
type: medium.type == AssetType.image ? 'image' : 'video',
|
||||
);
|
||||
_addAttachments([attachment]);
|
||||
});
|
||||
}
|
||||
|
||||
/// Adds an attachment to the [messageInputController.attachments] map
|
||||
void _addAttachments(Iterable<Attachment> attachments) {
|
||||
final limit = widget.attachmentLimit;
|
||||
final length =
|
||||
widget.messageInputController.attachments.length + attachments.length;
|
||||
if (length > limit) {
|
||||
final onAttachmentLimitExceed = widget.onAttachmentLimitExceeded;
|
||||
if (onAttachmentLimitExceed != null) {
|
||||
return onAttachmentLimitExceed(
|
||||
widget.attachmentLimit,
|
||||
context.translations.attachmentLimitExceedError(limit),
|
||||
);
|
||||
}
|
||||
return widget.onError?.call(
|
||||
context.translations.attachmentLimitExceedError(limit),
|
||||
);
|
||||
}
|
||||
for (final attachment in attachments) {
|
||||
widget.messageInputController.addAttachment(attachment);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class _PickerWidget extends StatefulWidget {
|
||||
const _PickerWidget({
|
||||
Key? key,
|
||||
required this.filePickerIndex,
|
||||
required this.containsFile,
|
||||
required this.selectedMedias,
|
||||
required this.onAddMoreFilesClick,
|
||||
required this.onMediaSelected,
|
||||
required this.streamChatTheme,
|
||||
required this.allowedAttachmentTypes,
|
||||
required this.customAttachmentTypes,
|
||||
}) : super(key: key);
|
||||
|
||||
final int filePickerIndex;
|
||||
final bool containsFile;
|
||||
final List<String> selectedMedias;
|
||||
final void Function(DefaultAttachmentTypes) onAddMoreFilesClick;
|
||||
final void Function(AssetEntity) onMediaSelected;
|
||||
final StreamChatThemeData streamChatTheme;
|
||||
final List<DefaultAttachmentTypes> allowedAttachmentTypes;
|
||||
final List<CustomAttachmentType> customAttachmentTypes;
|
||||
|
||||
@override
|
||||
_PickerWidgetState createState() => _PickerWidgetState();
|
||||
}
|
||||
|
||||
class _PickerWidgetState extends State<_PickerWidget> {
|
||||
Future<bool>? requestPermission;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
requestPermission = PhotoManager.requestPermission();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (widget.filePickerIndex != 0) {
|
||||
return widget.customAttachmentTypes[widget.filePickerIndex - 1]
|
||||
.pickerBuilder(context);
|
||||
}
|
||||
return FutureBuilder<bool>(
|
||||
future: requestPermission,
|
||||
builder: (context, snapshot) {
|
||||
if (!snapshot.hasData) {
|
||||
return const Offstage();
|
||||
}
|
||||
|
||||
if (snapshot.data!) {
|
||||
if (widget.containsFile ||
|
||||
!widget.allowedAttachmentTypes
|
||||
.contains(DefaultAttachmentTypes.image)) {
|
||||
return GestureDetector(
|
||||
onTap: () {
|
||||
widget.onAddMoreFilesClick(DefaultAttachmentTypes.file);
|
||||
},
|
||||
child: Container(
|
||||
constraints: const BoxConstraints.expand(),
|
||||
color: widget.streamChatTheme.colorTheme.inputBg,
|
||||
alignment: Alignment.center,
|
||||
child: Text(
|
||||
context.translations.addMoreFilesLabel,
|
||||
style: TextStyle(
|
||||
color: widget.streamChatTheme.colorTheme.accentPrimary,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
return StreamMediaListView(
|
||||
selectedIds: widget.selectedMedias,
|
||||
onSelect: widget.onMediaSelected,
|
||||
);
|
||||
}
|
||||
|
||||
return InkWell(
|
||||
onTap: () async {
|
||||
PhotoManager.openSetting();
|
||||
},
|
||||
child: Container(
|
||||
color: widget.streamChatTheme.colorTheme.inputBg,
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
SvgPicture.asset(
|
||||
'svgs/icon_picture_empty_state.svg',
|
||||
package: 'stream_chat_flutter',
|
||||
height: 140,
|
||||
color: widget.streamChatTheme.colorTheme.disabled,
|
||||
),
|
||||
Text(
|
||||
context.translations.enablePhotoAndVideoAccessMessage,
|
||||
style: widget.streamChatTheme.textTheme.body.copyWith(
|
||||
color: widget.streamChatTheme.colorTheme.textLowEmphasis,
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
const SizedBox(height: 6),
|
||||
Center(
|
||||
child: Text(
|
||||
context.translations.allowGalleryAccessMessage,
|
||||
style: widget.streamChatTheme.textTheme.bodyBold.copyWith(
|
||||
color: widget.streamChatTheme.colorTheme.accentPrimary,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Class which holds data for a custom attachment type in the attachment picker
|
||||
class CustomAttachmentType {
|
||||
/// Default constructor for creating a custom attachment for the attachment
|
||||
/// picker.
|
||||
CustomAttachmentType({
|
||||
required this.type,
|
||||
required this.iconBuilder,
|
||||
required this.pickerBuilder,
|
||||
});
|
||||
|
||||
/// Type name.
|
||||
String type;
|
||||
|
||||
/// Builds the icon in the attachment picker top row.
|
||||
CustomAttachmentIconBuilder iconBuilder;
|
||||
|
||||
/// Builds content in the attachment builder when icon is selected.
|
||||
WidgetBuilder pickerBuilder;
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
|
||||
|
||||
/// A widget that displays a sending button.
|
||||
class StreamMessageSendButton extends StatelessWidget {
|
||||
/// Returns a [StreamMessageSendButton] with the given [timeOut], [isIdle],
|
||||
/// [isCommandEnabled], [isEditEnabled], [idleSendButton], [activeSendButton],
|
||||
/// [onSendMessage].
|
||||
const StreamMessageSendButton({
|
||||
Key? key,
|
||||
this.timeOut = 0,
|
||||
this.isIdle = true,
|
||||
this.isCommandEnabled = false,
|
||||
this.isEditEnabled = false,
|
||||
this.idleSendButton,
|
||||
this.activeSendButton,
|
||||
required this.onSendMessage,
|
||||
}) : super(key: key);
|
||||
|
||||
/// Time out related to slow mode.
|
||||
final int timeOut;
|
||||
|
||||
/// If true the button will be disabled.
|
||||
final bool isIdle;
|
||||
|
||||
/// True if a command is being sent.
|
||||
final bool isCommandEnabled;
|
||||
|
||||
/// True if in editing mode.
|
||||
final bool isEditEnabled;
|
||||
|
||||
/// The widget to display when the button is disabled.
|
||||
final Widget? idleSendButton;
|
||||
|
||||
/// The widget to display when the button is enabled.
|
||||
final Widget? activeSendButton;
|
||||
|
||||
/// The callback to call when the button is pressed.
|
||||
final VoidCallback onSendMessage;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final _streamChatTheme = StreamChatTheme.of(context);
|
||||
|
||||
late Widget sendButton;
|
||||
if (timeOut > 0) {
|
||||
sendButton = StreamCountdownButton(count: timeOut);
|
||||
} else if (isIdle) {
|
||||
sendButton = idleSendButton ?? _buildIdleSendButton(context);
|
||||
} else {
|
||||
sendButton = activeSendButton != null
|
||||
? InkWell(
|
||||
onTap: onSendMessage,
|
||||
child: activeSendButton,
|
||||
)
|
||||
: _buildSendButton(context);
|
||||
}
|
||||
|
||||
return AnimatedSwitcher(
|
||||
duration: _streamChatTheme.messageInputTheme.sendAnimationDuration!,
|
||||
child: sendButton,
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildIdleSendButton(BuildContext context) {
|
||||
final _messageInputTheme = StreamMessageInputTheme.of(context);
|
||||
|
||||
return Padding(
|
||||
padding: const EdgeInsets.all(8),
|
||||
child: StreamSvgIcon(
|
||||
assetName: _getIdleSendIcon(),
|
||||
color: _messageInputTheme.sendButtonIdleColor,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildSendButton(BuildContext context) {
|
||||
final _messageInputTheme = StreamMessageInputTheme.of(context);
|
||||
|
||||
return Padding(
|
||||
padding: const EdgeInsets.all(8),
|
||||
child: IconButton(
|
||||
onPressed: onSendMessage,
|
||||
padding: const EdgeInsets.all(0),
|
||||
splashRadius: 24,
|
||||
constraints: const BoxConstraints.tightFor(
|
||||
height: 24,
|
||||
width: 24,
|
||||
),
|
||||
icon: StreamSvgIcon(
|
||||
assetName: _getSendIcon(),
|
||||
color: _messageInputTheme.sendButtonColor,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
String _getIdleSendIcon() {
|
||||
if (isCommandEnabled) {
|
||||
return 'Icon_search.svg';
|
||||
} else {
|
||||
return 'Icon_circle_right.svg';
|
||||
}
|
||||
}
|
||||
|
||||
String _getSendIcon() {
|
||||
if (isEditEnabled) {
|
||||
return 'Icon_circle_up.svg';
|
||||
} else if (isCommandEnabled) {
|
||||
return 'Icon_search.svg';
|
||||
} else {
|
||||
return 'Icon_circle_up.svg';
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,766 @@
|
||||
// ignore_for_file: prefer-trailing-comma, cascade_invocations, lines_longer_than_80_chars
|
||||
|
||||
import 'dart:ui' as ui show BoxHeightStyle, BoxWidthStyle;
|
||||
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/gestures.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/rendering.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
|
||||
|
||||
export 'package:flutter/services.dart'
|
||||
show
|
||||
TextInputType,
|
||||
TextInputAction,
|
||||
TextCapitalization,
|
||||
SmartQuotesType,
|
||||
SmartDashesType;
|
||||
|
||||
/// A widget the wraps the [TextField] and adds some StreamChat specifics.
|
||||
class StreamMessageTextField extends StatefulWidget {
|
||||
/// Creates a Material Design text field.
|
||||
///
|
||||
/// If [decoration] is non-null (which is the default), the text field
|
||||
/// requires one of its ancestors to be a [Material] widget.
|
||||
///
|
||||
/// To remove the decoration entirely (including the extra padding introduced
|
||||
/// by the decoration to save space for the labels), set the [decoration] to
|
||||
/// null.
|
||||
///
|
||||
/// The [maxLines] property can be set to null to remove the restriction on
|
||||
/// the number of lines. By default, it is one, meaning this is a single-line
|
||||
/// text field. [maxLines] must not be zero.
|
||||
///
|
||||
/// The [maxLength] property is set to null by default, which means the
|
||||
/// number of characters allowed in the text field is not restricted. If
|
||||
/// [maxLength] is set a character counter will be displayed below the
|
||||
/// field showing how many characters have been entered. If the value is
|
||||
/// set to a positive integer it will also display the maximum allowed
|
||||
/// number of characters to be entered. If the value is set to
|
||||
/// [TextField.noMaxLength] then only the current length is displayed.
|
||||
///
|
||||
/// After [maxLength] characters have been input, additional input
|
||||
/// is ignored, unless [maxLengthEnforcement] is set to
|
||||
/// [MaxLengthEnforcement.none].
|
||||
/// The text field enforces the length with a
|
||||
/// [LengthLimitingTextInputFormatter],
|
||||
/// which is evaluated after the supplied [inputFormatters], if any.
|
||||
/// The [maxLength] value must be either null or greater than zero.
|
||||
///
|
||||
/// The text cursor is not shown if [showCursor] is false or if [showCursor]
|
||||
/// is null (the default) and [readOnly] is true.
|
||||
///
|
||||
/// The [selectionHeightStyle] and [selectionWidthStyle] properties allow
|
||||
/// changing the shape of the selection highlighting. These properties default
|
||||
/// to [ui.BoxHeightStyle.tight] and [ui.BoxWidthStyle.tight] respectively and
|
||||
/// must not be null.
|
||||
///
|
||||
/// The [textAlign], [autofocus], [obscureText], [readOnly], [autocorrect],
|
||||
/// [scrollPadding], [maxLines], [maxLength],
|
||||
/// [selectionHeightStyle], [selectionWidthStyle], [enableSuggestions], and
|
||||
/// [enableIMEPersonalizedLearning] arguments must not be null.
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [maxLength], which discusses the precise meaning of "number of
|
||||
/// characters" and how it may differ from the intuitive meaning.
|
||||
const StreamMessageTextField({
|
||||
Key? key,
|
||||
this.controller,
|
||||
this.focusNode,
|
||||
this.decoration = const InputDecoration(),
|
||||
TextInputType? keyboardType,
|
||||
this.textInputAction,
|
||||
this.textCapitalization = TextCapitalization.none,
|
||||
this.style,
|
||||
this.strutStyle,
|
||||
this.textAlign = TextAlign.start,
|
||||
this.textAlignVertical,
|
||||
this.textDirection,
|
||||
this.readOnly = false,
|
||||
ToolbarOptions? toolbarOptions,
|
||||
this.showCursor,
|
||||
this.autofocus = false,
|
||||
this.obscuringCharacter = '•',
|
||||
this.obscureText = false,
|
||||
this.autocorrect = true,
|
||||
SmartDashesType? smartDashesType,
|
||||
SmartQuotesType? smartQuotesType,
|
||||
this.enableSuggestions = true,
|
||||
this.maxLines = 1,
|
||||
this.minLines,
|
||||
this.expands = false,
|
||||
this.maxLength,
|
||||
@Deprecated(
|
||||
'Use maxLengthEnforcement parameter which provides more specific '
|
||||
'behavior related to the maxLength limit. '
|
||||
'This feature was deprecated after v1.25.0-5.0.pre.',
|
||||
)
|
||||
this.maxLengthEnforced = true,
|
||||
this.maxLengthEnforcement,
|
||||
this.onChanged,
|
||||
this.onEditingComplete,
|
||||
this.onSubmitted,
|
||||
this.onAppPrivateCommand,
|
||||
this.inputFormatters,
|
||||
this.enabled,
|
||||
this.cursorWidth = 2.0,
|
||||
this.cursorHeight,
|
||||
this.cursorRadius,
|
||||
this.cursorColor,
|
||||
this.selectionHeightStyle = ui.BoxHeightStyle.tight,
|
||||
this.selectionWidthStyle = ui.BoxWidthStyle.tight,
|
||||
this.keyboardAppearance,
|
||||
this.scrollPadding = const EdgeInsets.all(20),
|
||||
this.dragStartBehavior = DragStartBehavior.start,
|
||||
this.enableInteractiveSelection = true,
|
||||
this.selectionControls,
|
||||
this.onTap,
|
||||
this.mouseCursor,
|
||||
this.buildCounter,
|
||||
this.scrollController,
|
||||
this.scrollPhysics,
|
||||
this.autofillHints,
|
||||
this.restorationId,
|
||||
this.enableIMEPersonalizedLearning = true,
|
||||
}) : assert(obscuringCharacter.length == 1,
|
||||
'`obscuringCharacter.length` must be 1'),
|
||||
smartDashesType = smartDashesType ??
|
||||
(obscureText ? SmartDashesType.disabled : SmartDashesType.enabled),
|
||||
smartQuotesType = smartQuotesType ??
|
||||
(obscureText ? SmartQuotesType.disabled : SmartQuotesType.enabled),
|
||||
assert(
|
||||
maxLengthEnforced || maxLengthEnforcement == null,
|
||||
'maxLengthEnforced is deprecated, use only maxLengthEnforcement',
|
||||
),
|
||||
assert(maxLines == null || maxLines > 0,
|
||||
'`maxLines` needs to be left as null or bigger than 0'),
|
||||
assert(minLines == null || minLines > 0,
|
||||
'`minLines` needs to be left as null or bigger than 0'),
|
||||
assert(
|
||||
(maxLines == null) || (minLines == null) || (maxLines >= minLines),
|
||||
"minLines can't be greater than maxLines",
|
||||
),
|
||||
assert(
|
||||
!expands || (maxLines == null && minLines == null),
|
||||
'minLines and maxLines must be null when expands is true.',
|
||||
),
|
||||
assert(!obscureText || maxLines == 1,
|
||||
'Obscured fields cannot be multiline.'),
|
||||
assert(
|
||||
maxLength == null ||
|
||||
maxLength == TextField.noMaxLength ||
|
||||
maxLength > 0,
|
||||
'`maxLength` needs to be null or a positive integer'),
|
||||
|
||||
// Assert the following instead of setting it directly to avoid
|
||||
// surprising the user by silently changing the value they set.
|
||||
assert(
|
||||
!identical(textInputAction, TextInputAction.newline) ||
|
||||
maxLines == 1 ||
|
||||
!identical(keyboardType, TextInputType.text),
|
||||
'''Use keyboardType TextInputType.multiline when using TextInputAction.newline on a multiline TextField.''',
|
||||
),
|
||||
keyboardType = keyboardType ??
|
||||
(maxLines == 1 ? TextInputType.text : TextInputType.multiline),
|
||||
toolbarOptions = toolbarOptions ??
|
||||
(obscureText
|
||||
? const ToolbarOptions(
|
||||
selectAll: true,
|
||||
paste: true,
|
||||
)
|
||||
: const ToolbarOptions(
|
||||
copy: true,
|
||||
cut: true,
|
||||
selectAll: true,
|
||||
paste: true,
|
||||
)),
|
||||
super(key: key);
|
||||
|
||||
/// Controls the message being edited.
|
||||
///
|
||||
/// If null, this widget will create its own [MessageInputController].
|
||||
final MessageInputController? controller;
|
||||
|
||||
/// Defines the keyboard focus for this widget.
|
||||
///
|
||||
/// The [focusNode] is a long-lived object that's typically managed by a
|
||||
/// [StatefulWidget] parent. See [FocusNode] for more information.
|
||||
///
|
||||
/// To give the keyboard focus to this widget, provide a [focusNode] and then
|
||||
/// use the current [FocusScope] to request the focus:
|
||||
///
|
||||
/// ```dart
|
||||
/// FocusScope.of(context).requestFocus(myFocusNode);
|
||||
/// ```
|
||||
///
|
||||
/// This happens automatically when the widget is tapped.
|
||||
///
|
||||
/// To be notified when the widget gains or loses the focus, add a listener
|
||||
/// to the [focusNode]:
|
||||
///
|
||||
/// ```dart
|
||||
/// focusNode.addListener(() { print(myFocusNode.hasFocus); });
|
||||
/// ```
|
||||
///
|
||||
/// If null, this widget will create its own [FocusNode].
|
||||
///
|
||||
/// ## Keyboard
|
||||
///
|
||||
/// Requesting the focus will typically cause the keyboard to be shown
|
||||
/// if it's not showing already.
|
||||
///
|
||||
/// On Android, the user can hide the keyboard - without changing the focus -
|
||||
/// with the system back button. They can restore the keyboard's visibility
|
||||
/// by tapping on a text field. The user might hide the keyboard and
|
||||
/// switch to a physical keyboard, or they might just need to get it
|
||||
/// out of the way for a moment, to expose something it's
|
||||
/// obscuring. In this case requesting the focus again will not
|
||||
/// cause the focus to change, and will not make the keyboard visible.
|
||||
///
|
||||
/// This widget builds an [EditableText] and will ensure that the keyboard is
|
||||
/// showing when it is tapped by calling
|
||||
/// [EditableTextState.requestKeyboard()].
|
||||
final FocusNode? focusNode;
|
||||
|
||||
/// The decoration to show around the text field.
|
||||
///
|
||||
/// By default, draws a horizontal line under the text field but can be
|
||||
/// configured to show an icon, label, hint text, and error text.
|
||||
///
|
||||
/// Specify null to remove the decoration entirely (including the
|
||||
/// extra padding introduced by the decoration to save space for the labels).
|
||||
final InputDecoration? decoration;
|
||||
|
||||
/// {@macro flutter.widgets.editableText.keyboardType}
|
||||
final TextInputType keyboardType;
|
||||
|
||||
/// The type of action button to use for the keyboard.
|
||||
///
|
||||
/// Defaults to [TextInputAction.newline] if [keyboardType] is
|
||||
/// [TextInputType.multiline] and [TextInputAction.done] otherwise.
|
||||
final TextInputAction? textInputAction;
|
||||
|
||||
/// {@macro flutter.widgets.editableText.textCapitalization}
|
||||
final TextCapitalization textCapitalization;
|
||||
|
||||
/// The style to use for the text being edited.
|
||||
///
|
||||
/// This text style is also used as the base style for the [decoration].
|
||||
///
|
||||
/// If null, defaults to the `subtitle1` text style from the current [Theme].
|
||||
final TextStyle? style;
|
||||
|
||||
/// {@macro flutter.widgets.editableText.strutStyle}
|
||||
final StrutStyle? strutStyle;
|
||||
|
||||
/// {@macro flutter.widgets.editableText.textAlign}
|
||||
final TextAlign textAlign;
|
||||
|
||||
/// {@macro flutter.material.InputDecorator.textAlignVertical}
|
||||
final TextAlignVertical? textAlignVertical;
|
||||
|
||||
/// {@macro flutter.widgets.editableText.textDirection}
|
||||
final TextDirection? textDirection;
|
||||
|
||||
/// {@macro flutter.widgets.editableText.autofocus}
|
||||
final bool autofocus;
|
||||
|
||||
/// {@macro flutter.widgets.editableText.obscuringCharacter}
|
||||
final String obscuringCharacter;
|
||||
|
||||
/// {@macro flutter.widgets.editableText.obscureText}
|
||||
final bool obscureText;
|
||||
|
||||
/// {@macro flutter.widgets.editableText.autocorrect}
|
||||
final bool autocorrect;
|
||||
|
||||
/// {@macro flutter.services.TextInputConfiguration.smartDashesType}
|
||||
final SmartDashesType smartDashesType;
|
||||
|
||||
/// {@macro flutter.services.TextInputConfiguration.smartQuotesType}
|
||||
final SmartQuotesType smartQuotesType;
|
||||
|
||||
/// {@macro flutter.services.TextInputConfiguration.enableSuggestions}
|
||||
final bool enableSuggestions;
|
||||
|
||||
/// {@macro flutter.widgets.editableText.maxLines}
|
||||
/// * [expands], which determines whether the field should fill the height of
|
||||
/// its parent.
|
||||
final int? maxLines;
|
||||
|
||||
/// {@macro flutter.widgets.editableText.minLines}
|
||||
/// * [expands], which determines whether the field should fill the height of
|
||||
/// its parent.
|
||||
final int? minLines;
|
||||
|
||||
/// {@macro flutter.widgets.editableText.expands}
|
||||
final bool expands;
|
||||
|
||||
/// {@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;
|
||||
|
||||
/// If [maxLength] is set to this value, only the "current input length"
|
||||
/// part of the character counter is shown.
|
||||
static const int noMaxLength = -1;
|
||||
|
||||
/// The maximum number of characters (Unicode scalar values) to allow in the
|
||||
/// text field.
|
||||
///
|
||||
/// If set, a character counter will be displayed below the
|
||||
/// field showing how many characters have been entered. If set to a number
|
||||
/// greater than 0, it will also display the maximum number allowed. If set
|
||||
/// to [TextField.noMaxLength] then only the current character count is
|
||||
/// displayed.
|
||||
///
|
||||
/// After [maxLength] characters have been input, additional input
|
||||
/// is ignored, unless [maxLengthEnforcement] is set to
|
||||
/// [MaxLengthEnforcement.none].
|
||||
///
|
||||
/// The text field enforces the length with a
|
||||
/// [LengthLimitingTextInputFormatter], which is evaluated after the supplied
|
||||
/// [inputFormatters], if any.
|
||||
///
|
||||
/// This value must be either null, [TextField.noMaxLength], or greater than
|
||||
/// 0.
|
||||
///
|
||||
/// If null (the default) then there is no limit to the number of characters
|
||||
/// that can be entered. If set to [TextField.noMaxLength], then no limit will
|
||||
/// be enforced, but the number of characters entered will still be displayed.
|
||||
///
|
||||
/// Whitespace characters (e.g. newline, space, tab) are included in the
|
||||
/// character count.
|
||||
///
|
||||
/// {@macro flutter.services.lengthLimitingTextInputFormatter.maxLength}
|
||||
final int? maxLength;
|
||||
|
||||
/// If [maxLength] is set, [maxLengthEnforced] indicates whether or not to
|
||||
/// enforce the limit, or merely provide a character counter and warning when
|
||||
/// [maxLength] is exceeded.
|
||||
///
|
||||
/// If true, prevents the field from allowing more than [maxLength]
|
||||
/// characters.
|
||||
@Deprecated(
|
||||
'Use maxLengthEnforcement parameter which provides more specific '
|
||||
'behavior related to the maxLength limit. '
|
||||
'This feature was deprecated after v1.25.0-5.0.pre.',
|
||||
)
|
||||
final bool maxLengthEnforced;
|
||||
|
||||
/// Determines how the [maxLength] limit should be enforced.
|
||||
///
|
||||
/// {@macro flutter.services.textFormatter.effectiveMaxLengthEnforcement}
|
||||
///
|
||||
/// {@macro flutter.services.textFormatter.maxLengthEnforcement}
|
||||
final MaxLengthEnforcement? maxLengthEnforcement;
|
||||
|
||||
/// {@macro flutter.widgets.editableText.onChanged}
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [inputFormatters], which are called before [onChanged]
|
||||
/// runs and can validate and change ("format") the input value.
|
||||
/// * [onEditingComplete], [onSubmitted]:
|
||||
/// which are more specialized input change notifications.
|
||||
final ValueChanged<String>? onChanged;
|
||||
|
||||
/// {@macro flutter.widgets.editableText.onEditingComplete}
|
||||
final VoidCallback? onEditingComplete;
|
||||
|
||||
/// {@macro flutter.widgets.editableText.onSubmitted}
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [TextInputAction.next] and [TextInputAction.previous], which
|
||||
/// automatically shift the focus to the next/previous focusable item when
|
||||
/// the user is done editing.
|
||||
final ValueChanged<String>? onSubmitted;
|
||||
|
||||
/// {@macro flutter.widgets.editableText.onAppPrivateCommand}
|
||||
final AppPrivateCommandCallback? onAppPrivateCommand;
|
||||
|
||||
/// {@macro flutter.widgets.editableText.inputFormatters}
|
||||
final List<TextInputFormatter>? inputFormatters;
|
||||
|
||||
/// If false the text field is "disabled": it ignores taps and its
|
||||
/// [decoration] is rendered in grey.
|
||||
///
|
||||
/// If non-null this property overrides the [decoration]'s
|
||||
/// [InputDecoration.enabled] property.
|
||||
final bool? enabled;
|
||||
|
||||
/// {@macro flutter.widgets.editableText.cursorWidth}
|
||||
final double cursorWidth;
|
||||
|
||||
/// {@macro flutter.widgets.editableText.cursorHeight}
|
||||
final double? cursorHeight;
|
||||
|
||||
/// {@macro flutter.widgets.editableText.cursorRadius}
|
||||
final Radius? cursorRadius;
|
||||
|
||||
/// The color of the cursor.
|
||||
///
|
||||
/// The cursor indicates the current location of text insertion point in
|
||||
/// the field.
|
||||
///
|
||||
/// If this is null it will default to the ambient
|
||||
/// [TextSelectionThemeData.cursorColor]. If that is null, and the
|
||||
/// [ThemeData.platform] is [TargetPlatform.iOS] or [TargetPlatform.macOS]
|
||||
/// it will use [CupertinoThemeData.primaryColor]. Otherwise it will use
|
||||
/// the value of [ColorScheme.primary] of [ThemeData.colorScheme].
|
||||
final Color? cursorColor;
|
||||
|
||||
/// Controls how tall the selection highlight boxes are computed to be.
|
||||
///
|
||||
/// See [ui.BoxHeightStyle] for details on available styles.
|
||||
final ui.BoxHeightStyle selectionHeightStyle;
|
||||
|
||||
/// Controls how wide the selection highlight boxes are computed to be.
|
||||
///
|
||||
/// See [ui.BoxWidthStyle] for details on available styles.
|
||||
final ui.BoxWidthStyle selectionWidthStyle;
|
||||
|
||||
/// The appearance of the keyboard.
|
||||
///
|
||||
/// This setting is only honored on iOS devices.
|
||||
///
|
||||
/// If unset, defaults to the brightness of
|
||||
/// [ThemeData.primaryColorBrightness].
|
||||
final Brightness? keyboardAppearance;
|
||||
|
||||
/// {@macro flutter.widgets.editableText.scrollPadding}
|
||||
final EdgeInsets scrollPadding;
|
||||
|
||||
/// {@macro flutter.widgets.editableText.enableInteractiveSelection}
|
||||
final bool enableInteractiveSelection;
|
||||
|
||||
/// {@macro flutter.widgets.editableText.selectionControls}
|
||||
final TextSelectionControls? selectionControls;
|
||||
|
||||
/// {@macro flutter.widgets.scrollable.dragStartBehavior}
|
||||
final DragStartBehavior dragStartBehavior;
|
||||
|
||||
/// {@macro flutter.widgets.editableText.selectionEnabled}
|
||||
bool get selectionEnabled => enableInteractiveSelection;
|
||||
|
||||
/// {@template flutter.material.textfield.onTap}
|
||||
/// Called for each distinct tap except for every second tap of a double tap.
|
||||
///
|
||||
/// The text field builds a [GestureDetector] to handle input events like tap,
|
||||
/// to trigger focus requests, to move the caret, adjust the selection, etc.
|
||||
/// Handling some of those events by wrapping the text field with a competing
|
||||
/// GestureDetector is problematic.
|
||||
///
|
||||
/// To unconditionally handle taps, without interfering with the text field's
|
||||
/// internal gesture detector, provide this callback.
|
||||
///
|
||||
/// If the text field is created with [enabled] false, taps will not be
|
||||
/// recognized.
|
||||
///
|
||||
/// To be notified when the text field gains or loses the focus, provide a
|
||||
/// [focusNode] and add a listener to that.
|
||||
///
|
||||
/// To listen to arbitrary pointer events without competing with the
|
||||
/// text field's internal gesture detector, use a [Listener].
|
||||
/// {@endtemplate}
|
||||
final GestureTapCallback? onTap;
|
||||
|
||||
/// The cursor for a mouse pointer when it enters or is hovering over the
|
||||
/// widget.
|
||||
///
|
||||
/// If [mouseCursor] is a [MaterialStateProperty<MouseCursor>],
|
||||
/// [MaterialStateProperty.resolve] is used for the following
|
||||
/// [MaterialState]s:
|
||||
///
|
||||
/// * [MaterialState.error].
|
||||
/// * [MaterialState.hovered].
|
||||
/// * [MaterialState.focused].
|
||||
/// * [MaterialState.disabled].
|
||||
///
|
||||
/// If this property is null, [MaterialStateMouseCursor.textable] will be
|
||||
/// used.
|
||||
///
|
||||
/// The [mouseCursor] is the only property of [TextField] that controls the
|
||||
/// appearance of the mouse pointer. All other properties related to "cursor"
|
||||
/// stand for the text cursor, which is usually a blinking vertical line at
|
||||
/// the editing position.
|
||||
final MouseCursor? mouseCursor;
|
||||
|
||||
/// Callback that generates a custom [InputDecoration.counter] widget.
|
||||
///
|
||||
/// See [InputCounterWidgetBuilder] for an explanation of the passed in
|
||||
/// arguments. The returned widget will be placed below the line in place of
|
||||
/// the default widget built when [InputDecoration.counterText] is specified.
|
||||
///
|
||||
/// The returned widget will be wrapped in a [Semantics] widget for
|
||||
/// accessibility, but it also needs to be accessible itself. For example,
|
||||
/// if returning a Text widget, set the [Text.semanticsLabel] property.
|
||||
///
|
||||
/// {@tool snippet}
|
||||
/// ```dart
|
||||
/// Widget counter(
|
||||
/// BuildContext context,
|
||||
/// {
|
||||
/// required int currentLength,
|
||||
/// required int? maxLength,
|
||||
/// required bool isFocused,
|
||||
/// }
|
||||
/// ) {
|
||||
/// return Text(
|
||||
/// '$currentLength of $maxLength characters',
|
||||
/// semanticsLabel: 'character count',
|
||||
/// );
|
||||
/// }
|
||||
/// ```
|
||||
/// {@end-tool}
|
||||
///
|
||||
/// If buildCounter returns null, then no counter and no Semantics widget will
|
||||
/// be created at all.
|
||||
final InputCounterWidgetBuilder? buildCounter;
|
||||
|
||||
/// {@macro flutter.widgets.editableText.scrollPhysics}
|
||||
final ScrollPhysics? scrollPhysics;
|
||||
|
||||
/// {@macro flutter.widgets.editableText.scrollController}
|
||||
final ScrollController? scrollController;
|
||||
|
||||
/// {@macro flutter.widgets.editableText.autofillHints}
|
||||
/// {@macro flutter.services.AutofillConfiguration.autofillHints}
|
||||
final Iterable<String>? autofillHints;
|
||||
|
||||
/// {@template flutter.material.textfield.restorationId}
|
||||
/// Restoration ID to save and restore the state of the text field.
|
||||
///
|
||||
/// If non-null, the text field will persist and restore its current scroll
|
||||
/// offset and - if no [controller] has been provided - the content of the
|
||||
/// text field. If a [controller] has been provided, it is the responsibility
|
||||
/// of the owner of that controller to persist and restore it, e.g. by using
|
||||
/// a [RestorableTextEditingController].
|
||||
///
|
||||
/// The state of this widget is persisted in a [RestorationBucket] claimed
|
||||
/// from the surrounding [RestorationScope] using the provided restoration ID.
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [RestorationManager], which explains how state restoration works in
|
||||
/// Flutter.
|
||||
/// {@endtemplate}
|
||||
final String? restorationId;
|
||||
|
||||
/// {@macro flutter.services.TextInputConfiguration.enableIMEPersonalizedLearning}
|
||||
final bool enableIMEPersonalizedLearning;
|
||||
|
||||
@override
|
||||
_StreamMessageTextFieldState createState() => _StreamMessageTextFieldState();
|
||||
|
||||
@override
|
||||
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
|
||||
super.debugFillProperties(properties);
|
||||
properties.add(DiagnosticsProperty<FocusNode>('focusNode', focusNode,
|
||||
defaultValue: null));
|
||||
properties
|
||||
.add(DiagnosticsProperty<bool>('enabled', enabled, defaultValue: null));
|
||||
properties.add(DiagnosticsProperty<InputDecoration>(
|
||||
'decoration', decoration,
|
||||
defaultValue: const InputDecoration()));
|
||||
properties.add(DiagnosticsProperty<TextInputType>(
|
||||
'keyboardType', keyboardType,
|
||||
defaultValue: TextInputType.text));
|
||||
properties.add(
|
||||
DiagnosticsProperty<TextStyle>('style', style, defaultValue: null));
|
||||
properties.add(
|
||||
DiagnosticsProperty<bool>('autofocus', autofocus, defaultValue: false));
|
||||
properties.add(DiagnosticsProperty<String>(
|
||||
'obscuringCharacter', obscuringCharacter,
|
||||
defaultValue: '•'));
|
||||
properties.add(DiagnosticsProperty<bool>('obscureText', obscureText,
|
||||
defaultValue: false));
|
||||
properties.add(DiagnosticsProperty<bool>('autocorrect', autocorrect,
|
||||
defaultValue: true));
|
||||
properties.add(EnumProperty<SmartDashesType>(
|
||||
'smartDashesType', smartDashesType,
|
||||
defaultValue:
|
||||
obscureText ? SmartDashesType.disabled : SmartDashesType.enabled));
|
||||
properties.add(EnumProperty<SmartQuotesType>(
|
||||
'smartQuotesType', smartQuotesType,
|
||||
defaultValue:
|
||||
obscureText ? SmartQuotesType.disabled : SmartQuotesType.enabled));
|
||||
properties.add(DiagnosticsProperty<bool>(
|
||||
'enableSuggestions', enableSuggestions,
|
||||
defaultValue: true));
|
||||
properties.add(IntProperty('maxLines', maxLines, defaultValue: 1));
|
||||
properties.add(IntProperty('minLines', minLines, defaultValue: null));
|
||||
properties.add(
|
||||
DiagnosticsProperty<bool>('expands', expands, defaultValue: false));
|
||||
properties.add(IntProperty('maxLength', maxLength, defaultValue: null));
|
||||
properties.add(EnumProperty<MaxLengthEnforcement>(
|
||||
'maxLengthEnforcement', maxLengthEnforcement,
|
||||
defaultValue: null));
|
||||
properties.add(EnumProperty<TextInputAction>(
|
||||
'textInputAction', textInputAction,
|
||||
defaultValue: null));
|
||||
properties.add(EnumProperty<TextCapitalization>(
|
||||
'textCapitalization', textCapitalization,
|
||||
defaultValue: TextCapitalization.none));
|
||||
properties.add(EnumProperty<TextAlign>('textAlign', textAlign,
|
||||
defaultValue: TextAlign.start));
|
||||
properties.add(DiagnosticsProperty<TextAlignVertical>(
|
||||
'textAlignVertical', textAlignVertical,
|
||||
defaultValue: null));
|
||||
properties.add(EnumProperty<TextDirection>('textDirection', textDirection,
|
||||
defaultValue: null));
|
||||
properties
|
||||
.add(DoubleProperty('cursorWidth', cursorWidth, defaultValue: 2.0));
|
||||
properties
|
||||
.add(DoubleProperty('cursorHeight', cursorHeight, defaultValue: null));
|
||||
properties.add(DiagnosticsProperty<Radius>('cursorRadius', cursorRadius,
|
||||
defaultValue: null));
|
||||
properties
|
||||
.add(ColorProperty('cursorColor', cursorColor, defaultValue: null));
|
||||
properties.add(DiagnosticsProperty<Brightness>(
|
||||
'keyboardAppearance', keyboardAppearance,
|
||||
defaultValue: null));
|
||||
properties.add(DiagnosticsProperty<EdgeInsetsGeometry>(
|
||||
'scrollPadding', scrollPadding,
|
||||
defaultValue: const EdgeInsets.all(20)));
|
||||
properties.add(FlagProperty('selectionEnabled',
|
||||
value: selectionEnabled,
|
||||
defaultValue: true,
|
||||
ifFalse: 'selection disabled'));
|
||||
properties.add(DiagnosticsProperty<TextSelectionControls>(
|
||||
'selectionControls', selectionControls,
|
||||
defaultValue: null));
|
||||
properties.add(DiagnosticsProperty<ScrollController>(
|
||||
'scrollController', scrollController,
|
||||
defaultValue: null));
|
||||
properties.add(DiagnosticsProperty<ScrollPhysics>(
|
||||
'scrollPhysics', scrollPhysics,
|
||||
defaultValue: null));
|
||||
properties.add(DiagnosticsProperty<bool>(
|
||||
'enableIMEPersonalizedLearning', enableIMEPersonalizedLearning,
|
||||
defaultValue: true));
|
||||
}
|
||||
}
|
||||
|
||||
class _StreamMessageTextFieldState extends State<StreamMessageTextField>
|
||||
with RestorationMixin<StreamMessageTextField> {
|
||||
RestorableMessageInputController? _controller;
|
||||
|
||||
MessageInputController get _effectiveController =>
|
||||
widget.controller ?? _controller!.value;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
if (widget.controller == null) {
|
||||
_createLocalController();
|
||||
}
|
||||
}
|
||||
|
||||
void _createLocalController([Message? message]) {
|
||||
assert(_controller == null, '');
|
||||
_controller = RestorableMessageInputController(message: message);
|
||||
}
|
||||
|
||||
@override
|
||||
void didUpdateWidget(covariant StreamMessageTextField oldWidget) {
|
||||
super.didUpdateWidget(oldWidget);
|
||||
if (widget.controller == null && oldWidget.controller != null) {
|
||||
_createLocalController(oldWidget.controller!.value);
|
||||
} else if (widget.controller != null && oldWidget.controller == null) {
|
||||
unregisterFromRestoration(_controller!);
|
||||
_controller!.dispose();
|
||||
_controller = null;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void restoreState(RestorationBucket? oldBucket, bool initialRestore) {
|
||||
if (_controller != null) {
|
||||
_registerController();
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
String? get restorationId => widget.restorationId;
|
||||
|
||||
void _registerController() {
|
||||
assert(_controller != null, '');
|
||||
registerForRestoration(_controller!, restorationId ?? 'controller');
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) => TextField(
|
||||
controller: _effectiveController.textEditingController,
|
||||
onChanged: (newText) {
|
||||
_effectiveController.text = newText;
|
||||
},
|
||||
focusNode: widget.focusNode,
|
||||
decoration: widget.decoration,
|
||||
keyboardType: widget.keyboardType,
|
||||
textInputAction: widget.textInputAction,
|
||||
textCapitalization: widget.textCapitalization,
|
||||
style: widget.style,
|
||||
strutStyle: widget.strutStyle,
|
||||
textAlign: widget.textAlign,
|
||||
textAlignVertical: widget.textAlignVertical,
|
||||
textDirection: widget.textDirection,
|
||||
readOnly: widget.readOnly,
|
||||
toolbarOptions: widget.toolbarOptions,
|
||||
showCursor: widget.showCursor,
|
||||
autofocus: widget.autofocus,
|
||||
obscuringCharacter: widget.obscuringCharacter,
|
||||
obscureText: widget.obscureText,
|
||||
autocorrect: widget.autocorrect,
|
||||
smartDashesType: widget.smartDashesType,
|
||||
smartQuotesType: widget.smartQuotesType,
|
||||
enableSuggestions: widget.enableSuggestions,
|
||||
maxLines: widget.maxLines,
|
||||
minLines: widget.minLines,
|
||||
expands: widget.expands,
|
||||
maxLength: widget.maxLength,
|
||||
maxLengthEnforcement: widget.maxLengthEnforcement,
|
||||
onEditingComplete: widget.onEditingComplete,
|
||||
onSubmitted: widget.onSubmitted,
|
||||
onAppPrivateCommand: widget.onAppPrivateCommand,
|
||||
inputFormatters: widget.inputFormatters,
|
||||
enabled: widget.enabled,
|
||||
cursorWidth: widget.cursorWidth,
|
||||
cursorHeight: widget.cursorHeight,
|
||||
cursorRadius: widget.cursorRadius,
|
||||
cursorColor: widget.cursorColor,
|
||||
selectionHeightStyle: widget.selectionHeightStyle,
|
||||
selectionWidthStyle: widget.selectionWidthStyle,
|
||||
keyboardAppearance: widget.keyboardAppearance,
|
||||
scrollPadding: widget.scrollPadding,
|
||||
dragStartBehavior: widget.dragStartBehavior,
|
||||
enableInteractiveSelection: widget.enableInteractiveSelection,
|
||||
selectionControls: widget.selectionControls,
|
||||
onTap: widget.onTap,
|
||||
mouseCursor: widget.mouseCursor,
|
||||
buildCounter: widget.buildCounter,
|
||||
scrollController: widget.scrollController,
|
||||
scrollPhysics: widget.scrollPhysics,
|
||||
autofillHints: widget.autofillHints,
|
||||
restorationId: widget.restorationId,
|
||||
enableIMEPersonalizedLearning: widget.enableIMEPersonalizedLearning,
|
||||
);
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_controller?.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -11,22 +11,22 @@ import 'package:stream_chat_flutter/stream_chat_flutter.dart';
|
||||
import 'package:visibility_detector/visibility_detector.dart';
|
||||
|
||||
/// Widget builder for message
|
||||
/// [defaultMessageWidget] is the default [MessageWidget] configuration
|
||||
/// [defaultMessageWidget] is the default [StreamMessageWidget] configuration
|
||||
/// Use [defaultMessageWidget.copyWith] to easily customize it
|
||||
typedef MessageBuilder = Widget Function(
|
||||
BuildContext,
|
||||
MessageDetails,
|
||||
List<Message>,
|
||||
MessageWidget defaultMessageWidget,
|
||||
StreamMessageWidget defaultMessageWidget,
|
||||
);
|
||||
|
||||
/// Widget builder for parent message
|
||||
/// [defaultMessageWidget] is the default [MessageWidget] configuration
|
||||
/// [defaultMessageWidget] is the default [StreamMessageWidget] configuration
|
||||
/// Use [defaultMessageWidget.copyWith] to easily customize it
|
||||
typedef ParentMessageBuilder = Widget Function(
|
||||
BuildContext,
|
||||
Message?,
|
||||
MessageWidget defaultMessageWidget,
|
||||
StreamMessageWidget defaultMessageWidget,
|
||||
);
|
||||
|
||||
/// Widget builder for system message
|
||||
@@ -123,6 +123,11 @@ class MessageDetails {
|
||||
final int index;
|
||||
}
|
||||
|
||||
/// {@macro message_list_view}
|
||||
@Deprecated("Use 'StreamMessageListView' instead")
|
||||
typedef MessageListView = StreamMessageListView;
|
||||
|
||||
/// {@template message_list_view}
|
||||
/// 
|
||||
/// 
|
||||
///
|
||||
@@ -165,9 +170,10 @@ class MessageDetails {
|
||||
/// The widget components render the ui based on the first
|
||||
/// ancestor of type [StreamChatTheme].
|
||||
/// Modify it to change the widget appearance.
|
||||
class MessageListView extends StatefulWidget {
|
||||
/// {@endtemplate}
|
||||
class StreamMessageListView extends StatefulWidget {
|
||||
/// Instantiate a new MessageListView
|
||||
const MessageListView({
|
||||
const StreamMessageListView({
|
||||
Key? key,
|
||||
this.showScrollToBottom = true,
|
||||
this.scrollToBottomBuilder,
|
||||
@@ -197,7 +203,6 @@ class MessageListView extends StatefulWidget {
|
||||
this.messageFilter,
|
||||
this.onMessageTap,
|
||||
this.onSystemMessageTap,
|
||||
this.pinPermissions = const [],
|
||||
this.showFloatingDateDivider = true,
|
||||
this.threadSeparatorBuilder,
|
||||
this.messageListController,
|
||||
@@ -320,9 +325,6 @@ class MessageListView extends StatefulWidget {
|
||||
/// Called when system message is tapped
|
||||
final OnMessageTap? onSystemMessageTap;
|
||||
|
||||
/// A List of user types that have permission to pin messages
|
||||
final List<String> pinPermissions;
|
||||
|
||||
/// Builder used to build the thread separator in case it's a thread view
|
||||
final WidgetBuilder? threadSeparatorBuilder;
|
||||
|
||||
@@ -340,10 +342,10 @@ class MessageListView extends StatefulWidget {
|
||||
final SpacingWidgetBuilder? spacingWidgetBuilder;
|
||||
|
||||
@override
|
||||
_MessageListViewState createState() => _MessageListViewState();
|
||||
_StreamMessageListViewState createState() => _StreamMessageListViewState();
|
||||
}
|
||||
|
||||
class _MessageListViewState extends State<MessageListView> {
|
||||
class _StreamMessageListViewState extends State<StreamMessageListView> {
|
||||
ItemScrollController? _scrollController;
|
||||
void Function(Message)? _onThreadTap;
|
||||
final ValueNotifier<bool> _showScrollToBottom = ValueNotifier(false);
|
||||
@@ -351,6 +353,7 @@ class _MessageListViewState extends State<MessageListView> {
|
||||
int? _messageListLength;
|
||||
StreamChannelState? streamChannel;
|
||||
late StreamChatThemeData _streamTheme;
|
||||
late List<String> _userPermissions;
|
||||
|
||||
int get _initialIndex {
|
||||
final initialScrollIndex = widget.initialScrollIndex;
|
||||
@@ -470,7 +473,7 @@ class _MessageListViewState extends State<MessageListView> {
|
||||
final child = Stack(
|
||||
alignment: Alignment.center,
|
||||
children: [
|
||||
ConnectionStatusBuilder(
|
||||
StreamConnectionStatusBuilder(
|
||||
statusBuilder: (context, status) {
|
||||
var statusString = '';
|
||||
var showStatus = true;
|
||||
@@ -487,7 +490,7 @@ class _MessageListViewState extends State<MessageListView> {
|
||||
break;
|
||||
}
|
||||
|
||||
return InfoTile(
|
||||
return StreamInfoTile(
|
||||
showMessage: widget.showConnectionStateTile && showStatus,
|
||||
tileAnchor: Alignment.topCenter,
|
||||
childAnchor: Alignment.topCenter,
|
||||
@@ -535,6 +538,7 @@ class _MessageListViewState extends State<MessageListView> {
|
||||
return ((index + 2) * 2) - 1;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
},
|
||||
|
||||
// Item Count -> 8 (1 parent, 2 header+footer, 2 top+bottom, 3 messages)
|
||||
@@ -600,7 +604,7 @@ class _MessageListViewState extends State<MessageListView> {
|
||||
)
|
||||
: Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 12),
|
||||
child: DateDivider(
|
||||
child: StreamDateDivider(
|
||||
dateTime: nextMessage.createdAt.toLocal(),
|
||||
),
|
||||
);
|
||||
@@ -738,8 +742,10 @@ class _MessageListViewState extends State<MessageListView> {
|
||||
],
|
||||
);
|
||||
|
||||
final backgroundColor = MessageListViewTheme.of(context).backgroundColor;
|
||||
final backgroundImage = MessageListViewTheme.of(context).backgroundImage;
|
||||
final backgroundColor =
|
||||
StreamMessageListViewTheme.of(context).backgroundColor;
|
||||
final backgroundImage =
|
||||
StreamMessageListViewTheme.of(context).backgroundImage;
|
||||
|
||||
if (backgroundColor != null || backgroundImage != null) {
|
||||
return Container(
|
||||
@@ -769,7 +775,7 @@ class _MessageListViewState extends State<MessageListView> {
|
||||
child: Text(
|
||||
context.translations.threadSeparatorText(replyCount),
|
||||
textAlign: TextAlign.center,
|
||||
style: ChannelHeaderTheme.of(context).subtitleStyle,
|
||||
style: StreamChannelHeaderTheme.of(context).subtitleStyle,
|
||||
),
|
||||
),
|
||||
);
|
||||
@@ -823,7 +829,7 @@ class _MessageListViewState extends State<MessageListView> {
|
||||
final message = messages[index - 2];
|
||||
return widget.dateDividerBuilder != null
|
||||
? widget.dateDividerBuilder!(message.createdAt.toLocal())
|
||||
: DateDivider(dateTime: message.createdAt.toLocal());
|
||||
: StreamDateDivider(dateTime: message.createdAt.toLocal());
|
||||
},
|
||||
),
|
||||
);
|
||||
@@ -992,7 +998,7 @@ class _MessageListViewState extends State<MessageListView> {
|
||||
final currentUserMember =
|
||||
members.firstWhereOrNull((e) => e.user!.id == currentUser!.id);
|
||||
|
||||
final defaultMessageWidget = MessageWidget(
|
||||
final defaultMessageWidget = StreamMessageWidget(
|
||||
showReplyMessage: false,
|
||||
showResendMessage: false,
|
||||
showThreadReplyMessage: false,
|
||||
@@ -1038,7 +1044,7 @@ class _MessageListViewState extends State<MessageListView> {
|
||||
FocusScope.of(context).unfocus();
|
||||
},
|
||||
showPinButton: currentUserMember != null &&
|
||||
widget.pinPermissions.contains(currentUserMember.role),
|
||||
_userPermissions.contains(PermissionType.pinMessage),
|
||||
);
|
||||
|
||||
if (widget.parentMessageBuilder != null) {
|
||||
@@ -1056,7 +1062,7 @@ class _MessageListViewState extends State<MessageListView> {
|
||||
if ((message.type == 'system' || message.type == 'error') &&
|
||||
message.text?.isNotEmpty == true) {
|
||||
return widget.systemMessageBuilder?.call(context, message) ??
|
||||
SystemMessage(
|
||||
StreamSystemMessage(
|
||||
message: message,
|
||||
onMessageTap: (message) {
|
||||
if (widget.onSystemMessageTap != null) {
|
||||
@@ -1126,7 +1132,7 @@ class _MessageListViewState extends State<MessageListView> {
|
||||
final currentUserMember =
|
||||
members.firstWhereOrNull((e) => e.user!.id == currentUser!.id);
|
||||
|
||||
Widget messageWidget = MessageWidget(
|
||||
Widget messageWidget = StreamMessageWidget(
|
||||
message: message,
|
||||
reverse: isMyMessage,
|
||||
showReactions: !message.isDeleted,
|
||||
@@ -1157,7 +1163,10 @@ class _MessageListViewState extends State<MessageListView> {
|
||||
},
|
||||
showEditMessage: isMyMessage,
|
||||
showDeleteMessage: isMyMessage,
|
||||
showThreadReplyMessage: !isThreadMessage,
|
||||
showThreadReplyMessage: !isThreadMessage &&
|
||||
streamChannel?.channel.ownCapabilities
|
||||
.contains(PermissionType.sendReply) ==
|
||||
true,
|
||||
showFlagButton: !isMyMessage,
|
||||
borderSide: borderSide,
|
||||
onThreadTap: _onThreadTap,
|
||||
@@ -1226,7 +1235,7 @@ class _MessageListViewState extends State<MessageListView> {
|
||||
FocusScope.of(context).unfocus();
|
||||
},
|
||||
showPinButton: currentUserMember != null &&
|
||||
widget.pinPermissions.contains(currentUserMember.role),
|
||||
_userPermissions.contains(PermissionType.pinMessage),
|
||||
);
|
||||
|
||||
if (widget.messageBuilder != null) {
|
||||
@@ -1239,7 +1248,7 @@ class _MessageListViewState extends State<MessageListView> {
|
||||
index,
|
||||
),
|
||||
messages,
|
||||
messageWidget as MessageWidget,
|
||||
messageWidget as StreamMessageWidget,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1306,6 +1315,7 @@ class _MessageListViewState extends State<MessageListView> {
|
||||
void didChangeDependencies() {
|
||||
final newStreamChannel = StreamChannel.of(context);
|
||||
_streamTheme = StreamChatTheme.of(context);
|
||||
_userPermissions = newStreamChannel.channel.ownCapabilities;
|
||||
|
||||
if (newStreamChannel != streamChannel) {
|
||||
streamChannel = newStreamChannel;
|
||||
|
||||
@@ -5,15 +5,21 @@ import 'package:stream_chat_flutter/src/extension.dart';
|
||||
import 'package:stream_chat_flutter/src/reaction_bubble.dart';
|
||||
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
|
||||
|
||||
/// {@macro message_reactions_modal}
|
||||
@Deprecated("Use 'StreamMessageReactionsModal' instead")
|
||||
typedef MessageReactionsModal = StreamMessageReactionsModal;
|
||||
|
||||
/// {@template message_reactions_modal}
|
||||
/// Modal widget for displaying message reactions
|
||||
class MessageReactionsModal extends StatelessWidget {
|
||||
/// Constructor for creating a [MessageReactionsModal] reactions
|
||||
const MessageReactionsModal({
|
||||
/// {@endtemplate}
|
||||
class StreamMessageReactionsModal extends StatelessWidget {
|
||||
/// Constructor for creating a [StreamMessageReactionsModal] reactions
|
||||
const StreamMessageReactionsModal({
|
||||
Key? key,
|
||||
required this.message,
|
||||
required this.messageWidget,
|
||||
required this.messageTheme,
|
||||
this.showReactions = true,
|
||||
this.showReactions,
|
||||
this.reverse = false,
|
||||
this.onUserAvatarTap,
|
||||
}) : super(key: key);
|
||||
@@ -24,14 +30,14 @@ class MessageReactionsModal extends StatelessWidget {
|
||||
/// Message to display reactions of
|
||||
final Message message;
|
||||
|
||||
/// [MessageThemeData] to apply to [message]
|
||||
final MessageThemeData messageTheme;
|
||||
/// [StreamMessageThemeData] to apply to [message]
|
||||
final StreamMessageThemeData messageTheme;
|
||||
|
||||
/// Flag to reverse message
|
||||
final bool reverse;
|
||||
|
||||
/// Flag to show reactions on message
|
||||
final bool showReactions;
|
||||
final bool? showReactions;
|
||||
|
||||
/// Callback when user avatar is tapped
|
||||
final void Function(User)? onUserAvatarTap;
|
||||
@@ -40,6 +46,10 @@ class MessageReactionsModal extends StatelessWidget {
|
||||
Widget build(BuildContext context) {
|
||||
final size = MediaQuery.of(context).size;
|
||||
final user = StreamChat.of(context).currentUser;
|
||||
final _userPermissions = StreamChannel.of(context).channel.ownCapabilities;
|
||||
|
||||
final hasReactionPermission =
|
||||
_userPermissions.contains(PermissionType.sendReaction);
|
||||
|
||||
final roughMaxSize = size.width * 2 / 3;
|
||||
var messageTextLength = message.text!.length;
|
||||
@@ -71,7 +81,7 @@ class MessageReactionsModal extends StatelessWidget {
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: <Widget>[
|
||||
if (showReactions &&
|
||||
if ((showReactions ?? hasReactionPermission) &&
|
||||
(message.status == MessageSendingStatus.sent))
|
||||
Align(
|
||||
alignment: Alignment(
|
||||
@@ -84,7 +94,7 @@ class MessageReactionsModal extends StatelessWidget {
|
||||
: -(1.2 - divFactor)),
|
||||
0,
|
||||
),
|
||||
child: ReactionPicker(
|
||||
child: StreamReactionPicker(
|
||||
message: message,
|
||||
),
|
||||
),
|
||||
@@ -196,7 +206,7 @@ class MessageReactionsModal extends StatelessWidget {
|
||||
Stack(
|
||||
clipBehavior: Clip.none,
|
||||
children: [
|
||||
UserAvatar(
|
||||
StreamUserAvatar(
|
||||
onTap: onUserAvatarTap,
|
||||
user: reaction.user!,
|
||||
constraints: const BoxConstraints.tightFor(
|
||||
@@ -216,7 +226,7 @@ class MessageReactionsModal extends StatelessWidget {
|
||||
child: Align(
|
||||
alignment:
|
||||
reverse ? Alignment.centerRight : Alignment.centerLeft,
|
||||
child: ReactionBubble(
|
||||
child: StreamReactionBubble(
|
||||
reactions: [reaction],
|
||||
flipTail: !reverse,
|
||||
borderColor:
|
||||
|
||||
@@ -2,17 +2,23 @@ import 'package:flutter/material.dart';
|
||||
import 'package:stream_chat_flutter/src/extension.dart';
|
||||
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
|
||||
|
||||
/// {@macro message_search_item}
|
||||
@Deprecated("Use 'StreamMessageSearchItem' instead")
|
||||
typedef MessageSearchItem = StreamMessageSearchItem;
|
||||
|
||||
/// {@template message_search_item}
|
||||
/// It shows the current [Message] preview.
|
||||
///
|
||||
/// Usually you don't use this widget as it's the default item used by
|
||||
/// [MessageSearchListView].
|
||||
/// [StreamMessageSearchListView].
|
||||
///
|
||||
/// The widget renders the ui based on the first ancestor of type
|
||||
/// [StreamChatTheme].
|
||||
/// Modify it to change the widget appearance.
|
||||
class MessageSearchItem extends StatelessWidget {
|
||||
/// {@endtemplate}
|
||||
class StreamMessageSearchItem extends StatelessWidget {
|
||||
/// Instantiate a new MessageSearchItem
|
||||
const MessageSearchItem({
|
||||
const StreamMessageSearchItem({
|
||||
Key? key,
|
||||
required this.getMessageResponse,
|
||||
this.onTap,
|
||||
@@ -25,7 +31,7 @@ class MessageSearchItem extends StatelessWidget {
|
||||
/// Function called when tapping this widget
|
||||
final VoidCallback? onTap;
|
||||
|
||||
/// If true the [MessageSearchItem] will show the current online Status
|
||||
/// If true the [StreamMessageSearchItem] will show the current online Status
|
||||
final bool showOnlineStatus;
|
||||
|
||||
@override
|
||||
@@ -34,10 +40,10 @@ class MessageSearchItem extends StatelessWidget {
|
||||
final channel = getMessageResponse.channel;
|
||||
final channelName = channel?.extraData['name'];
|
||||
final user = message.user!;
|
||||
final channelPreviewTheme = ChannelPreviewTheme.of(context);
|
||||
final channelPreviewTheme = StreamChannelPreviewTheme.of(context);
|
||||
return ListTile(
|
||||
onTap: onTap,
|
||||
leading: UserAvatar(
|
||||
leading: StreamUserAvatar(
|
||||
user: user,
|
||||
showOnlineStatus: showOnlineStatus,
|
||||
constraints: const BoxConstraints.tightFor(
|
||||
@@ -120,7 +126,7 @@ class MessageSearchItem extends StatelessWidget {
|
||||
text = parts.join(' ');
|
||||
}
|
||||
|
||||
final channelPreviewTheme = ChannelPreviewTheme.of(context);
|
||||
final channelPreviewTheme = StreamChannelPreviewTheme.of(context);
|
||||
return Text.rich(
|
||||
_getDisplayText(
|
||||
text!,
|
||||
|
||||
@@ -11,13 +11,17 @@ typedef MessageSearchItemBuilder = Widget Function(
|
||||
GetMessageResponse,
|
||||
);
|
||||
|
||||
/// Builder used when [MessageSearchListView] is empty
|
||||
/// Builder used when [StreamMessageSearchListView] is empty
|
||||
typedef EmptyMessageSearchBuilder = Widget Function(
|
||||
BuildContext context,
|
||||
String searchQuery,
|
||||
);
|
||||
|
||||
///
|
||||
/// {@macro message_search_list_view}
|
||||
@Deprecated("Use 'StreamMessageSearchListView' instead")
|
||||
typedef MessageSearchListView = StreamMessageSearchListView;
|
||||
|
||||
/// {@template message_search_list_view}
|
||||
/// It shows the list of searched messages.
|
||||
///
|
||||
/// ```dart
|
||||
@@ -47,9 +51,10 @@ typedef EmptyMessageSearchBuilder = Widget Function(
|
||||
/// The widget components render the ui based on the first ancestor of type
|
||||
/// [StreamChatTheme].
|
||||
/// Modify it to change the widget appearance.
|
||||
class MessageSearchListView extends StatefulWidget {
|
||||
/// {@endtemplate}
|
||||
class StreamMessageSearchListView extends StatefulWidget {
|
||||
/// Instantiate a new MessageSearchListView
|
||||
const MessageSearchListView({
|
||||
const StreamMessageSearchListView({
|
||||
Key? key,
|
||||
required this.filters,
|
||||
this.messageQuery,
|
||||
@@ -96,7 +101,7 @@ class MessageSearchListView extends StatefulWidget {
|
||||
/// Builder used to create a custom item preview
|
||||
final MessageSearchItemBuilder? itemBuilder;
|
||||
|
||||
/// Function called when tapping on a [MessageSearchItem]
|
||||
/// Function called when tapping on a [StreamMessageSearchItem]
|
||||
final MessageSearchItemTapCallback? onItemTap;
|
||||
|
||||
/// Builder used to create a custom item separator
|
||||
@@ -130,10 +135,12 @@ class MessageSearchListView extends StatefulWidget {
|
||||
final MessageSearchListController? messageSearchListController;
|
||||
|
||||
@override
|
||||
_MessageSearchListViewState createState() => _MessageSearchListViewState();
|
||||
_StreamMessageSearchListViewState createState() =>
|
||||
_StreamMessageSearchListViewState();
|
||||
}
|
||||
|
||||
class _MessageSearchListViewState extends State<MessageSearchListView> {
|
||||
class _StreamMessageSearchListViewState
|
||||
extends State<StreamMessageSearchListView> {
|
||||
late final _defaultController = MessageSearchListController();
|
||||
|
||||
MessageSearchListController get _messageSearchListController =>
|
||||
@@ -168,7 +175,7 @@ class _MessageSearchListViewState extends State<MessageSearchListView> {
|
||||
if (error is Error) {
|
||||
print(error.stackTrace);
|
||||
}
|
||||
return InfoTile(
|
||||
return StreamInfoTile(
|
||||
showMessage: widget.showErrorTile,
|
||||
tileAnchor: Alignment.topCenter,
|
||||
childAnchor: Alignment.topCenter,
|
||||
@@ -195,7 +202,7 @@ class _MessageSearchListViewState extends State<MessageSearchListView> {
|
||||
);
|
||||
|
||||
final backgroundColor =
|
||||
MessageSearchListViewTheme.of(context).backgroundColor;
|
||||
StreamMessageSearchListViewTheme.of(context).backgroundColor;
|
||||
|
||||
if (backgroundColor != null) {
|
||||
return ColoredBox(
|
||||
@@ -219,7 +226,7 @@ class _MessageSearchListViewState extends State<MessageSearchListView> {
|
||||
if (widget.itemBuilder != null) {
|
||||
return widget.itemBuilder!(context, getMessageResponse);
|
||||
}
|
||||
return MessageSearchItem(
|
||||
return StreamMessageSearchItem(
|
||||
getMessageResponse: getMessageResponse,
|
||||
onTap: () => widget.onItemTap!(getMessageResponse),
|
||||
);
|
||||
|
||||
@@ -1,12 +1,19 @@
|
||||
import 'package:collection/collection.dart' show IterableExtension;
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_markdown/flutter_markdown.dart';
|
||||
import 'package:stream_chat_flutter/src/extension.dart';
|
||||
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
|
||||
|
||||
/// {@macro message_text}
|
||||
@Deprecated("Use 'StreamMessageText' instead")
|
||||
typedef MessageText = StreamMessageText;
|
||||
|
||||
/// {@template message_text}
|
||||
/// Text widget to display in message
|
||||
class MessageText extends StatelessWidget {
|
||||
/// Constructor for creating a [MessageText] widget
|
||||
const MessageText({
|
||||
/// {@endtemplate}
|
||||
class StreamMessageText extends StatelessWidget {
|
||||
/// Constructor for creating a [StreamMessageText] widget
|
||||
const StreamMessageText({
|
||||
Key? key,
|
||||
required this.message,
|
||||
required this.messageTheme,
|
||||
@@ -23,8 +30,8 @@ class MessageText extends StatelessWidget {
|
||||
/// Callback for when link is tapped
|
||||
final void Function(String)? onLinkTap;
|
||||
|
||||
/// [MessageThemeData] whose text theme is to be applied
|
||||
final MessageThemeData messageTheme;
|
||||
/// [StreamMessageThemeData] whose text theme is to be applied
|
||||
final StreamMessageThemeData messageTheme;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@@ -34,13 +41,14 @@ class MessageText extends StatelessWidget {
|
||||
stream: streamChat.currentUserStream.map((it) => it!.language ?? 'en'),
|
||||
initialData: streamChat.currentUser!.language ?? 'en',
|
||||
builder: (context, language) {
|
||||
final translatedText =
|
||||
message.i18n?['${language}_text'] ?? message.text;
|
||||
final messageText =
|
||||
_replaceMentions(translatedText ?? '').replaceAll('\n', '\n\n');
|
||||
final messageText = message
|
||||
.translate(language)
|
||||
.replaceMentions()
|
||||
.text
|
||||
?.replaceAll('\n', '\n\n');
|
||||
final themeData = Theme.of(context);
|
||||
return MarkdownBody(
|
||||
data: messageText,
|
||||
data: messageText ?? '',
|
||||
onTapLink: (
|
||||
String link,
|
||||
String? href,
|
||||
@@ -80,17 +88,4 @@ class MessageText extends StatelessWidget {
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
String _replaceMentions(String text) {
|
||||
var messageTextToRender = text;
|
||||
for (final user in message.mentionedUsers.toSet()) {
|
||||
final userId = user.id;
|
||||
final userName = user.name;
|
||||
messageTextToRender = messageTextToRender.replaceAll(
|
||||
'@$userId',
|
||||
'[@$userName](@${userName.replaceAll(' ', '')})',
|
||||
);
|
||||
}
|
||||
return messageTextToRender;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,20 +32,26 @@ enum DisplayWidget {
|
||||
show,
|
||||
}
|
||||
|
||||
/// {@macro message_widget}
|
||||
@Deprecated("Use 'StreamMessageWidget' instead")
|
||||
typedef MessageWidget = StreamMessageWidget;
|
||||
|
||||
/// {@template message_widget}
|
||||
/// 
|
||||
/// 
|
||||
///
|
||||
/// It shows a message with reactions, replies and user avatar.
|
||||
///
|
||||
/// Usually you don't use this widget as it's the default message widget used by
|
||||
/// [MessageListView].
|
||||
/// [StreamMessageListView].
|
||||
///
|
||||
/// The widget components render the ui based on the first ancestor of type
|
||||
/// [StreamChatTheme].
|
||||
/// Modify it to change the widget appearance.
|
||||
class MessageWidget extends StatefulWidget {
|
||||
///
|
||||
MessageWidget({
|
||||
/// {@endtemplate}
|
||||
class StreamMessageWidget extends StatefulWidget {
|
||||
/// Creates a new instance of the message widget.
|
||||
StreamMessageWidget({
|
||||
Key? key,
|
||||
required this.message,
|
||||
required this.messageTheme,
|
||||
@@ -121,7 +127,7 @@ class MessageWidget extends StatefulWidget {
|
||||
context,
|
||||
Material(
|
||||
color: messageTheme.messageBackgroundColor,
|
||||
child: ImageGroup(
|
||||
child: StreamImageGroup(
|
||||
size: Size(
|
||||
mediaQueryData.size.width * 0.8,
|
||||
mediaQueryData.size.height * 0.3,
|
||||
@@ -142,7 +148,7 @@ class MessageWidget extends StatefulWidget {
|
||||
|
||||
return wrapAttachmentWidget(
|
||||
context,
|
||||
ImageAttachment(
|
||||
StreamImageAttachment(
|
||||
attachment: attachments[0],
|
||||
message: message,
|
||||
messageTheme: messageTheme,
|
||||
@@ -172,7 +178,7 @@ class MessageWidget extends StatefulWidget {
|
||||
Column(
|
||||
children: attachments.map((attachment) {
|
||||
final mediaQueryData = MediaQuery.of(context);
|
||||
return VideoAttachment(
|
||||
return StreamVideoAttachment(
|
||||
attachment: attachment,
|
||||
messageTheme: messageTheme,
|
||||
size: Size(
|
||||
@@ -204,7 +210,7 @@ class MessageWidget extends StatefulWidget {
|
||||
Column(
|
||||
children: attachments.map((attachment) {
|
||||
final mediaQueryData = MediaQuery.of(context);
|
||||
return GiphyAttachment(
|
||||
return StreamGiphyAttachment(
|
||||
attachment: attachment,
|
||||
message: message,
|
||||
size: Size(
|
||||
@@ -240,7 +246,7 @@ class MessageWidget extends StatefulWidget {
|
||||
final mediaQueryData = MediaQuery.of(context);
|
||||
return wrapAttachmentWidget(
|
||||
context,
|
||||
FileAttachment(
|
||||
StreamFileAttachment(
|
||||
message: message,
|
||||
attachment: attachment,
|
||||
size: Size(
|
||||
@@ -300,7 +306,7 @@ class MessageWidget extends StatefulWidget {
|
||||
final Message message;
|
||||
|
||||
/// The message theme
|
||||
final MessageThemeData messageTheme;
|
||||
final StreamMessageThemeData messageTheme;
|
||||
|
||||
/// If true the widget will be mirrored
|
||||
final bool reverse;
|
||||
@@ -356,7 +362,7 @@ class MessageWidget extends StatefulWidget {
|
||||
/// The function called when tapping on a link
|
||||
final void Function(String)? onLinkTap;
|
||||
|
||||
/// Used in [MessageReactionsModal] and [MessageActionsModal]
|
||||
/// Used in [StreamMessageReactionsModal] and [StreamMessageActionsModal]
|
||||
final bool showReactionPickerIndicator;
|
||||
|
||||
/// List of users who read
|
||||
@@ -417,13 +423,13 @@ class MessageWidget extends StatefulWidget {
|
||||
final void Function(Message)? onMessageTap;
|
||||
|
||||
/// List of custom actions shown on message long tap
|
||||
final List<MessageAction> customActions;
|
||||
final List<StreamMessageAction> customActions;
|
||||
|
||||
/// Customize onTap on attachment
|
||||
final void Function(Message message, Attachment attachment)? onAttachmentTap;
|
||||
|
||||
/// Creates a copy of [MessageWidget] with specified attributes overridden.
|
||||
MessageWidget copyWith({
|
||||
/// Creates a copy of [StreamMessageWidget] with specified attributes overridden.
|
||||
StreamMessageWidget copyWith({
|
||||
Key? key,
|
||||
void Function(User)? onMentionTap,
|
||||
void Function(Message)? onThreadTap,
|
||||
@@ -435,7 +441,7 @@ class MessageWidget extends StatefulWidget {
|
||||
Widget Function(BuildContext, Message)? deletedBottomRowBuilder,
|
||||
void Function(BuildContext, Message)? onMessageActions,
|
||||
Message? message,
|
||||
MessageThemeData? messageTheme,
|
||||
StreamMessageThemeData? messageTheme,
|
||||
bool? reverse,
|
||||
ShapeBorder? shape,
|
||||
ShapeBorder? attachmentShape,
|
||||
@@ -473,11 +479,11 @@ class MessageWidget extends StatefulWidget {
|
||||
bool? translateUserAvatar,
|
||||
OnQuotedMessageTap? onQuotedMessageTap,
|
||||
void Function(Message)? onMessageTap,
|
||||
List<MessageAction>? customActions,
|
||||
List<StreamMessageAction>? customActions,
|
||||
void Function(Message message, Attachment attachment)? onAttachmentTap,
|
||||
Widget Function(BuildContext, User)? userAvatarBuilder,
|
||||
}) =>
|
||||
MessageWidget(
|
||||
StreamMessageWidget(
|
||||
key: key ?? this.key,
|
||||
onMentionTap: onMentionTap ?? this.onMentionTap,
|
||||
onThreadTap: onThreadTap ?? this.onThreadTap,
|
||||
@@ -539,11 +545,11 @@ class MessageWidget extends StatefulWidget {
|
||||
);
|
||||
|
||||
@override
|
||||
_MessageWidgetState createState() => _MessageWidgetState();
|
||||
_StreamMessageWidgetState createState() => _StreamMessageWidgetState();
|
||||
}
|
||||
|
||||
class _MessageWidgetState extends State<MessageWidget>
|
||||
with AutomaticKeepAliveClientMixin<MessageWidget> {
|
||||
class _StreamMessageWidgetState extends State<StreamMessageWidget>
|
||||
with AutomaticKeepAliveClientMixin<StreamMessageWidget> {
|
||||
bool get showThreadReplyIndicator => widget.showThreadReplyIndicator;
|
||||
|
||||
bool get showSendingIndicator => widget.showSendingIndicator;
|
||||
@@ -717,7 +723,7 @@ class _MessageWidgetState extends State<MessageWidget>
|
||||
? 0
|
||||
: 4.0,
|
||||
),
|
||||
child: DeletedMessage(
|
||||
child: StreamDeletedMessage(
|
||||
borderRadiusGeometry: widget
|
||||
.borderRadiusGeometry,
|
||||
borderSide:
|
||||
@@ -854,7 +860,7 @@ class _MessageWidgetState extends State<MessageWidget>
|
||||
? () => widget.onQuotedMessageTap!(widget.message.quotedMessageId)
|
||||
: null;
|
||||
final chatThemeData = _streamChatTheme;
|
||||
return QuotedMessageWidget(
|
||||
return StreamQuotedMessageWidget(
|
||||
onTap: onTap,
|
||||
message: widget.message.quotedMessage!,
|
||||
messageTheme: isMyMessage
|
||||
@@ -1008,7 +1014,7 @@ class _MessageWidgetState extends State<MessageWidget>
|
||||
getWebsiteName(hostName.toLowerCase()) ??
|
||||
hostName.capitalize();
|
||||
|
||||
return UrlAttachment(
|
||||
return StreamUrlAttachment(
|
||||
urlAttachment: urlAttachment,
|
||||
hostDisplayName: hostDisplayName,
|
||||
textPadding: widget.textPadding,
|
||||
@@ -1042,7 +1048,7 @@ class _MessageWidgetState extends State<MessageWidget>
|
||||
child: _shouldShowReactions
|
||||
? GestureDetector(
|
||||
onTap: () => _showMessageReactionsModalBottomSheet(context),
|
||||
child: ReactionBubble(
|
||||
child: StreamReactionBubble(
|
||||
key: ValueKey('${widget.message.id}.reactions'),
|
||||
reverse: widget.reverse,
|
||||
flipTail: widget.reverse,
|
||||
@@ -1073,7 +1079,7 @@ class _MessageWidgetState extends State<MessageWidget>
|
||||
barrierColor: _streamChatTheme.colorTheme.overlay,
|
||||
builder: (context) => StreamChannel(
|
||||
channel: channel,
|
||||
child: MessageActionsModal(
|
||||
child: StreamMessageActionsModal(
|
||||
messageWidget: widget.copyWith(
|
||||
key: const Key('MessageWidget'),
|
||||
message: widget.message.copyWith(
|
||||
@@ -1088,7 +1094,8 @@ class _MessageWidgetState extends State<MessageWidget>
|
||||
showSendingIndicator: false,
|
||||
padding: const EdgeInsets.all(0),
|
||||
showReactionPickerIndicator: widget.showReactions &&
|
||||
(widget.message.status == MessageSendingStatus.sent),
|
||||
(widget.message.status == MessageSendingStatus.sent) &&
|
||||
channel.ownCapabilities.contains(PermissionType.sendReaction),
|
||||
showPinHighlight: false,
|
||||
showUserAvatar:
|
||||
widget.message.user!.id == channel.client.state.currentUser!.id
|
||||
@@ -1099,7 +1106,6 @@ class _MessageWidgetState extends State<MessageWidget>
|
||||
Clipboard.setData(ClipboardData(text: message.text)),
|
||||
messageTheme: widget.messageTheme,
|
||||
reverse: widget.reverse,
|
||||
showDeleteMessage: widget.showDeleteMessage || isDeleteFailed,
|
||||
message: widget.message,
|
||||
editMessageInputBuilder: widget.editMessageInputBuilder,
|
||||
onReplyTap: widget.onReplyTap,
|
||||
@@ -1109,11 +1115,6 @@ class _MessageWidgetState extends State<MessageWidget>
|
||||
showCopyMessage: widget.showCopyMessage &&
|
||||
!isFailedState &&
|
||||
widget.message.text?.trim().isNotEmpty == true,
|
||||
showEditMessage: widget.showEditMessage &&
|
||||
!isDeleteFailed &&
|
||||
!widget.message.attachments
|
||||
.any((element) => element.type == 'giphy'),
|
||||
showReactions: widget.showReactions,
|
||||
showReplyMessage: widget.showReplyMessage &&
|
||||
!isFailedState &&
|
||||
widget.onReplyTap != null,
|
||||
@@ -1121,7 +1122,6 @@ class _MessageWidgetState extends State<MessageWidget>
|
||||
!isFailedState &&
|
||||
widget.onThreadTap != null,
|
||||
showFlagButton: widget.showFlagButton,
|
||||
showPinButton: widget.showPinButton,
|
||||
customActions: widget.customActions,
|
||||
),
|
||||
),
|
||||
@@ -1136,7 +1136,7 @@ class _MessageWidgetState extends State<MessageWidget>
|
||||
barrierColor: _streamChatTheme.colorTheme.overlay,
|
||||
builder: (context) => StreamChannel(
|
||||
channel: channel,
|
||||
child: MessageReactionsModal(
|
||||
child: StreamMessageReactionsModal(
|
||||
messageWidget: widget.copyWith(
|
||||
key: const Key('MessageWidget'),
|
||||
message: widget.message.copyWith(
|
||||
@@ -1151,7 +1151,8 @@ class _MessageWidgetState extends State<MessageWidget>
|
||||
showSendingIndicator: false,
|
||||
padding: const EdgeInsets.all(0),
|
||||
showReactionPickerIndicator: widget.showReactions &&
|
||||
(widget.message.status == MessageSendingStatus.sent),
|
||||
(widget.message.status == MessageSendingStatus.sent) &&
|
||||
channel.ownCapabilities.contains(PermissionType.sendReaction),
|
||||
showPinHighlight: false,
|
||||
showUserAvatar:
|
||||
widget.message.user!.id == channel.client.state.currentUser!.id
|
||||
@@ -1162,7 +1163,8 @@ class _MessageWidgetState extends State<MessageWidget>
|
||||
messageTheme: widget.messageTheme,
|
||||
reverse: widget.reverse,
|
||||
message: widget.message,
|
||||
showReactions: widget.showReactions,
|
||||
showReactions: widget.showReactions &&
|
||||
channel.ownCapabilities.contains(PermissionType.sendReaction),
|
||||
),
|
||||
),
|
||||
);
|
||||
@@ -1250,6 +1252,13 @@ class _MessageWidgetState extends State<MessageWidget>
|
||||
|
||||
final channel = StreamChannel.of(context).channel;
|
||||
|
||||
if (!channel.ownCapabilities.contains(PermissionType.readEvents)) {
|
||||
return StreamSendingIndicator(
|
||||
message: message,
|
||||
size: style!.fontSize,
|
||||
);
|
||||
}
|
||||
|
||||
return BetterStreamBuilder<List<Read>>(
|
||||
stream: channel.state?.readStream,
|
||||
initialData: channel.state?.read,
|
||||
@@ -1259,7 +1268,7 @@ class _MessageWidgetState extends State<MessageWidget>
|
||||
(it.lastRead.isAfter(message.createdAt) ||
|
||||
it.lastRead.isAtSameMomentAs(message.createdAt)));
|
||||
final isMessageRead = readList.length >= (channel.memberCount ?? 0) - 1;
|
||||
Widget child = SendingIndicator(
|
||||
Widget child = StreamSendingIndicator(
|
||||
message: message,
|
||||
isMessageRead: isMessageRead,
|
||||
size: style!.fontSize,
|
||||
@@ -1293,7 +1302,7 @@ class _MessageWidgetState extends State<MessageWidget>
|
||||
: 0,
|
||||
),
|
||||
child: widget.userAvatarBuilder?.call(context, widget.message.user!) ??
|
||||
UserAvatar(
|
||||
StreamUserAvatar(
|
||||
user: widget.message.user!,
|
||||
onTap: widget.onUserAvatarTap,
|
||||
constraints: widget.messageTheme.avatarTheme!.constraints,
|
||||
@@ -1311,7 +1320,7 @@ class _MessageWidgetState extends State<MessageWidget>
|
||||
padding: isOnlyEmoji ? EdgeInsets.zero : widget.textPadding,
|
||||
child: widget.textBuilder != null
|
||||
? widget.textBuilder!(context, widget.message)
|
||||
: MessageText(
|
||||
: StreamMessageText(
|
||||
onLinkTap: widget.onLinkTap,
|
||||
message: widget.message,
|
||||
onMentionTap: widget.onMentionTap,
|
||||
@@ -1428,7 +1437,7 @@ class _ThreadParticipants extends StatelessWidget {
|
||||
color: _streamChatTheme.colorTheme.barsBg,
|
||||
),
|
||||
padding: const EdgeInsets.all(1),
|
||||
child: UserAvatar(
|
||||
child: StreamUserAvatar(
|
||||
user: user,
|
||||
constraints: BoxConstraints.loose(const Size.fromRadius(7)),
|
||||
showOnlineStatus: false,
|
||||
|
||||
@@ -2,15 +2,21 @@ import 'package:collection/collection.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:flutter_portal/flutter_portal.dart';
|
||||
|
||||
/// {@macro multi_overlay}
|
||||
@Deprecated("Use 'StreamMultiOverlay' instead")
|
||||
typedef MultiOverlay = StreamMultiOverlay;
|
||||
|
||||
/// {@template multi_overlay}
|
||||
/// Widget that renders a single overlay widget from a list of [overlayOptions]
|
||||
/// It shows the first one that is visible
|
||||
class MultiOverlay extends StatelessWidget {
|
||||
/// {@endtemplate}
|
||||
class StreamMultiOverlay extends StatelessWidget {
|
||||
/// Constructs a new MultiOverlay widget
|
||||
/// [overlayOptions] - the list of overlay options
|
||||
/// [overlayAnchor] - the anchor relative to the overlay
|
||||
/// [childAnchor] - the anchor relative to the child
|
||||
/// [child] - the child widget
|
||||
const MultiOverlay({
|
||||
const StreamMultiOverlay({
|
||||
Key? key,
|
||||
required this.overlayOptions,
|
||||
required this.child,
|
||||
|
||||
@@ -1,12 +1,18 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:stream_chat_flutter/src/stream_chat_theme.dart';
|
||||
|
||||
/// {@macro option_list_tile}
|
||||
@Deprecated("Use 'StreamOptionListTile' instead")
|
||||
typedef OptionListTile = StreamOptionListTile;
|
||||
|
||||
/// {@template option_list_tile}
|
||||
/// List tile for [ChannelBottomSheet]
|
||||
class OptionListTile extends StatelessWidget {
|
||||
/// Constructor for creating [OptionListTile]
|
||||
const OptionListTile({
|
||||
/// {@endtemplate}
|
||||
class StreamOptionListTile extends StatelessWidget {
|
||||
/// Constructor for creating [StreamOptionListTile]
|
||||
const StreamOptionListTile({
|
||||
Key? key,
|
||||
this.title,
|
||||
required this.title,
|
||||
this.leading,
|
||||
this.trailing,
|
||||
this.onTap,
|
||||
@@ -17,7 +23,7 @@ class OptionListTile extends StatelessWidget {
|
||||
}) : super(key: key);
|
||||
|
||||
/// Title for tile
|
||||
final String? title;
|
||||
final String title;
|
||||
|
||||
/// Leading widget (start)
|
||||
final Widget? leading;
|
||||
@@ -46,8 +52,8 @@ class OptionListTile extends StatelessWidget {
|
||||
return Column(
|
||||
children: [
|
||||
Container(
|
||||
color: separatorColor ?? chatThemeData.colorTheme.disabled,
|
||||
height: 1,
|
||||
color: separatorColor ?? chatThemeData.colorTheme.disabled,
|
||||
),
|
||||
Material(
|
||||
color: tileColor ?? chatThemeData.colorTheme.barsBg,
|
||||
@@ -57,15 +63,14 @@ class OptionListTile extends StatelessWidget {
|
||||
onTap: onTap,
|
||||
child: Row(
|
||||
children: [
|
||||
if (leading != null) Center(child: leading),
|
||||
if (leading == null)
|
||||
const SizedBox(
|
||||
width: 16,
|
||||
),
|
||||
if (leading != null)
|
||||
Center(child: leading)
|
||||
else
|
||||
const SizedBox(width: 16),
|
||||
Expanded(
|
||||
flex: 4,
|
||||
child: Text(
|
||||
title!,
|
||||
title,
|
||||
style: titleTextStyle ??
|
||||
(titleColor == null
|
||||
? chatThemeData.textTheme.bodyBold
|
||||
|
||||
@@ -10,54 +10,14 @@ typedef QuotedMessageAttachmentThumbnailBuilder = Widget Function(
|
||||
Attachment,
|
||||
);
|
||||
|
||||
class _VideoAttachmentThumbnail extends StatefulWidget {
|
||||
const _VideoAttachmentThumbnail({
|
||||
Key? key,
|
||||
required this.attachment,
|
||||
this.size = const Size(32, 32),
|
||||
}) : super(key: key);
|
||||
/// Widget for the quoted message.
|
||||
@Deprecated("Use 'StreamQuotedMessageWidget' instead")
|
||||
typedef QuotedMessageWidget = StreamQuotedMessageWidget;
|
||||
|
||||
final Size size;
|
||||
final Attachment attachment;
|
||||
|
||||
@override
|
||||
_VideoAttachmentThumbnailState createState() =>
|
||||
_VideoAttachmentThumbnailState();
|
||||
}
|
||||
|
||||
class _VideoAttachmentThumbnailState extends State<_VideoAttachmentThumbnail> {
|
||||
late VideoPlayerController _controller;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_controller = VideoPlayerController.network(widget.attachment.assetUrl!)
|
||||
..initialize().then((_) {
|
||||
// ignore: no-empty-block
|
||||
setState(() {}); //when your thumbnail will show.
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
super.dispose();
|
||||
_controller.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) => SizedBox(
|
||||
height: widget.size.height,
|
||||
width: widget.size.width,
|
||||
child: _controller.value.isInitialized
|
||||
? VideoPlayer(_controller)
|
||||
: const CircularProgressIndicator(),
|
||||
);
|
||||
}
|
||||
|
||||
///
|
||||
class QuotedMessageWidget extends StatelessWidget {
|
||||
///
|
||||
const QuotedMessageWidget({
|
||||
/// Widget for the quoted message.
|
||||
class StreamQuotedMessageWidget extends StatelessWidget {
|
||||
/// Creates a new instance of the widget.
|
||||
const StreamQuotedMessageWidget({
|
||||
Key? key,
|
||||
required this.message,
|
||||
required this.messageTheme,
|
||||
@@ -73,7 +33,7 @@ class QuotedMessageWidget extends StatelessWidget {
|
||||
final Message message;
|
||||
|
||||
/// The message theme
|
||||
final MessageThemeData messageTheme;
|
||||
final StreamMessageThemeData messageTheme;
|
||||
|
||||
/// If true the widget will be mirrored
|
||||
final bool reverse;
|
||||
@@ -134,7 +94,7 @@ class QuotedMessageWidget extends StatelessWidget {
|
||||
if (_hasAttachments) _parseAttachments(context),
|
||||
if (msg.text!.isNotEmpty)
|
||||
Flexible(
|
||||
child: MessageText(
|
||||
child: StreamMessageText(
|
||||
message: msg,
|
||||
messageTheme: isOnlyEmoji && _containsText
|
||||
? messageTheme.copyWith(
|
||||
@@ -231,7 +191,7 @@ class QuotedMessageWidget extends StatelessWidget {
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
);
|
||||
|
||||
Widget _buildUserAvatar() => UserAvatar(
|
||||
Widget _buildUserAvatar() => StreamUserAvatar(
|
||||
user: message.user!,
|
||||
constraints: const BoxConstraints.tightFor(
|
||||
height: 24,
|
||||
@@ -242,7 +202,7 @@ class QuotedMessageWidget extends StatelessWidget {
|
||||
|
||||
Map<String, QuotedMessageAttachmentThumbnailBuilder>
|
||||
get _defaultAttachmentBuilder => {
|
||||
'image': (_, attachment) => ImageAttachment(
|
||||
'image': (_, attachment) => StreamImageAttachment(
|
||||
attachment: attachment,
|
||||
message: message,
|
||||
messageTheme: messageTheme,
|
||||
@@ -288,3 +248,47 @@ class QuotedMessageWidget extends StatelessWidget {
|
||||
return messageTheme.messageBackgroundColor;
|
||||
}
|
||||
}
|
||||
|
||||
class _VideoAttachmentThumbnail extends StatefulWidget {
|
||||
const _VideoAttachmentThumbnail({
|
||||
Key? key,
|
||||
required this.attachment,
|
||||
this.size = const Size(32, 32),
|
||||
}) : super(key: key);
|
||||
|
||||
final Size size;
|
||||
final Attachment attachment;
|
||||
|
||||
@override
|
||||
_VideoAttachmentThumbnailState createState() =>
|
||||
_VideoAttachmentThumbnailState();
|
||||
}
|
||||
|
||||
class _VideoAttachmentThumbnailState extends State<_VideoAttachmentThumbnail> {
|
||||
late VideoPlayerController _controller;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_controller = VideoPlayerController.network(widget.attachment.assetUrl!)
|
||||
..initialize().then((_) {
|
||||
// ignore: no-empty-block
|
||||
setState(() {}); //when your thumbnail will show.
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
super.dispose();
|
||||
_controller.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) => SizedBox(
|
||||
height: widget.size.height,
|
||||
width: widget.size.width,
|
||||
child: _controller.value.isInitialized
|
||||
? VideoPlayer(_controller)
|
||||
: const CircularProgressIndicator(),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -4,10 +4,16 @@ import 'package:collection/collection.dart' show IterableExtension;
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
|
||||
|
||||
/// {@macro reaction_bubble}
|
||||
@Deprecated("Use 'StreamReactionBubble' instead")
|
||||
typedef ReactionBubble = StreamReactionBubble;
|
||||
|
||||
/// {@template reaction_bubble}
|
||||
/// Creates reaction bubble widget for displaying over messages
|
||||
class ReactionBubble extends StatelessWidget {
|
||||
/// Constructor for creating a [ReactionBubble]
|
||||
const ReactionBubble({
|
||||
/// {@endtemplate}
|
||||
class StreamReactionBubble extends StatelessWidget {
|
||||
/// Constructor for creating a [StreamReactionBubble]
|
||||
const StreamReactionBubble({
|
||||
Key? key,
|
||||
required this.reactions,
|
||||
required this.borderColor,
|
||||
@@ -111,7 +117,7 @@ class ReactionBubble extends StatelessWidget {
|
||||
}
|
||||
|
||||
Widget _buildReaction(
|
||||
List<ReactionIcon> reactionIcons,
|
||||
List<StreamReactionIcon> reactionIcons,
|
||||
Reaction reaction,
|
||||
BuildContext context,
|
||||
) {
|
||||
|
||||
@@ -1,9 +1,13 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
/// Reaction icon data
|
||||
class ReactionIcon {
|
||||
/// Constructor for creating [ReactionIcon]
|
||||
ReactionIcon({
|
||||
@Deprecated("Use 'StreamReactionIcon' instead")
|
||||
typedef ReactionIcon = StreamReactionIcon;
|
||||
|
||||
/// Reaction icon data
|
||||
class StreamReactionIcon {
|
||||
/// Constructor for creating [StreamReactionIcon]
|
||||
StreamReactionIcon({
|
||||
required this.type,
|
||||
required this.builder,
|
||||
});
|
||||
|
||||
@@ -3,16 +3,22 @@ import 'package:flutter/material.dart';
|
||||
import 'package:stream_chat_flutter/src/extension.dart';
|
||||
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
|
||||
|
||||
/// {@macro reaction_picker}
|
||||
@Deprecated("Use 'StreamReactionPicker' instead")
|
||||
typedef ReactionPicker = StreamReactionPicker;
|
||||
|
||||
/// {@template reaction_picker}
|
||||
/// 
|
||||
/// 
|
||||
///
|
||||
/// It shows a reaction picker
|
||||
///
|
||||
/// Usually you don't use this widget as it's one of the default widgets used
|
||||
/// by [MessageWidget.onMessageActions].
|
||||
class ReactionPicker extends StatefulWidget {
|
||||
/// Constructor for creating a [ReactionPicker] widget
|
||||
const ReactionPicker({
|
||||
/// by [StreamMessageWidget.onMessageActions].
|
||||
/// {@endtemplate}
|
||||
class StreamReactionPicker extends StatefulWidget {
|
||||
/// Constructor for creating a [StreamReactionPicker] widget
|
||||
const StreamReactionPicker({
|
||||
Key? key,
|
||||
required this.message,
|
||||
}) : super(key: key);
|
||||
@@ -21,10 +27,10 @@ class ReactionPicker extends StatefulWidget {
|
||||
final Message message;
|
||||
|
||||
@override
|
||||
_ReactionPickerState createState() => _ReactionPickerState();
|
||||
_StreamReactionPickerState createState() => _StreamReactionPickerState();
|
||||
}
|
||||
|
||||
class _ReactionPickerState extends State<ReactionPicker>
|
||||
class _StreamReactionPickerState extends State<StreamReactionPicker>
|
||||
with TickerProviderStateMixin {
|
||||
List<EzAnimation> animations = [];
|
||||
|
||||
|
||||
@@ -1,10 +1,16 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
|
||||
|
||||
/// {@macro sending_indicator}
|
||||
@Deprecated("Use 'StreamSendingIndicator' instead")
|
||||
typedef SendingIndicator = StreamSendingIndicator;
|
||||
|
||||
/// {@template sending_indicator}
|
||||
/// Used to show the sending status of the message
|
||||
class SendingIndicator extends StatelessWidget {
|
||||
/// Constructor for creating a [SendingIndicator] widget
|
||||
const SendingIndicator({
|
||||
/// {@endtemplate}
|
||||
class StreamSendingIndicator extends StatelessWidget {
|
||||
/// Constructor for creating a [StreamSendingIndicator] widget
|
||||
const StreamSendingIndicator({
|
||||
Key? key,
|
||||
required this.message,
|
||||
this.isMessageRead = false,
|
||||
|
||||
@@ -38,29 +38,29 @@ class StreamChatThemeData {
|
||||
/// Create a theme from scratch
|
||||
factory StreamChatThemeData({
|
||||
Brightness? brightness,
|
||||
TextTheme? textTheme,
|
||||
ColorTheme? colorTheme,
|
||||
ChannelListHeaderThemeData? channelListHeaderTheme,
|
||||
ChannelPreviewThemeData? channelPreviewTheme,
|
||||
ChannelHeaderThemeData? channelHeaderTheme,
|
||||
MessageThemeData? otherMessageTheme,
|
||||
MessageThemeData? ownMessageTheme,
|
||||
MessageInputThemeData? messageInputTheme,
|
||||
StreamTextTheme? textTheme,
|
||||
StreamColorTheme? colorTheme,
|
||||
StreamChannelListHeaderThemeData? channelListHeaderTheme,
|
||||
StreamChannelPreviewThemeData? channelPreviewTheme,
|
||||
StreamChannelHeaderThemeData? channelHeaderTheme,
|
||||
StreamMessageThemeData? otherMessageTheme,
|
||||
StreamMessageThemeData? ownMessageTheme,
|
||||
StreamMessageInputThemeData? messageInputTheme,
|
||||
Widget Function(BuildContext, User)? defaultUserImage,
|
||||
Widget Function(BuildContext, User)? placeholderUserImage,
|
||||
IconThemeData? primaryIconTheme,
|
||||
List<ReactionIcon>? reactionIcons,
|
||||
GalleryHeaderThemeData? imageHeaderTheme,
|
||||
GalleryFooterThemeData? imageFooterTheme,
|
||||
MessageListViewThemeData? messageListViewTheme,
|
||||
ChannelListViewThemeData? channelListViewTheme,
|
||||
UserListViewThemeData? userListViewTheme,
|
||||
MessageSearchListViewThemeData? messageSearchListViewTheme,
|
||||
List<StreamReactionIcon>? reactionIcons,
|
||||
StreamGalleryHeaderThemeData? imageHeaderTheme,
|
||||
StreamGalleryFooterThemeData? imageFooterTheme,
|
||||
StreamMessageListViewThemeData? messageListViewTheme,
|
||||
StreamChannelListViewThemeData? channelListViewTheme,
|
||||
StreamUserListViewThemeData? userListViewTheme,
|
||||
StreamMessageSearchListViewThemeData? messageSearchListViewTheme,
|
||||
}) {
|
||||
brightness ??= colorTheme?.brightness ?? Brightness.light;
|
||||
final isDark = brightness == Brightness.dark;
|
||||
textTheme ??= isDark ? TextTheme.dark() : TextTheme.light();
|
||||
colorTheme ??= isDark ? ColorTheme.dark() : ColorTheme.light();
|
||||
textTheme ??= isDark ? StreamTextTheme.dark() : StreamTextTheme.light();
|
||||
colorTheme ??= isDark ? StreamColorTheme.dark() : StreamColorTheme.light();
|
||||
|
||||
final defaultData = StreamChatThemeData.fromColorAndTextTheme(
|
||||
colorTheme,
|
||||
@@ -133,14 +133,14 @@ class StreamChatThemeData {
|
||||
|
||||
/// Create theme from color and text theme
|
||||
factory StreamChatThemeData.fromColorAndTextTheme(
|
||||
ColorTheme colorTheme,
|
||||
TextTheme textTheme,
|
||||
StreamColorTheme colorTheme,
|
||||
StreamTextTheme textTheme,
|
||||
) {
|
||||
final accentColor = colorTheme.accentPrimary;
|
||||
final iconTheme =
|
||||
IconThemeData(color: colorTheme.textHighEmphasis.withOpacity(0.5));
|
||||
final channelHeaderTheme = ChannelHeaderThemeData(
|
||||
avatarTheme: AvatarThemeData(
|
||||
final channelHeaderTheme = StreamChannelHeaderThemeData(
|
||||
avatarTheme: StreamAvatarThemeData(
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
constraints: const BoxConstraints.tightFor(
|
||||
height: 40,
|
||||
@@ -153,9 +153,9 @@ class StreamChatThemeData {
|
||||
color: const Color(0xff7A7A7A),
|
||||
),
|
||||
);
|
||||
final channelPreviewTheme = ChannelPreviewThemeData(
|
||||
final channelPreviewTheme = StreamChannelPreviewThemeData(
|
||||
unreadCounterColor: colorTheme.accentError,
|
||||
avatarTheme: AvatarThemeData(
|
||||
avatarTheme: StreamAvatarThemeData(
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
constraints: const BoxConstraints.tightFor(
|
||||
height: 40,
|
||||
@@ -176,14 +176,14 @@ class StreamChatThemeData {
|
||||
colorTheme: colorTheme,
|
||||
primaryIconTheme: iconTheme,
|
||||
defaultUserImage: (context, user) => Center(
|
||||
child: GradientAvatar(
|
||||
child: StreamGradientAvatar(
|
||||
name: user.name,
|
||||
userId: user.id,
|
||||
),
|
||||
),
|
||||
channelPreviewTheme: channelPreviewTheme,
|
||||
channelListHeaderTheme: ChannelListHeaderThemeData(
|
||||
avatarTheme: AvatarThemeData(
|
||||
channelListHeaderTheme: StreamChannelListHeaderThemeData(
|
||||
avatarTheme: StreamAvatarThemeData(
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
constraints: const BoxConstraints.tightFor(
|
||||
height: 40,
|
||||
@@ -194,7 +194,7 @@ class StreamChatThemeData {
|
||||
titleStyle: textTheme.headlineBold,
|
||||
),
|
||||
channelHeaderTheme: channelHeaderTheme,
|
||||
ownMessageTheme: MessageThemeData(
|
||||
ownMessageTheme: StreamMessageThemeData(
|
||||
messageAuthorStyle:
|
||||
textTheme.footnote.copyWith(color: colorTheme.textLowEmphasis),
|
||||
messageTextStyle: textTheme.body,
|
||||
@@ -206,7 +206,7 @@ class StreamChatThemeData {
|
||||
reactionsBorderColor: colorTheme.borders,
|
||||
reactionsMaskColor: colorTheme.appBg,
|
||||
messageBorderColor: colorTheme.disabled,
|
||||
avatarTheme: AvatarThemeData(
|
||||
avatarTheme: StreamAvatarThemeData(
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
constraints: const BoxConstraints.tightFor(
|
||||
height: 32,
|
||||
@@ -218,7 +218,7 @@ class StreamChatThemeData {
|
||||
),
|
||||
linkBackgroundColor: colorTheme.linkBg,
|
||||
),
|
||||
otherMessageTheme: MessageThemeData(
|
||||
otherMessageTheme: StreamMessageThemeData(
|
||||
reactionsBackgroundColor: colorTheme.disabled,
|
||||
reactionsBorderColor: colorTheme.barsBg,
|
||||
reactionsMaskColor: colorTheme.appBg,
|
||||
@@ -233,7 +233,7 @@ class StreamChatThemeData {
|
||||
),
|
||||
messageBackgroundColor: colorTheme.barsBg,
|
||||
messageBorderColor: colorTheme.borders,
|
||||
avatarTheme: AvatarThemeData(
|
||||
avatarTheme: StreamAvatarThemeData(
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
constraints: const BoxConstraints.tightFor(
|
||||
height: 32,
|
||||
@@ -242,7 +242,7 @@ class StreamChatThemeData {
|
||||
),
|
||||
linkBackgroundColor: colorTheme.linkBg,
|
||||
),
|
||||
messageInputTheme: MessageInputThemeData(
|
||||
messageInputTheme: StreamMessageInputThemeData(
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
sendAnimationDuration: const Duration(milliseconds: 300),
|
||||
actionButtonColor: colorTheme.accentPrimary,
|
||||
@@ -252,6 +252,7 @@ class StreamChatThemeData {
|
||||
sendButtonIdleColor: colorTheme.disabled,
|
||||
inputBackgroundColor: colorTheme.barsBg,
|
||||
inputTextStyle: textTheme.body,
|
||||
linkHighlightColor: colorTheme.accentPrimary,
|
||||
idleBorderGradient: LinearGradient(
|
||||
colors: [
|
||||
colorTheme.disabled,
|
||||
@@ -266,7 +267,7 @@ class StreamChatThemeData {
|
||||
),
|
||||
),
|
||||
reactionIcons: [
|
||||
ReactionIcon(
|
||||
StreamReactionIcon(
|
||||
type: 'love',
|
||||
builder: (context, highlighted, size) {
|
||||
final theme = StreamChatTheme.of(context);
|
||||
@@ -278,7 +279,7 @@ class StreamChatThemeData {
|
||||
);
|
||||
},
|
||||
),
|
||||
ReactionIcon(
|
||||
StreamReactionIcon(
|
||||
type: 'like',
|
||||
builder: (context, highlighted, size) {
|
||||
final theme = StreamChatTheme.of(context);
|
||||
@@ -290,7 +291,7 @@ class StreamChatThemeData {
|
||||
);
|
||||
},
|
||||
),
|
||||
ReactionIcon(
|
||||
StreamReactionIcon(
|
||||
type: 'sad',
|
||||
builder: (context, highlighted, size) {
|
||||
final theme = StreamChatTheme.of(context);
|
||||
@@ -302,7 +303,7 @@ class StreamChatThemeData {
|
||||
);
|
||||
},
|
||||
),
|
||||
ReactionIcon(
|
||||
StreamReactionIcon(
|
||||
type: 'haha',
|
||||
builder: (context, highlighted, size) {
|
||||
final theme = StreamChatTheme.of(context);
|
||||
@@ -314,7 +315,7 @@ class StreamChatThemeData {
|
||||
);
|
||||
},
|
||||
),
|
||||
ReactionIcon(
|
||||
StreamReactionIcon(
|
||||
type: 'wow',
|
||||
builder: (context, highlighted, size) {
|
||||
final theme = StreamChatTheme.of(context);
|
||||
@@ -327,7 +328,7 @@ class StreamChatThemeData {
|
||||
},
|
||||
),
|
||||
],
|
||||
galleryHeaderTheme: GalleryHeaderThemeData(
|
||||
galleryHeaderTheme: StreamGalleryHeaderThemeData(
|
||||
closeButtonColor: colorTheme.textHighEmphasis,
|
||||
backgroundColor: channelHeaderTheme.color,
|
||||
iconMenuPointColor: colorTheme.textHighEmphasis,
|
||||
@@ -335,7 +336,7 @@ class StreamChatThemeData {
|
||||
subtitleTextStyle: channelPreviewTheme.subtitleStyle,
|
||||
bottomSheetBarrierColor: colorTheme.overlay,
|
||||
),
|
||||
galleryFooterTheme: GalleryFooterThemeData(
|
||||
galleryFooterTheme: StreamGalleryFooterThemeData(
|
||||
backgroundColor: colorTheme.barsBg,
|
||||
shareIconColor: colorTheme.textHighEmphasis,
|
||||
titleTextStyle: textTheme.headlineBold,
|
||||
@@ -345,52 +346,52 @@ class StreamChatThemeData {
|
||||
bottomSheetPhotosTextStyle: textTheme.headlineBold,
|
||||
bottomSheetCloseIconColor: colorTheme.textHighEmphasis,
|
||||
),
|
||||
messageListViewTheme: MessageListViewThemeData(
|
||||
messageListViewTheme: StreamMessageListViewThemeData(
|
||||
backgroundColor: colorTheme.barsBg,
|
||||
),
|
||||
channelListViewTheme: ChannelListViewThemeData(
|
||||
channelListViewTheme: StreamChannelListViewThemeData(
|
||||
backgroundColor: colorTheme.appBg,
|
||||
),
|
||||
userListViewTheme: UserListViewThemeData(
|
||||
userListViewTheme: StreamUserListViewThemeData(
|
||||
backgroundColor: colorTheme.appBg,
|
||||
),
|
||||
messageSearchListViewTheme: MessageSearchListViewThemeData(
|
||||
messageSearchListViewTheme: StreamMessageSearchListViewThemeData(
|
||||
backgroundColor: colorTheme.appBg,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// The text themes used in the widgets
|
||||
final TextTheme textTheme;
|
||||
final StreamTextTheme textTheme;
|
||||
|
||||
/// The color themes used in the widgets
|
||||
final ColorTheme colorTheme;
|
||||
final StreamColorTheme colorTheme;
|
||||
|
||||
/// Theme of the [ChannelPreview]
|
||||
final ChannelPreviewThemeData channelPreviewTheme;
|
||||
/// Theme of the [StreamChannelPreview]
|
||||
final StreamChannelPreviewThemeData channelPreviewTheme;
|
||||
|
||||
/// Theme of the [ChannelListHeader]
|
||||
final ChannelListHeaderThemeData channelListHeaderTheme;
|
||||
/// Theme of the [StreamChannelListHeader]
|
||||
final StreamChannelListHeaderThemeData channelListHeaderTheme;
|
||||
|
||||
/// Theme of the chat widgets dedicated to a channel header
|
||||
final ChannelHeaderThemeData channelHeaderTheme;
|
||||
final StreamChannelHeaderThemeData channelHeaderTheme;
|
||||
|
||||
/// The default style for [GalleryHeader]s below the overall
|
||||
/// The default style for [StreamGalleryHeader]s below the overall
|
||||
/// [StreamChatTheme].
|
||||
final GalleryHeaderThemeData galleryHeaderTheme;
|
||||
final StreamGalleryHeaderThemeData galleryHeaderTheme;
|
||||
|
||||
/// The default style for [GalleryFooter]s below the overall
|
||||
/// The default style for [StreamGalleryFooter]s below the overall
|
||||
/// [StreamChatTheme].
|
||||
final GalleryFooterThemeData galleryFooterTheme;
|
||||
final StreamGalleryFooterThemeData galleryFooterTheme;
|
||||
|
||||
/// Theme of the current user messages
|
||||
final MessageThemeData ownMessageTheme;
|
||||
final StreamMessageThemeData ownMessageTheme;
|
||||
|
||||
/// Theme of other users messages
|
||||
final MessageThemeData otherMessageTheme;
|
||||
final StreamMessageThemeData otherMessageTheme;
|
||||
|
||||
/// Theme dedicated to the [MessageInput] widget
|
||||
final MessageInputThemeData messageInputTheme;
|
||||
/// Theme dedicated to the [StreamMessageInput] widget
|
||||
final StreamMessageInputThemeData messageInputTheme;
|
||||
|
||||
/// The widget that will be built when the user image is unavailable
|
||||
final Widget Function(BuildContext, User) defaultUserImage;
|
||||
@@ -402,41 +403,41 @@ class StreamChatThemeData {
|
||||
final IconThemeData primaryIconTheme;
|
||||
|
||||
/// Assets used for rendering reactions
|
||||
final List<ReactionIcon> reactionIcons;
|
||||
final List<StreamReactionIcon> reactionIcons;
|
||||
|
||||
/// Theme configuration for the [MessageListView] widget.
|
||||
final MessageListViewThemeData messageListViewTheme;
|
||||
/// Theme configuration for the [StreamMessageListView] widget.
|
||||
final StreamMessageListViewThemeData messageListViewTheme;
|
||||
|
||||
/// Theme configuration for the [ChannelListView] widget.
|
||||
final ChannelListViewThemeData channelListViewTheme;
|
||||
/// Theme configuration for the [StreamChannelListView] widget.
|
||||
final StreamChannelListViewThemeData channelListViewTheme;
|
||||
|
||||
/// Theme configuration for the [UserListView] widget.
|
||||
final UserListViewThemeData userListViewTheme;
|
||||
/// Theme configuration for the [StreamUserListView] widget.
|
||||
final StreamUserListViewThemeData userListViewTheme;
|
||||
|
||||
/// Theme configuration for the [MessageSearchListView] widget.
|
||||
final MessageSearchListViewThemeData messageSearchListViewTheme;
|
||||
/// Theme configuration for the [StreamMessageSearchListView] widget.
|
||||
final StreamMessageSearchListViewThemeData messageSearchListViewTheme;
|
||||
|
||||
/// Creates a copy of [StreamChatThemeData] with specified attributes
|
||||
/// overridden.
|
||||
StreamChatThemeData copyWith({
|
||||
TextTheme? textTheme,
|
||||
ColorTheme? colorTheme,
|
||||
ChannelPreviewThemeData? channelPreviewTheme,
|
||||
ChannelHeaderThemeData? channelHeaderTheme,
|
||||
MessageThemeData? ownMessageTheme,
|
||||
MessageThemeData? otherMessageTheme,
|
||||
MessageInputThemeData? messageInputTheme,
|
||||
StreamTextTheme? textTheme,
|
||||
StreamColorTheme? colorTheme,
|
||||
StreamChannelPreviewThemeData? channelPreviewTheme,
|
||||
StreamChannelHeaderThemeData? channelHeaderTheme,
|
||||
StreamMessageThemeData? ownMessageTheme,
|
||||
StreamMessageThemeData? otherMessageTheme,
|
||||
StreamMessageInputThemeData? messageInputTheme,
|
||||
Widget Function(BuildContext, User)? defaultUserImage,
|
||||
Widget Function(BuildContext, User)? placeholderUserImage,
|
||||
IconThemeData? primaryIconTheme,
|
||||
ChannelListHeaderThemeData? channelListHeaderTheme,
|
||||
List<ReactionIcon>? reactionIcons,
|
||||
GalleryHeaderThemeData? galleryHeaderTheme,
|
||||
GalleryFooterThemeData? galleryFooterTheme,
|
||||
MessageListViewThemeData? messageListViewTheme,
|
||||
ChannelListViewThemeData? channelListViewTheme,
|
||||
UserListViewThemeData? userListViewTheme,
|
||||
MessageSearchListViewThemeData? messageSearchListViewTheme,
|
||||
StreamChannelListHeaderThemeData? channelListHeaderTheme,
|
||||
List<StreamReactionIcon>? reactionIcons,
|
||||
StreamGalleryHeaderThemeData? galleryHeaderTheme,
|
||||
StreamGalleryFooterThemeData? galleryFooterTheme,
|
||||
StreamMessageListViewThemeData? messageListViewTheme,
|
||||
StreamChannelListViewThemeData? channelListViewTheme,
|
||||
StreamUserListViewThemeData? userListViewTheme,
|
||||
StreamMessageSearchListViewThemeData? messageSearchListViewTheme,
|
||||
}) =>
|
||||
StreamChatThemeData.raw(
|
||||
channelListHeaderTheme:
|
||||
|
||||
@@ -1,10 +1,16 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
|
||||
|
||||
/// It shows a date divider depending on the date difference
|
||||
class SystemMessage extends StatelessWidget {
|
||||
/// Constructor for creating a [SystemMessage]
|
||||
const SystemMessage({
|
||||
/// {@macro system_message}
|
||||
@Deprecated("Use 'StreamSystemMessage' instead")
|
||||
typedef SystemMessage = StreamSystemMessage;
|
||||
|
||||
/// {@template system_message}
|
||||
/// It shows a widget for the message with a system message type.
|
||||
/// {@endtemplate}
|
||||
class StreamSystemMessage extends StatelessWidget {
|
||||
/// Constructor for creating a [StreamSystemMessage]
|
||||
const StreamSystemMessage({
|
||||
Key? key,
|
||||
required this.message,
|
||||
this.onMessageTap,
|
||||
|
||||
@@ -1,11 +1,17 @@
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
/// {@macro avatar_theme_data}
|
||||
@Deprecated("Use 'StreamAvatarThemeData' instead")
|
||||
typedef AvatarThemeData = StreamAvatarThemeData;
|
||||
|
||||
/// {@template avatar_theme_data}
|
||||
/// A style that overrides the default appearance of various avatar widgets.
|
||||
/// {@endtemplate}
|
||||
// ignore: prefer-match-file-name
|
||||
class AvatarThemeData with Diagnosticable {
|
||||
/// Creates an [AvatarThemeData].
|
||||
const AvatarThemeData({
|
||||
class StreamAvatarThemeData with Diagnosticable {
|
||||
/// Creates an [StreamAvatarThemeData].
|
||||
const StreamAvatarThemeData({
|
||||
BoxConstraints? constraints,
|
||||
BorderRadius? borderRadius,
|
||||
}) : _constraints = constraints,
|
||||
@@ -25,12 +31,12 @@ class AvatarThemeData with Diagnosticable {
|
||||
/// Get border radius
|
||||
BorderRadius get borderRadius => _borderRadius ?? BorderRadius.circular(20);
|
||||
|
||||
/// Copy this [AvatarThemeData] to another.
|
||||
AvatarThemeData copyWith({
|
||||
/// Copy this [StreamAvatarThemeData] to another.
|
||||
StreamAvatarThemeData copyWith({
|
||||
BoxConstraints? constraints,
|
||||
BorderRadius? borderRadius,
|
||||
}) =>
|
||||
AvatarThemeData(
|
||||
StreamAvatarThemeData(
|
||||
constraints: constraints ?? _constraints,
|
||||
borderRadius: borderRadius ?? _borderRadius,
|
||||
);
|
||||
@@ -38,12 +44,12 @@ class AvatarThemeData with Diagnosticable {
|
||||
/// Linearly interpolate between two [UserAvatar] themes.
|
||||
///
|
||||
/// All the properties must be non-null.
|
||||
AvatarThemeData lerp(
|
||||
AvatarThemeData a,
|
||||
AvatarThemeData b,
|
||||
StreamAvatarThemeData lerp(
|
||||
StreamAvatarThemeData a,
|
||||
StreamAvatarThemeData b,
|
||||
double t,
|
||||
) =>
|
||||
AvatarThemeData(
|
||||
StreamAvatarThemeData(
|
||||
borderRadius: BorderRadius.lerp(a.borderRadius, b.borderRadius, t),
|
||||
constraints: BoxConstraints.lerp(a.constraints, b.constraints, t),
|
||||
);
|
||||
@@ -51,7 +57,7 @@ class AvatarThemeData with Diagnosticable {
|
||||
@override
|
||||
bool operator ==(Object other) =>
|
||||
identical(this, other) ||
|
||||
other is AvatarThemeData &&
|
||||
other is StreamAvatarThemeData &&
|
||||
runtimeType == other.runtimeType &&
|
||||
_constraints == other._constraints &&
|
||||
_borderRadius == other._borderRadius;
|
||||
@@ -59,8 +65,8 @@ class AvatarThemeData with Diagnosticable {
|
||||
@override
|
||||
int get hashCode => _constraints.hashCode ^ _borderRadius.hashCode;
|
||||
|
||||
/// Merges one [AvatarThemeData] with the another
|
||||
AvatarThemeData merge(AvatarThemeData? other) {
|
||||
/// Merges one [StreamAvatarThemeData] with the another
|
||||
StreamAvatarThemeData merge(StreamAvatarThemeData? other) {
|
||||
if (other == null) return this;
|
||||
return copyWith(
|
||||
constraints: other._constraints,
|
||||
|
||||
@@ -4,27 +4,33 @@ import 'package:stream_chat_flutter/src/stream_chat_theme.dart';
|
||||
import 'package:stream_chat_flutter/src/theme/avatar_theme.dart';
|
||||
import 'package:stream_chat_flutter/src/theme/themes.dart';
|
||||
|
||||
/// {@macro channel_header_theme}
|
||||
@Deprecated("Use 'StreamChannelHeaderTheme' instead")
|
||||
typedef ChannelHeaderTheme = StreamChannelHeaderTheme;
|
||||
|
||||
/// {@template channel_header_theme}
|
||||
/// Overrides the default style of [ChannelHeader] descendants.
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [ChannelHeaderThemeData], which is used to configure this theme.
|
||||
class ChannelHeaderTheme extends InheritedTheme {
|
||||
/// Creates a [ChannelHeaderTheme].
|
||||
/// * [StreamChannelHeaderThemeData], which is used to configure this theme.
|
||||
/// {@endtemplate}
|
||||
class StreamChannelHeaderTheme extends InheritedTheme {
|
||||
/// Creates a [StreamChannelHeaderTheme].
|
||||
///
|
||||
/// The [data] parameter must not be null.
|
||||
const ChannelHeaderTheme({
|
||||
const StreamChannelHeaderTheme({
|
||||
Key? key,
|
||||
required this.data,
|
||||
required Widget child,
|
||||
}) : super(key: key, child: child);
|
||||
|
||||
/// The configuration of this theme.
|
||||
final ChannelHeaderThemeData data;
|
||||
final StreamChannelHeaderThemeData data;
|
||||
|
||||
/// The closest instance of this class that encloses the given context.
|
||||
///
|
||||
/// If there is no enclosing [ChannelHeaderTheme] widget, then
|
||||
/// If there is no enclosing [StreamChannelHeaderTheme] widget, then
|
||||
/// [StreamChatThemeData.channelTheme.channelHeaderTheme] is used.
|
||||
///
|
||||
/// Typical usage is as follows:
|
||||
@@ -32,34 +38,40 @@ class ChannelHeaderTheme extends InheritedTheme {
|
||||
/// ```dart
|
||||
/// final theme = ChannelHeaderTheme.of(context);
|
||||
/// ```
|
||||
static ChannelHeaderThemeData of(BuildContext context) {
|
||||
static StreamChannelHeaderThemeData of(BuildContext context) {
|
||||
final channelHeaderTheme =
|
||||
context.dependOnInheritedWidgetOfExactType<ChannelHeaderTheme>();
|
||||
context.dependOnInheritedWidgetOfExactType<StreamChannelHeaderTheme>();
|
||||
return channelHeaderTheme?.data ??
|
||||
StreamChatTheme.of(context).channelHeaderTheme;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget wrap(BuildContext context, Widget child) =>
|
||||
ChannelHeaderTheme(data: data, child: child);
|
||||
StreamChannelHeaderTheme(data: data, child: child);
|
||||
|
||||
@override
|
||||
bool updateShouldNotify(ChannelHeaderTheme oldWidget) =>
|
||||
bool updateShouldNotify(StreamChannelHeaderTheme oldWidget) =>
|
||||
data != oldWidget.data;
|
||||
}
|
||||
|
||||
/// {@macro channel_header_theme_data}
|
||||
@Deprecated("Use 'StreamChannelHeaderThemeData' instead")
|
||||
typedef ChannelHeaderThemeData = StreamChannelHeaderThemeData;
|
||||
|
||||
/// {@template channel_header_theme_data}
|
||||
/// A style that overrides the default appearance of [ChannelHeader]s when used
|
||||
/// with [ChannelHeaderTheme] or with the overall [StreamChatTheme]'s
|
||||
/// with [StreamChannelHeaderTheme] or with the overall [StreamChatTheme]'s
|
||||
/// [StreamChatThemeData.channelHeaderTheme].
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [ChannelHeaderTheme], the theme which is configured with this class.
|
||||
/// * [StreamChannelHeaderTheme], the theme which is configured with this class.
|
||||
/// * [StreamChatThemeData.channelHeaderTheme], which can be used to override
|
||||
/// the default style for [ChannelHeader]s below the overall [StreamChatTheme].
|
||||
class ChannelHeaderThemeData with Diagnosticable {
|
||||
/// Creates a [ChannelHeaderThemeData]
|
||||
const ChannelHeaderThemeData({
|
||||
/// {@endtemplate}
|
||||
class StreamChannelHeaderThemeData with Diagnosticable {
|
||||
/// Creates a [StreamChannelHeaderThemeData]
|
||||
const StreamChannelHeaderThemeData({
|
||||
this.titleStyle,
|
||||
this.subtitleStyle,
|
||||
this.avatarTheme,
|
||||
@@ -73,43 +85,43 @@ class ChannelHeaderThemeData with Diagnosticable {
|
||||
final TextStyle? subtitleStyle;
|
||||
|
||||
/// Theme for avatar
|
||||
final AvatarThemeData? avatarTheme;
|
||||
final StreamAvatarThemeData? avatarTheme;
|
||||
|
||||
/// Color for [ChannelHeaderThemeData]
|
||||
/// Color for [StreamChannelHeaderThemeData]
|
||||
final Color? color;
|
||||
|
||||
/// Copy with theme
|
||||
ChannelHeaderThemeData copyWith({
|
||||
StreamChannelHeaderThemeData copyWith({
|
||||
TextStyle? titleStyle,
|
||||
TextStyle? subtitleStyle,
|
||||
AvatarThemeData? avatarTheme,
|
||||
StreamAvatarThemeData? avatarTheme,
|
||||
Color? color,
|
||||
}) =>
|
||||
ChannelHeaderThemeData(
|
||||
StreamChannelHeaderThemeData(
|
||||
titleStyle: titleStyle ?? this.titleStyle,
|
||||
subtitleStyle: subtitleStyle ?? this.subtitleStyle,
|
||||
avatarTheme: avatarTheme ?? this.avatarTheme,
|
||||
color: color ?? this.color,
|
||||
);
|
||||
|
||||
/// Linearly interpolate between two [ChannelHeaderThemeData].
|
||||
/// Linearly interpolate between two [StreamChannelHeaderThemeData].
|
||||
///
|
||||
/// All the properties must be non-null.
|
||||
ChannelHeaderThemeData lerp(
|
||||
ChannelHeaderThemeData a,
|
||||
ChannelHeaderThemeData b,
|
||||
StreamChannelHeaderThemeData lerp(
|
||||
StreamChannelHeaderThemeData a,
|
||||
StreamChannelHeaderThemeData b,
|
||||
double t,
|
||||
) =>
|
||||
ChannelHeaderThemeData(
|
||||
StreamChannelHeaderThemeData(
|
||||
titleStyle: TextStyle.lerp(a.titleStyle, b.titleStyle, t),
|
||||
subtitleStyle: TextStyle.lerp(a.subtitleStyle, b.subtitleStyle, t),
|
||||
avatarTheme:
|
||||
const AvatarThemeData().lerp(a.avatarTheme!, b.avatarTheme!, t),
|
||||
avatarTheme: const StreamAvatarThemeData()
|
||||
.lerp(a.avatarTheme!, b.avatarTheme!, t),
|
||||
color: Color.lerp(a.color, b.color, t),
|
||||
);
|
||||
|
||||
/// Merge with other [ChannelHeaderThemeData]
|
||||
ChannelHeaderThemeData merge(ChannelHeaderThemeData? other) {
|
||||
/// Merge with other [StreamChannelHeaderThemeData]
|
||||
StreamChannelHeaderThemeData merge(StreamChannelHeaderThemeData? other) {
|
||||
if (other == null) return this;
|
||||
return copyWith(
|
||||
titleStyle: titleStyle?.merge(other.titleStyle) ?? other.titleStyle,
|
||||
@@ -123,7 +135,7 @@ class ChannelHeaderThemeData with Diagnosticable {
|
||||
@override
|
||||
bool operator ==(Object other) =>
|
||||
identical(this, other) ||
|
||||
other is ChannelHeaderThemeData &&
|
||||
other is StreamChannelHeaderThemeData &&
|
||||
runtimeType == other.runtimeType &&
|
||||
titleStyle == other.titleStyle &&
|
||||
subtitleStyle == other.subtitleStyle &&
|
||||
|
||||
@@ -3,27 +3,33 @@ import 'package:flutter/material.dart';
|
||||
import 'package:stream_chat_flutter/src/stream_chat_theme.dart';
|
||||
import 'package:stream_chat_flutter/src/theme/avatar_theme.dart';
|
||||
|
||||
/// {@macro channel_list_header_theme}
|
||||
@Deprecated("Use 'StreamChannelListHeaderTheme' instead")
|
||||
typedef ChannelListHeaderTheme = StreamChannelListHeaderTheme;
|
||||
|
||||
/// {@template channel_list_header_theme}
|
||||
/// Overrides the default style of [ChannelListHeader] descendants.
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [ChannelListHeaderThemeData], which is used to configure this theme.
|
||||
class ChannelListHeaderTheme extends InheritedTheme {
|
||||
/// Creates a [ChannelListHeaderTheme].
|
||||
/// * [StreamChannelListHeaderThemeData], which is used to configure this theme.
|
||||
/// {@endtemplate}
|
||||
class StreamChannelListHeaderTheme extends InheritedTheme {
|
||||
/// Creates a [StreamChannelListHeaderTheme].
|
||||
///
|
||||
/// The [data] parameter must not be null.
|
||||
const ChannelListHeaderTheme({
|
||||
const StreamChannelListHeaderTheme({
|
||||
Key? key,
|
||||
required this.data,
|
||||
required Widget child,
|
||||
}) : super(key: key, child: child);
|
||||
|
||||
/// The configuration of this theme.
|
||||
final ChannelListHeaderThemeData data;
|
||||
final StreamChannelListHeaderThemeData data;
|
||||
|
||||
/// The closest instance of this class that encloses the given context.
|
||||
///
|
||||
/// If there is no enclosing [ChannelListHeaderTheme] widget, then
|
||||
/// If there is no enclosing [StreamChannelListHeaderTheme] widget, then
|
||||
/// [StreamChatThemeData.channelListHeaderTheme] is used.
|
||||
///
|
||||
/// Typical usage is as follows:
|
||||
@@ -31,26 +37,32 @@ class ChannelListHeaderTheme extends InheritedTheme {
|
||||
/// ```dart
|
||||
/// final theme = ChannelListHeaderTheme.of(context);
|
||||
/// ```
|
||||
static ChannelListHeaderThemeData of(BuildContext context) {
|
||||
final channelListHeaderTheme =
|
||||
context.dependOnInheritedWidgetOfExactType<ChannelListHeaderTheme>();
|
||||
static StreamChannelListHeaderThemeData of(BuildContext context) {
|
||||
final channelListHeaderTheme = context
|
||||
.dependOnInheritedWidgetOfExactType<StreamChannelListHeaderTheme>();
|
||||
return channelListHeaderTheme?.data ??
|
||||
StreamChatTheme.of(context).channelListHeaderTheme;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget wrap(BuildContext context, Widget child) =>
|
||||
ChannelListHeaderTheme(data: data, child: child);
|
||||
StreamChannelListHeaderTheme(data: data, child: child);
|
||||
|
||||
@override
|
||||
bool updateShouldNotify(ChannelListHeaderTheme oldWidget) =>
|
||||
bool updateShouldNotify(StreamChannelListHeaderTheme oldWidget) =>
|
||||
data != oldWidget.data;
|
||||
}
|
||||
|
||||
/// {@macro channel_list_header_theme_data}
|
||||
@Deprecated("Use ''StreamChannelListHeaderThemeData' instead")
|
||||
typedef ChannelListHeaderThemeData = StreamChannelListHeaderThemeData;
|
||||
|
||||
/// {@template channel_list_header_theme_data}
|
||||
/// Theme dedicated to the [ChannelListHeader]
|
||||
class ChannelListHeaderThemeData with Diagnosticable {
|
||||
/// Returns a new [ChannelListHeaderThemeData]
|
||||
const ChannelListHeaderThemeData({
|
||||
/// {@endtemplate}
|
||||
class StreamChannelListHeaderThemeData with Diagnosticable {
|
||||
/// Returns a new [StreamChannelListHeaderThemeData]
|
||||
const StreamChannelListHeaderThemeData({
|
||||
this.titleStyle,
|
||||
this.avatarTheme,
|
||||
this.color,
|
||||
@@ -60,39 +72,41 @@ class ChannelListHeaderThemeData with Diagnosticable {
|
||||
final TextStyle? titleStyle;
|
||||
|
||||
/// Theme dedicated to the userAvatar
|
||||
final AvatarThemeData? avatarTheme;
|
||||
final StreamAvatarThemeData? avatarTheme;
|
||||
|
||||
/// Background color of the appbar
|
||||
final Color? color;
|
||||
|
||||
/// Returns a new [ChannelListHeaderThemeData] replacing some of its
|
||||
/// Returns a new [StreamChannelListHeaderThemeData] replacing some of its
|
||||
/// properties
|
||||
ChannelListHeaderThemeData copyWith({
|
||||
StreamChannelListHeaderThemeData copyWith({
|
||||
TextStyle? titleStyle,
|
||||
AvatarThemeData? avatarTheme,
|
||||
StreamAvatarThemeData? avatarTheme,
|
||||
Color? color,
|
||||
}) =>
|
||||
ChannelListHeaderThemeData(
|
||||
StreamChannelListHeaderThemeData(
|
||||
titleStyle: titleStyle ?? this.titleStyle,
|
||||
avatarTheme: avatarTheme ?? this.avatarTheme,
|
||||
color: color ?? this.color,
|
||||
);
|
||||
|
||||
/// Linearly interpolate from one [ChannelListHeaderThemeData] to another.
|
||||
ChannelListHeaderThemeData lerp(
|
||||
ChannelListHeaderThemeData a,
|
||||
ChannelListHeaderThemeData b,
|
||||
/// Linearly interpolate from one [StreamChannelListHeaderThemeData] to another.
|
||||
StreamChannelListHeaderThemeData lerp(
|
||||
StreamChannelListHeaderThemeData a,
|
||||
StreamChannelListHeaderThemeData b,
|
||||
double t,
|
||||
) =>
|
||||
ChannelListHeaderThemeData(
|
||||
avatarTheme:
|
||||
const AvatarThemeData().lerp(a.avatarTheme!, b.avatarTheme!, t),
|
||||
StreamChannelListHeaderThemeData(
|
||||
avatarTheme: const StreamAvatarThemeData()
|
||||
.lerp(a.avatarTheme!, b.avatarTheme!, t),
|
||||
color: Color.lerp(a.color, b.color, t),
|
||||
titleStyle: TextStyle.lerp(a.titleStyle, b.titleStyle, t),
|
||||
);
|
||||
|
||||
/// Merges [this] [ChannelListHeaderThemeData] with the [other]
|
||||
ChannelListHeaderThemeData merge(ChannelListHeaderThemeData? other) {
|
||||
/// Merges [this] [StreamChannelListHeaderThemeData] with the [other]
|
||||
StreamChannelListHeaderThemeData merge(
|
||||
StreamChannelListHeaderThemeData? other,
|
||||
) {
|
||||
if (other == null) return this;
|
||||
return copyWith(
|
||||
titleStyle: titleStyle?.merge(other.titleStyle) ?? other.titleStyle,
|
||||
@@ -104,7 +118,7 @@ class ChannelListHeaderThemeData with Diagnosticable {
|
||||
@override
|
||||
bool operator ==(Object other) =>
|
||||
identical(this, other) ||
|
||||
other is ChannelListHeaderThemeData &&
|
||||
other is StreamChannelListHeaderThemeData &&
|
||||
runtimeType == other.runtimeType &&
|
||||
titleStyle == other.titleStyle &&
|
||||
avatarTheme == other.avatarTheme &&
|
||||
|
||||
@@ -2,27 +2,33 @@ import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:stream_chat_flutter/src/stream_chat_theme.dart';
|
||||
|
||||
/// {@macro channel_list_view_theme}
|
||||
@Deprecated("Use 'StreamChannelListViewTheme' instead")
|
||||
typedef ChannelListViewTheme = StreamChannelListViewTheme;
|
||||
|
||||
/// {@template channel_list_view_theme}
|
||||
/// Overrides the default style of [ChannelListView] descendants.
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [ChannelListViewThemeData], which is used to configure this theme.
|
||||
class ChannelListViewTheme extends InheritedTheme {
|
||||
/// Creates a [ChannelListViewTheme].
|
||||
/// * [StreamChannelListViewThemeData], which is used to configure this theme.
|
||||
/// {@endtemplate}
|
||||
class StreamChannelListViewTheme extends InheritedTheme {
|
||||
/// Creates a [StreamChannelListViewTheme].
|
||||
///
|
||||
/// The [data] parameter must not be null.
|
||||
const ChannelListViewTheme({
|
||||
const StreamChannelListViewTheme({
|
||||
Key? key,
|
||||
required this.data,
|
||||
required Widget child,
|
||||
}) : super(key: key, child: child);
|
||||
|
||||
/// The configuration of this theme.
|
||||
final ChannelListViewThemeData data;
|
||||
final StreamChannelListViewThemeData data;
|
||||
|
||||
/// The closest instance of this class that encloses the given context.
|
||||
///
|
||||
/// If there is no enclosing [ChannelListViewTheme] widget, then
|
||||
/// If there is no enclosing [StreamChannelListViewTheme] widget, then
|
||||
/// [StreamChatThemeData.channelListViewTheme] is used.
|
||||
///
|
||||
/// Typical usage is as follows:
|
||||
@@ -30,63 +36,69 @@ class ChannelListViewTheme extends InheritedTheme {
|
||||
/// ```dart
|
||||
/// ChannelListViewTheme theme = ChannelListViewTheme.of(context);
|
||||
/// ```
|
||||
static ChannelListViewThemeData of(BuildContext context) {
|
||||
final channelListViewTheme =
|
||||
context.dependOnInheritedWidgetOfExactType<ChannelListViewTheme>();
|
||||
static StreamChannelListViewThemeData of(BuildContext context) {
|
||||
final channelListViewTheme = context
|
||||
.dependOnInheritedWidgetOfExactType<StreamChannelListViewTheme>();
|
||||
return channelListViewTheme?.data ??
|
||||
StreamChatTheme.of(context).channelListViewTheme;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget wrap(BuildContext context, Widget child) =>
|
||||
ChannelListViewTheme(data: data, child: child);
|
||||
StreamChannelListViewTheme(data: data, child: child);
|
||||
|
||||
@override
|
||||
bool updateShouldNotify(ChannelListViewTheme oldWidget) =>
|
||||
bool updateShouldNotify(StreamChannelListViewTheme oldWidget) =>
|
||||
data != oldWidget.data;
|
||||
}
|
||||
|
||||
/// {@macro channel_list_view_theme_data}
|
||||
@Deprecated("Use 'StreamChannelListViewThemeData' instead")
|
||||
typedef ChannelListViewThemeData = StreamChannelListViewThemeData;
|
||||
|
||||
/// {@template channel_list_view_theme_data}
|
||||
/// A style that overrides the default appearance of [ChannelListView]s when
|
||||
/// used with [ChannelListViewTheme] or with the overall [StreamChatTheme]'s
|
||||
/// used with [StreamChannelListViewTheme] or with the overall [StreamChatTheme]'s
|
||||
/// [StreamChatThemeData.channelListViewTheme].
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [ChannelListViewTheme], the theme which is configured with this class.
|
||||
/// * [StreamChannelListViewTheme], the theme which is configured with this class.
|
||||
/// * [StreamChatThemeData.channelListViewTheme], which can be used to override
|
||||
/// the default style for [ChannelListView]s below the overall
|
||||
/// [StreamChatTheme].
|
||||
class ChannelListViewThemeData with Diagnosticable {
|
||||
/// Creates a [ChannelListViewThemeData].
|
||||
const ChannelListViewThemeData({
|
||||
/// {@endtemplate}
|
||||
class StreamChannelListViewThemeData with Diagnosticable {
|
||||
/// Creates a [StreamChannelListViewThemeData].
|
||||
const StreamChannelListViewThemeData({
|
||||
this.backgroundColor,
|
||||
});
|
||||
|
||||
/// The color of the [ChannelListView] background.
|
||||
final Color? backgroundColor;
|
||||
|
||||
/// Copies this [ChannelListViewThemeData] to another.
|
||||
ChannelListViewThemeData copyWith({
|
||||
/// Copies this [StreamChannelListViewThemeData] to another.
|
||||
StreamChannelListViewThemeData copyWith({
|
||||
Color? backgroundColor,
|
||||
}) =>
|
||||
ChannelListViewThemeData(
|
||||
StreamChannelListViewThemeData(
|
||||
backgroundColor: backgroundColor ?? this.backgroundColor,
|
||||
);
|
||||
|
||||
/// Linearly interpolate between two [ChannelListViewThemeData] themes.
|
||||
/// Linearly interpolate between two [StreamChannelListViewThemeData] themes.
|
||||
///
|
||||
/// All the properties must be non-null.
|
||||
ChannelListViewThemeData lerp(
|
||||
ChannelListViewThemeData a,
|
||||
ChannelListViewThemeData b,
|
||||
StreamChannelListViewThemeData lerp(
|
||||
StreamChannelListViewThemeData a,
|
||||
StreamChannelListViewThemeData b,
|
||||
double t,
|
||||
) =>
|
||||
ChannelListViewThemeData(
|
||||
StreamChannelListViewThemeData(
|
||||
backgroundColor: Color.lerp(a.backgroundColor, b.backgroundColor, t),
|
||||
);
|
||||
|
||||
/// Merges one [ChannelListViewThemeData] with another.
|
||||
ChannelListViewThemeData merge(ChannelListViewThemeData? other) {
|
||||
/// Merges one [StreamChannelListViewThemeData] with another.
|
||||
StreamChannelListViewThemeData merge(StreamChannelListViewThemeData? other) {
|
||||
if (other == null) return this;
|
||||
return copyWith(
|
||||
backgroundColor: other.backgroundColor,
|
||||
@@ -96,7 +108,7 @@ class ChannelListViewThemeData with Diagnosticable {
|
||||
@override
|
||||
bool operator ==(Object other) =>
|
||||
identical(this, other) ||
|
||||
other is ChannelListViewThemeData &&
|
||||
other is StreamChannelListViewThemeData &&
|
||||
runtimeType == other.runtimeType &&
|
||||
backgroundColor == other.backgroundColor;
|
||||
|
||||
|
||||
@@ -3,27 +3,33 @@ import 'package:flutter/material.dart';
|
||||
import 'package:stream_chat_flutter/src/stream_chat_theme.dart';
|
||||
import 'package:stream_chat_flutter/src/theme/avatar_theme.dart';
|
||||
|
||||
/// {@macro channel_preview_theme}
|
||||
@Deprecated("Use 'StreamChannelPreviewTheme' instead")
|
||||
typedef ChannelPreviewTheme = StreamChannelPreviewTheme;
|
||||
|
||||
/// {@template channel_preview_theme}
|
||||
/// Overrides the default style of [ChannelPreview] descendants.
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [ChannelPreviewThemeData], which is used to configure this theme.
|
||||
class ChannelPreviewTheme extends InheritedTheme {
|
||||
/// Creates a [ChannelPreviewTheme].
|
||||
/// * [StreamChannelPreviewThemeData], which is used to configure this theme.
|
||||
/// {@endtemplate}
|
||||
class StreamChannelPreviewTheme extends InheritedTheme {
|
||||
/// Creates a [StreamChannelPreviewTheme].
|
||||
///
|
||||
/// The [data] parameter must not be null.
|
||||
const ChannelPreviewTheme({
|
||||
const StreamChannelPreviewTheme({
|
||||
Key? key,
|
||||
required this.data,
|
||||
required Widget child,
|
||||
}) : super(key: key, child: child);
|
||||
|
||||
/// The configuration of this theme.
|
||||
final ChannelPreviewThemeData data;
|
||||
final StreamChannelPreviewThemeData data;
|
||||
|
||||
/// The closest instance of this class that encloses the given context.
|
||||
///
|
||||
/// If there is no enclosing [ChannelPreviewTheme] widget, then
|
||||
/// If there is no enclosing [StreamChannelPreviewTheme] widget, then
|
||||
/// [StreamChatThemeData.channelPreviewTheme] is used.
|
||||
///
|
||||
/// Typical usage is as follows:
|
||||
@@ -31,34 +37,40 @@ class ChannelPreviewTheme extends InheritedTheme {
|
||||
/// ```dart
|
||||
/// final theme = ChannelPreviewTheme.of(context);
|
||||
/// ```
|
||||
static ChannelPreviewThemeData of(BuildContext context) {
|
||||
static StreamChannelPreviewThemeData of(BuildContext context) {
|
||||
final channelPreviewTheme =
|
||||
context.dependOnInheritedWidgetOfExactType<ChannelPreviewTheme>();
|
||||
context.dependOnInheritedWidgetOfExactType<StreamChannelPreviewTheme>();
|
||||
return channelPreviewTheme?.data ??
|
||||
StreamChatTheme.of(context).channelPreviewTheme;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget wrap(BuildContext context, Widget child) =>
|
||||
ChannelPreviewTheme(data: data, child: child);
|
||||
StreamChannelPreviewTheme(data: data, child: child);
|
||||
|
||||
@override
|
||||
bool updateShouldNotify(ChannelPreviewTheme oldWidget) =>
|
||||
bool updateShouldNotify(StreamChannelPreviewTheme oldWidget) =>
|
||||
data != oldWidget.data;
|
||||
}
|
||||
|
||||
/// {@macro channel_preview_theme_data}
|
||||
@Deprecated("Use 'StreamChannelPreviewThemeData' instead")
|
||||
typedef ChannelPreviewThemeData = StreamChannelPreviewThemeData;
|
||||
|
||||
/// {@template channel_preview_theme_data}
|
||||
/// A style that overrides the default appearance of [ChannelPreview]s when used
|
||||
/// with [ChannelPreviewTheme] or with the overall [StreamChatTheme]'s
|
||||
/// with [StreamChannelPreviewTheme] or with the overall [StreamChatTheme]'s
|
||||
/// [StreamChatThemeData.channelPreviewTheme].
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [ChannelPreviewTheme], the theme which is configured with this class.
|
||||
/// * [StreamChannelPreviewTheme], the theme which is configured with this class.
|
||||
/// * [StreamChatThemeData.channelPreviewTheme], which can be used to override
|
||||
/// the default style for [ChannelHeader]s below the overall [StreamChatTheme].
|
||||
class ChannelPreviewThemeData with Diagnosticable {
|
||||
/// Creates a [ChannelPreviewThemeData].
|
||||
const ChannelPreviewThemeData({
|
||||
/// {@endtemplate}
|
||||
class StreamChannelPreviewThemeData with Diagnosticable {
|
||||
/// Creates a [StreamChannelPreviewThemeData].
|
||||
const StreamChannelPreviewThemeData({
|
||||
this.titleStyle,
|
||||
this.subtitleStyle,
|
||||
this.lastMessageAtStyle,
|
||||
@@ -77,7 +89,7 @@ class ChannelPreviewThemeData with Diagnosticable {
|
||||
final TextStyle? lastMessageAtStyle;
|
||||
|
||||
/// Avatar theme
|
||||
final AvatarThemeData? avatarTheme;
|
||||
final StreamAvatarThemeData? avatarTheme;
|
||||
|
||||
/// Unread counter color
|
||||
final Color? unreadCounterColor;
|
||||
@@ -86,15 +98,15 @@ class ChannelPreviewThemeData with Diagnosticable {
|
||||
final double? indicatorIconSize;
|
||||
|
||||
/// Copy with theme
|
||||
ChannelPreviewThemeData copyWith({
|
||||
StreamChannelPreviewThemeData copyWith({
|
||||
TextStyle? titleStyle,
|
||||
TextStyle? subtitleStyle,
|
||||
TextStyle? lastMessageAtStyle,
|
||||
AvatarThemeData? avatarTheme,
|
||||
StreamAvatarThemeData? avatarTheme,
|
||||
Color? unreadCounterColor,
|
||||
double? indicatorIconSize,
|
||||
}) =>
|
||||
ChannelPreviewThemeData(
|
||||
StreamChannelPreviewThemeData(
|
||||
titleStyle: titleStyle ?? this.titleStyle,
|
||||
subtitleStyle: subtitleStyle ?? this.subtitleStyle,
|
||||
lastMessageAtStyle: lastMessageAtStyle ?? this.lastMessageAtStyle,
|
||||
@@ -103,15 +115,15 @@ class ChannelPreviewThemeData with Diagnosticable {
|
||||
indicatorIconSize: indicatorIconSize ?? this.indicatorIconSize,
|
||||
);
|
||||
|
||||
/// Linearly interpolate one [ChannelPreviewThemeData] to another.
|
||||
ChannelPreviewThemeData lerp(
|
||||
ChannelPreviewThemeData a,
|
||||
ChannelPreviewThemeData b,
|
||||
/// Linearly interpolate one [StreamChannelPreviewThemeData] to another.
|
||||
StreamChannelPreviewThemeData lerp(
|
||||
StreamChannelPreviewThemeData a,
|
||||
StreamChannelPreviewThemeData b,
|
||||
double t,
|
||||
) =>
|
||||
ChannelPreviewThemeData(
|
||||
avatarTheme:
|
||||
const AvatarThemeData().lerp(a.avatarTheme!, b.avatarTheme!, t),
|
||||
StreamChannelPreviewThemeData(
|
||||
avatarTheme: const StreamAvatarThemeData()
|
||||
.lerp(a.avatarTheme!, b.avatarTheme!, t),
|
||||
indicatorIconSize: a.indicatorIconSize,
|
||||
lastMessageAtStyle:
|
||||
TextStyle.lerp(a.lastMessageAtStyle, b.lastMessageAtStyle, t),
|
||||
@@ -122,7 +134,7 @@ class ChannelPreviewThemeData with Diagnosticable {
|
||||
);
|
||||
|
||||
/// Merge with theme
|
||||
ChannelPreviewThemeData merge(ChannelPreviewThemeData? other) {
|
||||
StreamChannelPreviewThemeData merge(StreamChannelPreviewThemeData? other) {
|
||||
if (other == null) return this;
|
||||
return copyWith(
|
||||
titleStyle: titleStyle?.merge(other.titleStyle) ?? other.titleStyle,
|
||||
@@ -138,7 +150,7 @@ class ChannelPreviewThemeData with Diagnosticable {
|
||||
@override
|
||||
bool operator ==(Object other) =>
|
||||
identical(this, other) ||
|
||||
other is ChannelPreviewThemeData &&
|
||||
other is StreamChannelPreviewThemeData &&
|
||||
runtimeType == other.runtimeType &&
|
||||
titleStyle == other.titleStyle &&
|
||||
subtitleStyle == other.subtitleStyle &&
|
||||
|
||||
@@ -1,9 +1,15 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
/// {@macro color_theme}
|
||||
@Deprecated("Use 'StreamColorTheme' instead")
|
||||
typedef ColorTheme = StreamColorTheme;
|
||||
|
||||
/// {@template color_theme}
|
||||
/// Theme that holds colors
|
||||
class ColorTheme {
|
||||
/// {@endtemplate}
|
||||
class StreamColorTheme {
|
||||
/// Initialise with light theme
|
||||
ColorTheme.light({
|
||||
StreamColorTheme.light({
|
||||
this.textHighEmphasis = const Color(0xff000000),
|
||||
this.textLowEmphasis = const Color(0xff7a7a7a),
|
||||
this.disabled = const Color(0xffdbdbdb),
|
||||
@@ -55,7 +61,7 @@ class ColorTheme {
|
||||
}) : brightness = Brightness.light;
|
||||
|
||||
/// Initialise with dark theme
|
||||
ColorTheme.dark({
|
||||
StreamColorTheme.dark({
|
||||
this.textHighEmphasis = const Color(0xffffffff),
|
||||
this.textLowEmphasis = const Color(0xff7a7a7a),
|
||||
this.disabled = const Color(0xff2d2f2f),
|
||||
@@ -169,7 +175,7 @@ class ColorTheme {
|
||||
final Brightness brightness;
|
||||
|
||||
/// Copy with theme
|
||||
ColorTheme copyWith({
|
||||
StreamColorTheme copyWith({
|
||||
Brightness brightness = Brightness.light,
|
||||
Color? textHighEmphasis,
|
||||
Color? textLowEmphasis,
|
||||
@@ -192,7 +198,7 @@ class ColorTheme {
|
||||
Gradient? bgGradient,
|
||||
}) =>
|
||||
brightness == Brightness.light
|
||||
? ColorTheme.light(
|
||||
? StreamColorTheme.light(
|
||||
textHighEmphasis: textHighEmphasis ?? this.textHighEmphasis,
|
||||
textLowEmphasis: textLowEmphasis ?? this.textLowEmphasis,
|
||||
disabled: disabled ?? this.disabled,
|
||||
@@ -213,7 +219,7 @@ class ColorTheme {
|
||||
overlayDark: overlayDark ?? this.overlayDark,
|
||||
bgGradient: bgGradient ?? this.bgGradient,
|
||||
)
|
||||
: ColorTheme.dark(
|
||||
: StreamColorTheme.dark(
|
||||
textHighEmphasis: textHighEmphasis ?? this.textHighEmphasis,
|
||||
textLowEmphasis: textLowEmphasis ?? this.textLowEmphasis,
|
||||
disabled: disabled ?? this.disabled,
|
||||
@@ -236,7 +242,7 @@ class ColorTheme {
|
||||
);
|
||||
|
||||
/// Merge color theme
|
||||
ColorTheme merge(ColorTheme? other) {
|
||||
StreamColorTheme merge(StreamColorTheme? other) {
|
||||
if (other == null) return this;
|
||||
return copyWith(
|
||||
textHighEmphasis: other.textHighEmphasis,
|
||||
|
||||
@@ -2,27 +2,33 @@ import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:stream_chat_flutter/src/stream_chat_theme.dart';
|
||||
|
||||
/// {@macro gallery_footer_theme}
|
||||
@Deprecated("Use 'StreamGalleryFooterTheme' instead")
|
||||
typedef GalleryFooterTheme = StreamGalleryFooterTheme;
|
||||
|
||||
/// {@template gallery_footer_theme}
|
||||
/// Overrides the default style of [GalleryFooter] descendants.
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [GalleryFooterThemeData], which is used to configure this theme.
|
||||
class GalleryFooterTheme extends InheritedTheme {
|
||||
/// Creates an [GalleryFooterTheme].
|
||||
/// * [StreamGalleryFooterThemeData], which is used to configure this theme.
|
||||
/// {@endtemplate}
|
||||
class StreamGalleryFooterTheme extends InheritedTheme {
|
||||
/// Creates an [StreamGalleryFooterTheme].
|
||||
///
|
||||
/// The [data] parameter must not be null.
|
||||
const GalleryFooterTheme({
|
||||
const StreamGalleryFooterTheme({
|
||||
Key? key,
|
||||
required this.data,
|
||||
required Widget child,
|
||||
}) : super(key: key, child: child);
|
||||
|
||||
/// The configuration of this theme.
|
||||
final GalleryFooterThemeData data;
|
||||
final StreamGalleryFooterThemeData data;
|
||||
|
||||
/// The closest instance of this class that encloses the given context.
|
||||
///
|
||||
/// If there is no enclosing [GalleryFooterTheme] widget, then
|
||||
/// If there is no enclosing [StreamGalleryFooterTheme] widget, then
|
||||
/// [StreamChatThemeData.galleryFooterTheme] is used.
|
||||
///
|
||||
/// Typical usage is as follows:
|
||||
@@ -30,34 +36,40 @@ class GalleryFooterTheme extends InheritedTheme {
|
||||
/// ```dart
|
||||
/// ImageFooterTheme theme = ImageFooterTheme.of(context);
|
||||
/// ```
|
||||
static GalleryFooterThemeData of(BuildContext context) {
|
||||
static StreamGalleryFooterThemeData of(BuildContext context) {
|
||||
final imageFooterTheme =
|
||||
context.dependOnInheritedWidgetOfExactType<GalleryFooterTheme>();
|
||||
context.dependOnInheritedWidgetOfExactType<StreamGalleryFooterTheme>();
|
||||
return imageFooterTheme?.data ??
|
||||
StreamChatTheme.of(context).galleryFooterTheme;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget wrap(BuildContext context, Widget child) =>
|
||||
GalleryFooterTheme(data: data, child: child);
|
||||
StreamGalleryFooterTheme(data: data, child: child);
|
||||
|
||||
@override
|
||||
bool updateShouldNotify(GalleryFooterTheme oldWidget) =>
|
||||
bool updateShouldNotify(StreamGalleryFooterTheme oldWidget) =>
|
||||
data != oldWidget.data;
|
||||
}
|
||||
|
||||
/// {@macro gallery_footer_theme_data}
|
||||
@Deprecated("Use 'StreamGalleryFooterThemeData' instead")
|
||||
typedef GalleryFooterThemeData = StreamGalleryFooterThemeData;
|
||||
|
||||
/// {@template gallery_footer_theme_data}
|
||||
/// A style that overrides the default appearance of [GalleryFooter]s when used
|
||||
/// with [GalleryFooterTheme] or with the overall [StreamChatTheme]'s
|
||||
/// with [StreamGalleryFooterTheme] or with the overall [StreamChatTheme]'s
|
||||
/// [StreamChatThemeData.galleryFooterTheme].
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [GalleryFooterTheme], the theme which is configured with this class.
|
||||
/// * [StreamGalleryFooterTheme], the theme which is configured with this class.
|
||||
/// * [StreamChatThemeData.galleryFooterTheme], which can be used to override
|
||||
/// the default style for [GalleryFooter]s below the overall [StreamChatTheme].
|
||||
class GalleryFooterThemeData with Diagnosticable {
|
||||
/// Creates an [GalleryFooterThemeData].
|
||||
const GalleryFooterThemeData({
|
||||
/// {@endtemplate}
|
||||
class StreamGalleryFooterThemeData with Diagnosticable {
|
||||
/// Creates an [StreamGalleryFooterThemeData].
|
||||
const StreamGalleryFooterThemeData({
|
||||
this.backgroundColor,
|
||||
this.shareIconColor,
|
||||
this.titleTextStyle,
|
||||
@@ -108,8 +120,8 @@ class GalleryFooterThemeData with Diagnosticable {
|
||||
/// Defaults to [ColorTheme.textHighEmphasis].
|
||||
final Color? bottomSheetCloseIconColor;
|
||||
|
||||
/// Copies this [GalleryFooterThemeData] to another.
|
||||
GalleryFooterThemeData copyWith({
|
||||
/// Copies this [StreamGalleryFooterThemeData] to another.
|
||||
StreamGalleryFooterThemeData copyWith({
|
||||
Color? backgroundColor,
|
||||
Color? shareIconColor,
|
||||
TextStyle? titleTextStyle,
|
||||
@@ -119,7 +131,7 @@ class GalleryFooterThemeData with Diagnosticable {
|
||||
TextStyle? bottomSheetPhotosTextStyle,
|
||||
Color? bottomSheetCloseIconColor,
|
||||
}) =>
|
||||
GalleryFooterThemeData(
|
||||
StreamGalleryFooterThemeData(
|
||||
backgroundColor: backgroundColor ?? this.backgroundColor,
|
||||
shareIconColor: shareIconColor ?? this.shareIconColor,
|
||||
titleTextStyle: titleTextStyle ?? this.titleTextStyle,
|
||||
@@ -137,12 +149,12 @@ class GalleryFooterThemeData with Diagnosticable {
|
||||
/// Linearly interpolate between two [GalleryFooter] themes.
|
||||
///
|
||||
/// All the properties must be non-null.
|
||||
GalleryFooterThemeData lerp(
|
||||
GalleryFooterThemeData a,
|
||||
GalleryFooterThemeData b,
|
||||
StreamGalleryFooterThemeData lerp(
|
||||
StreamGalleryFooterThemeData a,
|
||||
StreamGalleryFooterThemeData b,
|
||||
double t,
|
||||
) =>
|
||||
GalleryFooterThemeData(
|
||||
StreamGalleryFooterThemeData(
|
||||
backgroundColor: Color.lerp(a.backgroundColor, b.backgroundColor, t),
|
||||
shareIconColor: Color.lerp(a.shareIconColor, b.shareIconColor, t),
|
||||
titleTextStyle: TextStyle.lerp(a.titleTextStyle, b.titleTextStyle, t),
|
||||
@@ -167,8 +179,8 @@ class GalleryFooterThemeData with Diagnosticable {
|
||||
),
|
||||
);
|
||||
|
||||
/// Merges one [GalleryFooterThemeData] with another.
|
||||
GalleryFooterThemeData merge(GalleryFooterThemeData? other) {
|
||||
/// Merges one [StreamGalleryFooterThemeData] with another.
|
||||
StreamGalleryFooterThemeData merge(StreamGalleryFooterThemeData? other) {
|
||||
if (other == null) return this;
|
||||
return copyWith(
|
||||
backgroundColor: other.backgroundColor,
|
||||
@@ -185,7 +197,7 @@ class GalleryFooterThemeData with Diagnosticable {
|
||||
@override
|
||||
bool operator ==(Object other) =>
|
||||
identical(this, other) ||
|
||||
other is GalleryFooterThemeData &&
|
||||
other is StreamGalleryFooterThemeData &&
|
||||
runtimeType == other.runtimeType &&
|
||||
backgroundColor == other.backgroundColor &&
|
||||
shareIconColor == other.shareIconColor &&
|
||||
|
||||
@@ -2,27 +2,33 @@ import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:stream_chat_flutter/src/stream_chat_theme.dart';
|
||||
|
||||
/// {@macro gallery_header_them}
|
||||
@Deprecated("Use 'StreamGalleryHeaderTheme' instead")
|
||||
typedef GalleryHeaderTheme = StreamGalleryHeaderTheme;
|
||||
|
||||
/// {@template gallery_header_theme}
|
||||
/// Overrides the default style of [GalleryHeader] descendants.
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [GalleryHeaderThemeData], which is used to configure this theme.
|
||||
class GalleryHeaderTheme extends InheritedTheme {
|
||||
/// Creates a [GalleryHeaderTheme].
|
||||
/// * [StreamGalleryHeaderThemeData], which is used to configure this theme.
|
||||
/// {@endtemplate}
|
||||
class StreamGalleryHeaderTheme extends InheritedTheme {
|
||||
/// Creates a [StreamGalleryHeaderTheme].
|
||||
///
|
||||
/// The [data] parameter must not be null.
|
||||
const GalleryHeaderTheme({
|
||||
const StreamGalleryHeaderTheme({
|
||||
Key? key,
|
||||
required this.data,
|
||||
required Widget child,
|
||||
}) : super(key: key, child: child);
|
||||
|
||||
/// The configuration of this theme.
|
||||
final GalleryHeaderThemeData data;
|
||||
final StreamGalleryHeaderThemeData data;
|
||||
|
||||
/// The closest instance of this class that encloses the given context.
|
||||
///
|
||||
/// If there is no enclosing [GalleryHeaderTheme] widget, then
|
||||
/// If there is no enclosing [StreamGalleryHeaderTheme] widget, then
|
||||
/// [StreamChatThemeData.galleryHeaderTheme] is used.
|
||||
///
|
||||
/// Typical usage is as follows:
|
||||
@@ -30,34 +36,40 @@ class GalleryHeaderTheme extends InheritedTheme {
|
||||
/// ```dart
|
||||
/// ImageHeaderTheme theme = ImageHeaderTheme.of(context);
|
||||
/// ```
|
||||
static GalleryHeaderThemeData of(BuildContext context) {
|
||||
static StreamGalleryHeaderThemeData of(BuildContext context) {
|
||||
final galleryHeaderTheme =
|
||||
context.dependOnInheritedWidgetOfExactType<GalleryHeaderTheme>();
|
||||
context.dependOnInheritedWidgetOfExactType<StreamGalleryHeaderTheme>();
|
||||
return galleryHeaderTheme?.data ??
|
||||
StreamChatTheme.of(context).galleryHeaderTheme;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget wrap(BuildContext context, Widget child) =>
|
||||
GalleryHeaderTheme(data: data, child: child);
|
||||
StreamGalleryHeaderTheme(data: data, child: child);
|
||||
|
||||
@override
|
||||
bool updateShouldNotify(GalleryHeaderTheme oldWidget) =>
|
||||
bool updateShouldNotify(StreamGalleryHeaderTheme oldWidget) =>
|
||||
data != oldWidget.data;
|
||||
}
|
||||
|
||||
/// {@macro gallery_header_theme_data}
|
||||
@Deprecated("Use 'StreamGalleryHeaderThemeData' instead")
|
||||
typedef GalleryHeaderThemeData = StreamGalleryHeaderThemeData;
|
||||
|
||||
/// {@template gallery_header_theme_data}
|
||||
/// A style that overrides the default appearance of [GalleryHeader]s when used
|
||||
/// with [GalleryHeaderTheme] or with the overall [StreamChatTheme]'s
|
||||
/// with [StreamGalleryHeaderTheme] or with the overall [StreamChatTheme]'s
|
||||
/// [StreamChatThemeData.galleryHeaderTheme].
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [GalleryHeaderTheme], the theme which is configured with this class.
|
||||
/// * [StreamGalleryHeaderTheme], the theme which is configured with this class.
|
||||
/// * [StreamChatThemeData.galleryHeaderTheme], which can be used to override
|
||||
/// the default style for [GalleryHeader]s below the overall [StreamChatTheme].
|
||||
class GalleryHeaderThemeData with Diagnosticable {
|
||||
/// Creates an [GalleryHeaderThemeData].
|
||||
const GalleryHeaderThemeData({
|
||||
/// {@endtemplate}
|
||||
class StreamGalleryHeaderThemeData with Diagnosticable {
|
||||
/// Creates an [StreamGalleryHeaderThemeData].
|
||||
const StreamGalleryHeaderThemeData({
|
||||
this.closeButtonColor,
|
||||
this.backgroundColor,
|
||||
this.iconMenuPointColor,
|
||||
@@ -92,8 +104,8 @@ class GalleryHeaderThemeData with Diagnosticable {
|
||||
///
|
||||
final Color? bottomSheetBarrierColor;
|
||||
|
||||
/// Copies this [GalleryHeaderThemeData] to another.
|
||||
GalleryHeaderThemeData copyWith({
|
||||
/// Copies this [StreamGalleryHeaderThemeData] to another.
|
||||
StreamGalleryHeaderThemeData copyWith({
|
||||
Color? closeButtonColor,
|
||||
Color? backgroundColor,
|
||||
Color? iconMenuPointColor,
|
||||
@@ -101,7 +113,7 @@ class GalleryHeaderThemeData with Diagnosticable {
|
||||
TextStyle? subtitleTextStyle,
|
||||
Color? bottomSheetBarrierColor,
|
||||
}) =>
|
||||
GalleryHeaderThemeData(
|
||||
StreamGalleryHeaderThemeData(
|
||||
closeButtonColor: closeButtonColor ?? this.closeButtonColor,
|
||||
backgroundColor: backgroundColor ?? this.backgroundColor,
|
||||
iconMenuPointColor: iconMenuPointColor ?? this.iconMenuPointColor,
|
||||
@@ -114,12 +126,12 @@ class GalleryHeaderThemeData with Diagnosticable {
|
||||
/// Linearly interpolate between two [GalleryHeader] themes.
|
||||
///
|
||||
/// All the properties must be non-null.
|
||||
GalleryHeaderThemeData lerp(
|
||||
GalleryHeaderThemeData a,
|
||||
GalleryHeaderThemeData b,
|
||||
StreamGalleryHeaderThemeData lerp(
|
||||
StreamGalleryHeaderThemeData a,
|
||||
StreamGalleryHeaderThemeData b,
|
||||
double t,
|
||||
) =>
|
||||
GalleryHeaderThemeData(
|
||||
StreamGalleryHeaderThemeData(
|
||||
closeButtonColor: Color.lerp(a.closeButtonColor, b.closeButtonColor, t),
|
||||
backgroundColor: Color.lerp(a.backgroundColor, b.backgroundColor, t),
|
||||
iconMenuPointColor:
|
||||
@@ -131,8 +143,8 @@ class GalleryHeaderThemeData with Diagnosticable {
|
||||
Color.lerp(a.bottomSheetBarrierColor, b.bottomSheetBarrierColor, t),
|
||||
);
|
||||
|
||||
/// Merges one [GalleryHeaderThemeData] with the another
|
||||
GalleryHeaderThemeData merge(GalleryHeaderThemeData? other) {
|
||||
/// Merges one [StreamGalleryHeaderThemeData] with the another
|
||||
StreamGalleryHeaderThemeData merge(StreamGalleryHeaderThemeData? other) {
|
||||
if (other == null) return this;
|
||||
return copyWith(
|
||||
closeButtonColor: other.closeButtonColor,
|
||||
@@ -147,7 +159,7 @@ class GalleryHeaderThemeData with Diagnosticable {
|
||||
@override
|
||||
bool operator ==(Object other) =>
|
||||
identical(this, other) ||
|
||||
other is GalleryHeaderThemeData &&
|
||||
other is StreamGalleryHeaderThemeData &&
|
||||
runtimeType == other.runtimeType &&
|
||||
closeButtonColor == other.closeButtonColor &&
|
||||
backgroundColor == other.backgroundColor &&
|
||||
|
||||
@@ -1,29 +1,37 @@
|
||||
import 'dart:ui';
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:stream_chat_flutter/src/extension.dart';
|
||||
import 'package:stream_chat_flutter/src/stream_chat_theme.dart';
|
||||
|
||||
/// {@macro message_input_theme}
|
||||
@Deprecated("Use 'StreamMessageInputTheme' instead")
|
||||
typedef MessageInputTheme = StreamMessageInputTheme;
|
||||
|
||||
/// {@template message_input_theme}
|
||||
/// Overrides the default style of [MessageInput] descendants.
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [MessageInputThemeData], which is used to configure this theme.
|
||||
class MessageInputTheme extends InheritedTheme {
|
||||
/// Creates a [MessageInputTheme].
|
||||
/// * [StreamMessageInputThemeData], which is used to configure this theme.
|
||||
/// {@endtemplate}
|
||||
class StreamMessageInputTheme extends InheritedTheme {
|
||||
/// Creates a [StreamMessageInputTheme].
|
||||
///
|
||||
/// The [data] parameter must not be null.
|
||||
const MessageInputTheme({
|
||||
const StreamMessageInputTheme({
|
||||
Key? key,
|
||||
required this.data,
|
||||
required Widget child,
|
||||
}) : super(key: key, child: child);
|
||||
|
||||
/// The configuration of this theme.
|
||||
final MessageInputThemeData data;
|
||||
final StreamMessageInputThemeData data;
|
||||
|
||||
/// The closest instance of this class that encloses the given context.
|
||||
///
|
||||
/// If there is no enclosing [MessageInputTheme] widget, then
|
||||
/// If there is no enclosing [StreamMessageInputTheme] widget, then
|
||||
/// [StreamChatThemeData.messageInputTheme] is used.
|
||||
///
|
||||
/// Typical usage is as follows:
|
||||
@@ -31,28 +39,34 @@ class MessageInputTheme extends InheritedTheme {
|
||||
/// ```dart
|
||||
/// final theme = MessageInputTheme.of(context);
|
||||
/// ```
|
||||
static MessageInputThemeData of(BuildContext context) {
|
||||
static StreamMessageInputThemeData of(BuildContext context) {
|
||||
final messageInputTheme =
|
||||
context.dependOnInheritedWidgetOfExactType<MessageInputTheme>();
|
||||
context.dependOnInheritedWidgetOfExactType<StreamMessageInputTheme>();
|
||||
return messageInputTheme?.data ??
|
||||
StreamChatTheme.of(context).messageInputTheme;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget wrap(BuildContext context, Widget child) =>
|
||||
MessageInputTheme(data: data, child: child);
|
||||
StreamMessageInputTheme(data: data, child: child);
|
||||
|
||||
@override
|
||||
bool updateShouldNotify(MessageInputTheme oldWidget) =>
|
||||
bool updateShouldNotify(StreamMessageInputTheme oldWidget) =>
|
||||
data != oldWidget.data;
|
||||
}
|
||||
|
||||
/// {@macro message_input_theme_data}
|
||||
@Deprecated("Use 'StreamMessageInputThemeData' instead")
|
||||
typedef MessageInputThemeData = StreamMessageInputThemeData;
|
||||
|
||||
/// {@template message_input_theme_data}
|
||||
/// A style that overrides the default appearance of [MessageInput] widgets
|
||||
/// when used with [MessageInputTheme] or with the overall [StreamChatTheme]'s
|
||||
/// when used with [StreamMessageInputTheme] or with the overall [StreamChatTheme]'s
|
||||
/// [StreamChatThemeData.messageInputTheme].
|
||||
class MessageInputThemeData with Diagnosticable {
|
||||
/// Creates a [MessageInputThemeData].
|
||||
const MessageInputThemeData({
|
||||
/// {@endtemplate}
|
||||
class StreamMessageInputThemeData with Diagnosticable {
|
||||
/// Creates a [StreamMessageInputThemeData].
|
||||
const StreamMessageInputThemeData({
|
||||
this.sendAnimationDuration,
|
||||
this.actionButtonColor,
|
||||
this.sendButtonColor,
|
||||
@@ -65,6 +79,10 @@ class MessageInputThemeData with Diagnosticable {
|
||||
this.idleBorderGradient,
|
||||
this.borderRadius,
|
||||
this.expandButtonColor,
|
||||
this.linkHighlightColor,
|
||||
this.enableSafeArea,
|
||||
this.elevation,
|
||||
this.shadow,
|
||||
});
|
||||
|
||||
/// Duration of the [MessageInput] send button animation
|
||||
@@ -73,6 +91,9 @@ class MessageInputThemeData with Diagnosticable {
|
||||
/// Background color of [MessageInput] send button
|
||||
final Color? sendButtonColor;
|
||||
|
||||
/// Color of a link
|
||||
final Color? linkHighlightColor;
|
||||
|
||||
/// Background color of [MessageInput] action buttons
|
||||
final Color? actionButtonColor;
|
||||
|
||||
@@ -103,13 +124,23 @@ class MessageInputThemeData with Diagnosticable {
|
||||
/// Border radius of [MessageInput]
|
||||
final BorderRadius? borderRadius;
|
||||
|
||||
/// Returns a new [MessageInputThemeData] replacing some of its properties
|
||||
MessageInputThemeData copyWith({
|
||||
/// Wrap [MessageInput] with a [SafeArea widget]
|
||||
final bool? enableSafeArea;
|
||||
|
||||
/// Elevation of the [MessageInput]
|
||||
final double? elevation;
|
||||
|
||||
/// Shadow for the [MessageInput] widget
|
||||
final BoxShadow? shadow;
|
||||
|
||||
/// Returns a new [StreamMessageInputThemeData] replacing some of its properties
|
||||
StreamMessageInputThemeData copyWith({
|
||||
Duration? sendAnimationDuration,
|
||||
Color? inputBackgroundColor,
|
||||
Color? actionButtonColor,
|
||||
Color? sendButtonColor,
|
||||
Color? actionButtonIdleColor,
|
||||
Color? linkHighlightColor,
|
||||
Color? sendButtonIdleColor,
|
||||
Color? expandButtonColor,
|
||||
TextStyle? inputTextStyle,
|
||||
@@ -117,8 +148,11 @@ class MessageInputThemeData with Diagnosticable {
|
||||
Gradient? activeBorderGradient,
|
||||
Gradient? idleBorderGradient,
|
||||
BorderRadius? borderRadius,
|
||||
bool? enableSafeArea,
|
||||
double? elevation,
|
||||
BoxShadow? shadow,
|
||||
}) =>
|
||||
MessageInputThemeData(
|
||||
StreamMessageInputThemeData(
|
||||
sendAnimationDuration:
|
||||
sendAnimationDuration ?? this.sendAnimationDuration,
|
||||
inputBackgroundColor: inputBackgroundColor ?? this.inputBackgroundColor,
|
||||
@@ -133,15 +167,19 @@ class MessageInputThemeData with Diagnosticable {
|
||||
activeBorderGradient: activeBorderGradient ?? this.activeBorderGradient,
|
||||
idleBorderGradient: idleBorderGradient ?? this.idleBorderGradient,
|
||||
borderRadius: borderRadius ?? this.borderRadius,
|
||||
linkHighlightColor: linkHighlightColor ?? this.linkHighlightColor,
|
||||
enableSafeArea: enableSafeArea ?? this.enableSafeArea,
|
||||
elevation: elevation ?? this.elevation,
|
||||
shadow: shadow ?? this.shadow,
|
||||
);
|
||||
|
||||
/// Linearly interpolate from one [MessageInputThemeData] to another.
|
||||
MessageInputThemeData lerp(
|
||||
MessageInputThemeData a,
|
||||
MessageInputThemeData b,
|
||||
/// Linearly interpolate from one [StreamMessageInputThemeData] to another.
|
||||
StreamMessageInputThemeData lerp(
|
||||
StreamMessageInputThemeData a,
|
||||
StreamMessageInputThemeData b,
|
||||
double t,
|
||||
) =>
|
||||
MessageInputThemeData(
|
||||
StreamMessageInputThemeData(
|
||||
actionButtonColor:
|
||||
Color.lerp(a.actionButtonColor, b.actionButtonColor, t),
|
||||
actionButtonIdleColor:
|
||||
@@ -161,10 +199,15 @@ class MessageInputThemeData with Diagnosticable {
|
||||
Color.lerp(a.sendButtonIdleColor, b.sendButtonIdleColor, t),
|
||||
sendAnimationDuration: a.sendAnimationDuration,
|
||||
inputDecoration: a.inputDecoration,
|
||||
linkHighlightColor:
|
||||
Color.lerp(a.linkHighlightColor, b.linkHighlightColor, t),
|
||||
enableSafeArea: a.enableSafeArea,
|
||||
elevation: lerpDouble(a.elevation, b.elevation, t),
|
||||
shadow: BoxShadow.lerp(a.shadow, b.shadow, t),
|
||||
);
|
||||
|
||||
/// Merges [this] [MessageInputThemeData] with the [other]
|
||||
MessageInputThemeData merge(MessageInputThemeData? other) {
|
||||
/// Merges [this] [StreamMessageInputThemeData] with the [other]
|
||||
StreamMessageInputThemeData merge(StreamMessageInputThemeData? other) {
|
||||
if (other == null) return this;
|
||||
return copyWith(
|
||||
sendAnimationDuration: other.sendAnimationDuration,
|
||||
@@ -181,13 +224,17 @@ class MessageInputThemeData with Diagnosticable {
|
||||
idleBorderGradient: other.idleBorderGradient,
|
||||
borderRadius: other.borderRadius,
|
||||
expandButtonColor: other.expandButtonColor,
|
||||
linkHighlightColor: other.linkHighlightColor,
|
||||
enableSafeArea: other.enableSafeArea,
|
||||
elevation: other.elevation,
|
||||
shadow: other.shadow,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) =>
|
||||
identical(this, other) ||
|
||||
other is MessageInputThemeData &&
|
||||
other is StreamMessageInputThemeData &&
|
||||
runtimeType == other.runtimeType &&
|
||||
sendAnimationDuration == other.sendAnimationDuration &&
|
||||
sendButtonColor == other.sendButtonColor &&
|
||||
@@ -200,7 +247,11 @@ class MessageInputThemeData with Diagnosticable {
|
||||
inputDecoration == other.inputDecoration &&
|
||||
idleBorderGradient == other.idleBorderGradient &&
|
||||
activeBorderGradient == other.activeBorderGradient &&
|
||||
borderRadius == other.borderRadius;
|
||||
borderRadius == other.borderRadius &&
|
||||
linkHighlightColor == other.linkHighlightColor &&
|
||||
enableSafeArea == other.enableSafeArea &&
|
||||
elevation == other.elevation &&
|
||||
shadow == other.shadow;
|
||||
|
||||
@override
|
||||
int get hashCode =>
|
||||
@@ -215,7 +266,11 @@ class MessageInputThemeData with Diagnosticable {
|
||||
inputDecoration.hashCode ^
|
||||
idleBorderGradient.hashCode ^
|
||||
activeBorderGradient.hashCode ^
|
||||
borderRadius.hashCode;
|
||||
borderRadius.hashCode ^
|
||||
linkHighlightColor.hashCode ^
|
||||
elevation.hashCode ^
|
||||
shadow.hashCode ^
|
||||
enableSafeArea.hashCode;
|
||||
|
||||
@override
|
||||
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
|
||||
@@ -232,6 +287,10 @@ class MessageInputThemeData with Diagnosticable {
|
||||
..add(DiagnosticsProperty('activeBorderGradient', activeBorderGradient))
|
||||
..add(DiagnosticsProperty('idleBorderGradient', idleBorderGradient))
|
||||
..add(DiagnosticsProperty('borderRadius', borderRadius))
|
||||
..add(ColorProperty('expandButtonColor', expandButtonColor));
|
||||
..add(ColorProperty('expandButtonColor', expandButtonColor))
|
||||
..add(ColorProperty('linkHighlightColor', linkHighlightColor))
|
||||
..add(DiagnosticsProperty('elevation', elevation))
|
||||
..add(DiagnosticsProperty('shadow', shadow))
|
||||
..add(DiagnosticsProperty('enableSafeArea', enableSafeArea));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,27 +2,33 @@ import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:stream_chat_flutter/src/stream_chat_theme.dart';
|
||||
|
||||
/// {@macro message_list_view_theme}
|
||||
@Deprecated("Use 'StreamMessageListViewTheme' instead")
|
||||
typedef MessageListViewTheme = StreamMessageListViewTheme;
|
||||
|
||||
/// {@template message_list_view_theme}
|
||||
/// Overrides the default style of [MessageListView] descendants.
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [MessageListViewThemeData], which is used to configure this theme.
|
||||
class MessageListViewTheme extends InheritedTheme {
|
||||
/// Creates a [MessageListViewTheme].
|
||||
/// * [StreamMessageListViewThemeData], which is used to configure this theme.
|
||||
/// {@endtemplate}
|
||||
class StreamMessageListViewTheme extends InheritedTheme {
|
||||
/// Creates a [StreamMessageListViewTheme].
|
||||
///
|
||||
/// The [data] parameter must not be null.
|
||||
const MessageListViewTheme({
|
||||
const StreamMessageListViewTheme({
|
||||
Key? key,
|
||||
required this.data,
|
||||
required Widget child,
|
||||
}) : super(key: key, child: child);
|
||||
|
||||
/// The configuration of this theme.
|
||||
final MessageListViewThemeData data;
|
||||
final StreamMessageListViewThemeData data;
|
||||
|
||||
/// The closest instance of this class that encloses the given context.
|
||||
///
|
||||
/// If there is no enclosing [MessageListViewTheme] widget, then
|
||||
/// If there is no enclosing [StreamMessageListViewTheme] widget, then
|
||||
/// [StreamChatThemeData.messageListViewTheme] is used.
|
||||
///
|
||||
/// Typical usage is as follows:
|
||||
@@ -30,35 +36,41 @@ class MessageListViewTheme extends InheritedTheme {
|
||||
/// ```dart
|
||||
/// MessageListViewTheme theme = MessageListViewTheme.of(context);
|
||||
/// ```
|
||||
static MessageListViewThemeData of(BuildContext context) {
|
||||
final messageListViewTheme =
|
||||
context.dependOnInheritedWidgetOfExactType<MessageListViewTheme>();
|
||||
static StreamMessageListViewThemeData of(BuildContext context) {
|
||||
final messageListViewTheme = context
|
||||
.dependOnInheritedWidgetOfExactType<StreamMessageListViewTheme>();
|
||||
return messageListViewTheme?.data ??
|
||||
StreamChatTheme.of(context).messageListViewTheme;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget wrap(BuildContext context, Widget child) =>
|
||||
MessageListViewTheme(data: data, child: child);
|
||||
StreamMessageListViewTheme(data: data, child: child);
|
||||
|
||||
@override
|
||||
bool updateShouldNotify(MessageListViewTheme oldWidget) =>
|
||||
bool updateShouldNotify(StreamMessageListViewTheme oldWidget) =>
|
||||
data != oldWidget.data;
|
||||
}
|
||||
|
||||
/// {@macro message_list_view_theme_data}
|
||||
@Deprecated("Use 'StreamMessageListViewThemeData' instead")
|
||||
typedef MessageListViewThemeData = StreamMessageListViewThemeData;
|
||||
|
||||
/// {@template message_list_view_theme_data}
|
||||
/// A style that overrides the default appearance of [MessageListView]s when
|
||||
/// used with [MessageListViewTheme] or with the overall [StreamChatTheme]'s
|
||||
/// used with [StreamMessageListViewTheme] or with the overall [StreamChatTheme]'s
|
||||
/// [StreamChatThemeData.messageListViewTheme].
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [MessageListViewTheme], the theme which is configured with this class.
|
||||
/// * [StreamMessageListViewTheme], the theme which is configured with this class.
|
||||
/// * [StreamChatThemeData.messageListViewTheme], which can be used to override
|
||||
/// the default style for [MessageListView]s below the overall
|
||||
/// [StreamChatTheme].
|
||||
class MessageListViewThemeData with Diagnosticable {
|
||||
/// Creates a [MessageListViewThemeData].
|
||||
const MessageListViewThemeData({
|
||||
/// {@endtemplate}
|
||||
class StreamMessageListViewThemeData with Diagnosticable {
|
||||
/// Creates a [StreamMessageListViewThemeData].
|
||||
const StreamMessageListViewThemeData({
|
||||
this.backgroundColor,
|
||||
this.backgroundImage,
|
||||
});
|
||||
@@ -69,12 +81,12 @@ class MessageListViewThemeData with Diagnosticable {
|
||||
/// The image of the [MessageListView] background.
|
||||
final DecorationImage? backgroundImage;
|
||||
|
||||
/// Copies this [MessageListViewThemeData] to another.
|
||||
MessageListViewThemeData copyWith({
|
||||
/// Copies this [StreamMessageListViewThemeData] to another.
|
||||
StreamMessageListViewThemeData copyWith({
|
||||
Color? backgroundColor,
|
||||
DecorationImage? backgroundImage,
|
||||
}) =>
|
||||
MessageListViewThemeData(
|
||||
StreamMessageListViewThemeData(
|
||||
backgroundColor: backgroundColor ?? this.backgroundColor,
|
||||
backgroundImage: backgroundImage ?? this.backgroundImage,
|
||||
);
|
||||
@@ -82,18 +94,18 @@ class MessageListViewThemeData with Diagnosticable {
|
||||
/// Linearly interpolate between two [MessageListView] themes.
|
||||
///
|
||||
/// All the properties must be non-null.
|
||||
MessageListViewThemeData lerp(
|
||||
MessageListViewThemeData a,
|
||||
MessageListViewThemeData b,
|
||||
StreamMessageListViewThemeData lerp(
|
||||
StreamMessageListViewThemeData a,
|
||||
StreamMessageListViewThemeData b,
|
||||
double t,
|
||||
) =>
|
||||
MessageListViewThemeData(
|
||||
StreamMessageListViewThemeData(
|
||||
backgroundColor: Color.lerp(a.backgroundColor, b.backgroundColor, t),
|
||||
backgroundImage: t < 0.5 ? a.backgroundImage : b.backgroundImage,
|
||||
);
|
||||
|
||||
/// Merges one [MessageListViewThemeData] with another.
|
||||
MessageListViewThemeData merge(MessageListViewThemeData? other) {
|
||||
/// Merges one [StreamMessageListViewThemeData] with another.
|
||||
StreamMessageListViewThemeData merge(StreamMessageListViewThemeData? other) {
|
||||
if (other == null) return this;
|
||||
return copyWith(
|
||||
backgroundColor: other.backgroundColor,
|
||||
@@ -104,7 +116,7 @@ class MessageListViewThemeData with Diagnosticable {
|
||||
@override
|
||||
bool operator ==(Object other) =>
|
||||
identical(this, other) ||
|
||||
other is MessageListViewThemeData &&
|
||||
other is StreamMessageListViewThemeData &&
|
||||
runtimeType == other.runtimeType &&
|
||||
backgroundColor == other.backgroundColor &&
|
||||
backgroundImage == other.backgroundImage;
|
||||
|
||||
@@ -2,23 +2,29 @@ import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:stream_chat_flutter/src/stream_chat_theme.dart';
|
||||
|
||||
/// {@macro message_search_list_view_theme}
|
||||
@Deprecated("Use 'StreamMessageSearchListViewTheme' instead")
|
||||
typedef MessageSearchListViewTheme = StreamMessageSearchListViewTheme;
|
||||
|
||||
/// {@template message_search_list_view_theme}
|
||||
/// Overrides the default style of [MessageSearchListView] descendants.
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [UserListViewThemeData], which is used to configure this theme.
|
||||
class MessageSearchListViewTheme extends InheritedTheme {
|
||||
/// {@endtemplate}
|
||||
class StreamMessageSearchListViewTheme extends InheritedTheme {
|
||||
/// Creates a [UserListViewTheme].
|
||||
///
|
||||
/// The [data] parameter must not be null.
|
||||
const MessageSearchListViewTheme({
|
||||
const StreamMessageSearchListViewTheme({
|
||||
Key? key,
|
||||
required this.data,
|
||||
required Widget child,
|
||||
}) : super(key: key, child: child);
|
||||
|
||||
/// The configuration of this theme.
|
||||
final MessageSearchListViewThemeData data;
|
||||
final StreamMessageSearchListViewThemeData data;
|
||||
|
||||
/// The closest instance of this class that encloses the given context.
|
||||
///
|
||||
@@ -30,64 +36,72 @@ class MessageSearchListViewTheme extends InheritedTheme {
|
||||
/// ```dart
|
||||
/// MessageSearchListViewTheme theme = MessageSearchListViewTheme.of(context);
|
||||
/// ```
|
||||
static MessageSearchListViewThemeData of(BuildContext context) {
|
||||
static StreamMessageSearchListViewThemeData of(BuildContext context) {
|
||||
final messageSearchListViewTheme = context
|
||||
.dependOnInheritedWidgetOfExactType<MessageSearchListViewTheme>();
|
||||
.dependOnInheritedWidgetOfExactType<StreamMessageSearchListViewTheme>();
|
||||
return messageSearchListViewTheme?.data ??
|
||||
StreamChatTheme.of(context).messageSearchListViewTheme;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget wrap(BuildContext context, Widget child) =>
|
||||
MessageSearchListViewTheme(data: data, child: child);
|
||||
StreamMessageSearchListViewTheme(data: data, child: child);
|
||||
|
||||
@override
|
||||
bool updateShouldNotify(MessageSearchListViewTheme oldWidget) =>
|
||||
bool updateShouldNotify(StreamMessageSearchListViewTheme oldWidget) =>
|
||||
data != oldWidget.data;
|
||||
}
|
||||
|
||||
/// {@macro message_search_list_view_theme_data}
|
||||
@Deprecated("Use 'StreamMessageSearchListViewThemeData' instead")
|
||||
typedef MessageSearchListViewThemeData = StreamMessageSearchListViewThemeData;
|
||||
|
||||
/// {@macro message_search_list_view_theme_data}
|
||||
/// A style that overrides the default appearance of [MessageSearchListView]s
|
||||
/// when used with [MessageSearchListView] or with the overall
|
||||
/// [StreamChatTheme]'s [StreamChatThemeData.messageSearchListViewTheme].
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [MessageSearchListViewTheme], the theme which is configured with this
|
||||
/// * [StreamMessageSearchListViewTheme], the theme which is configured with this
|
||||
/// class.
|
||||
/// * [StreamChatThemeData.messageSearchListViewTheme], which can be used to
|
||||
/// override the default style for [UserListView]s below the overall
|
||||
/// [StreamChatTheme].
|
||||
class MessageSearchListViewThemeData with Diagnosticable {
|
||||
/// Creates a [MessageSearchListViewThemeData].
|
||||
const MessageSearchListViewThemeData({
|
||||
/// {@endtemplate}
|
||||
class StreamMessageSearchListViewThemeData with Diagnosticable {
|
||||
/// Creates a [StreamMessageSearchListViewThemeData].
|
||||
const StreamMessageSearchListViewThemeData({
|
||||
this.backgroundColor,
|
||||
});
|
||||
|
||||
/// The color of the [MessageSearchListView] background.
|
||||
final Color? backgroundColor;
|
||||
|
||||
/// Copies this [MessageSearchListViewThemeData] to another.
|
||||
MessageSearchListViewThemeData copyWith({
|
||||
/// Copies this [StreamMessageSearchListViewThemeData] to another.
|
||||
StreamMessageSearchListViewThemeData copyWith({
|
||||
Color? backgroundColor,
|
||||
}) =>
|
||||
MessageSearchListViewThemeData(
|
||||
StreamMessageSearchListViewThemeData(
|
||||
backgroundColor: backgroundColor ?? this.backgroundColor,
|
||||
);
|
||||
|
||||
/// Linearly interpolate between two [UserListViewThemeData] themes.
|
||||
///
|
||||
/// All the properties must be non-null.
|
||||
MessageSearchListViewThemeData lerp(
|
||||
MessageSearchListViewThemeData a,
|
||||
MessageSearchListViewThemeData b,
|
||||
StreamMessageSearchListViewThemeData lerp(
|
||||
StreamMessageSearchListViewThemeData a,
|
||||
StreamMessageSearchListViewThemeData b,
|
||||
double t,
|
||||
) =>
|
||||
MessageSearchListViewThemeData(
|
||||
StreamMessageSearchListViewThemeData(
|
||||
backgroundColor: Color.lerp(a.backgroundColor, b.backgroundColor, t),
|
||||
);
|
||||
|
||||
/// Merges one [MessageSearchListViewThemeData] with another.
|
||||
MessageSearchListViewThemeData merge(MessageSearchListViewThemeData? other) {
|
||||
/// Merges one [StreamMessageSearchListViewThemeData] with another.
|
||||
StreamMessageSearchListViewThemeData merge(
|
||||
StreamMessageSearchListViewThemeData? other,
|
||||
) {
|
||||
if (other == null) return this;
|
||||
return copyWith(
|
||||
backgroundColor: other.backgroundColor,
|
||||
@@ -97,7 +111,7 @@ class MessageSearchListViewThemeData with Diagnosticable {
|
||||
@override
|
||||
bool operator ==(Object other) =>
|
||||
identical(this, other) ||
|
||||
other is MessageSearchListViewThemeData &&
|
||||
other is StreamMessageSearchListViewThemeData &&
|
||||
runtimeType == other.runtimeType &&
|
||||
backgroundColor == other.backgroundColor;
|
||||
|
||||
|
||||
@@ -2,11 +2,17 @@ import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:stream_chat_flutter/src/theme/avatar_theme.dart';
|
||||
|
||||
/// {@macro message_theme_data}
|
||||
@Deprecated("Use 'StreamMessageThemeData' instead")
|
||||
typedef MessageThemeData = StreamMessageThemeData;
|
||||
|
||||
/// {@template message_theme_data}
|
||||
/// Class for getting message theme
|
||||
/// {@endtemplate}
|
||||
// ignore: prefer-match-file-name
|
||||
class MessageThemeData with Diagnosticable {
|
||||
/// Creates a [MessageThemeData].
|
||||
const MessageThemeData({
|
||||
class StreamMessageThemeData with Diagnosticable {
|
||||
/// Creates a [StreamMessageThemeData].
|
||||
const StreamMessageThemeData({
|
||||
this.repliesStyle,
|
||||
this.messageTextStyle,
|
||||
this.messageAuthorStyle,
|
||||
@@ -52,13 +58,13 @@ class MessageThemeData with Diagnosticable {
|
||||
final Color? reactionsMaskColor;
|
||||
|
||||
/// Theme of the avatar
|
||||
final AvatarThemeData? avatarTheme;
|
||||
final StreamAvatarThemeData? avatarTheme;
|
||||
|
||||
/// Background color for messages with url attachments.
|
||||
final Color? linkBackgroundColor;
|
||||
|
||||
/// Copy with a theme
|
||||
MessageThemeData copyWith({
|
||||
StreamMessageThemeData copyWith({
|
||||
TextStyle? messageTextStyle,
|
||||
TextStyle? messageAuthorStyle,
|
||||
TextStyle? messageLinksStyle,
|
||||
@@ -66,13 +72,13 @@ class MessageThemeData with Diagnosticable {
|
||||
TextStyle? repliesStyle,
|
||||
Color? messageBackgroundColor,
|
||||
Color? messageBorderColor,
|
||||
AvatarThemeData? avatarTheme,
|
||||
StreamAvatarThemeData? avatarTheme,
|
||||
Color? reactionsBackgroundColor,
|
||||
Color? reactionsBorderColor,
|
||||
Color? reactionsMaskColor,
|
||||
Color? linkBackgroundColor,
|
||||
}) =>
|
||||
MessageThemeData(
|
||||
StreamMessageThemeData(
|
||||
messageTextStyle: messageTextStyle ?? this.messageTextStyle,
|
||||
messageAuthorStyle: messageAuthorStyle ?? this.messageAuthorStyle,
|
||||
messageLinksStyle: messageLinksStyle ?? this.messageLinksStyle,
|
||||
@@ -89,11 +95,15 @@ class MessageThemeData with Diagnosticable {
|
||||
linkBackgroundColor: linkBackgroundColor ?? this.linkBackgroundColor,
|
||||
);
|
||||
|
||||
/// Linearly interpolate from one [MessageThemeData] to another.
|
||||
MessageThemeData lerp(MessageThemeData a, MessageThemeData b, double t) =>
|
||||
MessageThemeData(
|
||||
avatarTheme:
|
||||
const AvatarThemeData().lerp(a.avatarTheme!, b.avatarTheme!, t),
|
||||
/// Linearly interpolate from one [StreamMessageThemeData] to another.
|
||||
StreamMessageThemeData lerp(
|
||||
StreamMessageThemeData a,
|
||||
StreamMessageThemeData b,
|
||||
double t,
|
||||
) =>
|
||||
StreamMessageThemeData(
|
||||
avatarTheme: const StreamAvatarThemeData()
|
||||
.lerp(a.avatarTheme!, b.avatarTheme!, t),
|
||||
createdAtStyle: TextStyle.lerp(a.createdAtStyle, b.createdAtStyle, t),
|
||||
messageAuthorStyle:
|
||||
TextStyle.lerp(a.messageAuthorStyle, b.messageAuthorStyle, t),
|
||||
@@ -120,7 +130,7 @@ class MessageThemeData with Diagnosticable {
|
||||
);
|
||||
|
||||
/// Merge with a theme
|
||||
MessageThemeData merge(MessageThemeData? other) {
|
||||
StreamMessageThemeData merge(StreamMessageThemeData? other) {
|
||||
if (other == null) return this;
|
||||
return copyWith(
|
||||
messageTextStyle: messageTextStyle?.merge(other.messageTextStyle) ??
|
||||
@@ -146,7 +156,7 @@ class MessageThemeData with Diagnosticable {
|
||||
@override
|
||||
bool operator ==(Object other) =>
|
||||
identical(this, other) ||
|
||||
other is MessageThemeData &&
|
||||
other is StreamMessageThemeData &&
|
||||
runtimeType == other.runtimeType &&
|
||||
messageTextStyle == other.messageTextStyle &&
|
||||
messageAuthorStyle == other.messageAuthorStyle &&
|
||||
|
||||
@@ -1,9 +1,15 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
/// {@macro text_theme}
|
||||
@Deprecated("Use 'StreamTextTheme' instead")
|
||||
typedef TextTheme = StreamTextTheme;
|
||||
|
||||
/// {@template text_theme}
|
||||
/// Class for holding text theme
|
||||
class TextTheme {
|
||||
/// {@endtemplate}
|
||||
class StreamTextTheme {
|
||||
/// Initialise light text theme
|
||||
TextTheme.light({
|
||||
StreamTextTheme.light({
|
||||
this.title = const TextStyle(
|
||||
fontSize: 22,
|
||||
fontWeight: FontWeight.bold,
|
||||
@@ -46,7 +52,7 @@ class TextTheme {
|
||||
});
|
||||
|
||||
/// Initialise with dark theme
|
||||
TextTheme.dark({
|
||||
StreamTextTheme.dark({
|
||||
this.title = const TextStyle(
|
||||
fontSize: 22,
|
||||
fontWeight: FontWeight.bold,
|
||||
@@ -113,7 +119,7 @@ class TextTheme {
|
||||
final TextStyle captionBold;
|
||||
|
||||
/// Copy with theme
|
||||
TextTheme copyWith({
|
||||
StreamTextTheme copyWith({
|
||||
Brightness brightness = Brightness.light,
|
||||
TextStyle? body,
|
||||
TextStyle? title,
|
||||
@@ -125,7 +131,7 @@ class TextTheme {
|
||||
TextStyle? captionBold,
|
||||
}) =>
|
||||
brightness == Brightness.light
|
||||
? TextTheme.light(
|
||||
? StreamTextTheme.light(
|
||||
body: body ?? this.body,
|
||||
title: title ?? this.title,
|
||||
headlineBold: headlineBold ?? this.headlineBold,
|
||||
@@ -135,7 +141,7 @@ class TextTheme {
|
||||
footnote: footnote ?? this.footnote,
|
||||
captionBold: captionBold ?? this.captionBold,
|
||||
)
|
||||
: TextTheme.dark(
|
||||
: StreamTextTheme.dark(
|
||||
body: body ?? this.body,
|
||||
title: title ?? this.title,
|
||||
headlineBold: headlineBold ?? this.headlineBold,
|
||||
@@ -147,7 +153,7 @@ class TextTheme {
|
||||
);
|
||||
|
||||
/// Merge text theme
|
||||
TextTheme merge(TextTheme? other) {
|
||||
StreamTextTheme merge(StreamTextTheme? other) {
|
||||
if (other == null) return this;
|
||||
return copyWith(
|
||||
body: body.merge(other.body),
|
||||
|
||||
@@ -2,27 +2,33 @@ import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:stream_chat_flutter/src/stream_chat_theme.dart';
|
||||
|
||||
/// {@macro user_list_view_theme}
|
||||
@Deprecated("Use 'StreamUserListViewTheme' instead")
|
||||
typedef UserListViewTheme = StreamUserListViewTheme;
|
||||
|
||||
/// {@template user_list_view_theme}
|
||||
/// Overrides the default style of [UserListView] descendants.
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [UserListViewThemeData], which is used to configure this theme.
|
||||
class UserListViewTheme extends InheritedTheme {
|
||||
/// Creates a [UserListViewTheme].
|
||||
/// * [StreamUserListViewThemeData], which is used to configure this theme.
|
||||
/// {@endtemplate}
|
||||
class StreamUserListViewTheme extends InheritedTheme {
|
||||
/// Creates a [StreamUserListViewTheme].
|
||||
///
|
||||
/// The [data] parameter must not be null.
|
||||
const UserListViewTheme({
|
||||
const StreamUserListViewTheme({
|
||||
Key? key,
|
||||
required this.data,
|
||||
required Widget child,
|
||||
}) : super(key: key, child: child);
|
||||
|
||||
/// The configuration of this theme.
|
||||
final UserListViewThemeData data;
|
||||
final StreamUserListViewThemeData data;
|
||||
|
||||
/// The closest instance of this class that encloses the given context.
|
||||
///
|
||||
/// If there is no enclosing [UserListViewTheme] widget, then
|
||||
/// If there is no enclosing [StreamUserListViewTheme] widget, then
|
||||
/// [StreamChatThemeData.userListViewTheme] is used.
|
||||
///
|
||||
/// Typical usage is as follows:
|
||||
@@ -30,35 +36,41 @@ class UserListViewTheme extends InheritedTheme {
|
||||
/// ```dart
|
||||
/// UserListViewTheme theme = UserListViewTheme.of(context);
|
||||
/// ```
|
||||
static UserListViewThemeData of(BuildContext context) {
|
||||
static StreamUserListViewThemeData of(BuildContext context) {
|
||||
final userListViewTheme =
|
||||
context.dependOnInheritedWidgetOfExactType<UserListViewTheme>();
|
||||
context.dependOnInheritedWidgetOfExactType<StreamUserListViewTheme>();
|
||||
return userListViewTheme?.data ??
|
||||
StreamChatTheme.of(context).userListViewTheme;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget wrap(BuildContext context, Widget child) =>
|
||||
UserListViewTheme(data: data, child: child);
|
||||
StreamUserListViewTheme(data: data, child: child);
|
||||
|
||||
@override
|
||||
bool updateShouldNotify(UserListViewTheme oldWidget) =>
|
||||
bool updateShouldNotify(StreamUserListViewTheme oldWidget) =>
|
||||
data != oldWidget.data;
|
||||
}
|
||||
|
||||
/// {@macro user_list_view_theme_data}
|
||||
@Deprecated("Use 'StreamUserListViewThemeData' instead")
|
||||
typedef UserListViewThemeData = StreamUserListViewThemeData;
|
||||
|
||||
/// {@template user_list_view_theme_data}
|
||||
/// A style that overrides the default appearance of [UserListView]s when
|
||||
/// used with [UserListViewTheme] or with the overall [StreamChatTheme]'s
|
||||
/// used with [StreamUserListViewTheme] or with the overall [StreamChatTheme]'s
|
||||
/// [StreamChatThemeData.userListViewTheme].
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [UserListViewTheme], the theme which is configured with this class.
|
||||
/// * [StreamUserListViewTheme], the theme which is configured with this class.
|
||||
/// * [StreamChatThemeData.userListViewTheme], which can be used to override
|
||||
/// the default style for [UserListView]s below the overall
|
||||
/// [StreamChatTheme].
|
||||
class UserListViewThemeData with Diagnosticable {
|
||||
/// Creates a [UserListViewThemeData].
|
||||
const UserListViewThemeData({
|
||||
/// {@endtemplate}
|
||||
class StreamUserListViewThemeData with Diagnosticable {
|
||||
/// Creates a [StreamUserListViewThemeData].
|
||||
const StreamUserListViewThemeData({
|
||||
this.backgroundColor,
|
||||
});
|
||||
|
||||
@@ -66,27 +78,27 @@ class UserListViewThemeData with Diagnosticable {
|
||||
final Color? backgroundColor;
|
||||
|
||||
/// Copies this [ChannelListViewThemeData] to another.
|
||||
UserListViewThemeData copyWith({
|
||||
StreamUserListViewThemeData copyWith({
|
||||
Color? backgroundColor,
|
||||
}) =>
|
||||
UserListViewThemeData(
|
||||
StreamUserListViewThemeData(
|
||||
backgroundColor: backgroundColor ?? this.backgroundColor,
|
||||
);
|
||||
|
||||
/// Linearly interpolate between two [UserListViewThemeData] themes.
|
||||
/// Linearly interpolate between two [StreamUserListViewThemeData] themes.
|
||||
///
|
||||
/// All the properties must be non-null.
|
||||
UserListViewThemeData lerp(
|
||||
UserListViewThemeData a,
|
||||
UserListViewThemeData b,
|
||||
StreamUserListViewThemeData lerp(
|
||||
StreamUserListViewThemeData a,
|
||||
StreamUserListViewThemeData b,
|
||||
double t,
|
||||
) =>
|
||||
UserListViewThemeData(
|
||||
StreamUserListViewThemeData(
|
||||
backgroundColor: Color.lerp(a.backgroundColor, b.backgroundColor, t),
|
||||
);
|
||||
|
||||
/// Merges one [UserListViewThemeData] with another.
|
||||
UserListViewThemeData merge(UserListViewThemeData? other) {
|
||||
/// Merges one [StreamUserListViewThemeData] with another.
|
||||
StreamUserListViewThemeData merge(StreamUserListViewThemeData? other) {
|
||||
if (other == null) return this;
|
||||
return copyWith(
|
||||
backgroundColor: other.backgroundColor,
|
||||
@@ -96,7 +108,7 @@ class UserListViewThemeData with Diagnosticable {
|
||||
@override
|
||||
bool operator ==(Object other) =>
|
||||
identical(this, other) ||
|
||||
other is UserListViewThemeData &&
|
||||
other is StreamUserListViewThemeData &&
|
||||
runtimeType == other.runtimeType &&
|
||||
backgroundColor == other.backgroundColor;
|
||||
|
||||
|
||||
@@ -3,6 +3,11 @@ import 'package:flutter/services.dart';
|
||||
import 'package:stream_chat_flutter/src/extension.dart';
|
||||
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
|
||||
|
||||
/// {@macro thread_header}
|
||||
@Deprecated("Use 'StreamThreadHeader' instead")
|
||||
typedef ThreadHeader = StreamThreadHeader;
|
||||
|
||||
/// {@template thread_header}
|
||||
/// 
|
||||
/// 
|
||||
///
|
||||
@@ -56,9 +61,11 @@ import 'package:stream_chat_flutter/stream_chat_flutter.dart';
|
||||
/// The widget components render the ui based on the first ancestor of type
|
||||
/// [StreamChatTheme] and on its [ChannelTheme.channelHeaderTheme] property.
|
||||
/// Modify it to change the widget appearance.
|
||||
class ThreadHeader extends StatelessWidget implements PreferredSizeWidget {
|
||||
/// {@endtemplate}
|
||||
class StreamThreadHeader extends StatelessWidget
|
||||
implements PreferredSizeWidget {
|
||||
/// Instantiate a new ThreadHeader
|
||||
const ThreadHeader({
|
||||
const StreamThreadHeader({
|
||||
Key? key,
|
||||
required this.parent,
|
||||
this.showBackButton = true,
|
||||
@@ -102,12 +109,12 @@ class ThreadHeader extends StatelessWidget implements PreferredSizeWidget {
|
||||
/// if a user is typing in this thread
|
||||
final bool showTypingIndicator;
|
||||
|
||||
/// The background color of this [ThreadHeader].
|
||||
/// The background color of this [StreamThreadHeader].
|
||||
final Color? backgroundColor;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final channelHeaderTheme = ChannelHeaderTheme.of(context);
|
||||
final channelHeaderTheme = StreamChannelHeaderTheme.of(context);
|
||||
|
||||
final defaultSubtitle = subtitle ??
|
||||
Row(
|
||||
@@ -161,12 +168,13 @@ class ThreadHeader extends StatelessWidget implements PreferredSizeWidget {
|
||||
),
|
||||
const SizedBox(height: 2),
|
||||
if (showTypingIndicator)
|
||||
TypingIndicator(
|
||||
alignment: Alignment.center,
|
||||
channel: StreamChannel.of(context).channel,
|
||||
style: channelHeaderTheme.subtitleStyle,
|
||||
parentId: parent.id,
|
||||
alternativeWidget: defaultSubtitle,
|
||||
Align(
|
||||
child: StreamTypingIndicator(
|
||||
channel: StreamChannel.of(context).channel,
|
||||
style: channelHeaderTheme.subtitleStyle,
|
||||
parentId: parent.id,
|
||||
alternativeWidget: defaultSubtitle,
|
||||
),
|
||||
)
|
||||
else
|
||||
defaultSubtitle,
|
||||
|
||||
@@ -3,15 +3,20 @@ import 'package:lottie/lottie.dart';
|
||||
import 'package:stream_chat_flutter/src/extension.dart';
|
||||
import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart';
|
||||
|
||||
/// {@macro typing_indicator}
|
||||
@Deprecated("Use 'StreamTypingIndicator' instead")
|
||||
typedef TypingIndicator = StreamTypingIndicator;
|
||||
|
||||
/// {@template typing_indicator}
|
||||
/// Widget to show the current list of typing users
|
||||
class TypingIndicator extends StatelessWidget {
|
||||
/// {@endtemplate}
|
||||
class StreamTypingIndicator extends StatelessWidget {
|
||||
/// Instantiate a new TypingIndicator
|
||||
const TypingIndicator({
|
||||
const StreamTypingIndicator({
|
||||
Key? key,
|
||||
this.channel,
|
||||
this.alternativeWidget,
|
||||
this.style,
|
||||
this.alignment = Alignment.centerLeft,
|
||||
this.padding = const EdgeInsets.all(0),
|
||||
this.parentId,
|
||||
}) : super(key: key);
|
||||
@@ -28,9 +33,6 @@ class TypingIndicator extends StatelessWidget {
|
||||
/// The padding of this widget
|
||||
final EdgeInsets padding;
|
||||
|
||||
/// Alignment of the typing indicator
|
||||
final Alignment alignment;
|
||||
|
||||
/// Id of the parent message in case of a thread
|
||||
final String? parentId;
|
||||
|
||||
@@ -46,30 +48,25 @@ class TypingIndicator extends StatelessWidget {
|
||||
stream: channelState.typingEventsStream.map((typings) => typings.entries
|
||||
.where((element) => element.value.parentId == parentId)
|
||||
.map((e) => e.key)),
|
||||
builder: (context, data) => AnimatedSwitcher(
|
||||
builder: (context, users) => AnimatedSwitcher(
|
||||
duration: const Duration(milliseconds: 300),
|
||||
child: data.isNotEmpty
|
||||
child: users.isNotEmpty
|
||||
? Padding(
|
||||
key: const Key('main'),
|
||||
padding: padding,
|
||||
child: Align(
|
||||
key: const Key('typings'),
|
||||
alignment: alignment,
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Lottie.asset(
|
||||
'animations/typing_dots.json',
|
||||
package: 'stream_chat_flutter',
|
||||
height: 4,
|
||||
),
|
||||
Text(
|
||||
context.translations.userTypingText(data),
|
||||
maxLines: 1,
|
||||
style: style,
|
||||
),
|
||||
],
|
||||
),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Lottie.asset(
|
||||
'animations/typing_dots.json',
|
||||
package: 'stream_chat_flutter',
|
||||
height: 4,
|
||||
),
|
||||
Text(
|
||||
context.translations.userTypingText(users),
|
||||
maxLines: 1,
|
||||
style: style,
|
||||
),
|
||||
],
|
||||
),
|
||||
)
|
||||
: altWidget,
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user