Merge branch 'develop' of https://github.com/GetStream/stream-chat-flutter into cds-189
Conflicts: packages/stream_chat/lib/src/client/channel.dart packages/stream_chat_flutter/example/lib/main.dart packages/stream_chat_flutter/lib/src/message_list_view.dart
This commit is contained in:
@@ -1,3 +1,15 @@
|
||||
## Upcoming
|
||||
|
||||
🐞 Fixed
|
||||
|
||||
- Fixed `channel.markAllRead` throwing failed host lookup.
|
||||
|
||||
✅ Added
|
||||
|
||||
- `User` and `OwnUser` classes now have an `image` property. Setting an image will also set the 'image' key on `extraData`, so `user.image` and `user.extraData['image']` is the same.
|
||||
- `User` and `OwnUser` classes now have a `name` property. Setting a name will also set the 'name' key on `extraData`, so `user.name` and `user.extraData['name']` is the same.
|
||||
- `Channel` class now has extra `image` getter and setter. As well as an `updateImage` to do a partial update after a channel has been initialized.
|
||||
- `Channel` class now has extra `name` getter and setter. As well as an `updateName` to do a partial update after a channel has been initialized.
|
||||
## 2.1.1
|
||||
|
||||
🐞 Fixed
|
||||
|
||||
@@ -13,10 +13,9 @@ Future<void> main() async {
|
||||
await client.connectUser(
|
||||
User(
|
||||
id: 'cool-shadow-7',
|
||||
extraData: const {
|
||||
'image':
|
||||
'https://getstream.io/random_png/?id=cool-shadow-7&name=Cool+shadow',
|
||||
},
|
||||
name: 'Cool Shadow',
|
||||
image:
|
||||
'https://getstream.io/random_png/?id=cool-shadow-7&name=Cool+shadow',
|
||||
),
|
||||
'''eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoiY29vbC1zaGFkb3ctNyJ9.gkOlCRb1qgy4joHPaxFwPOdXcGvSPvp6QY0S4mpRkVo''',
|
||||
);
|
||||
|
||||
@@ -15,20 +15,77 @@ import 'package:stream_chat/src/core/util/utils.dart';
|
||||
import 'package:stream_chat/src/event_type.dart';
|
||||
import 'package:stream_chat/stream_chat.dart';
|
||||
|
||||
/// This a the class that manages a specific channel.
|
||||
/// Class that manages a specific channel.
|
||||
///
|
||||
/// #### Channel name
|
||||
///
|
||||
/// {@template name}
|
||||
/// If an optional [name] argument is provided in the constructor then it
|
||||
/// will be set on [extraData] with a key of 'name'.
|
||||
///
|
||||
/// ```dart
|
||||
/// final channel = Channel(client, type, id, name: 'Channel name');
|
||||
/// print(channel.name == channel.extraData['name']); // true
|
||||
/// ```
|
||||
///
|
||||
/// Before the channel is initialized the name can be set directly:
|
||||
/// ```dart
|
||||
/// channel.name = 'New channel name';
|
||||
/// ```
|
||||
///
|
||||
/// To update the name after the channel has been initialized, call:
|
||||
/// ```dart
|
||||
/// channel.updateName('Updated channel name');
|
||||
/// ```
|
||||
///
|
||||
/// This will do a partial update to update the name.
|
||||
/// {@endtemplate}
|
||||
///
|
||||
/// #### Channel image
|
||||
///
|
||||
/// {@template image}
|
||||
/// If an optional [image] argument is provided in the constructor then it
|
||||
/// will be set on [extraData] with a key of 'image'.
|
||||
///
|
||||
/// ```dart
|
||||
/// final channel = Channel(client, type, id, image: 'https://getstream.io/image.png');
|
||||
/// print(channel.image == channel.extraData['image']); // true
|
||||
/// ```
|
||||
///
|
||||
/// Before the channel is initialized the image can be set directly:
|
||||
/// ```dart
|
||||
/// channel.image = 'https://getstream.io/new-image';
|
||||
/// ```
|
||||
///
|
||||
/// To update the image after the channel has been initialized, call:
|
||||
/// ```dart
|
||||
/// channel.updateImage('https://getstream.io/new-image');
|
||||
/// ```
|
||||
///
|
||||
/// This will do a partial update to update the image.
|
||||
/// {@endtemplate}
|
||||
class Channel {
|
||||
/// Create a channel client instance.
|
||||
/// Class that manages a specific channel.
|
||||
///
|
||||
/// Optional [extraData] and [image] properties can be provided. The [image]
|
||||
/// is exposed to easily set a key of 'image' on [extraData].
|
||||
Channel(
|
||||
this._client,
|
||||
this._type,
|
||||
this._id, {
|
||||
String? name,
|
||||
String? image,
|
||||
Map<String, Object?>? extraData,
|
||||
}) : _cid = _id != null ? '$_type:$_id' : null,
|
||||
_extraData = extraData ?? {} {
|
||||
_client.logger.info('New Channel instance not initialized created');
|
||||
_extraData = {
|
||||
...?extraData,
|
||||
if (name != null) 'name': name,
|
||||
if (image != null) 'image': image,
|
||||
} {
|
||||
_client.logger.info('New Channel instance created, not yet initialized');
|
||||
}
|
||||
|
||||
/// Create a channel client instance from a [ChannelState] object
|
||||
/// Create a channel client instance from a [ChannelState] object.
|
||||
Channel.fromState(this._client, ChannelState channelState)
|
||||
: assert(
|
||||
channelState.channel != null,
|
||||
@@ -40,7 +97,7 @@ class Channel {
|
||||
_extraData = channelState.channel!.extraData {
|
||||
state = ChannelClientState(this, channelState);
|
||||
_initializedCompleter.complete(true);
|
||||
_client.logger.info('New Channel instance initialized created');
|
||||
_client.logger.info('New Channel instance initialized');
|
||||
}
|
||||
|
||||
/// This client state
|
||||
@@ -53,66 +110,92 @@ class Channel {
|
||||
String? _cid;
|
||||
final Map<String, Object?> _extraData;
|
||||
|
||||
/// Shortcut to set channel name.
|
||||
///
|
||||
/// {@macro name}
|
||||
set name(String? name) {
|
||||
if (_initializedCompleter.isCompleted) {
|
||||
throw StateError(
|
||||
'Once the channel is initialized you should use `channel.updateName` '
|
||||
'to update the channel name',
|
||||
);
|
||||
}
|
||||
_extraData.addAll({'name': name});
|
||||
}
|
||||
|
||||
/// Shortcut to set channel image.
|
||||
///
|
||||
/// {@macro image}
|
||||
set image(String? image) {
|
||||
if (_initializedCompleter.isCompleted) {
|
||||
throw StateError(
|
||||
'Once the channel is initialized you should use `channel.updateImage` '
|
||||
'to update the channel image',
|
||||
);
|
||||
}
|
||||
_extraData.addAll({'image': image});
|
||||
}
|
||||
|
||||
set extraData(Map<String, Object?> extraData) {
|
||||
if (_initializedCompleter.isCompleted) {
|
||||
throw StateError(
|
||||
'Once the channel is initialized you should use channel.update '
|
||||
'Once the channel is initialized you should use `channel.update` '
|
||||
'to update channel data',
|
||||
);
|
||||
}
|
||||
_extraData.addAll(extraData);
|
||||
}
|
||||
|
||||
/// Returns true if the channel is muted
|
||||
/// Returns true if the channel is muted.
|
||||
bool get isMuted =>
|
||||
_client.state.currentUser?.channelMutes
|
||||
.any((element) => element.channel.cid == cid) ==
|
||||
true;
|
||||
|
||||
/// Returns true if the channel is muted as a stream
|
||||
/// Returns true if the channel is muted, as a stream.
|
||||
Stream<bool>? get isMutedStream => _client.state.currentUserStream
|
||||
.map((event) =>
|
||||
event!.channelMutes.any((element) => element.channel.cid == cid) ==
|
||||
true)
|
||||
.distinct();
|
||||
|
||||
/// True if the channel is a group
|
||||
/// True if the channel is a group.
|
||||
bool get isGroup => memberCount != 2;
|
||||
|
||||
/// True if the channel is distinct
|
||||
/// True if the channel is distinct.
|
||||
bool get isDistinct => id?.startsWith('!members') == true;
|
||||
|
||||
/// Channel configuration
|
||||
/// Channel configuration.
|
||||
ChannelConfig? get config {
|
||||
_checkInitialized();
|
||||
return state?._channelState.channel?.config;
|
||||
}
|
||||
|
||||
/// Channel configuration as a stream
|
||||
/// Channel configuration as a stream.
|
||||
Stream<ChannelConfig?>? get configStream {
|
||||
_checkInitialized();
|
||||
return state?.channelStateStream.map((cs) => cs.channel?.config);
|
||||
}
|
||||
|
||||
/// Channel user creator
|
||||
/// Channel user creator.
|
||||
User? get createdBy {
|
||||
_checkInitialized();
|
||||
return state?._channelState.channel?.createdBy;
|
||||
}
|
||||
|
||||
/// Channel user creator as a stream
|
||||
/// Channel user creator as a stream.
|
||||
Stream<User?>? get createdByStream {
|
||||
_checkInitialized();
|
||||
return state?.channelStateStream.map((cs) => cs.channel?.createdBy);
|
||||
}
|
||||
|
||||
/// Channel frozen status
|
||||
/// Channel frozen status.
|
||||
bool? get frozen {
|
||||
_checkInitialized();
|
||||
return state?._channelState.channel?.frozen;
|
||||
}
|
||||
|
||||
/// Channel frozen status as a stream
|
||||
/// Channel frozen status as a stream.
|
||||
Stream<bool?>? get frozenStream {
|
||||
_checkInitialized();
|
||||
return state?.channelStateStream.map((cs) => cs.channel?.frozen);
|
||||
@@ -133,90 +216,90 @@ class Channel {
|
||||
///
|
||||
DateTime? cooldownStartedAt;
|
||||
|
||||
/// Channel creation date
|
||||
/// Channel creation date.
|
||||
DateTime? get createdAt {
|
||||
_checkInitialized();
|
||||
return state?._channelState.channel?.createdAt;
|
||||
}
|
||||
|
||||
/// Channel creation date as a stream
|
||||
/// Channel creation date as a stream.
|
||||
Stream<DateTime?>? get createdAtStream {
|
||||
_checkInitialized();
|
||||
return state?.channelStateStream.map((cs) => cs.channel?.createdAt);
|
||||
}
|
||||
|
||||
/// Channel last message date
|
||||
/// Channel last message date.
|
||||
DateTime? get lastMessageAt {
|
||||
_checkInitialized();
|
||||
|
||||
return state?._channelState.channel?.lastMessageAt;
|
||||
}
|
||||
|
||||
/// Channel last message date as a stream
|
||||
/// Channel last message date as a stream.
|
||||
Stream<DateTime?>? get lastMessageAtStream {
|
||||
_checkInitialized();
|
||||
|
||||
return state?.channelStateStream.map((cs) => cs.channel?.lastMessageAt);
|
||||
}
|
||||
|
||||
/// Channel updated date
|
||||
/// Channel updated date.
|
||||
DateTime? get updatedAt {
|
||||
_checkInitialized();
|
||||
|
||||
return state?._channelState.channel?.updatedAt;
|
||||
}
|
||||
|
||||
/// Channel updated date as a stream
|
||||
/// Channel updated date as a stream.
|
||||
Stream<DateTime?>? get updatedAtStream {
|
||||
_checkInitialized();
|
||||
|
||||
return state?.channelStateStream.map((cs) => cs.channel?.updatedAt);
|
||||
}
|
||||
|
||||
/// Channel deletion date
|
||||
/// Channel deletion date.
|
||||
DateTime? get deletedAt {
|
||||
_checkInitialized();
|
||||
|
||||
return state?._channelState.channel?.deletedAt;
|
||||
}
|
||||
|
||||
/// Channel deletion date as a stream
|
||||
/// Channel deletion date as a stream.
|
||||
Stream<DateTime?>? get deletedAtStream {
|
||||
_checkInitialized();
|
||||
|
||||
return state?.channelStateStream.map((cs) => cs.channel?.deletedAt);
|
||||
}
|
||||
|
||||
/// Channel member count
|
||||
/// Channel member count.
|
||||
int? get memberCount {
|
||||
_checkInitialized();
|
||||
|
||||
return state?._channelState.channel?.memberCount;
|
||||
}
|
||||
|
||||
/// Channel member count as a stream
|
||||
/// Channel member count as a stream.
|
||||
Stream<int?>? get memberCountStream {
|
||||
_checkInitialized();
|
||||
|
||||
return state?.channelStateStream.map((cs) => cs.channel?.memberCount);
|
||||
}
|
||||
|
||||
/// Channel id
|
||||
/// Channel id.
|
||||
String? get id => state?._channelState.channel?.id ?? _id;
|
||||
|
||||
/// Channel type
|
||||
/// Channel type.
|
||||
String get type => state?._channelState.channel?.type ?? _type;
|
||||
|
||||
/// Channel cid
|
||||
/// Channel cid.
|
||||
String? get cid => state?._channelState.channel?.cid ?? _cid;
|
||||
|
||||
/// Channel team
|
||||
/// Channel team.
|
||||
String? get team {
|
||||
_checkInitialized();
|
||||
return state?._channelState.channel?.team;
|
||||
}
|
||||
|
||||
/// Channel extra data
|
||||
/// Channel extra data.
|
||||
Map<String, Object?> get extraData {
|
||||
var data = state?._channelState.channel?.extraData;
|
||||
if (data == null || data.isEmpty) {
|
||||
@@ -225,7 +308,7 @@ class Channel {
|
||||
return data;
|
||||
}
|
||||
|
||||
/// Channel extra data as a stream
|
||||
/// Channel extra data as a stream.
|
||||
Stream<Map<String, dynamic>> get extraDataStream {
|
||||
_checkInitialized();
|
||||
return state!.channelStateStream.map(
|
||||
@@ -233,15 +316,46 @@ class Channel {
|
||||
);
|
||||
}
|
||||
|
||||
/// The main Stream chat client
|
||||
/// Shortcut to get channel name.
|
||||
///
|
||||
/// {@macro name}
|
||||
String? get name => extraData['name'] as String?;
|
||||
|
||||
/// Channel [name] as a stream.
|
||||
///
|
||||
/// The channel needs to be initialized.
|
||||
///
|
||||
/// {@macro name}
|
||||
Stream<String?> get nameStream {
|
||||
_checkInitialized();
|
||||
return extraDataStream.map((it) => it['name'] as String?);
|
||||
}
|
||||
|
||||
/// Shortcut to get channel image.
|
||||
///
|
||||
/// {@macro image}
|
||||
String? get image => extraData['image'] as String?;
|
||||
|
||||
/// Channel [image] as a stream.
|
||||
///
|
||||
/// The channel needs to be initialized.
|
||||
///
|
||||
/// {@macro image}
|
||||
Stream<String?> get imageStream {
|
||||
_checkInitialized();
|
||||
return extraDataStream.map((it) => it['image'] as String?);
|
||||
}
|
||||
|
||||
/// The main Stream chat client.
|
||||
StreamChatClient get client => _client;
|
||||
final StreamChatClient _client;
|
||||
|
||||
final Completer<bool> _initializedCompleter = Completer();
|
||||
|
||||
/// True if this is initialized
|
||||
/// True if this is initialized.
|
||||
///
|
||||
/// Call [watch] to initialize the client or instantiate it using
|
||||
/// [Channel.fromState]
|
||||
/// [Channel.fromState].
|
||||
Future<bool> get initialized => _initializedCompleter.future;
|
||||
|
||||
final _cancelableAttachmentUploadRequest = <String, CancelToken>{};
|
||||
@@ -377,7 +491,9 @@ class Channel {
|
||||
}
|
||||
|
||||
/// Send a [message] to this channel.
|
||||
/// If [skipPush] is true the message will not send a push notification
|
||||
///
|
||||
/// If [skipPush] is true the message will not send a push notification.
|
||||
///
|
||||
/// Waits for a [_messageAttachmentsUploadCompleter] to complete
|
||||
/// before actually sending the message.
|
||||
Future<SendMessageResponse> sendMessage(
|
||||
@@ -445,6 +561,7 @@ class Channel {
|
||||
}
|
||||
|
||||
/// Updates the [message] in this channel.
|
||||
///
|
||||
/// Waits for a [_messageAttachmentsUploadCompleter] to complete
|
||||
/// before actually updating the message.
|
||||
Future<UpdateMessageResponse> updateMessage(Message message) async {
|
||||
@@ -507,8 +624,10 @@ class Channel {
|
||||
}
|
||||
|
||||
/// Partially updates the [message] in this channel.
|
||||
/// Use [set] to define values to be set
|
||||
/// Use [unset] to define values to be unset
|
||||
///
|
||||
/// Use [set] to define values to be set.
|
||||
///
|
||||
/// Use [unset] to define values to be unset.
|
||||
Future<UpdateMessageResponse> partialUpdateMessage(
|
||||
Message message, {
|
||||
Map<String, Object?>? set,
|
||||
@@ -608,7 +727,7 @@ class Channel {
|
||||
);
|
||||
}
|
||||
|
||||
/// Unpins provided message
|
||||
/// Unpins provided message.
|
||||
Future<UpdateMessageResponse> unpinMessage(Message message) =>
|
||||
partialUpdateMessage(
|
||||
message,
|
||||
@@ -617,7 +736,7 @@ class Channel {
|
||||
},
|
||||
);
|
||||
|
||||
/// Send a file to this channel
|
||||
/// Send a file to this channel.
|
||||
Future<SendFileResponse> sendFile(
|
||||
AttachmentFile file, {
|
||||
ProgressCallback? onSendProgress,
|
||||
@@ -633,7 +752,7 @@ class Channel {
|
||||
);
|
||||
}
|
||||
|
||||
/// Send an image to this channel
|
||||
/// Send an image to this channel.
|
||||
Future<SendImageResponse> sendImage(
|
||||
AttachmentFile file, {
|
||||
ProgressCallback? onSendProgress,
|
||||
@@ -649,7 +768,7 @@ class Channel {
|
||||
);
|
||||
}
|
||||
|
||||
/// A message search.
|
||||
/// Search for a message with the given options.
|
||||
Future<SearchMessagesResponse> search({
|
||||
String? query,
|
||||
Filter? messageFilters,
|
||||
@@ -666,7 +785,7 @@ class Channel {
|
||||
);
|
||||
}
|
||||
|
||||
/// Delete a file from this channel
|
||||
/// Delete a file from this channel.
|
||||
Future<EmptyResponse> deleteFile(
|
||||
String url, {
|
||||
CancelToken? cancelToken,
|
||||
@@ -680,7 +799,7 @@ class Channel {
|
||||
);
|
||||
}
|
||||
|
||||
/// Delete an image from this channel
|
||||
/// Delete an image from this channel.
|
||||
Future<EmptyResponse> deleteImage(
|
||||
String url, {
|
||||
CancelToken? cancelToken,
|
||||
@@ -694,14 +813,15 @@ class Channel {
|
||||
);
|
||||
}
|
||||
|
||||
/// Send an event on this channel
|
||||
/// Send an event on this channel.
|
||||
Future<EmptyResponse> sendEvent(Event event) {
|
||||
_checkInitialized();
|
||||
return _client.sendEvent(id!, type, event);
|
||||
}
|
||||
|
||||
/// Send a reaction to this channel
|
||||
/// Set [enforceUnique] to true to remove the existing user reaction
|
||||
/// Send a reaction to this channel.
|
||||
///
|
||||
/// Set [enforceUnique] to true to remove the existing user reaction.
|
||||
Future<SendReactionResponse> sendReaction(
|
||||
Message message,
|
||||
String type, {
|
||||
@@ -764,7 +884,7 @@ class Channel {
|
||||
}
|
||||
}
|
||||
|
||||
/// Delete a reaction from this channel
|
||||
/// Delete a reaction from this channel.
|
||||
Future<EmptyResponse> deleteReaction(
|
||||
Message message, Reaction reaction) async {
|
||||
final type = reaction.type;
|
||||
@@ -810,7 +930,49 @@ class Channel {
|
||||
}
|
||||
}
|
||||
|
||||
/// Edit the channel custom data
|
||||
/// Update the channel's [name].
|
||||
///
|
||||
/// This is the same as calling [updatePartial] and providing a map with a
|
||||
/// 'name' key:
|
||||
///
|
||||
/// ```dart
|
||||
/// channel.updatePartial(
|
||||
/// set: {'name': 'Updated channel name'}
|
||||
/// );
|
||||
/// ```
|
||||
///
|
||||
/// Instead do:
|
||||
/// ```dart
|
||||
/// channel.updateName('Updated channel name');
|
||||
/// ```
|
||||
Future<PartialUpdateChannelResponse> updateName(String name) =>
|
||||
updatePartial(set: {'name': name});
|
||||
|
||||
/// Update the channel's [image].
|
||||
///
|
||||
/// This is the same as calling [updatePartial] and providing a map with an
|
||||
/// 'image' key:
|
||||
///
|
||||
/// ```dart
|
||||
/// channel.updatePartial(
|
||||
/// set: {'image': 'https://getstream.io/new-image'}
|
||||
/// );
|
||||
/// ```
|
||||
///
|
||||
/// Instead do:
|
||||
/// ```dart
|
||||
/// channel.updateImage('https://getstream.io/new-image');
|
||||
/// ```
|
||||
Future<PartialUpdateChannelResponse> updateImage(String image) =>
|
||||
updatePartial(set: {'image': image});
|
||||
|
||||
/// Update the channel custom data. This replaces all of the channel data
|
||||
/// with the given [channelData].
|
||||
///
|
||||
/// If you instead want to do a partial update, use [updatePartial].
|
||||
///
|
||||
/// See, https://getstream.io/chat/docs/other-rest/channel_update/?language=dart
|
||||
/// for more information.
|
||||
Future<UpdateChannelResponse> update(
|
||||
Map<String, Object?> channelData, [
|
||||
Message? updateMessage,
|
||||
@@ -824,7 +986,18 @@ class Channel {
|
||||
);
|
||||
}
|
||||
|
||||
/// Edit the channel custom data
|
||||
/// A partial update can be used to set and unset specific custom data fields
|
||||
/// when it is necessary to retain additional custom data fields on the
|
||||
/// object.
|
||||
///
|
||||
/// - [set] will add, or update existing attributes.
|
||||
/// - [unset] will remove the attributes with the provided list of
|
||||
/// values (keys).
|
||||
///
|
||||
/// If you want to do a full update/replacement, use [update] instead.
|
||||
///
|
||||
/// See, https://getstream.io/chat/docs/other-rest/channel_update/?language=dart
|
||||
/// for more information.
|
||||
Future<PartialUpdateChannelResponse> updatePartial({
|
||||
Map<String, Object?>? set,
|
||||
List<String>? unset,
|
||||
@@ -853,25 +1026,25 @@ class Channel {
|
||||
return _client.deleteChannel(id!, type);
|
||||
}
|
||||
|
||||
/// Removes all messages from the channel
|
||||
/// Removes all messages from the channel.
|
||||
Future<EmptyResponse> truncate() async {
|
||||
_checkInitialized();
|
||||
return _client.truncateChannel(id!, type);
|
||||
}
|
||||
|
||||
/// Accept invitation to the channel
|
||||
/// Accept invitation to the channel.
|
||||
Future<AcceptInviteResponse> acceptInvite([Message? message]) async {
|
||||
_checkInitialized();
|
||||
return _client.acceptChannelInvite(id!, type, message: message);
|
||||
}
|
||||
|
||||
/// Reject invitation to the channel
|
||||
/// Reject invitation to the channel.
|
||||
Future<RejectInviteResponse> rejectInvite([Message? message]) async {
|
||||
_checkInitialized();
|
||||
return _client.rejectChannelInvite(id!, type, message: message);
|
||||
}
|
||||
|
||||
/// Add members to the channel
|
||||
/// Add members to the channel.
|
||||
Future<AddMembersResponse> addMembers(
|
||||
List<String> memberIds, [
|
||||
Message? message,
|
||||
@@ -880,7 +1053,7 @@ class Channel {
|
||||
return _client.addChannelMembers(id!, type, memberIds, message: message);
|
||||
}
|
||||
|
||||
/// Invite members to the channel
|
||||
/// Invite members to the channel.
|
||||
Future<InviteMembersResponse> inviteMembers(
|
||||
List<String> memberIds, [
|
||||
Message? message,
|
||||
@@ -889,7 +1062,7 @@ class Channel {
|
||||
return _client.inviteChannelMembers(id!, type, memberIds, message: message);
|
||||
}
|
||||
|
||||
/// Remove members from the channel
|
||||
/// Remove members from the channel.
|
||||
Future<RemoveMembersResponse> removeMembers(
|
||||
List<String> memberIds, [
|
||||
Message? message,
|
||||
@@ -898,7 +1071,7 @@ class Channel {
|
||||
return _client.removeChannelMembers(id!, type, memberIds, message: message);
|
||||
}
|
||||
|
||||
/// Send action for a specific message of this channel
|
||||
/// Send action for a specific message of this channel.
|
||||
Future<SendActionResponse> sendAction(
|
||||
Message message,
|
||||
Map<String, dynamic> formData,
|
||||
@@ -945,9 +1118,10 @@ class Channel {
|
||||
return res;
|
||||
}
|
||||
|
||||
/// Mark all messages as read
|
||||
/// Mark all messages as read.
|
||||
///
|
||||
/// Optionally provide a [messageId] if you want to mark a
|
||||
/// particular message as read
|
||||
/// particular message as read.
|
||||
Future<EmptyResponse> markRead({String? messageId}) async {
|
||||
_checkInitialized();
|
||||
client.state.totalUnreadCount =
|
||||
@@ -956,7 +1130,7 @@ class Channel {
|
||||
return _client.markChannelRead(id!, type, messageId: messageId);
|
||||
}
|
||||
|
||||
/// Loads the initial channel state and watches for changes
|
||||
/// Loads the initial channel state and watches for changes.
|
||||
Future<ChannelState> watch() async {
|
||||
ChannelState response;
|
||||
|
||||
@@ -987,15 +1161,16 @@ class Channel {
|
||||
}
|
||||
}
|
||||
|
||||
/// Stop watching the channel
|
||||
/// Stop watching the channel.
|
||||
Future<EmptyResponse> stopWatching() async {
|
||||
_checkInitialized();
|
||||
return _client.stopChannelWatching(id!, type);
|
||||
}
|
||||
|
||||
/// List the message replies for a parent message
|
||||
/// List the message replies for a parent message.
|
||||
///
|
||||
/// Set [preferOffline] to true to avoid the api call if the data is already
|
||||
/// in the offline storage
|
||||
/// in the offline storage.
|
||||
Future<QueryRepliesResponse> getReplies(
|
||||
String parentId, {
|
||||
PaginationParams? options,
|
||||
@@ -1019,7 +1194,7 @@ class Channel {
|
||||
return repliesResponse;
|
||||
}
|
||||
|
||||
/// List the reactions for a message in the channel
|
||||
/// List the reactions for a message in the channel.
|
||||
Future<QueryReactionsResponse> getReactions(
|
||||
String messageId, {
|
||||
PaginationParams? pagination,
|
||||
@@ -1029,7 +1204,7 @@ class Channel {
|
||||
pagination: pagination,
|
||||
);
|
||||
|
||||
/// Retrieves a list of messages by ID
|
||||
/// Retrieves a list of messages by given [messageIDs].
|
||||
Future<GetMessagesByIdResponse> getMessagesById(
|
||||
List<String> messageIDs,
|
||||
) async {
|
||||
@@ -1040,7 +1215,7 @@ class Channel {
|
||||
return res;
|
||||
}
|
||||
|
||||
/// Retrieves a list of messages by ID
|
||||
/// Translate a message by given [messageId] and [language].
|
||||
Future<TranslateMessageResponse> translateMessage(
|
||||
String messageId,
|
||||
String language,
|
||||
@@ -1050,12 +1225,13 @@ class Channel {
|
||||
language,
|
||||
);
|
||||
|
||||
/// Creates a new channel
|
||||
/// Creates a new channel.
|
||||
Future<ChannelState> create() async => query(state: false);
|
||||
|
||||
/// Query the API, get messages, members or other channel fields
|
||||
/// Set [preferOffline] to true to avoid the api call if the data is already
|
||||
/// in the offline storage
|
||||
/// Query the API, get messages, members or other channel fields.
|
||||
///
|
||||
/// Set [preferOffline] to true to avoid the API call if the data is already
|
||||
/// in the offline storage.
|
||||
Future<ChannelState> query({
|
||||
bool state = true,
|
||||
bool watch = false,
|
||||
@@ -1109,7 +1285,7 @@ class Channel {
|
||||
}
|
||||
}
|
||||
|
||||
/// Query channel members
|
||||
/// Query channel members.
|
||||
Future<QueryMembersResponse> queryMembers({
|
||||
Filter? filter,
|
||||
List<SortOption>? sort,
|
||||
@@ -1124,19 +1300,19 @@ class Channel {
|
||||
pagination: pagination,
|
||||
);
|
||||
|
||||
/// Mutes the channel
|
||||
/// Mutes the channel.
|
||||
Future<EmptyResponse> mute({Duration? expiration}) {
|
||||
_checkInitialized();
|
||||
return _client.muteChannel(cid!, expiration: expiration);
|
||||
}
|
||||
|
||||
/// Unmutes the channel
|
||||
/// Unmute the channel.
|
||||
Future<EmptyResponse> unmute() {
|
||||
_checkInitialized();
|
||||
return _client.unmuteChannel(cid!);
|
||||
}
|
||||
|
||||
/// Bans a user from the channel
|
||||
/// Bans the user with given [userID] from the channel.
|
||||
Future<EmptyResponse> banUser(
|
||||
String userID,
|
||||
Map<String, dynamic> options,
|
||||
@@ -1150,7 +1326,7 @@ class Channel {
|
||||
return _client.banUser(userID, opts);
|
||||
}
|
||||
|
||||
/// Remove the ban for a user in the channel
|
||||
/// Remove the ban for the user with given [userID] in the channel.
|
||||
Future<EmptyResponse> unbanUser(String userID) async {
|
||||
_checkInitialized();
|
||||
return _client.unbanUser(userID, {
|
||||
@@ -1159,7 +1335,7 @@ class Channel {
|
||||
});
|
||||
}
|
||||
|
||||
/// Shadow bans a user from the channel
|
||||
/// Shadow bans the user with the given [userID] from the channel.
|
||||
Future<EmptyResponse> shadowBan(
|
||||
String userID,
|
||||
Map<String, dynamic> options,
|
||||
@@ -1173,7 +1349,7 @@ class Channel {
|
||||
return _client.shadowBan(userID, opts);
|
||||
}
|
||||
|
||||
/// Remove the shadow ban for a user in the channel
|
||||
/// Remove the shadow ban for the user with the given [userID] in the channel.
|
||||
Future<EmptyResponse> removeShadowBan(String userID) async {
|
||||
_checkInitialized();
|
||||
return _client.removeShadowBan(userID, {
|
||||
@@ -1183,8 +1359,10 @@ class Channel {
|
||||
}
|
||||
|
||||
/// Hides the channel from [StreamChatClient.queryChannels] for the user
|
||||
/// until a message is added If [clearHistory] is set to true - all messages
|
||||
/// will be removed for the user
|
||||
/// until a message is added.
|
||||
///
|
||||
/// If [clearHistory] is set to true - all messages
|
||||
/// will be removed for the user.
|
||||
Future<EmptyResponse> hide({bool clearHistory = false}) async {
|
||||
_checkInitialized();
|
||||
final response = await _client.hideChannel(
|
||||
@@ -1202,7 +1380,7 @@ class Channel {
|
||||
return response;
|
||||
}
|
||||
|
||||
/// Removes the hidden status for the channel
|
||||
/// Removes the hidden status for the channel.
|
||||
Future<EmptyResponse> show() async {
|
||||
_checkInitialized();
|
||||
return _client.showChannel(id!, type);
|
||||
@@ -1210,7 +1388,7 @@ class Channel {
|
||||
|
||||
/// Stream of [Event] coming from websocket connection specific for the
|
||||
/// channel. Pass an eventType as parameter in order to filter just a type
|
||||
/// of event
|
||||
/// of event.
|
||||
Stream<Event> on([
|
||||
String? eventType,
|
||||
String? eventType2,
|
||||
@@ -1248,7 +1426,7 @@ class Channel {
|
||||
}
|
||||
}
|
||||
|
||||
/// Sets last typing to null and sends the typing.stop event
|
||||
/// Sets last typing to null and sends the typing.stop event.
|
||||
Future<void> stopTyping([String? parentId]) async {
|
||||
if (config?.typingEvents == false) {
|
||||
return;
|
||||
@@ -1262,7 +1440,7 @@ class Channel {
|
||||
));
|
||||
}
|
||||
|
||||
/// Call this method to dispose the channel client
|
||||
/// Call this method to dispose the channel client.
|
||||
void dispose() {
|
||||
state?.dispose();
|
||||
}
|
||||
@@ -1276,9 +1454,9 @@ class Channel {
|
||||
}
|
||||
}
|
||||
|
||||
/// The class that handles the state of the channel listening to the events
|
||||
/// The class that handles the state of the channel listening to the events.
|
||||
class ChannelClientState {
|
||||
/// Creates a new instance listening to events and updating the state
|
||||
/// Creates a new instance listening to events and updating the state.
|
||||
ChannelClientState(
|
||||
this._channel,
|
||||
ChannelState channelState,
|
||||
@@ -1425,23 +1603,25 @@ class ChannelClientState {
|
||||
}
|
||||
|
||||
/// Flag which indicates if [ChannelClientState] contain latest/recent messages or not.
|
||||
///
|
||||
/// This flag should be managed by UI sdks.
|
||||
/// When false, any new message (received by WebSocket event
|
||||
/// - [EventType.messageNew]) will not be pushed on to message list.
|
||||
///
|
||||
/// When false, any new message received by WebSocket event
|
||||
/// [EventType.messageNew] will not be pushed on to message list.
|
||||
bool get isUpToDate => _isUpToDateController.value;
|
||||
|
||||
set isUpToDate(bool isUpToDate) => _isUpToDateController.add(isUpToDate);
|
||||
|
||||
/// [isUpToDate] flag count as a stream
|
||||
/// [isUpToDate] flag count as a stream.
|
||||
Stream<bool> get isUpToDateStream => _isUpToDateController.stream;
|
||||
|
||||
final BehaviorSubject<bool> _isUpToDateController =
|
||||
BehaviorSubject.seeded(true);
|
||||
|
||||
/// The retry queue associated to this channel
|
||||
/// The retry queue associated to this channel.
|
||||
late final RetryQueue _retryQueue;
|
||||
|
||||
/// Retry failed message
|
||||
/// Retry failed message.
|
||||
Future<void> retryFailedMessages() async {
|
||||
final failedMessages =
|
||||
<Message>[...messages, ...threads.values.expand((v) => v)]
|
||||
@@ -1534,7 +1714,7 @@ class ChannelClientState {
|
||||
}));
|
||||
}
|
||||
|
||||
/// Add a message to this channel
|
||||
/// Add a message to this channel.
|
||||
void addMessage(Message message) {
|
||||
if (message.parentId == null || message.showInChannel == true) {
|
||||
final newMessages = List<Message>.from(_channelState.messages);
|
||||
@@ -1599,36 +1779,36 @@ class ChannelClientState {
|
||||
);
|
||||
}
|
||||
|
||||
/// Channel message list
|
||||
/// Channel message list.
|
||||
List<Message> get messages => _channelState.messages;
|
||||
|
||||
/// Channel message list as a stream
|
||||
/// Channel message list as a stream.
|
||||
Stream<List<Message>?> get messagesStream => channelStateStream
|
||||
.map((cs) => cs.messages)
|
||||
.distinct(const ListEquality().equals);
|
||||
|
||||
/// Channel pinned message list
|
||||
/// Channel pinned message list.
|
||||
List<Message>? get pinnedMessages => _channelState.pinnedMessages.toList();
|
||||
|
||||
/// Channel pinned message list as a stream
|
||||
/// Channel pinned message list as a stream.
|
||||
Stream<List<Message>?> get pinnedMessagesStream =>
|
||||
channelStateStream.map((cs) => cs.pinnedMessages.toList());
|
||||
|
||||
/// Get channel last message
|
||||
/// Get channel last message.
|
||||
Message? get lastMessage => _channelState.messages.isNotEmpty == true
|
||||
? _channelState.messages.last
|
||||
: null;
|
||||
|
||||
/// Get channel last message
|
||||
/// Get channel last message.
|
||||
Stream<Message?> get lastMessageStream => messagesStream
|
||||
.map((event) => event?.isNotEmpty == true ? event!.last : null);
|
||||
|
||||
/// Channel members list
|
||||
/// Channel members list.
|
||||
List<Member> get members => _channelState.members
|
||||
.map((e) => e.copyWith(user: _channel.client.state.users[e.user!.id]))
|
||||
.toList();
|
||||
|
||||
/// Channel members list as a stream
|
||||
/// Channel members list as a stream.
|
||||
Stream<List<Member>> get membersStream => CombineLatestStream.combine2<
|
||||
List<Member?>?, Map<String?, User?>, List<Member>>(
|
||||
channelStateStream.map((cs) => cs.members),
|
||||
@@ -1637,19 +1817,19 @@ class ChannelClientState {
|
||||
members!.map((e) => e!.copyWith(user: users[e.user!.id])).toList(),
|
||||
).distinct(const ListEquality().equals);
|
||||
|
||||
/// Channel watcher count
|
||||
/// Channel watcher count.
|
||||
int? get watcherCount => _channelState.watcherCount;
|
||||
|
||||
/// Channel watcher count as a stream
|
||||
/// Channel watcher count as a stream.
|
||||
Stream<int?> get watcherCountStream =>
|
||||
channelStateStream.map((cs) => cs.watcherCount);
|
||||
|
||||
/// Channel watchers list
|
||||
/// Channel watchers list.
|
||||
List<User> get watchers => _channelState.watchers
|
||||
.map((e) => _channel.client.state.users[e.id] ?? e)
|
||||
.toList();
|
||||
|
||||
/// Channel watchers list as a stream
|
||||
/// Channel watchers list as a stream.
|
||||
Stream<List<User>> get watchersStream => CombineLatestStream.combine2<
|
||||
List<User>?, Map<String?, User?>, List<User>>(
|
||||
channelStateStream.map((cs) => cs.watchers),
|
||||
@@ -1657,20 +1837,20 @@ class ChannelClientState {
|
||||
(watchers, users) => watchers!.map((e) => users[e.id] ?? e).toList(),
|
||||
);
|
||||
|
||||
/// Channel read list
|
||||
/// Channel read list.
|
||||
List<Read>? get read => _channelState.read;
|
||||
|
||||
/// Channel read list as a stream
|
||||
/// Channel read list as a stream.
|
||||
Stream<List<Read>?> get readStream => channelStateStream.map((cs) => cs.read);
|
||||
|
||||
final BehaviorSubject<int> _unreadCountController = BehaviorSubject.seeded(0);
|
||||
|
||||
set unreadCount(int value) => _unreadCountController.add(value);
|
||||
|
||||
/// Unread count getter as a stream
|
||||
/// Unread count getter as a stream.
|
||||
Stream<int> get unreadCountStream => _unreadCountController.stream.distinct();
|
||||
|
||||
/// Unread count getter
|
||||
/// Unread count getter.
|
||||
int get unreadCount => _unreadCountController.value;
|
||||
|
||||
bool _countMessageAsUnread(Message message) {
|
||||
@@ -1686,7 +1866,7 @@ class ChannelClientState {
|
||||
!userIsMuted;
|
||||
}
|
||||
|
||||
/// Update threads with updated information about messages
|
||||
/// Update threads with updated information about messages.
|
||||
void updateThreadInfo(String parentId, List<Message> messages) {
|
||||
final newThreads = Map<String, List<Message>>.from(threads);
|
||||
|
||||
@@ -1708,7 +1888,7 @@ class ChannelClientState {
|
||||
_threads = newThreads;
|
||||
}
|
||||
|
||||
/// Delete all channel messages
|
||||
/// Delete all channel messages.
|
||||
void truncate() {
|
||||
_channelState = _channelState.copyWith(
|
||||
messages: [],
|
||||
@@ -1717,7 +1897,7 @@ class ChannelClientState {
|
||||
|
||||
final List<String> _updatedMessagesIds = [];
|
||||
|
||||
/// Update channelState with updated information
|
||||
/// Update channelState with updated information.
|
||||
void updateChannelState(ChannelState updatedState) {
|
||||
final newMessages = <Message>[
|
||||
...updatedState.messages,
|
||||
@@ -1769,13 +1949,13 @@ class ChannelClientState {
|
||||
int _sortByCreatedAt(Message a, Message b) =>
|
||||
a.createdAt.compareTo(b.createdAt);
|
||||
|
||||
/// The channel state related to this client
|
||||
/// The channel state related to this client.
|
||||
ChannelState get _channelState => _channelStateController.value;
|
||||
|
||||
/// The channel state related to this client as a stream
|
||||
/// The channel state related to this client as a stream.
|
||||
Stream<ChannelState> get channelStateStream => _channelStateController.stream;
|
||||
|
||||
/// The channel state related to this client
|
||||
/// The channel state related to this client.
|
||||
ChannelState get channelState => _channelStateController.value;
|
||||
late BehaviorSubject<ChannelState> _channelStateController;
|
||||
|
||||
@@ -1786,11 +1966,11 @@ class ChannelClientState {
|
||||
_debouncedUpdatePersistenceChannelState.call([v]);
|
||||
}
|
||||
|
||||
/// The channel threads related to this channel
|
||||
/// The channel threads related to this channel.
|
||||
Map<String, List<Message>> get threads =>
|
||||
_threadsController.value.map((key, value) => MapEntry(key, value));
|
||||
|
||||
/// The channel threads related to this channel as a stream
|
||||
/// The channel threads related to this channel as a stream.
|
||||
Stream<Map<String, List<Message>>> get threadsStream =>
|
||||
_threadsController.stream;
|
||||
final BehaviorSubject<Map<String, List<Message>>> _threadsController =
|
||||
@@ -1804,10 +1984,10 @@ class ChannelClientState {
|
||||
_threadsController.add(v);
|
||||
}
|
||||
|
||||
/// Channel related typing users last value
|
||||
/// Channel related typing users last value.
|
||||
Map<User, Event> get typingEvents => _typingEventsController.value;
|
||||
|
||||
/// Channel related typing users stream
|
||||
/// Channel related typing users stream.
|
||||
Stream<Map<User, Event>> get typingEventsStream =>
|
||||
_typingEventsController.stream;
|
||||
|
||||
@@ -1935,7 +2115,7 @@ class ChannelClientState {
|
||||
});
|
||||
}
|
||||
|
||||
/// Call this method to dispose this object
|
||||
/// Call this method to dispose this object.
|
||||
void dispose() {
|
||||
_debouncedUpdatePersistenceChannelState.cancel();
|
||||
_unreadCountController.close();
|
||||
|
||||
@@ -767,7 +767,9 @@ class StreamChatClient {
|
||||
cancelToken: cancelToken,
|
||||
);
|
||||
|
||||
/// Replaces the [channelId] of type [ChannelType] data with [data]
|
||||
/// Replaces the [channelId] of type [ChannelType] data with [data].
|
||||
///
|
||||
/// Use [updateChannelPartial] for a partial update.
|
||||
Future<UpdateChannelResponse> updateChannel(
|
||||
String channelId,
|
||||
String channelType,
|
||||
@@ -781,7 +783,10 @@ class StreamChatClient {
|
||||
message: message,
|
||||
);
|
||||
|
||||
/// Updates the [channelId] of type [ChannelType] data with [data]
|
||||
/// Partial update for the [channelId] of type [ChannelType]. Sets the
|
||||
/// data provided in [set], and removes the attributes given in [unset].
|
||||
///
|
||||
/// Use [updateChannel] for a full update.
|
||||
Future<PartialUpdateChannelResponse> updateChannelPartial(
|
||||
String channelId,
|
||||
String channelType, {
|
||||
|
||||
@@ -84,7 +84,7 @@ class ChannelApi {
|
||||
|
||||
/// Mark all channels for this user as read
|
||||
Future<EmptyResponse> markAllRead() async {
|
||||
final response = await _client.post('channels/read');
|
||||
final response = await _client.post('/channels/read');
|
||||
return EmptyResponse.fromJson(response.data);
|
||||
}
|
||||
|
||||
|
||||
@@ -7,11 +7,12 @@ import 'package:stream_chat/stream_chat.dart';
|
||||
|
||||
part 'own_user.g.dart';
|
||||
|
||||
/// The class that defines the own user model
|
||||
/// This object can be found in [Event]
|
||||
/// The class that defines the own user model.
|
||||
///
|
||||
/// This object can be found in [Event].
|
||||
@JsonSerializable(createToJson: false)
|
||||
class OwnUser extends User {
|
||||
/// Constructor used for json serialization
|
||||
/// Constructor used for json serialization.
|
||||
OwnUser({
|
||||
this.devices = const [],
|
||||
this.mutes = const [],
|
||||
@@ -20,6 +21,8 @@ class OwnUser extends User {
|
||||
this.channelMutes = const [],
|
||||
required String id,
|
||||
String? role,
|
||||
String? name,
|
||||
String? image,
|
||||
DateTime? createdAt,
|
||||
DateTime? updatedAt,
|
||||
DateTime? lastActive,
|
||||
@@ -31,6 +34,8 @@ class OwnUser extends User {
|
||||
}) : super(
|
||||
id: id,
|
||||
role: role,
|
||||
name: name,
|
||||
image: image,
|
||||
createdAt: createdAt,
|
||||
updatedAt: updatedAt,
|
||||
lastActive: lastActive,
|
||||
@@ -41,14 +46,16 @@ class OwnUser extends User {
|
||||
language: language,
|
||||
);
|
||||
|
||||
/// Create a new instance from a json
|
||||
/// Create a new instance from json.
|
||||
factory OwnUser.fromJson(Map<String, dynamic> json) => _$OwnUserFromJson(
|
||||
Serializer.moveToExtraDataFromRoot(json, topLevelFields));
|
||||
|
||||
/// Create a new instance from [User] object
|
||||
/// Create a new instance from [User] object.
|
||||
factory OwnUser.fromUser(User user) => OwnUser(
|
||||
id: user.id,
|
||||
role: user.role,
|
||||
name: user.name,
|
||||
image: user.image,
|
||||
createdAt: user.createdAt,
|
||||
updatedAt: user.updatedAt,
|
||||
lastActive: user.lastActive,
|
||||
@@ -64,6 +71,8 @@ class OwnUser extends User {
|
||||
OwnUser copyWith({
|
||||
String? id,
|
||||
String? role,
|
||||
String? name,
|
||||
String? image,
|
||||
DateTime? createdAt,
|
||||
DateTime? updatedAt,
|
||||
DateTime? lastActive,
|
||||
@@ -80,8 +89,12 @@ class OwnUser extends User {
|
||||
}) =>
|
||||
OwnUser(
|
||||
id: id ?? this.id,
|
||||
banned: banned ?? this.banned,
|
||||
role: role ?? this.role,
|
||||
/* if null, it will be retrieved from extraData['name']*/
|
||||
name: name,
|
||||
/* if null, it will be retrieved from extraData['image']*/
|
||||
image: image,
|
||||
banned: banned ?? this.banned,
|
||||
createdAt: createdAt ?? this.createdAt,
|
||||
updatedAt: updatedAt ?? this.updatedAt,
|
||||
lastActive: lastActive ?? this.lastActive,
|
||||
@@ -101,16 +114,18 @@ class OwnUser extends User {
|
||||
OwnUser merge(OwnUser? other) {
|
||||
if (other == null) return this;
|
||||
return copyWith(
|
||||
id: other.id,
|
||||
role: other.role,
|
||||
name: other.name,
|
||||
image: other.image,
|
||||
banned: other.banned,
|
||||
channelMutes: other.channelMutes,
|
||||
createdAt: other.createdAt,
|
||||
devices: other.devices,
|
||||
extraData: other.extraData,
|
||||
id: other.id,
|
||||
lastActive: other.lastActive,
|
||||
mutes: other.mutes,
|
||||
online: other.online,
|
||||
role: other.role,
|
||||
teams: other.teams,
|
||||
totalUnreadCount: other.totalUnreadCount,
|
||||
unreadChannels: other.unreadChannels,
|
||||
@@ -119,27 +134,28 @@ class OwnUser extends User {
|
||||
);
|
||||
}
|
||||
|
||||
/// List of user devices
|
||||
/// List of user devices.
|
||||
@JsonKey(includeIfNull: false, defaultValue: <Device>[])
|
||||
final List<Device> devices;
|
||||
|
||||
/// List of users muted by the user
|
||||
/// List of users muted by the user.
|
||||
@JsonKey(includeIfNull: false, defaultValue: <Mute>[])
|
||||
final List<Mute> mutes;
|
||||
|
||||
/// List of users muted by the user
|
||||
/// List of users muted by the user.
|
||||
@JsonKey(includeIfNull: false, defaultValue: <Mute>[])
|
||||
final List<Mute> channelMutes;
|
||||
|
||||
/// Total unread messages by the user
|
||||
/// Total unread messages by the user.
|
||||
@JsonKey(includeIfNull: false, defaultValue: 0)
|
||||
final int totalUnreadCount;
|
||||
|
||||
/// Total unread channels by the user
|
||||
/// Total unread channels by the user.
|
||||
@JsonKey(includeIfNull: false)
|
||||
final int? unreadChannels;
|
||||
|
||||
/// Known top level fields.
|
||||
///
|
||||
/// Useful for [Serializer] methods.
|
||||
static final topLevelFields = [
|
||||
'devices',
|
||||
|
||||
@@ -4,29 +4,60 @@ import 'package:stream_chat/src/core/util/serializer.dart';
|
||||
|
||||
part 'user.g.dart';
|
||||
|
||||
/// The class that defines the user model
|
||||
/// Class that defines a Stream Chat User.
|
||||
@JsonSerializable()
|
||||
class User extends Equatable {
|
||||
/// Constructor used for json serialization
|
||||
/// Creates a new user.
|
||||
///
|
||||
/// {@template name}
|
||||
/// If an [name] is provided it will be set on [extraData] with a `key`
|
||||
/// of 'name'.
|
||||
///
|
||||
/// For example:
|
||||
/// ```dart
|
||||
/// final user = User(id: 'id', name: 'Sahil Kumar');
|
||||
/// print(user.name == user.extraData['name']); // true
|
||||
/// ```
|
||||
/// {@endtemplate}
|
||||
///
|
||||
/// {@template image}
|
||||
/// If an [image] is provided it will be set on [extraData] with a `key`
|
||||
/// of 'image'.
|
||||
///
|
||||
/// For example:
|
||||
/// ```dart
|
||||
/// final user = User(id: 'id', image: 'https://getstream.io/image.png');
|
||||
/// print(user.image == user.extraData['image']); // true
|
||||
/// ```
|
||||
/// {@endtemplate}
|
||||
User({
|
||||
required this.id,
|
||||
this.role,
|
||||
String? name,
|
||||
String? image,
|
||||
DateTime? createdAt,
|
||||
DateTime? updatedAt,
|
||||
this.lastActive,
|
||||
Map<String, Object?> extraData = const {},
|
||||
this.online = false,
|
||||
this.extraData = const {},
|
||||
this.banned = false,
|
||||
this.teams = const [],
|
||||
this.language,
|
||||
}) : createdAt = createdAt ?? DateTime.now(),
|
||||
updatedAt = updatedAt ?? DateTime.now();
|
||||
updatedAt = updatedAt ?? DateTime.now(),
|
||||
/*For backwards compatibility, set 'name', 'image' in [extraData].*/
|
||||
extraData = {
|
||||
...extraData,
|
||||
if (name != null) 'name': name,
|
||||
if (image != null) 'image': image,
|
||||
};
|
||||
|
||||
/// Create a new instance from a json
|
||||
/// Create a new instance from json.
|
||||
factory User.fromJson(Map<String, dynamic> json) =>
|
||||
_$UserFromJson(Serializer.moveToExtraDataFromRoot(json, topLevelFields));
|
||||
|
||||
/// Known top level fields.
|
||||
///
|
||||
/// Useful for [Serializer] methods.
|
||||
static const topLevelFields = [
|
||||
'id',
|
||||
@@ -40,57 +71,13 @@ class User extends Equatable {
|
||||
'language',
|
||||
];
|
||||
|
||||
/// User id
|
||||
/// User id.
|
||||
final String id;
|
||||
|
||||
/// User role
|
||||
@JsonKey(includeIfNull: false, toJson: Serializer.readOnly)
|
||||
final String? role;
|
||||
|
||||
/// User role
|
||||
@JsonKey(
|
||||
includeIfNull: false,
|
||||
toJson: Serializer.readOnly,
|
||||
defaultValue: <String>[],
|
||||
)
|
||||
final List<String> teams;
|
||||
|
||||
/// Date of user creation
|
||||
@JsonKey(includeIfNull: false, toJson: Serializer.readOnly)
|
||||
final DateTime createdAt;
|
||||
|
||||
/// Date of last user update
|
||||
@JsonKey(includeIfNull: false, toJson: Serializer.readOnly)
|
||||
final DateTime updatedAt;
|
||||
|
||||
/// Date of last user connection
|
||||
@JsonKey(includeIfNull: false, toJson: Serializer.readOnly)
|
||||
final DateTime? lastActive;
|
||||
|
||||
/// True if user is online
|
||||
@JsonKey(
|
||||
includeIfNull: false, toJson: Serializer.readOnly, defaultValue: false)
|
||||
final bool online;
|
||||
|
||||
/// True if user is banned from the chat
|
||||
@JsonKey(
|
||||
includeIfNull: false, toJson: Serializer.readOnly, defaultValue: false)
|
||||
final bool banned;
|
||||
|
||||
/// Map of custom user extraData
|
||||
@JsonKey(
|
||||
includeIfNull: false,
|
||||
defaultValue: {},
|
||||
)
|
||||
final Map<String, Object?> extraData;
|
||||
|
||||
/// The language this user prefers.
|
||||
/// Shortcut for user name.
|
||||
///
|
||||
/// Defaults to 'en'.
|
||||
@JsonKey(includeIfNull: false)
|
||||
final String? language;
|
||||
|
||||
/// Shortcut for user name
|
||||
/// {@macro name}
|
||||
@JsonKey(ignore: true)
|
||||
String get name {
|
||||
if (extraData.containsKey('name')) {
|
||||
final name = extraData['name']! as String;
|
||||
@@ -99,11 +86,62 @@ class User extends Equatable {
|
||||
return id;
|
||||
}
|
||||
|
||||
/// List of users to list of userIds
|
||||
/// Shortcut for user image.
|
||||
///
|
||||
/// {@macro image}
|
||||
@JsonKey(ignore: true)
|
||||
String? get image => extraData['image'] as String?;
|
||||
|
||||
/// User role.
|
||||
@JsonKey(includeIfNull: false, toJson: Serializer.readOnly)
|
||||
final String? role;
|
||||
|
||||
/// User teams
|
||||
@JsonKey(
|
||||
includeIfNull: false,
|
||||
toJson: Serializer.readOnly,
|
||||
defaultValue: <String>[],
|
||||
)
|
||||
final List<String> teams;
|
||||
|
||||
/// Date of user creation.
|
||||
@JsonKey(includeIfNull: false, toJson: Serializer.readOnly)
|
||||
final DateTime createdAt;
|
||||
|
||||
/// Date of last user update.
|
||||
@JsonKey(includeIfNull: false, toJson: Serializer.readOnly)
|
||||
final DateTime updatedAt;
|
||||
|
||||
/// Date of last user connection.
|
||||
@JsonKey(includeIfNull: false, toJson: Serializer.readOnly)
|
||||
final DateTime? lastActive;
|
||||
|
||||
/// True if user is online.
|
||||
@JsonKey(
|
||||
includeIfNull: false, toJson: Serializer.readOnly, defaultValue: false)
|
||||
final bool online;
|
||||
|
||||
/// True if user is banned from the chat.
|
||||
@JsonKey(
|
||||
includeIfNull: false, toJson: Serializer.readOnly, defaultValue: false)
|
||||
final bool banned;
|
||||
|
||||
/// Map of custom user extraData.
|
||||
@JsonKey(
|
||||
includeIfNull: false,
|
||||
defaultValue: {},
|
||||
)
|
||||
final Map<String, Object?> extraData;
|
||||
|
||||
/// The language this user prefers.
|
||||
@JsonKey(includeIfNull: false)
|
||||
final String? language;
|
||||
|
||||
/// List of users to list of userIds.
|
||||
static List<String>? toIds(List<User>? users) =>
|
||||
users?.map((u) => u.id).toList();
|
||||
|
||||
/// Serialize to json
|
||||
/// Serialize to json.
|
||||
Map<String, dynamic> toJson() => Serializer.moveFromExtraDataToRoot(
|
||||
_$UserToJson(this),
|
||||
);
|
||||
@@ -112,6 +150,8 @@ class User extends Equatable {
|
||||
User copyWith({
|
||||
String? id,
|
||||
String? role,
|
||||
String? name,
|
||||
String? image,
|
||||
DateTime? createdAt,
|
||||
DateTime? updatedAt,
|
||||
DateTime? lastActive,
|
||||
@@ -124,6 +164,10 @@ class User extends Equatable {
|
||||
User(
|
||||
id: id ?? this.id,
|
||||
role: role ?? this.role,
|
||||
/* if null, it will be retrieved from extraData['name']*/
|
||||
name: name,
|
||||
/* if null, it will be retrieved from extraData['image']*/
|
||||
image: image,
|
||||
createdAt: createdAt ?? this.createdAt,
|
||||
updatedAt: updatedAt ?? this.updatedAt,
|
||||
lastActive: lastActive ?? this.lastActive,
|
||||
@@ -135,5 +179,5 @@ class User extends Equatable {
|
||||
);
|
||||
|
||||
@override
|
||||
List<Object?> get props => [id];
|
||||
List<Object?> get props => [id, role];
|
||||
}
|
||||
|
||||
@@ -19,8 +19,8 @@ User _$UserFromJson(Map<String, dynamic> json) {
|
||||
lastActive: json['last_active'] == null
|
||||
? null
|
||||
: DateTime.parse(json['last_active'] as String),
|
||||
online: json['online'] as bool? ?? false,
|
||||
extraData: json['extra_data'] as Map<String, dynamic>? ?? {},
|
||||
online: json['online'] as bool? ?? false,
|
||||
banned: json['banned'] as bool? ?? false,
|
||||
teams:
|
||||
(json['teams'] as List<dynamic>?)?.map((e) => e as String).toList() ??
|
||||
|
||||
+11
@@ -2,5 +2,16 @@
|
||||
"id": "bbb19d9a-ee50-45bc-84e5-0584e79d0c9e",
|
||||
"role": "test-role",
|
||||
"name": "John",
|
||||
"image": "https://getstream.io/random_png/?id=cool-shadow-7&name=Cool+shadow",
|
||||
"extraDataStringTest": "Extra data test",
|
||||
"extraDataIntTest": 1,
|
||||
"extraDataDoubleTest": 1.1,
|
||||
"extraDataBoolTest": true,
|
||||
"banned": true,
|
||||
"online": true,
|
||||
"teams": ["team-1", "team-2"],
|
||||
"created_at": "2021-08-03 12:39:21.817646",
|
||||
"updated_at": "2021-08-04 12:39:21.817646",
|
||||
"last_active" : "2021-08-05 12:39:21.817646",
|
||||
"language": "en"
|
||||
}
|
||||
@@ -72,6 +72,40 @@ void main() {
|
||||
expect(channel.extraData.containsKey('name'), isTrue);
|
||||
expect(channel.extraData['name'], 'test-channel-name');
|
||||
});
|
||||
|
||||
test('should be able to get and set `image`', () {
|
||||
expect(channel.extraData.isEmpty, isTrue);
|
||||
|
||||
const imageUrl = 'https://getstream.io/some-image';
|
||||
channel.image = imageUrl;
|
||||
|
||||
expect(channel.image, imageUrl);
|
||||
expect(channel.extraData['image'], imageUrl);
|
||||
|
||||
const newImage = 'https://getstream.io/new-image';
|
||||
final newChannelInstance =
|
||||
Channel(client, channelType, channelId, image: newImage);
|
||||
|
||||
expect(newChannelInstance.image, newImage);
|
||||
expect(newChannelInstance.extraData['image'], newImage);
|
||||
});
|
||||
|
||||
test('should be able to get and set `name`', () {
|
||||
expect(channel.extraData.isEmpty, isTrue);
|
||||
|
||||
const name = 'Channel name';
|
||||
channel.name = name;
|
||||
|
||||
expect(channel.name, name);
|
||||
expect(channel.extraData['name'], name);
|
||||
|
||||
const newName = 'New channel name';
|
||||
final newChannelInstance =
|
||||
Channel(client, channelType, channelId, name: newName);
|
||||
|
||||
expect(newChannelInstance.name, newName);
|
||||
expect(newChannelInstance.extraData['name'], newName);
|
||||
});
|
||||
});
|
||||
|
||||
// TODO : test all persistence related logic in this group
|
||||
@@ -192,6 +226,22 @@ void main() {
|
||||
}
|
||||
});
|
||||
|
||||
test('should throw if trying to set `image`', () {
|
||||
try {
|
||||
channel.image = 'https://stream.io/some-image';
|
||||
} catch (e) {
|
||||
expect(e, isA<StateError>());
|
||||
}
|
||||
});
|
||||
|
||||
test('should throw if trying to set `name`', () {
|
||||
try {
|
||||
channel.name = 'New name';
|
||||
} catch (e) {
|
||||
expect(e, isA<StateError>());
|
||||
}
|
||||
});
|
||||
|
||||
group('`.sendMessage`', () {
|
||||
test('should work fine', () async {
|
||||
final message = Message(id: 'test-message-id');
|
||||
@@ -1192,6 +1242,62 @@ void main() {
|
||||
message: any(named: 'message'))).called(1);
|
||||
});
|
||||
|
||||
test('`.updateImage`', () async {
|
||||
const image = 'https://getstream.io/new-image';
|
||||
|
||||
final channelModel = ChannelModel(
|
||||
cid: channelCid,
|
||||
extraData: {'image': image},
|
||||
);
|
||||
|
||||
when(() => client.updateChannelPartial(
|
||||
channelId,
|
||||
channelType,
|
||||
set: {'image': image},
|
||||
)).thenAnswer(
|
||||
(_) async => PartialUpdateChannelResponse()..channel = channelModel,
|
||||
);
|
||||
|
||||
final res = await channel.updateImage(image);
|
||||
|
||||
expect(res, isNotNull);
|
||||
expect(res.channel.extraData['image'], image);
|
||||
|
||||
verify(() => client.updateChannelPartial(
|
||||
channelId,
|
||||
channelType,
|
||||
set: {'image': image},
|
||||
)).called(1);
|
||||
});
|
||||
|
||||
test('`.updateName`', () async {
|
||||
const name = 'Name';
|
||||
|
||||
final channelModel = ChannelModel(
|
||||
cid: channelCid,
|
||||
extraData: {'name': name},
|
||||
);
|
||||
|
||||
when(() => client.updateChannelPartial(
|
||||
channelId,
|
||||
channelType,
|
||||
set: {'name': name},
|
||||
)).thenAnswer(
|
||||
(_) async => PartialUpdateChannelResponse()..channel = channelModel,
|
||||
);
|
||||
|
||||
final res = await channel.updateName(name);
|
||||
|
||||
expect(res, isNotNull);
|
||||
expect(res.channel.extraData['name'], name);
|
||||
|
||||
verify(() => client.updateChannelPartial(
|
||||
channelId,
|
||||
channelType,
|
||||
set: {'name': name},
|
||||
)).called(1);
|
||||
});
|
||||
|
||||
test('`.updatePartial`', () async {
|
||||
const set = {
|
||||
'name': 'Stream Team',
|
||||
|
||||
@@ -176,7 +176,7 @@ void main() {
|
||||
});
|
||||
|
||||
test('markAllRead', () async {
|
||||
const path = 'channels/read';
|
||||
const path = '/channels/read';
|
||||
when(() => client.post(path)).thenAnswer(
|
||||
(_) async => successResponse(path, data: <String, dynamic>{}));
|
||||
|
||||
|
||||
@@ -1,10 +1,23 @@
|
||||
import 'package:mocktail/mocktail.dart';
|
||||
import 'package:stream_chat/src/core/models/own_user.dart';
|
||||
import 'package:stream_chat/src/core/models/user.dart';
|
||||
import 'package:stream_chat/stream_chat.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
import '../../utils.dart';
|
||||
|
||||
class MockMute extends Mock implements Mute {}
|
||||
|
||||
class MockDevice extends Mock implements Device {}
|
||||
|
||||
void main() {
|
||||
final devices = [MockDevice(), MockDevice()];
|
||||
final mutes = [MockMute(), MockMute()];
|
||||
final channelMutes = [MockMute()];
|
||||
final createdAt = DateTime.parse('2021-05-03 12:39:21.817646');
|
||||
final updatedAt = DateTime.parse('2021-04-03 12:39:21.817646');
|
||||
final lastActive = DateTime.parse('2021-03-03 12:39:21.817646');
|
||||
|
||||
group('src/models/own_user', () {
|
||||
test('should parse json correctly', () {
|
||||
final ownUser = OwnUser.fromJson(jsonFixture('own_user.json'));
|
||||
@@ -22,6 +35,7 @@ void main() {
|
||||
expect(ownUser.channelMutes.length, 1);
|
||||
expect(ownUser.totalUnreadCount, 0);
|
||||
expect(ownUser.unreadChannels, 0);
|
||||
expect(ownUser.image, 'https://placehold.jp/150x150.png');
|
||||
expect(ownUser.extraData['image'], 'https://placehold.jp/150x150.png');
|
||||
expect(ownUser.extraData['name'], 'Proud darkness');
|
||||
expect(ownUser.extraData['username'], 'Rioland');
|
||||
@@ -40,6 +54,7 @@ void main() {
|
||||
expect(ownUser.online, user.online);
|
||||
expect(ownUser.banned, user.banned);
|
||||
expect(ownUser.extraData, user.extraData);
|
||||
expect(ownUser.image, user.image);
|
||||
});
|
||||
|
||||
test('copyWith', () {
|
||||
@@ -49,35 +64,116 @@ void main() {
|
||||
expect(newUser.id, user.id);
|
||||
expect(newUser.role, user.role);
|
||||
expect(newUser.name, user.name);
|
||||
expect(newUser.devices, user.devices);
|
||||
expect(newUser.mutes, user.mutes);
|
||||
expect(newUser.totalUnreadCount, user.totalUnreadCount);
|
||||
expect(newUser.channelMutes, user.channelMutes);
|
||||
expect(newUser.createdAt, user.createdAt);
|
||||
expect(newUser.updatedAt, user.updatedAt);
|
||||
expect(newUser.lastActive, user.lastActive);
|
||||
expect(newUser.online, user.online);
|
||||
expect(newUser.extraData, user.extraData);
|
||||
expect(newUser.banned, user.banned);
|
||||
expect(newUser.teams, user.teams);
|
||||
expect(newUser.language, user.language);
|
||||
expect(newUser.image, user.image);
|
||||
|
||||
newUser = user.copyWith(
|
||||
id: 'test',
|
||||
role: 'test',
|
||||
image: 'https://getstream.io/image-new',
|
||||
extraData: {
|
||||
'name': 'test',
|
||||
},
|
||||
devices: devices,
|
||||
mutes: mutes,
|
||||
totalUnreadCount: 10,
|
||||
unreadChannels: 5,
|
||||
channelMutes: channelMutes,
|
||||
createdAt: createdAt,
|
||||
updatedAt: updatedAt,
|
||||
lastActive: lastActive,
|
||||
online: true,
|
||||
banned: true,
|
||||
teams: ['team1', 'team2'],
|
||||
language: 'fr',
|
||||
);
|
||||
|
||||
expect(newUser.id, 'test');
|
||||
expect(newUser.role, 'test');
|
||||
expect(newUser.name, 'test');
|
||||
expect(newUser.image, 'https://getstream.io/image-new');
|
||||
expect(
|
||||
newUser.extraData,
|
||||
{
|
||||
'name': 'test',
|
||||
'image': 'https://getstream.io/image-new',
|
||||
},
|
||||
reason: 'Should get image from user.image',
|
||||
);
|
||||
expect(newUser.devices, devices);
|
||||
expect(newUser.mutes, mutes);
|
||||
expect(newUser.totalUnreadCount, 10);
|
||||
expect(newUser.unreadChannels, 5);
|
||||
expect(newUser.channelMutes, channelMutes);
|
||||
expect(newUser.createdAt, createdAt);
|
||||
expect(newUser.updatedAt, updatedAt);
|
||||
expect(newUser.createdAt, createdAt);
|
||||
expect(newUser.online, true);
|
||||
expect(newUser.banned, true);
|
||||
expect(newUser.teams, ['team1', 'team2']);
|
||||
expect(newUser.language, 'fr');
|
||||
});
|
||||
|
||||
test('merge', () {
|
||||
final user = OwnUser.fromJson(jsonFixture('own_user.json'));
|
||||
final newUser = user.merge(OwnUser(
|
||||
id: 'test',
|
||||
role: 'test',
|
||||
extraData: const {
|
||||
'name': 'test',
|
||||
},
|
||||
banned: true,
|
||||
));
|
||||
final newUser = user.merge(
|
||||
OwnUser(
|
||||
id: 'test',
|
||||
role: 'test',
|
||||
extraData: const {
|
||||
'name': 'test',
|
||||
},
|
||||
image: 'https://getstream.io/image-new',
|
||||
devices: devices,
|
||||
mutes: mutes,
|
||||
totalUnreadCount: 10,
|
||||
unreadChannels: 5,
|
||||
channelMutes: channelMutes,
|
||||
createdAt: createdAt,
|
||||
updatedAt: updatedAt,
|
||||
lastActive: lastActive,
|
||||
online: true,
|
||||
banned: true,
|
||||
teams: const ['team1', 'team2'],
|
||||
language: 'fr',
|
||||
),
|
||||
);
|
||||
|
||||
expect(newUser.id, 'test');
|
||||
expect(newUser.role, 'test');
|
||||
expect(newUser.name, 'test');
|
||||
expect(newUser.image, 'https://getstream.io/image-new');
|
||||
expect(
|
||||
newUser.extraData,
|
||||
{
|
||||
'name': 'test',
|
||||
'image': 'https://getstream.io/image-new',
|
||||
},
|
||||
reason: 'Should get image from user.image',
|
||||
);
|
||||
expect(newUser.devices, devices);
|
||||
expect(newUser.mutes, mutes);
|
||||
expect(newUser.totalUnreadCount, 10);
|
||||
expect(newUser.unreadChannels, 5);
|
||||
expect(newUser.channelMutes, channelMutes);
|
||||
expect(newUser.createdAt, createdAt);
|
||||
expect(newUser.updatedAt, updatedAt);
|
||||
expect(newUser.createdAt, createdAt);
|
||||
expect(newUser.online, true);
|
||||
expect(newUser.banned, true);
|
||||
expect(newUser.teams, ['team1', 'team2']);
|
||||
expect(newUser.language, 'fr');
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -13,10 +13,11 @@ void main() {
|
||||
expect(reaction.type, 'wow');
|
||||
expect(
|
||||
reaction.user?.toJson(),
|
||||
User(id: '2de0297c-f3f2-489d-b930-ef77342edccf', extraData: const {
|
||||
'image': 'https://randomuser.me/api/portraits/women/45.jpg',
|
||||
'name': 'Daisy Morgan'
|
||||
}).toJson(),
|
||||
User(
|
||||
id: '2de0297c-f3f2-489d-b930-ef77342edccf',
|
||||
image: 'https://randomuser.me/api/portraits/women/45.jpg',
|
||||
name: 'Daisy Morgan',
|
||||
).toJson(),
|
||||
);
|
||||
expect(reaction.score, 1);
|
||||
expect(reaction.userId, '2de0297c-f3f2-489d-b930-ef77342edccf');
|
||||
@@ -28,11 +29,11 @@ void main() {
|
||||
messageId: '76cd8c82-b557-4e48-9d12-87995d3a0e04',
|
||||
createdAt: DateTime.parse('2020-01-28T22:17:31.108742Z'),
|
||||
type: 'wow',
|
||||
user:
|
||||
User(id: '2de0297c-f3f2-489d-b930-ef77342edccf', extraData: const {
|
||||
'image': 'https://randomuser.me/api/portraits/women/45.jpg',
|
||||
'name': 'Daisy Morgan'
|
||||
}),
|
||||
user: User(
|
||||
id: '2de0297c-f3f2-489d-b930-ef77342edccf',
|
||||
image: 'https://randomuser.me/api/portraits/women/45.jpg',
|
||||
name: 'Daisy Morgan',
|
||||
),
|
||||
userId: '2de0297c-f3f2-489d-b930-ef77342edccf',
|
||||
extraData: {'bananas': 'yes'},
|
||||
score: 1,
|
||||
@@ -58,10 +59,11 @@ void main() {
|
||||
expect(newReaction.type, 'wow');
|
||||
expect(
|
||||
newReaction.user?.toJson(),
|
||||
User(id: '2de0297c-f3f2-489d-b930-ef77342edccf', extraData: const {
|
||||
'image': 'https://randomuser.me/api/portraits/women/45.jpg',
|
||||
'name': 'Daisy Morgan',
|
||||
}).toJson(),
|
||||
User(
|
||||
id: '2de0297c-f3f2-489d-b930-ef77342edccf',
|
||||
image: 'https://randomuser.me/api/portraits/women/45.jpg',
|
||||
name: 'Daisy Morgan',
|
||||
).toJson(),
|
||||
);
|
||||
expect(newReaction.score, 1);
|
||||
expect(newReaction.userId, '2de0297c-f3f2-489d-b930-ef77342edccf');
|
||||
|
||||
@@ -4,21 +4,73 @@ import 'package:test/test.dart';
|
||||
import '../../utils.dart';
|
||||
|
||||
void main() {
|
||||
const id = 'bbb19d9a-ee50-45bc-84e5-0584e79d0c9e';
|
||||
const role = 'test-role';
|
||||
const name = 'John';
|
||||
const image =
|
||||
'https://getstream.io/random_png/?id=cool-shadow-7&name=Cool+shadow';
|
||||
const extraDataStringTest = 'Extra data test';
|
||||
const extraDataIntTest = 1;
|
||||
const extraDataDoubleTest = 1.1;
|
||||
const extraDataBoolTest = true;
|
||||
const online = true;
|
||||
const banned = true;
|
||||
const teams = ['team-1', 'team-2'];
|
||||
const createdAtString = '2021-08-03 12:39:21.817646';
|
||||
const updatedAtString = '2021-08-04 12:39:21.817646';
|
||||
const lastActiveString = '2021-08-05 12:39:21.817646';
|
||||
|
||||
group('src/models/user', () {
|
||||
test('should parse json correctly', () {
|
||||
final user = User.fromJson(jsonFixture('user.json'));
|
||||
expect(user.id, 'bbb19d9a-ee50-45bc-84e5-0584e79d0c9e');
|
||||
expect(user.name, 'John');
|
||||
expect(user.id, id);
|
||||
expect(user.role, role);
|
||||
expect(user.name, name);
|
||||
expect(user.image, image);
|
||||
expect(user.extraData['image'], image);
|
||||
expect(user.extraData['extraDataStringTest'], extraDataStringTest);
|
||||
expect(user.extraData['extraDataIntTest'], extraDataIntTest);
|
||||
expect(user.extraData['extraDataDoubleTest'], extraDataDoubleTest);
|
||||
expect(user.extraData['extraDataBoolTest'], extraDataBoolTest);
|
||||
expect(user.online, online);
|
||||
expect(user.banned, banned);
|
||||
expect(user.teams, teams);
|
||||
expect(user.createdAt, DateTime.parse(createdAtString));
|
||||
expect(user.updatedAt, DateTime.parse(updatedAtString));
|
||||
expect(user.lastActive, DateTime.parse(lastActiveString));
|
||||
expect(user.language, 'en');
|
||||
});
|
||||
|
||||
test('should serialize to json correctly', () {
|
||||
final user = User(
|
||||
id: 'bbb19d9a-ee50-45bc-84e5-0584e79d0c9e',
|
||||
role: 'abc',
|
||||
id: id,
|
||||
role: role,
|
||||
name: name,
|
||||
image: image,
|
||||
extraData: const {
|
||||
'extraDataStringTest': extraDataStringTest,
|
||||
'extraDataIntTest': extraDataIntTest,
|
||||
'extraDataDoubleTest': extraDataDoubleTest,
|
||||
'extraDataBoolTest': extraDataBoolTest,
|
||||
},
|
||||
createdAt: DateTime.parse(createdAtString),
|
||||
updatedAt: DateTime.parse(updatedAtString),
|
||||
lastActive: DateTime.parse(lastActiveString),
|
||||
banned: online,
|
||||
online: banned,
|
||||
teams: const ['team-1', 'team-2'],
|
||||
language: 'fr',
|
||||
);
|
||||
|
||||
expect(user.toJson(), {
|
||||
'id': 'bbb19d9a-ee50-45bc-84e5-0584e79d0c9e',
|
||||
'id': id,
|
||||
'name': name,
|
||||
'image': image,
|
||||
'extraDataStringTest': extraDataStringTest,
|
||||
'extraDataIntTest': extraDataIntTest,
|
||||
'extraDataDoubleTest': extraDataDoubleTest,
|
||||
'extraDataBoolTest': extraDataBoolTest,
|
||||
'language': 'fr',
|
||||
});
|
||||
});
|
||||
|
||||
@@ -29,18 +81,141 @@ void main() {
|
||||
expect(newUser.id, user.id);
|
||||
expect(newUser.role, user.role);
|
||||
expect(newUser.name, user.name);
|
||||
expect(newUser.image, user.image);
|
||||
expect(newUser.online, user.online);
|
||||
expect(newUser.banned, user.banned);
|
||||
expect(newUser.teams, user.teams);
|
||||
expect(newUser.createdAt, user.createdAt);
|
||||
expect(newUser.updatedAt, user.updatedAt);
|
||||
expect(newUser.lastActive, user.lastActive);
|
||||
expect(newUser.language, user.language);
|
||||
|
||||
newUser = user.copyWith(
|
||||
id: 'test',
|
||||
role: 'test',
|
||||
extraData: {
|
||||
'name': 'test',
|
||||
},
|
||||
name: 'test',
|
||||
image: 'https://stream.io/new-image',
|
||||
online: false,
|
||||
banned: false,
|
||||
teams: ['new-team1', 'new-team2'],
|
||||
createdAt: DateTime.parse('2021-05-03 12:39:21.817646'),
|
||||
updatedAt: DateTime.parse('2021-05-04 12:39:21.817646'),
|
||||
lastActive: DateTime.parse('2021-05-06 12:39:21.817646'),
|
||||
language: 'it',
|
||||
);
|
||||
|
||||
expect(newUser.id, 'test');
|
||||
expect(newUser.role, 'test');
|
||||
expect(newUser.name, 'test');
|
||||
expect(newUser.image, 'https://stream.io/new-image');
|
||||
expect(newUser.extraData['image'], 'https://stream.io/new-image');
|
||||
expect(newUser.online, false);
|
||||
expect(newUser.banned, false);
|
||||
expect(newUser.teams, ['new-team1', 'new-team2']);
|
||||
expect(newUser.createdAt, DateTime.parse('2021-05-03 12:39:21.817646'));
|
||||
expect(newUser.updatedAt, DateTime.parse('2021-05-04 12:39:21.817646'));
|
||||
expect(newUser.lastActive, DateTime.parse('2021-05-06 12:39:21.817646'));
|
||||
expect(newUser.language, 'it');
|
||||
});
|
||||
|
||||
test('name property and extraData manipulation', () {
|
||||
final user = User(id: id, name: name);
|
||||
|
||||
expect(user.name, name);
|
||||
expect(user.extraData['name'], name);
|
||||
expect(user.toJson(), {'id': id, 'name': name});
|
||||
expect(User.fromJson(user.toJson()).toJson(), {'id': id, 'name': name});
|
||||
|
||||
const nameOne = 'Name One';
|
||||
var newUser = user.copyWith(
|
||||
extraData: {'name': nameOne},
|
||||
);
|
||||
|
||||
expect(newUser.extraData['name'], nameOne);
|
||||
expect(newUser.name, nameOne);
|
||||
|
||||
const nameTwo = 'Name Two';
|
||||
newUser = user.copyWith(
|
||||
name: nameTwo,
|
||||
);
|
||||
|
||||
expect(newUser.extraData['name'], nameTwo);
|
||||
expect(newUser.name, nameTwo);
|
||||
|
||||
const nameThree = 'Name Three';
|
||||
newUser = user.copyWith(
|
||||
name: nameThree,
|
||||
extraData: {'name': nameThree},
|
||||
);
|
||||
|
||||
expect(newUser.extraData['name'], nameThree);
|
||||
expect(newUser.name, nameThree);
|
||||
});
|
||||
|
||||
test('image property and extraData manipulation', () {
|
||||
final user = User(id: id, image: image);
|
||||
|
||||
expect(user.image, image);
|
||||
expect(user.extraData['image'], image);
|
||||
expect(user.toJson(), {'id': id, 'image': image});
|
||||
expect(User.fromJson(user.toJson()).toJson(), {'id': id, 'image': image});
|
||||
|
||||
const imageURLOne = 'https://stream.io/image-one';
|
||||
var newUser = user.copyWith(
|
||||
extraData: {'image': imageURLOne},
|
||||
);
|
||||
|
||||
expect(newUser.extraData['image'], imageURLOne);
|
||||
expect(newUser.image, imageURLOne);
|
||||
|
||||
const imageURLTwo = 'https://stream.io/image-two';
|
||||
newUser = user.copyWith(
|
||||
image: imageURLTwo,
|
||||
);
|
||||
|
||||
expect(newUser.extraData['image'], imageURLTwo);
|
||||
expect(newUser.image, imageURLTwo);
|
||||
|
||||
const imageURLThree = 'https://stream.io/image-three';
|
||||
newUser = user.copyWith(
|
||||
image: imageURLThree,
|
||||
extraData: {'image': imageURLThree},
|
||||
);
|
||||
|
||||
expect(newUser.extraData['image'], imageURLThree);
|
||||
expect(newUser.image, imageURLThree);
|
||||
});
|
||||
|
||||
test('default values, constructor', () {
|
||||
final user = User(id: id);
|
||||
|
||||
expect(user.id, id);
|
||||
expect(user.role, null);
|
||||
expect(user.name, id, reason: 'if a name is not supplied, default to id');
|
||||
expect(user.image, null);
|
||||
expect(user.extraData, const {});
|
||||
expect(user.online, false);
|
||||
expect(user.banned, false);
|
||||
expect(user.teams, []);
|
||||
expect(user.lastActive, null);
|
||||
expect(user.createdAt, isNotNull);
|
||||
expect(user.updatedAt, isNotNull);
|
||||
});
|
||||
|
||||
test('default values, parse json', () {
|
||||
final user = User.fromJson(const {'id': id});
|
||||
|
||||
expect(user.id, id);
|
||||
expect(user.role, null);
|
||||
expect(user.name, id, reason: 'if a name is not supplied, default to id');
|
||||
expect(user.image, null);
|
||||
expect(user.extraData, const {});
|
||||
expect(user.online, false);
|
||||
expect(user.banned, false);
|
||||
expect(user.teams, []);
|
||||
expect(user.lastActive, null);
|
||||
expect(user.createdAt, isNotNull);
|
||||
expect(user.updatedAt, isNotNull);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,3 +1,58 @@
|
||||
## Upcoming
|
||||
|
||||
✅ Added
|
||||
|
||||
- [#516](https://github.com/GetStream/stream-chat-flutter/issues/516):
|
||||
Added `StreamChatThemeData.placeholderUserImage` for building a widget when the `UserAvatar` image
|
||||
is loading
|
||||
- Added a `backgroundColor` property to the following widgets:
|
||||
- `ChannelHeader`
|
||||
- `ChannelListHeader`
|
||||
- `GalleryHeader`
|
||||
- `GalleryFooter`
|
||||
- `ThreadHeader`
|
||||
|
||||
- Added `MessageInput.attachmentButtonBuilder` and `MessageInput.commandButtonBuilder` for more
|
||||
customizations.
|
||||
|
||||
```dart
|
||||
typedef ActionButtonBuilder = Widget Function(
|
||||
BuildContext context,
|
||||
IconButton defaultActionButton,
|
||||
);
|
||||
```
|
||||
|
||||
> **_NOTE:_** The last parameter is the default `ActionButton`
|
||||
You can call `.copyWith` to customize just a subset of properties.
|
||||
|
||||
🔄 Changed
|
||||
|
||||
Theming has been upgraded! Most theme classes now have `InheritedTheme` classes associated with
|
||||
them, and have been upgraded with some goodies like `lerp` functions. Here's the full naming
|
||||
breakdown:
|
||||
|
||||
* `AvatarTheme` is now `AvatarThemeData`
|
||||
* `ChannelHeaderTheme` is now `ChannelHeaderThemeData`
|
||||
* `ChannelListHeaderTheme` is now `ChannelListHeaderThemeData`
|
||||
* `ChannelListViewTheme` is now `ChannelListViewThemeData`
|
||||
* `ChannelPreviewTheme` is now `ChannelPreviewThemeData`
|
||||
* `MessageInputTheme` is now `MessageInputThemeData`
|
||||
* `MessageListViewTheme` is now `MessageListViewTheme`
|
||||
* `MessageSearchListViewTheme` is now `MessageSearchListViewThemeData`
|
||||
* `MessageTheme` is now `MessageThemeData`
|
||||
* `UserListViewTheme` is now `UserListViewThemeData`
|
||||
|
||||
🐞 Fixed
|
||||
|
||||
- Fixed `MessageInput` textField case where `input` is not enabled if the file picked from the camera is null.
|
||||
|
||||
## 2.1.2
|
||||
|
||||
🐞 Fixed
|
||||
|
||||
- [#590](https://github.com/GetStream/stream-chat-flutter/issues/590): livestream use case, no
|
||||
members when sending message
|
||||
|
||||
## 2.1.1
|
||||
|
||||
- Updated core dependency
|
||||
@@ -14,7 +69,8 @@
|
||||
🔄 Changed
|
||||
|
||||
- `StreamChat.of(context).user` is now deprecated in favor of `StreamChat.of(context).currentUser`.
|
||||
- `StreamChat.of(context).userStream` is now deprecated in favor of `StreamChat.of(context).currentUserStream`.
|
||||
- `StreamChat.of(context).userStream` is now deprecated in favor
|
||||
of `StreamChat.of(context).currentUserStream`.
|
||||
|
||||
🐞 Fixed
|
||||
|
||||
@@ -28,17 +84,17 @@
|
||||
- Renamed `ChannelImage` to `ChannelAvatar`
|
||||
- Updated `StreamChatThemeData.reactionIcons` to accept custom builder
|
||||
- Renamed `ColorTheme` properties to reflect the purpose of the colors
|
||||
- `ColorTheme.black` -> `ColorTheme.textHighEmphasis`
|
||||
- `ColorTheme.grey` -> `ColorTheme.textLowEmphasis`
|
||||
- `ColorTheme.greyGainsboro` -> `ColorTheme.disabled`
|
||||
- `ColorTheme.greyWhisper` -> `ColorTheme.borders`
|
||||
- `ColorTheme.whiteSmoke` -> `ColorTheme.inputBg`
|
||||
- `ColorTheme.whiteSnow` -> `ColorTheme.appBg`
|
||||
- `ColorTheme.white` -> `ColorTheme.barsBg`
|
||||
- `ColorTheme.blueAlice` -> `ColorTheme.linkBg`
|
||||
- `ColorTheme.accentBlue` -> `ColorTheme.accentPrimary`
|
||||
- `ColorTheme.accentRed` -> `ColorTheme.accentError`
|
||||
- `ColorTheme.accentGreen` -> `ColorTheme.accentInfo`
|
||||
- `ColorTheme.black` -> `ColorTheme.textHighEmphasis`
|
||||
- `ColorTheme.grey` -> `ColorTheme.textLowEmphasis`
|
||||
- `ColorTheme.greyGainsboro` -> `ColorTheme.disabled`
|
||||
- `ColorTheme.greyWhisper` -> `ColorTheme.borders`
|
||||
- `ColorTheme.whiteSmoke` -> `ColorTheme.inputBg`
|
||||
- `ColorTheme.whiteSnow` -> `ColorTheme.appBg`
|
||||
- `ColorTheme.white` -> `ColorTheme.barsBg`
|
||||
- `ColorTheme.blueAlice` -> `ColorTheme.linkBg`
|
||||
- `ColorTheme.accentBlue` -> `ColorTheme.accentPrimary`
|
||||
- `ColorTheme.accentRed` -> `ColorTheme.accentError`
|
||||
- `ColorTheme.accentGreen` -> `ColorTheme.accentInfo`
|
||||
|
||||
- `ChannelListCore` options property is removed in favor of individual properties
|
||||
- `options.state` -> bool state
|
||||
@@ -59,7 +115,7 @@ typedef MessageBuilder = Widget Function(
|
||||
);
|
||||
```
|
||||
|
||||
the last parameter is the default `MessageWidget`
|
||||
> **_NOTE:_** the last parameter is the default `MessageWidget`
|
||||
You can call `.copyWith` to customize just a subset of properties
|
||||
|
||||
|
||||
@@ -67,7 +123,8 @@ You can call `.copyWith` to customize just a subset of properties
|
||||
|
||||
- Added video compress options (frame and quality) to `MessageInput`
|
||||
- TypingIndicator now has a property called `parentId` to show typing indicator specific to threads
|
||||
- [#493](https://github.com/GetStream/stream-chat-flutter/pull/493): add support for messageListView header/footer
|
||||
- [#493](https://github.com/GetStream/stream-chat-flutter/pull/493): add support for messageListView
|
||||
header/footer
|
||||
- `MessageWidget` accepts a `userAvatarBuilder`
|
||||
- Added pinMessage ui support
|
||||
- Added `MessageListView.threadSeparatorBuilder` property
|
||||
@@ -76,10 +133,12 @@ You can call `.copyWith` to customize just a subset of properties
|
||||
|
||||
🐞 Fixed
|
||||
|
||||
- [#483](https://github.com/GetStream/stream-chat-flutter/issues/483): Keyboard covers input text box when editing
|
||||
message
|
||||
- Modals are shown using the nearest `Navigator` to make using the SDK easier in a nested navigator use case
|
||||
- [#484](https://github.com/GetStream/stream-chat-flutter/issues/484): messages don't update without a reload
|
||||
- [#483](https://github.com/GetStream/stream-chat-flutter/issues/483): Keyboard covers input text
|
||||
box when editing message
|
||||
- Modals are shown using the nearest `Navigator` to make using the SDK easier in a nested navigator
|
||||
use case
|
||||
- [#484](https://github.com/GetStream/stream-chat-flutter/issues/484): messages don't update without
|
||||
a reload
|
||||
- `MessageListView` not rendering if the user is not a member of the channel
|
||||
- Fix `MessageInput` overflow when there are no actions
|
||||
- Minor fixes and improvements
|
||||
@@ -89,17 +148,17 @@ You can call `.copyWith` to customize just a subset of properties
|
||||
🛑️ Breaking Changes from `2.0.0-nullsafety.8`
|
||||
|
||||
- Renamed `ColorTheme` properties to reflect the purpose of the colors
|
||||
- `ColorTheme.black` -> `ColorTheme.textHighEmphasis`
|
||||
- `ColorTheme.grey` -> `ColorTheme.textLowEmphasis`
|
||||
- `ColorTheme.greyGainsboro` -> `ColorTheme.disabled`
|
||||
- `ColorTheme.greyWhisper` -> `ColorTheme.borders`
|
||||
- `ColorTheme.whiteSmoke` -> `ColorTheme.inputBg`
|
||||
- `ColorTheme.whiteSnow` -> `ColorTheme.appBg`
|
||||
- `ColorTheme.white` -> `ColorTheme.barsBg`
|
||||
- `ColorTheme.blueAlice` -> `ColorTheme.linkBg`
|
||||
- `ColorTheme.accentBlue` -> `ColorTheme.accentPrimary`
|
||||
- `ColorTheme.accentRed` -> `ColorTheme.accentError`
|
||||
- `ColorTheme.accentGreen` -> `ColorTheme.accentInfo`
|
||||
- `ColorTheme.black` -> `ColorTheme.textHighEmphasis`
|
||||
- `ColorTheme.grey` -> `ColorTheme.textLowEmphasis`
|
||||
- `ColorTheme.greyGainsboro` -> `ColorTheme.disabled`
|
||||
- `ColorTheme.greyWhisper` -> `ColorTheme.borders`
|
||||
- `ColorTheme.whiteSmoke` -> `ColorTheme.inputBg`
|
||||
- `ColorTheme.whiteSnow` -> `ColorTheme.appBg`
|
||||
- `ColorTheme.white` -> `ColorTheme.barsBg`
|
||||
- `ColorTheme.blueAlice` -> `ColorTheme.linkBg`
|
||||
- `ColorTheme.accentBlue` -> `ColorTheme.accentPrimary`
|
||||
- `ColorTheme.accentRed` -> `ColorTheme.accentError`
|
||||
- `ColorTheme.accentGreen` -> `ColorTheme.accentInfo`
|
||||
|
||||
✅ Added
|
||||
|
||||
@@ -126,21 +185,24 @@ typedef MessageBuilder = Widget Function(
|
||||
);
|
||||
```
|
||||
|
||||
the last parameter is the default `MessageWidget`
|
||||
You can call `.copyWith` to customize just a subset of properties
|
||||
> **_NOTE:_** The last parameter is the default `MessageWidget`
|
||||
You can call `.copyWith` to customize just a subset of properties.
|
||||
|
||||
✅ Added
|
||||
|
||||
- TypingIndicator now has a property called `parentId` to show typing indicator specific to threads
|
||||
- [#493](https://github.com/GetStream/stream-chat-flutter/pull/493): add support for messageListView header/footer
|
||||
- [#493](https://github.com/GetStream/stream-chat-flutter/pull/493): add support for messageListView
|
||||
header/footer
|
||||
- `MessageWidget` accepts a `userAvatarBuilder`
|
||||
|
||||
🐞 Fixed
|
||||
|
||||
- [#483](https://github.com/GetStream/stream-chat-flutter/issues/483): Keyboard covers input text box when editing
|
||||
message
|
||||
- Modals are shown using the nearest `Navigator` to make using the SDK easier in a nested navigator use case
|
||||
- [#484](https://github.com/GetStream/stream-chat-flutter/issues/484): messages don't update without a reload
|
||||
- [#483](https://github.com/GetStream/stream-chat-flutter/issues/483): Keyboard covers input text
|
||||
box when editing message
|
||||
- Modals are shown using the nearest `Navigator` to make using the SDK easier in a nested navigator
|
||||
use case
|
||||
- [#484](https://github.com/GetStream/stream-chat-flutter/issues/484): messages don't update without
|
||||
a reload
|
||||
- `MessageListView` not rendering if the user is not a member of the channel
|
||||
|
||||
## 2.0.0-nullsafety.7
|
||||
@@ -210,7 +272,8 @@ You can call `.copyWith` to customize just a subset of properties
|
||||
- Show error messages as system and keep them in the message input
|
||||
- Remove notification badge logic
|
||||
- Use shimmer while loading images
|
||||
- Polished `StreamChatTheme` adding more options and a new `MessageInputTheme` dedicated to `MessageInput`
|
||||
- Polished `StreamChatTheme` adding more options and a new `MessageInputTheme` dedicated
|
||||
to `MessageInput`
|
||||
- Add possibility to specify custom message actions using `MessageWidget.customActions`
|
||||
- Added `MessageListView.onAttachmentTap` callback
|
||||
- Fixed message newline issue
|
||||
@@ -267,7 +330,8 @@ You can call `.copyWith` to customize just a subset of properties
|
||||
- Improved api documentation
|
||||
- Updated `stream_chat` dependency to `^1.0.0-beta`
|
||||
- Extracted sample app into dedicated [repo](https://github.com/GetStream/flutter-samples)
|
||||
- Reimplemented existing widgets using [stream_chat_flutter_core](https://pub.dev/packages/stream_chat_flutter_core)
|
||||
- Reimplemented existing widgets
|
||||
using [stream_chat_flutter_core](https://pub.dev/packages/stream_chat_flutter_core)
|
||||
|
||||
## 0.2.21
|
||||
|
||||
@@ -284,8 +348,8 @@ You can call `.copyWith` to customize just a subset of properties
|
||||
|
||||
## 0.2.20+2
|
||||
|
||||
- Added `shouldAddChannel` to ChannelsBloc in order to check if a channel has to be added to the list when a new message
|
||||
arrives
|
||||
- Added `shouldAddChannel` to ChannelsBloc in order to check if a channel has to be added to the
|
||||
list when a new message arrives
|
||||
|
||||
## 0.2.20+1
|
||||
|
||||
@@ -319,7 +383,8 @@ You can call `.copyWith` to customize just a subset of properties
|
||||
|
||||
## 0.2.16
|
||||
|
||||
- Do not wrap channel preview builder. Users will have to implement they're custom onTap/onLongPress implementation
|
||||
- Do not wrap channel preview builder. Users will have to implement they're custom onTap/onLongPress
|
||||
implementation
|
||||
- Make public autofocus field of the TextField of message_input
|
||||
|
||||
## 0.2.15
|
||||
@@ -504,10 +569,11 @@ You can call `.copyWith` to customize just a subset of properties
|
||||
|
||||
## 0.2.1-alpha+1
|
||||
|
||||
- Removed the additional `Navigator` in `StreamChat` widget. It was added to make the app have the `StreamChat` widget
|
||||
as ancestor in every route. Now the recommended way to add `StreamChat` to your app is using the `builder` property of
|
||||
your `MaterialApp` widget. Otherwise you can use it in the usual way, but you need to add a `StreamChat` widget to
|
||||
every route of your app. Read [this issue](https://github.com/GetStream/stream-chat-flutter/issues/47) for more
|
||||
- Removed the additional `Navigator` in `StreamChat` widget. It was added to make the app have
|
||||
the `StreamChat` widget as ancestor in every route. Now the recommended way to add `StreamChat` to
|
||||
your app is using the `builder` property of your `MaterialApp` widget. Otherwise you can use it in
|
||||
the usual way, but you need to add a `StreamChat` widget to every route of your app.
|
||||
Read [this issue](https://github.com/GetStream/stream-chat-flutter/issues/47) for more
|
||||
information.
|
||||
|
||||
```dart
|
||||
@@ -525,6 +591,7 @@ Widget build(BuildContext context) {
|
||||
},
|
||||
home: ChannelListPage(),
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
- Fix reaction bubble going below previous message on iOS
|
||||
@@ -608,8 +675,8 @@ Widget build(BuildContext context) {
|
||||
|
||||
- Add gesture (vertical drag down) to close the keyboard
|
||||
|
||||
- Add keyboard type parameters (set it to TextInputType.text to show the submit button that will even close the
|
||||
keyboard)
|
||||
- Add keyboard type parameters (set it to TextInputType.text to show the submit button that will
|
||||
even close the keyboard)
|
||||
|
||||
The property showVideoFullScreen was added mainly because of this issue brianegan/chewie#261
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ void main() async {
|
||||
/// Create a new instance of [StreamChatClient] passing the apikey obtained
|
||||
/// from your project dashboard.
|
||||
final client = StreamChatClient(
|
||||
'kv7mcsxr24p8',
|
||||
's2dxdhpxd94g',
|
||||
logLevel: Level.INFO,
|
||||
);
|
||||
|
||||
@@ -21,19 +21,14 @@ void main() async {
|
||||
/// Please see the following for more information:
|
||||
/// https://getstream.io/chat/docs/ios_user_setup_and_tokens/
|
||||
await client.connectUser(
|
||||
User(id: 'salvatore'),
|
||||
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoic2FsdmF0b3JlIn0.pgiJz7sIc7iP29BHKFwe3nLm5-OaR_1l2P-SlgiC9a8',
|
||||
User(id: 'super-band-9'),
|
||||
'''eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoic3VwZXItYmFuZC05In0.0L6lGoeLwkz0aZRUcpZKsvaXtNEDHBcezVTZ0oPq40A''',
|
||||
);
|
||||
|
||||
final channel = client.channel('messaging', id: 'godevs2', extraData: {
|
||||
'members': ['salvatore'],
|
||||
});
|
||||
final channel = client.channel('messaging', id: 'godevs');
|
||||
|
||||
await channel.watch();
|
||||
|
||||
await channel.enableSlowMode(cooldownInterval: 20);
|
||||
print('Channel cooldown set to ${channel.cooldown}');
|
||||
|
||||
runApp(
|
||||
MyApp(
|
||||
client: client,
|
||||
@@ -78,6 +73,7 @@ class MyApp extends StatelessWidget {
|
||||
Locale('hi'),
|
||||
Locale('fr'),
|
||||
Locale('it'),
|
||||
Locale('es'),
|
||||
],
|
||||
localizationsDelegates: GlobalStreamChatLocalizations.delegates,
|
||||
builder: (context, widget) => StreamChat(
|
||||
|
||||
@@ -118,13 +118,12 @@ class ChannelListPage extends StatelessWidget {
|
||||
channel: channel,
|
||||
),
|
||||
title: ChannelName(
|
||||
textStyle:
|
||||
StreamChatTheme.of(context).channelPreviewTheme.title!.copyWith(
|
||||
color: StreamChatTheme.of(context)
|
||||
.colorTheme
|
||||
.textHighEmphasis
|
||||
.withOpacity(opacity),
|
||||
),
|
||||
textStyle: ChannelPreviewTheme.of(context).titleStyle!.copyWith(
|
||||
color: StreamChatTheme.of(context)
|
||||
.colorTheme
|
||||
.textHighEmphasis
|
||||
.withOpacity(opacity),
|
||||
),
|
||||
),
|
||||
subtitle: Text(subtitle),
|
||||
trailing: channel.state!.unreadCount > 0
|
||||
|
||||
@@ -58,17 +58,17 @@ class MyApp extends StatelessWidget {
|
||||
final defaultTheme = StreamChatThemeData.fromTheme(themeData);
|
||||
final colorTheme = defaultTheme.colorTheme;
|
||||
final customTheme = defaultTheme.merge(StreamChatThemeData(
|
||||
channelPreviewTheme: ChannelPreviewTheme(
|
||||
avatarTheme: AvatarTheme(
|
||||
channelPreviewTheme: ChannelPreviewThemeData(
|
||||
avatarTheme: AvatarThemeData(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
),
|
||||
otherMessageTheme: MessageTheme(
|
||||
otherMessageTheme: MessageThemeData(
|
||||
messageBackgroundColor: colorTheme.textHighEmphasis,
|
||||
messageText: TextStyle(
|
||||
messageTextStyle: TextStyle(
|
||||
color: colorTheme.barsBg,
|
||||
),
|
||||
avatarTheme: AvatarTheme(
|
||||
avatarTheme: AvatarThemeData(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
),
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:stream_chat_flutter/src/theme/themes.dart';
|
||||
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
|
||||
import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart';
|
||||
|
||||
@@ -12,7 +13,7 @@ class AttachmentTitle extends StatelessWidget {
|
||||
}) : super(key: key);
|
||||
|
||||
/// Theme to apply to text
|
||||
final MessageTheme messageTheme;
|
||||
final MessageThemeData messageTheme;
|
||||
|
||||
/// Attachment data to display
|
||||
final Attachment attachment;
|
||||
@@ -34,7 +35,7 @@ class AttachmentTitle extends StatelessWidget {
|
||||
Text(
|
||||
attachment.title!,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
style: messageTheme.messageText?.copyWith(
|
||||
style: messageTheme.messageTextStyle?.copyWith(
|
||||
color: StreamChatTheme.of(context).colorTheme.accentPrimary,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
@@ -50,7 +51,7 @@ class AttachmentTitle extends StatelessWidget {
|
||||
.toList()
|
||||
.reversed
|
||||
.join('.'),
|
||||
style: messageTheme.messageText,
|
||||
style: messageTheme.messageTextStyle,
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
@@ -4,6 +4,7 @@ import 'package:shimmer/shimmer.dart';
|
||||
import 'package:stream_chat_flutter/src/attachment/attachment_title.dart';
|
||||
import 'package:stream_chat_flutter/src/attachment/attachment_upload_state_builder.dart';
|
||||
import 'package:stream_chat_flutter/src/attachment/attachment_widget.dart';
|
||||
import 'package:stream_chat_flutter/src/theme/themes.dart';
|
||||
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
|
||||
import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart';
|
||||
|
||||
@@ -27,8 +28,8 @@ class ImageAttachment extends AttachmentWidget {
|
||||
size: size,
|
||||
);
|
||||
|
||||
/// [MessageTheme] for showing image title
|
||||
final MessageTheme messageTheme;
|
||||
/// [MessageThemeData] for showing image title
|
||||
final MessageThemeData messageTheme;
|
||||
|
||||
/// Flag for showing title
|
||||
final bool showTitle;
|
||||
|
||||
@@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
|
||||
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/src/full_screen_media.dart';
|
||||
import 'package:stream_chat_flutter/src/theme/themes.dart';
|
||||
import 'package:stream_chat_flutter/src/video_thumbnail_image.dart';
|
||||
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
|
||||
|
||||
@@ -24,8 +25,8 @@ class VideoAttachment extends AttachmentWidget {
|
||||
size: size,
|
||||
);
|
||||
|
||||
/// [MessageTheme] for showing title
|
||||
final MessageTheme messageTheme;
|
||||
/// [MessageThemeData] for showing title
|
||||
final MessageThemeData messageTheme;
|
||||
|
||||
/// Callback when show message is tapped
|
||||
final ShowMessageCallback? onShowMessage;
|
||||
|
||||
@@ -19,6 +19,7 @@ class _ChannelBottomSheetState extends State<ChannelBottomSheet> {
|
||||
bool _showActions = true;
|
||||
|
||||
late StreamChannelState _streamChannelState;
|
||||
late ChannelPreviewThemeData _channelPreviewThemeData;
|
||||
late StreamChatThemeData _streamChatThemeData;
|
||||
late StreamChatState _streamChatState;
|
||||
|
||||
@@ -64,8 +65,7 @@ class _ChannelBottomSheetState extends State<ChannelBottomSheet> {
|
||||
child: ChannelInfo(
|
||||
showTypingIndicator: false,
|
||||
channel: _streamChannelState.channel,
|
||||
textStyle:
|
||||
_streamChatThemeData.channelPreviewTheme.subtitle,
|
||||
textStyle: _channelPreviewThemeData.subtitleStyle,
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
@@ -213,6 +213,7 @@ class _ChannelBottomSheetState extends State<ChannelBottomSheet> {
|
||||
void didChangeDependencies() {
|
||||
_streamChannelState = StreamChannel.of(context);
|
||||
_streamChatThemeData = StreamChatTheme.of(context);
|
||||
_channelPreviewThemeData = ChannelPreviewTheme.of(context);
|
||||
_streamChatState = StreamChat.of(context);
|
||||
super.didChangeDependencies();
|
||||
}
|
||||
|
||||
@@ -67,6 +67,7 @@ class ChannelHeader extends StatelessWidget implements PreferredSizeWidget {
|
||||
this.subtitle,
|
||||
this.leading,
|
||||
this.actions,
|
||||
this.backgroundColor,
|
||||
}) : preferredSize = const Size.fromHeight(kToolbarHeight),
|
||||
super(key: key);
|
||||
|
||||
@@ -102,10 +103,13 @@ class ChannelHeader extends StatelessWidget implements PreferredSizeWidget {
|
||||
/// By default it shows the [ChannelAvatar]
|
||||
final List<Widget>? actions;
|
||||
|
||||
/// The background color for this [ChannelHeader].
|
||||
final Color? backgroundColor;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final channel = StreamChannel.of(context).channel;
|
||||
final chatThemeData = StreamChatTheme.of(context);
|
||||
final channelHeaderTheme = ChannelHeaderTheme.of(context);
|
||||
|
||||
final leadingWidget = leading ??
|
||||
(showBackButton
|
||||
@@ -141,18 +145,17 @@ class ChannelHeader extends StatelessWidget implements PreferredSizeWidget {
|
||||
brightness: Theme.of(context).brightness,
|
||||
elevation: 1,
|
||||
leading: leadingWidget,
|
||||
backgroundColor:
|
||||
chatThemeData.channelTheme.channelHeaderTheme.color,
|
||||
backgroundColor: backgroundColor ?? channelHeaderTheme.color,
|
||||
actions: actions ??
|
||||
<Widget>[
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(right: 10),
|
||||
child: Center(
|
||||
child: ChannelAvatar(
|
||||
borderRadius: chatThemeData.channelTheme
|
||||
.channelHeaderTheme.avatarTheme?.borderRadius,
|
||||
constraints: chatThemeData.channelTheme
|
||||
.channelHeaderTheme.avatarTheme?.constraints,
|
||||
borderRadius:
|
||||
channelHeaderTheme.avatarTheme?.borderRadius,
|
||||
constraints:
|
||||
channelHeaderTheme.avatarTheme?.constraints,
|
||||
onTap: onImageTap,
|
||||
),
|
||||
),
|
||||
@@ -169,16 +172,14 @@ class ChannelHeader extends StatelessWidget implements PreferredSizeWidget {
|
||||
children: <Widget>[
|
||||
title ??
|
||||
ChannelName(
|
||||
textStyle: chatThemeData
|
||||
.channelTheme.channelHeaderTheme.title,
|
||||
textStyle: channelHeaderTheme.titleStyle,
|
||||
),
|
||||
const SizedBox(height: 2),
|
||||
subtitle ??
|
||||
ChannelInfo(
|
||||
showTypingIndicator: showTypingIndicator,
|
||||
channel: channel,
|
||||
textStyle: chatThemeData
|
||||
.channelTheme.channelHeaderTheme.subtitle,
|
||||
textStyle: channelHeaderTheme.subtitleStyle,
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
@@ -65,10 +65,7 @@ class ChannelInfo extends StatelessWidget {
|
||||
}
|
||||
alternativeWidget = Text(
|
||||
text,
|
||||
style: StreamChatTheme.of(context)
|
||||
.channelTheme
|
||||
.channelHeaderTheme
|
||||
.subtitle,
|
||||
style: ChannelHeaderTheme.of(context).subtitleStyle,
|
||||
);
|
||||
} else {
|
||||
final userId = StreamChat.of(context).currentUser?.id;
|
||||
|
||||
@@ -2,10 +2,10 @@ import 'dart:ui';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_svg/flutter_svg.dart';
|
||||
import 'package:stream_chat_flutter/src/extension.dart';
|
||||
import 'package:stream_chat_flutter/src/stream_neumorphic_button.dart';
|
||||
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
|
||||
import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart';
|
||||
import 'package:stream_chat_flutter/src/extension.dart';
|
||||
|
||||
/// Widget builder for title
|
||||
typedef TitleBuilder = Widget Function(
|
||||
@@ -46,7 +46,7 @@ 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 [ChannelListHeaderTheme] property.
|
||||
/// [StreamChatTheme] and on its [ChannelListHeaderThemeData] property.
|
||||
/// Modify it to change the widget appearance.
|
||||
class ChannelListHeader extends StatelessWidget implements PreferredSizeWidget {
|
||||
/// Instantiates a ChannelListHeader
|
||||
@@ -61,6 +61,7 @@ class ChannelListHeader extends StatelessWidget implements PreferredSizeWidget {
|
||||
this.subtitle,
|
||||
this.leading,
|
||||
this.actions,
|
||||
this.backgroundColor,
|
||||
}) : super(key: key);
|
||||
|
||||
/// Pass this if you don't have a [StreamChatClient] in your widget tree.
|
||||
@@ -93,6 +94,9 @@ class ChannelListHeader extends StatelessWidget implements PreferredSizeWidget {
|
||||
/// By default it shows the new chat button
|
||||
final List<Widget>? actions;
|
||||
|
||||
/// The background color for this [ChannelListHeader].
|
||||
final Color? backgroundColor;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final _client = client ?? StreamChat.of(context).client;
|
||||
@@ -116,6 +120,7 @@ class ChannelListHeader extends StatelessWidget implements PreferredSizeWidget {
|
||||
}
|
||||
|
||||
final chatThemeData = StreamChatTheme.of(context);
|
||||
final channelListHeaderThemeData = ChannelListHeaderTheme.of(context);
|
||||
return InfoTile(
|
||||
showMessage: showConnectionStateTile && showStatus,
|
||||
message: statusString,
|
||||
@@ -123,7 +128,8 @@ class ChannelListHeader extends StatelessWidget implements PreferredSizeWidget {
|
||||
textTheme: Theme.of(context).textTheme,
|
||||
brightness: Theme.of(context).brightness,
|
||||
elevation: 1,
|
||||
backgroundColor: chatThemeData.channelListHeaderTheme.color,
|
||||
backgroundColor:
|
||||
backgroundColor ?? channelListHeaderThemeData.color,
|
||||
centerTitle: true,
|
||||
leading: leading ??
|
||||
Center(
|
||||
@@ -138,10 +144,10 @@ class ChannelListHeader extends StatelessWidget implements PreferredSizeWidget {
|
||||
}
|
||||
Scaffold.of(context).openDrawer();
|
||||
},
|
||||
borderRadius: chatThemeData
|
||||
.channelListHeaderTheme.avatarTheme?.borderRadius,
|
||||
constraints: chatThemeData
|
||||
.channelListHeaderTheme.avatarTheme?.constraints,
|
||||
borderRadius: channelListHeaderThemeData
|
||||
.avatarTheme?.borderRadius,
|
||||
constraints: channelListHeaderThemeData
|
||||
.avatarTheme?.constraints,
|
||||
)
|
||||
: const Offstage(),
|
||||
),
|
||||
@@ -227,10 +233,7 @@ class ChannelListHeader extends StatelessWidget implements PreferredSizeWidget {
|
||||
const SizedBox(width: 10),
|
||||
Text(
|
||||
context.translations.searchingForNetworkText,
|
||||
style: StreamChatTheme.of(context)
|
||||
.channelListHeaderTheme
|
||||
.title
|
||||
?.copyWith(
|
||||
style: ChannelListHeaderTheme.of(context).titleStyle?.copyWith(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
@@ -243,12 +246,13 @@ class ChannelListHeader extends StatelessWidget implements PreferredSizeWidget {
|
||||
StreamChatClient client,
|
||||
) {
|
||||
final chatThemeData = StreamChatTheme.of(context);
|
||||
final channelListHeaderTheme = ChannelListHeaderTheme.of(context);
|
||||
return Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Text(
|
||||
context.translations.offlineLabel,
|
||||
style: chatThemeData.channelListHeaderTheme.title?.copyWith(
|
||||
style: channelListHeaderTheme.titleStyle?.copyWith(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
@@ -259,7 +263,7 @@ class ChannelListHeader extends StatelessWidget implements PreferredSizeWidget {
|
||||
..openConnection(),
|
||||
child: Text(
|
||||
context.translations.tryAgainLabel,
|
||||
style: chatThemeData.channelListHeaderTheme.title?.copyWith(
|
||||
style: channelListHeaderTheme.titleStyle?.copyWith(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: chatThemeData.colorTheme.accentPrimary,
|
||||
|
||||
@@ -5,6 +5,7 @@ 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/stream_svg_icon.dart';
|
||||
import 'package:stream_chat_flutter/src/theme/themes.dart';
|
||||
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
|
||||
import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart';
|
||||
import 'package:stream_chat_flutter/src/extension.dart';
|
||||
|
||||
@@ -3,10 +3,10 @@ import 'package:collection/collection.dart'
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:jiffy/jiffy.dart';
|
||||
import 'package:stream_chat_flutter/src/extension.dart';
|
||||
import 'package:stream_chat_flutter/src/stream_svg_icon.dart';
|
||||
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
|
||||
import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart';
|
||||
import 'package:stream_chat_flutter/src/extension.dart';
|
||||
|
||||
/// 
|
||||
/// 
|
||||
@@ -69,7 +69,7 @@ class ChannelPreview extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final channelPreviewTheme = StreamChatTheme.of(context).channelPreviewTheme;
|
||||
final channelPreviewTheme = ChannelPreviewTheme.of(context);
|
||||
final streamChatState = StreamChat.of(context);
|
||||
return BetterStreamBuilder<bool>(
|
||||
stream: channel.isMutedStream,
|
||||
@@ -91,7 +91,7 @@ class ChannelPreview extends StatelessWidget {
|
||||
Flexible(
|
||||
child: title ??
|
||||
ChannelName(
|
||||
textStyle: channelPreviewTheme.title,
|
||||
textStyle: channelPreviewTheme.titleStyle,
|
||||
),
|
||||
),
|
||||
BetterStreamBuilder<List<Member>?>(
|
||||
@@ -183,14 +183,13 @@ class ChannelPreview extends StatelessWidget {
|
||||
|
||||
return Text(
|
||||
stringDate,
|
||||
style:
|
||||
StreamChatTheme.of(context).channelPreviewTheme.lastMessageAt,
|
||||
style: ChannelPreviewTheme.of(context).lastMessageAtStyle,
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
Widget _buildSubtitle(BuildContext context) {
|
||||
final chatThemeData = StreamChatTheme.of(context);
|
||||
final channelPreviewTheme = ChannelPreviewTheme.of(context);
|
||||
if (channel.isMuted) {
|
||||
return Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.end,
|
||||
@@ -200,7 +199,7 @@ class ChannelPreview extends StatelessWidget {
|
||||
),
|
||||
Text(
|
||||
' ${context.translations.channelIsMutedText}',
|
||||
style: chatThemeData.channelPreviewTheme.subtitle,
|
||||
style: channelPreviewTheme.subtitleStyle,
|
||||
),
|
||||
],
|
||||
);
|
||||
@@ -208,7 +207,7 @@ class ChannelPreview extends StatelessWidget {
|
||||
return TypingIndicator(
|
||||
channel: channel,
|
||||
alternativeWidget: _buildLastMessage(context),
|
||||
style: chatThemeData.channelPreviewTheme.subtitle,
|
||||
style: channelPreviewTheme.subtitleStyle,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -243,19 +242,19 @@ class ChannelPreview extends StatelessWidget {
|
||||
|
||||
text = parts.join(' ');
|
||||
|
||||
final chatThemeData = StreamChatTheme.of(context);
|
||||
final channelPreviewTheme = ChannelPreviewTheme.of(context);
|
||||
return Text.rich(
|
||||
_getDisplayText(
|
||||
text,
|
||||
lastMessage.mentionedUsers,
|
||||
lastMessage.attachments,
|
||||
chatThemeData.channelPreviewTheme.subtitle?.copyWith(
|
||||
color: chatThemeData.channelPreviewTheme.subtitle?.color,
|
||||
channelPreviewTheme.subtitleStyle?.copyWith(
|
||||
color: channelPreviewTheme.subtitleStyle?.color,
|
||||
fontStyle: (lastMessage.isSystem || lastMessage.isDeleted)
|
||||
? FontStyle.italic
|
||||
: FontStyle.normal),
|
||||
chatThemeData.channelPreviewTheme.subtitle?.copyWith(
|
||||
color: chatThemeData.channelPreviewTheme.subtitle?.color,
|
||||
channelPreviewTheme.subtitleStyle?.copyWith(
|
||||
color: channelPreviewTheme.subtitleStyle?.color,
|
||||
fontStyle: (lastMessage.isSystem || lastMessage.isDeleted)
|
||||
? FontStyle.italic
|
||||
: FontStyle.normal,
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:stream_chat_flutter/src/stream_chat_theme.dart';
|
||||
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 {
|
||||
@@ -15,7 +16,7 @@ class DeletedMessage extends StatelessWidget {
|
||||
}) : super(key: key);
|
||||
|
||||
/// The theme of the message
|
||||
final MessageTheme messageTheme;
|
||||
final MessageThemeData messageTheme;
|
||||
|
||||
/// The border radius of the message text
|
||||
final BorderRadiusGeometry? borderRadiusGeometry;
|
||||
@@ -51,9 +52,9 @@ class DeletedMessage extends StatelessWidget {
|
||||
),
|
||||
child: Text(
|
||||
context.translations.messageDeletedLabel,
|
||||
style: messageTheme.messageText?.copyWith(
|
||||
style: messageTheme.messageTextStyle?.copyWith(
|
||||
fontStyle: FontStyle.italic,
|
||||
color: messageTheme.createdAt?.color,
|
||||
color: messageTheme.createdAtStyle?.color,
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
@@ -46,9 +46,9 @@ extension PlatformFileX on PlatformFile {
|
||||
);
|
||||
}
|
||||
|
||||
///
|
||||
/// Extension on [InputDecoration]
|
||||
extension InputDecorationX on InputDecoration {
|
||||
///
|
||||
/// Merges this [InputDecoration] with the [other]
|
||||
InputDecoration merge(InputDecoration? other) {
|
||||
if (other == null) return this;
|
||||
return copyWith(
|
||||
@@ -123,3 +123,50 @@ extension FlipBorder on BorderRadius {
|
||||
bottomRight: bottomLeft)
|
||||
: this;
|
||||
}
|
||||
|
||||
/// Extension on [IconButton]
|
||||
extension IconButtonX on IconButton {
|
||||
/// Creates a copy of [IconButton] with specified attributes overridden.
|
||||
IconButton copyWith({
|
||||
double? iconSize,
|
||||
VisualDensity? visualDensity,
|
||||
EdgeInsetsGeometry? padding,
|
||||
AlignmentGeometry? alignment,
|
||||
double? splashRadius,
|
||||
Color? color,
|
||||
Color? focusColor,
|
||||
Color? hoverColor,
|
||||
Color? highlightColor,
|
||||
Color? splashColor,
|
||||
Color? disabledColor,
|
||||
void Function()? onPressed,
|
||||
MouseCursor? mouseCursor,
|
||||
FocusNode? focusNode,
|
||||
bool? autofocus,
|
||||
String? tooltip,
|
||||
bool? enableFeedback,
|
||||
BoxConstraints? constraints,
|
||||
Widget? icon,
|
||||
}) =>
|
||||
IconButton(
|
||||
iconSize: iconSize ?? this.iconSize,
|
||||
visualDensity: visualDensity ?? this.visualDensity,
|
||||
padding: padding ?? this.padding,
|
||||
alignment: alignment ?? this.alignment,
|
||||
splashRadius: splashRadius ?? this.splashRadius,
|
||||
color: color ?? this.color,
|
||||
focusColor: focusColor ?? this.focusColor,
|
||||
hoverColor: hoverColor ?? this.hoverColor,
|
||||
highlightColor: highlightColor ?? this.highlightColor,
|
||||
splashColor: splashColor ?? this.splashColor,
|
||||
disabledColor: disabledColor ?? this.disabledColor,
|
||||
onPressed: onPressed ?? this.onPressed,
|
||||
mouseCursor: mouseCursor ?? this.mouseCursor,
|
||||
focusNode: focusNode ?? this.focusNode,
|
||||
autofocus: autofocus ?? this.autofocus,
|
||||
tooltip: tooltip ?? this.tooltip,
|
||||
enableFeedback: enableFeedback ?? this.enableFeedback,
|
||||
constraints: constraints ?? this.constraints,
|
||||
icon: icon ?? this.icon,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -124,10 +124,7 @@ class _FullScreenMediaState extends State<FullScreenMedia>
|
||||
),
|
||||
backgroundDecoration: BoxDecoration(
|
||||
color: ColorTween(
|
||||
begin: StreamChatTheme.of(context)
|
||||
.channelTheme
|
||||
.channelHeaderTheme
|
||||
.color,
|
||||
begin: ChannelHeaderTheme.of(context).color,
|
||||
end: Colors.black,
|
||||
).lerp(_controller.value),
|
||||
),
|
||||
|
||||
@@ -8,6 +8,7 @@ import 'package:flutter/material.dart';
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
import 'package:share_plus/share_plus.dart';
|
||||
import 'package:stream_chat_flutter/src/stream_chat_theme.dart';
|
||||
import 'package:stream_chat_flutter/src/theme/themes.dart';
|
||||
import 'package:stream_chat_flutter/src/video_thumbnail_image.dart';
|
||||
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
|
||||
import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart';
|
||||
@@ -26,6 +27,7 @@ class GalleryFooter extends StatefulWidget implements PreferredSizeWidget {
|
||||
this.totalPages = 0,
|
||||
this.mediaAttachments = const [],
|
||||
this.mediaSelectedCallBack,
|
||||
this.backgroundColor,
|
||||
}) : preferredSize = const Size.fromHeight(kToolbarHeight),
|
||||
super(key: key);
|
||||
|
||||
@@ -54,6 +56,9 @@ class GalleryFooter extends StatefulWidget implements PreferredSizeWidget {
|
||||
/// Callback when media is selected
|
||||
final ValueChanged<int>? mediaSelectedCallBack;
|
||||
|
||||
/// The background color of this [GalleryFooter].
|
||||
final Color? backgroundColor;
|
||||
|
||||
@override
|
||||
_GalleryFooterState createState() => _GalleryFooterState();
|
||||
|
||||
@@ -89,7 +94,8 @@ class _GalleryFooterState extends State<GalleryFooter> {
|
||||
context: context,
|
||||
removeTop: true,
|
||||
child: BottomAppBar(
|
||||
color: galleryFooterThemeData.backgroundColor,
|
||||
color:
|
||||
widget.backgroundColor ?? galleryFooterThemeData.backgroundColor,
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
|
||||
@@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
|
||||
import 'package:stream_chat_flutter/src/attachment_actions_modal.dart';
|
||||
import 'package:stream_chat_flutter/src/stream_chat_theme.dart';
|
||||
import 'package:stream_chat_flutter/src/stream_svg_icon.dart';
|
||||
import 'package:stream_chat_flutter/src/theme/themes.dart';
|
||||
import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart';
|
||||
|
||||
/// Header/AppBar widget for media display screen
|
||||
@@ -18,6 +19,7 @@ class GalleryHeader extends StatelessWidget implements PreferredSizeWidget {
|
||||
this.onImageTap,
|
||||
this.userName = '',
|
||||
this.sentAt = '',
|
||||
this.backgroundColor,
|
||||
}) : preferredSize = const Size.fromHeight(kToolbarHeight),
|
||||
super(key: key);
|
||||
|
||||
@@ -49,6 +51,9 @@ class GalleryHeader extends StatelessWidget implements PreferredSizeWidget {
|
||||
/// Stores the current index of media shown
|
||||
final int currentIndex;
|
||||
|
||||
/// The background color of this [GalleryHeader].
|
||||
final Color? backgroundColor;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final galleryHeaderThemeData = GalleryHeaderTheme.of(context);
|
||||
@@ -65,7 +70,8 @@ class GalleryHeader extends StatelessWidget implements PreferredSizeWidget {
|
||||
onPressed: onBackPressed,
|
||||
)
|
||||
: const SizedBox(),
|
||||
backgroundColor: galleryHeaderThemeData.backgroundColor,
|
||||
backgroundColor:
|
||||
backgroundColor ?? galleryHeaderThemeData.backgroundColor,
|
||||
actions: <Widget>[
|
||||
if (!message.isEphemeral)
|
||||
IconButton(
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:stream_chat_flutter/src/full_screen_media.dart';
|
||||
import 'package:stream_chat_flutter/src/theme/themes.dart';
|
||||
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
|
||||
import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart';
|
||||
|
||||
@@ -25,8 +26,8 @@ class ImageGroup extends StatelessWidget {
|
||||
/// Message which images are attached to
|
||||
final Message message;
|
||||
|
||||
/// [MessageTheme] to apply to message
|
||||
final MessageTheme messageTheme;
|
||||
/// [MessageThemeData] to apply to message
|
||||
final MessageThemeData messageTheme;
|
||||
|
||||
/// Size of iamges
|
||||
final Size size;
|
||||
|
||||
@@ -6,6 +6,7 @@ import 'package:stream_chat_flutter/src/extension.dart';
|
||||
import 'package:stream_chat_flutter/src/message_action.dart';
|
||||
import 'package:stream_chat_flutter/src/reaction_picker.dart';
|
||||
import 'package:stream_chat_flutter/src/stream_svg_icon.dart';
|
||||
import 'package:stream_chat_flutter/src/theme/themes.dart';
|
||||
import 'package:stream_chat_flutter/src/utils.dart';
|
||||
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
|
||||
|
||||
@@ -49,8 +50,8 @@ class MessageActionsModal extends StatefulWidget {
|
||||
/// Message in focus for actions
|
||||
final Message message;
|
||||
|
||||
/// [MessageTheme] for message
|
||||
final MessageTheme messageTheme;
|
||||
/// [MessageThemeData] for message
|
||||
final MessageThemeData messageTheme;
|
||||
|
||||
/// Flag for showing reactions
|
||||
final bool showReactions;
|
||||
@@ -116,7 +117,7 @@ class _MessageActionsModalState extends State<MessageActionsModal> {
|
||||
}
|
||||
}
|
||||
final roughSentenceSize = messageTextLength *
|
||||
(widget.messageTheme.messageText?.fontSize ?? 1) *
|
||||
(widget.messageTheme.messageTextStyle?.fontSize ?? 1) *
|
||||
1.2;
|
||||
final divFactor = widget.message.attachments.isNotEmpty == true
|
||||
? 1
|
||||
@@ -565,7 +566,7 @@ class _MessageActionsModalState extends State<MessageActionsModal> {
|
||||
elevation: 2,
|
||||
clipBehavior: Clip.hardEdge,
|
||||
isScrollControlled: true,
|
||||
backgroundColor: streamChatThemeData.messageInputTheme.inputBackground,
|
||||
backgroundColor: MessageInputTheme.of(context).inputBackgroundColor,
|
||||
shape: const RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.only(
|
||||
topLeft: Radius.circular(16),
|
||||
|
||||
@@ -50,6 +50,15 @@ typedef MentionTileBuilder = Widget Function(
|
||||
Member member,
|
||||
);
|
||||
|
||||
/// Widget builder for action button.
|
||||
///
|
||||
/// [defaultActionButton] is the default [IconButton] configuration,
|
||||
/// use [defaultActionButton.copyWith] to easily customize it.
|
||||
typedef ActionButtonBuilder = Widget Function(
|
||||
BuildContext context,
|
||||
IconButton defaultActionButton,
|
||||
);
|
||||
|
||||
/// Location for actions on the [MessageInput]
|
||||
enum ActionsLocation {
|
||||
/// Align to left
|
||||
@@ -164,6 +173,8 @@ class MessageInput extends StatefulWidget {
|
||||
this.compressedVideoQuality = VideoQuality.DefaultQuality,
|
||||
this.compressedVideoFrameRate = 30,
|
||||
this.onError,
|
||||
this.attachmentButtonBuilder,
|
||||
this.commandButtonBuilder,
|
||||
}) : super(key: key);
|
||||
|
||||
/// Message to edit
|
||||
@@ -247,6 +258,18 @@ class MessageInput extends StatefulWidget {
|
||||
/// A callback for error reporting
|
||||
final ErrorListener? onError;
|
||||
|
||||
/// Builder for customizing the attachment button.
|
||||
///
|
||||
/// The builder contains the default [IconButton] that can be customized by
|
||||
/// calling `.copyWith`.
|
||||
final ActionButtonBuilder? attachmentButtonBuilder;
|
||||
|
||||
/// Builder for customizing the command button.
|
||||
///
|
||||
/// The builder contains the default [IconButton] that can be customized by
|
||||
/// calling `.copyWith`.
|
||||
final ActionButtonBuilder? commandButtonBuilder;
|
||||
|
||||
@override
|
||||
MessageInputState createState() => MessageInputState();
|
||||
|
||||
@@ -288,6 +311,7 @@ class MessageInputState extends State<MessageInput> {
|
||||
late final TextEditingController textEditingController;
|
||||
|
||||
late StreamChatThemeData _streamChatTheme;
|
||||
late MessageInputThemeData _messageInputTheme;
|
||||
|
||||
bool get _hasQuotedMessage => widget.quotedMessage != null;
|
||||
|
||||
@@ -351,7 +375,7 @@ class MessageInputState extends State<MessageInput> {
|
||||
Widget build(BuildContext context) {
|
||||
Widget child = DecoratedBox(
|
||||
decoration: BoxDecoration(
|
||||
color: _streamChatTheme.messageInputTheme.inputBackground,
|
||||
color: _messageInputTheme.inputBackgroundColor,
|
||||
),
|
||||
child: SafeArea(
|
||||
child: GestureDetector(
|
||||
@@ -425,11 +449,11 @@ class MessageInputState extends State<MessageInput> {
|
||||
children: <Widget>[
|
||||
if (!_commandEnabled &&
|
||||
widget.actionsLocation == ActionsLocation.left)
|
||||
_buildExpandActionsButton(),
|
||||
_buildExpandActionsButton(context),
|
||||
_buildTextInput(context),
|
||||
if (!_commandEnabled &&
|
||||
widget.actionsLocation == ActionsLocation.right)
|
||||
_buildExpandActionsButton(),
|
||||
_buildExpandActionsButton(context),
|
||||
if (widget.sendButtonLocation == SendButtonLocation.outside)
|
||||
_animateSendButton(context),
|
||||
],
|
||||
@@ -538,12 +562,12 @@ class MessageInputState extends State<MessageInput> {
|
||||
: CrossFadeState.showSecond,
|
||||
firstChild: sendButton,
|
||||
secondChild: widget.idleSendButton ?? _buildIdleSendButton(context),
|
||||
duration: _streamChatTheme.messageInputTheme.sendAnimationDuration!,
|
||||
duration: _messageInputTheme.sendAnimationDuration!,
|
||||
alignment: Alignment.center,
|
||||
);*/
|
||||
}
|
||||
|
||||
Widget _buildExpandActionsButton() {
|
||||
Widget _buildExpandActionsButton(BuildContext context) {
|
||||
final channel = StreamChannel.of(context).channel;
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8),
|
||||
@@ -563,7 +587,7 @@ class MessageInputState extends State<MessageInput> {
|
||||
? pi
|
||||
: 0,
|
||||
child: StreamSvgIcon.emptyCircleLeft(
|
||||
color: _streamChatTheme.messageInputTheme.expandButtonColor,
|
||||
color: _messageInputTheme.expandButtonColor,
|
||||
),
|
||||
),
|
||||
padding: const EdgeInsets.all(0),
|
||||
@@ -581,12 +605,13 @@ class MessageInputState extends State<MessageInput> {
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||
children: <Widget>[
|
||||
if (!widget.disableAttachments) _buildAttachmentButton(),
|
||||
if (!widget.disableAttachments)
|
||||
_buildAttachmentButton(context),
|
||||
if (widget.showCommandsButton &&
|
||||
widget.editMessage == null &&
|
||||
channel.state != null &&
|
||||
channel.config?.commands.isNotEmpty == true)
|
||||
_buildCommandButton(),
|
||||
_buildCommandButton(context),
|
||||
...widget.actions ?? [],
|
||||
].insertBetween(const SizedBox(width: 8)),
|
||||
),
|
||||
@@ -609,17 +634,17 @@ class MessageInputState extends State<MessageInput> {
|
||||
clipBehavior: Clip.hardEdge,
|
||||
margin: margin,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: _streamChatTheme.messageInputTheme.borderRadius,
|
||||
borderRadius: _messageInputTheme.borderRadius,
|
||||
gradient: _focusNode.hasFocus
|
||||
? _streamChatTheme.messageInputTheme.activeBorderGradient
|
||||
: _streamChatTheme.messageInputTheme.idleBorderGradient,
|
||||
? _messageInputTheme.activeBorderGradient
|
||||
: _messageInputTheme.idleBorderGradient,
|
||||
),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(1.5),
|
||||
child: DecoratedBox(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: _streamChatTheme.messageInputTheme.borderRadius,
|
||||
color: _streamChatTheme.messageInputTheme.inputBackground,
|
||||
borderRadius: _messageInputTheme.borderRadius,
|
||||
color: _messageInputTheme.inputBackgroundColor,
|
||||
),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
@@ -637,7 +662,7 @@ class MessageInputState extends State<MessageInput> {
|
||||
keyboardType: widget.keyboardType,
|
||||
controller: textEditingController,
|
||||
focusNode: _focusNode,
|
||||
style: _streamChatTheme.messageInputTheme.inputTextStyle,
|
||||
style: _messageInputTheme.inputTextStyle,
|
||||
autofocus: widget.autofocus,
|
||||
textAlignVertical: TextAlignVertical.center,
|
||||
decoration: _getInputDecoration(context),
|
||||
@@ -653,11 +678,11 @@ class MessageInputState extends State<MessageInput> {
|
||||
}
|
||||
|
||||
InputDecoration _getInputDecoration(BuildContext context) {
|
||||
final passedDecoration = _streamChatTheme.messageInputTheme.inputDecoration;
|
||||
final passedDecoration = _messageInputTheme.inputDecoration;
|
||||
return InputDecoration(
|
||||
isDense: true,
|
||||
hintText: _getHint(context),
|
||||
hintStyle: _streamChatTheme.messageInputTheme.inputTextStyle!.copyWith(
|
||||
hintStyle: _messageInputTheme.inputTextStyle!.copyWith(
|
||||
color: _streamChatTheme.colorTheme.textLowEmphasis,
|
||||
),
|
||||
border: const OutlineInputBorder(
|
||||
@@ -722,9 +747,7 @@ class MessageInputState extends State<MessageInput> {
|
||||
: (widget.actionsLocation == ActionsLocation.leftInside
|
||||
? Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
_buildExpandActionsButton(),
|
||||
],
|
||||
children: [_buildExpandActionsButton(context)],
|
||||
)
|
||||
: null),
|
||||
suffixIconConstraints: const BoxConstraints.tightFor(height: 40),
|
||||
@@ -750,7 +773,7 @@ class MessageInputState extends State<MessageInput> {
|
||||
),
|
||||
if (!_commandEnabled &&
|
||||
widget.actionsLocation == ActionsLocation.rightInside)
|
||||
_buildExpandActionsButton(),
|
||||
_buildExpandActionsButton(context),
|
||||
if (widget.sendButtonLocation == SendButtonLocation.inside)
|
||||
_animateSendButton(context),
|
||||
],
|
||||
@@ -1734,16 +1757,15 @@ class MessageInputState extends State<MessageInput> {
|
||||
}
|
||||
}
|
||||
|
||||
Widget _buildCommandButton() {
|
||||
Widget _buildCommandButton(BuildContext context) {
|
||||
final s = textEditingController.text.trim();
|
||||
|
||||
return IconButton(
|
||||
final defaultButton = IconButton(
|
||||
icon: StreamSvgIcon.lightning(
|
||||
color: s.isNotEmpty
|
||||
? _streamChatTheme.colorTheme.disabled
|
||||
: (_commandsOverlay != null
|
||||
? _streamChatTheme.messageInputTheme.actionButtonColor
|
||||
: _streamChatTheme.messageInputTheme.actionButtonIdleColor),
|
||||
? _messageInputTheme.actionButtonColor
|
||||
: _messageInputTheme.actionButtonIdleColor),
|
||||
),
|
||||
padding: const EdgeInsets.all(0),
|
||||
constraints: const BoxConstraints.tightFor(
|
||||
@@ -1775,38 +1797,46 @@ class MessageInputState extends State<MessageInput> {
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
return widget.commandButtonBuilder?.call(context, defaultButton) ??
|
||||
defaultButton;
|
||||
}
|
||||
|
||||
Widget _buildAttachmentButton() => IconButton(
|
||||
icon: StreamSvgIcon.attach(
|
||||
color: _openFilePickerSection
|
||||
? _streamChatTheme.messageInputTheme.actionButtonColor
|
||||
: _streamChatTheme.messageInputTheme.actionButtonIdleColor,
|
||||
),
|
||||
padding: const EdgeInsets.all(0),
|
||||
constraints: const BoxConstraints.tightFor(
|
||||
height: 24,
|
||||
width: 24,
|
||||
),
|
||||
splashRadius: 24,
|
||||
onPressed: () async {
|
||||
_emojiOverlay?.remove();
|
||||
_emojiOverlay = null;
|
||||
_commandsOverlay?.remove();
|
||||
_commandsOverlay = null;
|
||||
_mentionsOverlay?.remove();
|
||||
_mentionsOverlay = null;
|
||||
Widget _buildAttachmentButton(BuildContext context) {
|
||||
final defaultButton = IconButton(
|
||||
icon: StreamSvgIcon.attach(
|
||||
color: _openFilePickerSection
|
||||
? _messageInputTheme.actionButtonColor
|
||||
: _messageInputTheme.actionButtonIdleColor,
|
||||
),
|
||||
padding: const EdgeInsets.all(0),
|
||||
constraints: const BoxConstraints.tightFor(
|
||||
height: 24,
|
||||
width: 24,
|
||||
),
|
||||
splashRadius: 24,
|
||||
onPressed: () async {
|
||||
_emojiOverlay?.remove();
|
||||
_emojiOverlay = null;
|
||||
_commandsOverlay?.remove();
|
||||
_commandsOverlay = null;
|
||||
_mentionsOverlay?.remove();
|
||||
_mentionsOverlay = null;
|
||||
|
||||
if (_openFilePickerSection) {
|
||||
setState(() {
|
||||
_openFilePickerSection = false;
|
||||
_filePickerSize = _kMinMediaPickerSize;
|
||||
});
|
||||
} else {
|
||||
showAttachmentModal();
|
||||
}
|
||||
},
|
||||
);
|
||||
if (_openFilePickerSection) {
|
||||
setState(() {
|
||||
_openFilePickerSection = false;
|
||||
_filePickerSize = _kMinMediaPickerSize;
|
||||
});
|
||||
} else {
|
||||
showAttachmentModal();
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
return widget.attachmentButtonBuilder?.call(context, defaultButton) ??
|
||||
defaultButton;
|
||||
}
|
||||
|
||||
/// Show the attachment modal, making the user choose where to
|
||||
/// pick a media from
|
||||
@@ -1922,15 +1952,14 @@ class MessageInputState extends State<MessageInput> {
|
||||
} else if (fileType == DefaultAttachmentTypes.video) {
|
||||
pickedFile = await _imagePicker.pickVideo(source: ImageSource.camera);
|
||||
}
|
||||
if (pickedFile == null) {
|
||||
return;
|
||||
if (pickedFile != null) {
|
||||
final bytes = await pickedFile.readAsBytes();
|
||||
file = AttachmentFile(
|
||||
size: bytes.length,
|
||||
path: pickedFile.path,
|
||||
bytes: bytes,
|
||||
);
|
||||
}
|
||||
final bytes = await pickedFile.readAsBytes();
|
||||
file = AttachmentFile(
|
||||
size: bytes.length,
|
||||
path: pickedFile.path,
|
||||
bytes: bytes,
|
||||
);
|
||||
} else {
|
||||
late FileType type;
|
||||
if (fileType == DefaultAttachmentTypes.image) {
|
||||
@@ -2017,7 +2046,7 @@ class MessageInputState extends State<MessageInput> {
|
||||
padding: const EdgeInsets.all(8),
|
||||
child: StreamSvgIcon(
|
||||
assetName: _getIdleSendIcon(),
|
||||
color: _streamChatTheme.messageInputTheme.sendButtonIdleColor,
|
||||
color: _messageInputTheme.sendButtonIdleColor,
|
||||
),
|
||||
);
|
||||
|
||||
@@ -2033,7 +2062,7 @@ class MessageInputState extends State<MessageInput> {
|
||||
),
|
||||
icon: StreamSvgIcon(
|
||||
assetName: _getSendIcon(),
|
||||
color: _streamChatTheme.messageInputTheme.sendButtonColor,
|
||||
color: _messageInputTheme.sendButtonColor,
|
||||
),
|
||||
),
|
||||
);
|
||||
@@ -2241,6 +2270,7 @@ class MessageInputState extends State<MessageInput> {
|
||||
@override
|
||||
void didChangeDependencies() {
|
||||
_streamChatTheme = StreamChatTheme.of(context);
|
||||
_messageInputTheme = MessageInputTheme.of(context);
|
||||
if (widget.editMessage != null && !_initialized) {
|
||||
FocusScope.of(context).requestFocus(_focusNode);
|
||||
_initialized = true;
|
||||
|
||||
@@ -14,6 +14,7 @@ import 'package:stream_chat_flutter/src/message_widget.dart';
|
||||
import 'package:stream_chat_flutter/src/stream_svg_icon.dart';
|
||||
import 'package:stream_chat_flutter/src/swipeable.dart';
|
||||
import 'package:stream_chat_flutter/src/system_message.dart';
|
||||
import 'package:stream_chat_flutter/src/theme/themes.dart';
|
||||
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
|
||||
import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart';
|
||||
import 'package:visibility_detector/visibility_detector.dart';
|
||||
@@ -628,7 +629,7 @@ class _MessageListViewState extends State<MessageListView> {
|
||||
child: Text(
|
||||
context.translations.threadSeparatorText(replyCount),
|
||||
textAlign: TextAlign.center,
|
||||
style: _streamTheme.channelTheme.channelHeaderTheme.subtitle,
|
||||
style: ChannelHeaderTheme.of(context).subtitleStyle,
|
||||
),
|
||||
),
|
||||
);
|
||||
@@ -1072,7 +1073,8 @@ class _MessageListViewState extends State<MessageListView> {
|
||||
}
|
||||
FocusScope.of(context).unfocus();
|
||||
},
|
||||
showPinButton: widget.pinPermissions.contains(currentUserMember?.role),
|
||||
showPinButton: currentUserMember != null &&
|
||||
widget.pinPermissions.contains(currentUserMember.role),
|
||||
);
|
||||
|
||||
if (widget.messageBuilder != null) {
|
||||
|
||||
@@ -4,6 +4,7 @@ import 'package:flutter/material.dart';
|
||||
import 'package:stream_chat_flutter/src/reaction_bubble.dart';
|
||||
import 'package:stream_chat_flutter/src/reaction_picker.dart';
|
||||
import 'package:stream_chat_flutter/src/stream_chat.dart';
|
||||
import 'package:stream_chat_flutter/src/theme/themes.dart';
|
||||
import 'package:stream_chat_flutter/src/user_avatar.dart';
|
||||
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
|
||||
import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart';
|
||||
@@ -28,8 +29,8 @@ class MessageReactionsModal extends StatelessWidget {
|
||||
/// Message to display reactions of
|
||||
final Message message;
|
||||
|
||||
/// [MessageTheme] to apply to [message]
|
||||
final MessageTheme messageTheme;
|
||||
/// [MessageThemeData] to apply to [message]
|
||||
final MessageThemeData messageTheme;
|
||||
|
||||
/// Flag to reverse message
|
||||
final bool reverse;
|
||||
@@ -56,8 +57,9 @@ class MessageReactionsModal extends StatelessWidget {
|
||||
messageTextLength = quotedMessageLength;
|
||||
}
|
||||
}
|
||||
final roughSentenceSize =
|
||||
messageTextLength * (messageTheme.messageText?.fontSize ?? 1) * 1.2;
|
||||
final roughSentenceSize = messageTextLength *
|
||||
(messageTheme.messageTextStyle?.fontSize ?? 1) *
|
||||
1.2;
|
||||
final divFactor = message.attachments.isNotEmpty == true
|
||||
? 1
|
||||
: (roughSentenceSize == 0 ? 1 : (roughSentenceSize / roughMaxSize));
|
||||
|
||||
@@ -36,7 +36,7 @@ class MessageSearchItem extends StatelessWidget {
|
||||
final channel = getMessageResponse.channel;
|
||||
final channelName = channel?.extraData['name'];
|
||||
final user = message.user!;
|
||||
final chatThemeData = StreamChatTheme.of(context);
|
||||
final channelPreviewTheme = ChannelPreviewTheme.of(context);
|
||||
return ListTile(
|
||||
onTap: onTap,
|
||||
leading: UserAvatar(
|
||||
@@ -53,18 +53,18 @@ class MessageSearchItem extends StatelessWidget {
|
||||
user.id == StreamChat.of(context).currentUser?.id
|
||||
? context.translations.youText
|
||||
: user.name,
|
||||
style: chatThemeData.channelPreviewTheme.title,
|
||||
style: channelPreviewTheme.titleStyle,
|
||||
),
|
||||
if (channelName != null) ...[
|
||||
Text(
|
||||
' ${context.translations.inText} ',
|
||||
style: chatThemeData.channelPreviewTheme.title?.copyWith(
|
||||
style: channelPreviewTheme.titleStyle?.copyWith(
|
||||
fontWeight: FontWeight.normal,
|
||||
),
|
||||
),
|
||||
Text(
|
||||
channelName as String,
|
||||
style: chatThemeData.channelPreviewTheme.title,
|
||||
style: channelPreviewTheme.titleStyle,
|
||||
),
|
||||
],
|
||||
],
|
||||
@@ -94,7 +94,7 @@ class MessageSearchItem extends StatelessWidget {
|
||||
|
||||
return Text(
|
||||
stringDate,
|
||||
style: StreamChatTheme.of(context).channelPreviewTheme.lastMessageAt,
|
||||
style: StreamChatTheme.of(context).channelPreviewTheme.lastMessageAtStyle,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -122,18 +122,18 @@ class MessageSearchItem extends StatelessWidget {
|
||||
text = parts.join(' ');
|
||||
}
|
||||
|
||||
final chatThemeData = StreamChatTheme.of(context);
|
||||
final channelPreviewTheme = ChannelPreviewTheme.of(context);
|
||||
return Text.rich(
|
||||
_getDisplayText(
|
||||
text!,
|
||||
message.mentionedUsers,
|
||||
message.attachments,
|
||||
chatThemeData.channelPreviewTheme.subtitle?.copyWith(
|
||||
channelPreviewTheme.subtitleStyle?.copyWith(
|
||||
fontStyle: (message.isSystem || message.isDeleted)
|
||||
? FontStyle.italic
|
||||
: FontStyle.normal,
|
||||
),
|
||||
chatThemeData.channelPreviewTheme.subtitle?.copyWith(
|
||||
channelPreviewTheme.subtitleStyle?.copyWith(
|
||||
fontStyle: (message.isSystem || message.isDeleted)
|
||||
? FontStyle.italic
|
||||
: FontStyle.normal,
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:stream_chat_flutter/src/info_tile.dart';
|
||||
import 'package:stream_chat_flutter/src/message_search_item.dart';
|
||||
import 'package:stream_chat_flutter/src/theme/themes.dart';
|
||||
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
|
||||
import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart';
|
||||
import 'package:stream_chat_flutter/src/extension.dart';
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
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/theme/themes.dart';
|
||||
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
|
||||
import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart';
|
||||
|
||||
@@ -24,8 +25,8 @@ class MessageText extends StatelessWidget {
|
||||
/// Callback for when link is tapped
|
||||
final void Function(String)? onLinkTap;
|
||||
|
||||
/// [MessageTheme] whose text theme is to be applied
|
||||
final MessageTheme messageTheme;
|
||||
/// [MessageThemeData] whose text theme is to be applied
|
||||
final MessageThemeData messageTheme;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@@ -66,16 +67,16 @@ class MessageText extends StatelessWidget {
|
||||
styleSheet: MarkdownStyleSheet.fromTheme(
|
||||
themeData.copyWith(
|
||||
textTheme: themeData.textTheme.apply(
|
||||
bodyColor: messageTheme.messageText?.color,
|
||||
decoration: messageTheme.messageText?.decoration,
|
||||
decorationColor: messageTheme.messageText?.decorationColor,
|
||||
decorationStyle: messageTheme.messageText?.decorationStyle,
|
||||
fontFamily: messageTheme.messageText?.fontFamily,
|
||||
bodyColor: messageTheme.messageTextStyle?.color,
|
||||
decoration: messageTheme.messageTextStyle?.decoration,
|
||||
decorationColor: messageTheme.messageTextStyle?.decorationColor,
|
||||
decorationStyle: messageTheme.messageTextStyle?.decorationStyle,
|
||||
fontFamily: messageTheme.messageTextStyle?.fontFamily,
|
||||
),
|
||||
),
|
||||
).copyWith(
|
||||
a: messageTheme.messageLinks,
|
||||
p: messageTheme.messageText,
|
||||
a: messageTheme.messageLinksStyle,
|
||||
p: messageTheme.messageTextStyle,
|
||||
),
|
||||
);
|
||||
},
|
||||
|
||||
@@ -14,6 +14,7 @@ import 'package:stream_chat_flutter/src/message_actions_modal.dart';
|
||||
import 'package:stream_chat_flutter/src/message_reactions_modal.dart';
|
||||
import 'package:stream_chat_flutter/src/quoted_message_widget.dart';
|
||||
import 'package:stream_chat_flutter/src/reaction_bubble.dart';
|
||||
import 'package:stream_chat_flutter/src/theme/themes.dart';
|
||||
import 'package:stream_chat_flutter/src/url_attachment.dart';
|
||||
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
|
||||
|
||||
@@ -290,7 +291,7 @@ class MessageWidget extends StatefulWidget {
|
||||
final Message message;
|
||||
|
||||
/// The message theme
|
||||
final MessageTheme messageTheme;
|
||||
final MessageThemeData messageTheme;
|
||||
|
||||
/// If true the widget will be mirrored
|
||||
final bool reverse;
|
||||
@@ -422,7 +423,7 @@ class MessageWidget extends StatefulWidget {
|
||||
Widget Function(BuildContext, Message)? deletedBottomRowBuilder,
|
||||
void Function(BuildContext, Message)? onMessageActions,
|
||||
Message? message,
|
||||
MessageTheme? messageTheme,
|
||||
MessageThemeData? messageTheme,
|
||||
bool? reverse,
|
||||
ShapeBorder? shape,
|
||||
ShapeBorder? attachmentShape,
|
||||
@@ -893,14 +894,14 @@ class _MessageWidgetState extends State<MessageWidget>
|
||||
),
|
||||
InkWell(
|
||||
onTap: widget.onThreadTap != null ? onThreadTap : null,
|
||||
child: Text(msg, style: widget.messageTheme.replies),
|
||||
child: Text(msg, style: widget.messageTheme.repliesStyle),
|
||||
),
|
||||
],
|
||||
if (showUsername) _buildUsername(usernameKey),
|
||||
if (showTimeStamp)
|
||||
Text(
|
||||
Jiffy(widget.message.createdAt.toLocal()).jm,
|
||||
style: widget.messageTheme.createdAt,
|
||||
style: widget.messageTheme.createdAtStyle,
|
||||
),
|
||||
if (showSendingIndicator) _buildSendingIndicator(),
|
||||
]);
|
||||
@@ -917,7 +918,7 @@ class _MessageWidgetState extends State<MessageWidget>
|
||||
Container(
|
||||
margin: EdgeInsets.only(
|
||||
bottom: context.textScaleFactor *
|
||||
((widget.messageTheme.replies?.fontSize ?? 1) / 2),
|
||||
((widget.messageTheme.repliesStyle?.fontSize ?? 1) / 2),
|
||||
),
|
||||
child: CustomPaint(
|
||||
size: const Size(16, 32) * context.textScaleFactor,
|
||||
@@ -944,7 +945,7 @@ class _MessageWidgetState extends State<MessageWidget>
|
||||
Container(
|
||||
margin: EdgeInsets.only(
|
||||
bottom: context.textScaleFactor *
|
||||
((widget.messageTheme.replies?.fontSize ?? 1) / 2),
|
||||
((widget.messageTheme.repliesStyle?.fontSize ?? 1) / 2),
|
||||
),
|
||||
child: CustomPaint(
|
||||
size: const Size(16, 32) * context.textScaleFactor,
|
||||
@@ -967,7 +968,7 @@ class _MessageWidgetState extends State<MessageWidget>
|
||||
widget.message.user!.name,
|
||||
maxLines: 1,
|
||||
key: usernameKey,
|
||||
style: widget.messageTheme.messageAuthor,
|
||||
style: widget.messageTheme.messageAuthorStyle,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
);
|
||||
}
|
||||
@@ -1191,7 +1192,7 @@ class _MessageWidgetState extends State<MessageWidget>
|
||||
}
|
||||
|
||||
Widget _buildSendingIndicator() {
|
||||
final style = widget.messageTheme.createdAt;
|
||||
final style = widget.messageTheme.createdAtStyle;
|
||||
final message = widget.message;
|
||||
|
||||
if (hasNonUrlAttachments &&
|
||||
@@ -1271,8 +1272,8 @@ class _MessageWidgetState extends State<MessageWidget>
|
||||
onMentionTap: widget.onMentionTap,
|
||||
messageTheme: isOnlyEmoji
|
||||
? widget.messageTheme.copyWith(
|
||||
messageText:
|
||||
widget.messageTheme.messageText!.copyWith(
|
||||
messageTextStyle:
|
||||
widget.messageTheme.messageTextStyle!.copyWith(
|
||||
fontSize: 42,
|
||||
))
|
||||
: widget.messageTheme,
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import 'package:cached_network_image/cached_network_image.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:stream_chat_flutter/src/extension.dart';
|
||||
import 'package:stream_chat_flutter/src/theme/themes.dart';
|
||||
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
|
||||
import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart';
|
||||
import 'package:video_player/video_player.dart';
|
||||
@@ -73,7 +74,7 @@ class QuotedMessageWidget extends StatelessWidget {
|
||||
final Message message;
|
||||
|
||||
/// The message theme
|
||||
final MessageTheme messageTheme;
|
||||
final MessageThemeData messageTheme;
|
||||
|
||||
/// If true the widget will be mirrored
|
||||
final bool reverse;
|
||||
@@ -138,11 +139,11 @@ class QuotedMessageWidget extends StatelessWidget {
|
||||
message: msg,
|
||||
messageTheme: isOnlyEmoji && _containsText
|
||||
? messageTheme.copyWith(
|
||||
messageText: messageTheme.messageText?.copyWith(
|
||||
messageTextStyle: messageTheme.messageTextStyle?.copyWith(
|
||||
fontSize: 32,
|
||||
))
|
||||
: messageTheme.copyWith(
|
||||
messageText: messageTheme.messageText?.copyWith(
|
||||
messageTextStyle: messageTheme.messageTextStyle?.copyWith(
|
||||
fontSize: 12,
|
||||
)),
|
||||
),
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,77 @@
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
/// A style that overrides the default appearance of various avatar widgets.
|
||||
class AvatarThemeData with Diagnosticable {
|
||||
/// Creates an [AvatarThemeData].
|
||||
const AvatarThemeData({
|
||||
BoxConstraints? constraints,
|
||||
BorderRadius? borderRadius,
|
||||
}) : _constraints = constraints,
|
||||
_borderRadius = borderRadius;
|
||||
|
||||
final BoxConstraints? _constraints;
|
||||
final BorderRadius? _borderRadius;
|
||||
|
||||
/// Get constraints for avatar
|
||||
BoxConstraints get constraints =>
|
||||
_constraints ??
|
||||
const BoxConstraints.tightFor(
|
||||
height: 32,
|
||||
width: 32,
|
||||
);
|
||||
|
||||
/// Get border radius
|
||||
BorderRadius get borderRadius => _borderRadius ?? BorderRadius.circular(20);
|
||||
|
||||
/// Copy this [AvatarThemeData] to another.
|
||||
AvatarThemeData copyWith({
|
||||
BoxConstraints? constraints,
|
||||
BorderRadius? borderRadius,
|
||||
}) =>
|
||||
AvatarThemeData(
|
||||
constraints: constraints ?? _constraints,
|
||||
borderRadius: borderRadius ?? _borderRadius,
|
||||
);
|
||||
|
||||
/// Linearly interpolate between two [UserAvatar] themes.
|
||||
///
|
||||
/// All the properties must be non-null.
|
||||
AvatarThemeData lerp(
|
||||
AvatarThemeData a,
|
||||
AvatarThemeData b,
|
||||
double t,
|
||||
) =>
|
||||
AvatarThemeData(
|
||||
borderRadius: BorderRadius.lerp(a.borderRadius, b.borderRadius, t),
|
||||
constraints: BoxConstraints.lerp(a.constraints, b.constraints, t),
|
||||
);
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) =>
|
||||
identical(this, other) ||
|
||||
other is AvatarThemeData &&
|
||||
runtimeType == other.runtimeType &&
|
||||
_constraints == other._constraints &&
|
||||
_borderRadius == other._borderRadius;
|
||||
|
||||
@override
|
||||
int get hashCode => _constraints.hashCode ^ _borderRadius.hashCode;
|
||||
|
||||
/// Merges one [AvatarThemeData] with the another
|
||||
AvatarThemeData merge(AvatarThemeData? other) {
|
||||
if (other == null) return this;
|
||||
return copyWith(
|
||||
constraints: other._constraints,
|
||||
borderRadius: other._borderRadius,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
|
||||
super.debugFillProperties(properties);
|
||||
properties
|
||||
..add(DiagnosticsProperty('borderRadius', borderRadius))
|
||||
..add(DiagnosticsProperty('constraints', constraints));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,149 @@
|
||||
import 'package:flutter/foundation.dart';
|
||||
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';
|
||||
import 'package:stream_chat_flutter/src/theme/themes.dart';
|
||||
|
||||
/// Overrides the default style of [ChannelHeader] descendants.
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [ChannelHeaderThemeData], which is used to configure this theme.
|
||||
class ChannelHeaderTheme extends InheritedTheme {
|
||||
/// Creates a [ChannelHeaderTheme].
|
||||
///
|
||||
/// The [data] parameter must not be null.
|
||||
const ChannelHeaderTheme({
|
||||
Key? key,
|
||||
required this.data,
|
||||
required Widget child,
|
||||
}) : super(key: key, child: child);
|
||||
|
||||
/// The configuration of this theme.
|
||||
final ChannelHeaderThemeData data;
|
||||
|
||||
/// The closest instance of this class that encloses the given context.
|
||||
///
|
||||
/// If there is no enclosing [ChannelHeaderTheme] widget, then
|
||||
/// [StreamChatThemeData.channelTheme.channelHeaderTheme] is used.
|
||||
///
|
||||
/// Typical usage is as follows:
|
||||
///
|
||||
/// ```dart
|
||||
/// final theme = ChannelHeaderTheme.of(context);
|
||||
/// ```
|
||||
static ChannelHeaderThemeData of(BuildContext context) {
|
||||
final channelHeaderTheme =
|
||||
context.dependOnInheritedWidgetOfExactType<ChannelHeaderTheme>();
|
||||
return channelHeaderTheme?.data ??
|
||||
StreamChatTheme.of(context).channelHeaderTheme;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget wrap(BuildContext context, Widget child) =>
|
||||
ChannelHeaderTheme(data: data, child: child);
|
||||
|
||||
@override
|
||||
bool updateShouldNotify(ChannelHeaderTheme oldWidget) =>
|
||||
data != oldWidget.data;
|
||||
}
|
||||
|
||||
/// A style that overrides the default appearance of [ChannelHeader]s when used
|
||||
/// with [ChannelHeaderTheme] or with the overall [StreamChatTheme]'s
|
||||
/// [StreamChatThemeData.channelHeaderTheme].
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [ChannelHeaderTheme], 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({
|
||||
this.titleStyle,
|
||||
this.subtitleStyle,
|
||||
this.avatarTheme,
|
||||
this.color,
|
||||
});
|
||||
|
||||
/// Theme for title
|
||||
final TextStyle? titleStyle;
|
||||
|
||||
/// Theme for subtitle
|
||||
final TextStyle? subtitleStyle;
|
||||
|
||||
/// Theme for avatar
|
||||
final AvatarThemeData? avatarTheme;
|
||||
|
||||
/// Color for [ChannelHeaderThemeData]
|
||||
final Color? color;
|
||||
|
||||
/// Copy with theme
|
||||
ChannelHeaderThemeData copyWith({
|
||||
TextStyle? titleStyle,
|
||||
TextStyle? subtitleStyle,
|
||||
AvatarThemeData? avatarTheme,
|
||||
Color? color,
|
||||
}) =>
|
||||
ChannelHeaderThemeData(
|
||||
titleStyle: titleStyle ?? this.titleStyle,
|
||||
subtitleStyle: subtitleStyle ?? this.subtitleStyle,
|
||||
avatarTheme: avatarTheme ?? this.avatarTheme,
|
||||
color: color ?? this.color,
|
||||
);
|
||||
|
||||
/// Linearly interpolate between two [ChannelHeaderThemeData].
|
||||
///
|
||||
/// All the properties must be non-null.
|
||||
ChannelHeaderThemeData lerp(
|
||||
ChannelHeaderThemeData a,
|
||||
ChannelHeaderThemeData b,
|
||||
double t,
|
||||
) =>
|
||||
ChannelHeaderThemeData(
|
||||
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),
|
||||
color: Color.lerp(a.color, b.color, t),
|
||||
);
|
||||
|
||||
/// Merge with other [ChannelHeaderThemeData]
|
||||
ChannelHeaderThemeData merge(ChannelHeaderThemeData? other) {
|
||||
if (other == null) return this;
|
||||
return copyWith(
|
||||
titleStyle: titleStyle?.merge(other.titleStyle) ?? other.titleStyle,
|
||||
subtitleStyle:
|
||||
subtitleStyle?.merge(other.subtitleStyle) ?? other.subtitleStyle,
|
||||
avatarTheme: avatarTheme?.merge(other.avatarTheme) ?? other.avatarTheme,
|
||||
color: other.color,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) =>
|
||||
identical(this, other) ||
|
||||
other is ChannelHeaderThemeData &&
|
||||
runtimeType == other.runtimeType &&
|
||||
titleStyle == other.titleStyle &&
|
||||
subtitleStyle == other.subtitleStyle &&
|
||||
avatarTheme == other.avatarTheme &&
|
||||
color == other.color;
|
||||
|
||||
@override
|
||||
int get hashCode =>
|
||||
titleStyle.hashCode ^
|
||||
subtitleStyle.hashCode ^
|
||||
avatarTheme.hashCode ^
|
||||
color.hashCode;
|
||||
|
||||
@override
|
||||
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
|
||||
super.debugFillProperties(properties);
|
||||
properties
|
||||
..add(DiagnosticsProperty('title', titleStyle))
|
||||
..add(DiagnosticsProperty('subtitle', subtitleStyle))
|
||||
..add(DiagnosticsProperty('avatarTheme', avatarTheme))
|
||||
..add(ColorProperty('color', color));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,125 @@
|
||||
import 'package:flutter/foundation.dart';
|
||||
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';
|
||||
|
||||
/// Overrides the default style of [ChannelListHeader] descendants.
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [ChannelListHeaderThemeData], which is used to configure this theme.
|
||||
class ChannelListHeaderTheme extends InheritedTheme {
|
||||
/// Creates a [ChannelListHeaderTheme].
|
||||
///
|
||||
/// The [data] parameter must not be null.
|
||||
const ChannelListHeaderTheme({
|
||||
Key? key,
|
||||
required this.data,
|
||||
required Widget child,
|
||||
}) : super(key: key, child: child);
|
||||
|
||||
/// The configuration of this theme.
|
||||
final ChannelListHeaderThemeData data;
|
||||
|
||||
/// The closest instance of this class that encloses the given context.
|
||||
///
|
||||
/// If there is no enclosing [ChannelListHeaderTheme] widget, then
|
||||
/// [StreamChatThemeData.channelListHeaderTheme] is used.
|
||||
///
|
||||
/// Typical usage is as follows:
|
||||
///
|
||||
/// ```dart
|
||||
/// final theme = ChannelListHeaderTheme.of(context);
|
||||
/// ```
|
||||
static ChannelListHeaderThemeData of(BuildContext context) {
|
||||
final channelListHeaderTheme =
|
||||
context.dependOnInheritedWidgetOfExactType<ChannelListHeaderTheme>();
|
||||
return channelListHeaderTheme?.data ??
|
||||
StreamChatTheme.of(context).channelListHeaderTheme;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget wrap(BuildContext context, Widget child) =>
|
||||
ChannelListHeaderTheme(data: data, child: child);
|
||||
|
||||
@override
|
||||
bool updateShouldNotify(ChannelListHeaderTheme oldWidget) =>
|
||||
data != oldWidget.data;
|
||||
}
|
||||
|
||||
/// Theme dedicated to the [ChannelListHeader]
|
||||
class ChannelListHeaderThemeData with Diagnosticable {
|
||||
/// Returns a new [ChannelListHeaderThemeData]
|
||||
const ChannelListHeaderThemeData({
|
||||
this.titleStyle,
|
||||
this.avatarTheme,
|
||||
this.color,
|
||||
});
|
||||
|
||||
/// Style of the title text
|
||||
final TextStyle? titleStyle;
|
||||
|
||||
/// Theme dedicated to the userAvatar
|
||||
final AvatarThemeData? avatarTheme;
|
||||
|
||||
/// Background color of the appbar
|
||||
final Color? color;
|
||||
|
||||
/// Returns a new [ChannelListHeaderThemeData] replacing some of its
|
||||
/// properties
|
||||
ChannelListHeaderThemeData copyWith({
|
||||
TextStyle? titleStyle,
|
||||
AvatarThemeData? avatarTheme,
|
||||
Color? color,
|
||||
}) =>
|
||||
ChannelListHeaderThemeData(
|
||||
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,
|
||||
double t,
|
||||
) =>
|
||||
ChannelListHeaderThemeData(
|
||||
avatarTheme:
|
||||
const AvatarThemeData().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) {
|
||||
if (other == null) return this;
|
||||
return copyWith(
|
||||
titleStyle: titleStyle?.merge(other.titleStyle) ?? other.titleStyle,
|
||||
avatarTheme: avatarTheme?.merge(other.avatarTheme) ?? other.avatarTheme,
|
||||
color: other.color,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) =>
|
||||
identical(this, other) ||
|
||||
other is ChannelListHeaderThemeData &&
|
||||
runtimeType == other.runtimeType &&
|
||||
titleStyle == other.titleStyle &&
|
||||
avatarTheme == other.avatarTheme &&
|
||||
color == other.color;
|
||||
|
||||
@override
|
||||
int get hashCode =>
|
||||
titleStyle.hashCode ^ avatarTheme.hashCode ^ color.hashCode;
|
||||
|
||||
@override
|
||||
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
|
||||
super.debugFillProperties(properties);
|
||||
properties
|
||||
..add(DiagnosticsProperty('titleStyle', titleStyle))
|
||||
..add(DiagnosticsProperty('avatarTheme', avatarTheme))
|
||||
..add(ColorProperty('color', color));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:stream_chat_flutter/src/stream_chat_theme.dart';
|
||||
|
||||
/// Overrides the default style of [ChannelListView] descendants.
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [ChannelListViewThemeData], which is used to configure this theme.
|
||||
class ChannelListViewTheme extends InheritedTheme {
|
||||
/// Creates a [ChannelListViewTheme].
|
||||
///
|
||||
/// The [data] parameter must not be null.
|
||||
const ChannelListViewTheme({
|
||||
Key? key,
|
||||
required this.data,
|
||||
required Widget child,
|
||||
}) : super(key: key, child: child);
|
||||
|
||||
/// The configuration of this theme.
|
||||
final ChannelListViewThemeData data;
|
||||
|
||||
/// The closest instance of this class that encloses the given context.
|
||||
///
|
||||
/// If there is no enclosing [ChannelListViewTheme] widget, then
|
||||
/// [StreamChatThemeData.channelListViewTheme] is used.
|
||||
///
|
||||
/// Typical usage is as follows:
|
||||
///
|
||||
/// ```dart
|
||||
/// ChannelListViewTheme theme = ChannelListViewTheme.of(context);
|
||||
/// ```
|
||||
static ChannelListViewThemeData of(BuildContext context) {
|
||||
final channelListViewTheme =
|
||||
context.dependOnInheritedWidgetOfExactType<ChannelListViewTheme>();
|
||||
return channelListViewTheme?.data ??
|
||||
StreamChatTheme.of(context).channelListViewTheme;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget wrap(BuildContext context, Widget child) =>
|
||||
ChannelListViewTheme(data: data, child: child);
|
||||
|
||||
@override
|
||||
bool updateShouldNotify(ChannelListViewTheme oldWidget) =>
|
||||
data != oldWidget.data;
|
||||
}
|
||||
|
||||
/// A style that overrides the default appearance of [ChannelListView]s when
|
||||
/// used with [ChannelListViewTheme] or with the overall [StreamChatTheme]'s
|
||||
/// [StreamChatThemeData.channelListViewTheme].
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [ChannelListViewTheme], 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({
|
||||
this.backgroundColor,
|
||||
});
|
||||
|
||||
/// The color of the [ChannelListView] background.
|
||||
final Color? backgroundColor;
|
||||
|
||||
/// Copies this [ChannelListViewThemeData] to another.
|
||||
ChannelListViewThemeData copyWith({
|
||||
Color? backgroundColor,
|
||||
}) =>
|
||||
ChannelListViewThemeData(
|
||||
backgroundColor: backgroundColor ?? this.backgroundColor,
|
||||
);
|
||||
|
||||
/// Linearly interpolate between two [ChannelListViewThemeData] themes.
|
||||
///
|
||||
/// All the properties must be non-null.
|
||||
ChannelListViewThemeData lerp(
|
||||
ChannelListViewThemeData a,
|
||||
ChannelListViewThemeData b,
|
||||
double t,
|
||||
) =>
|
||||
ChannelListViewThemeData(
|
||||
backgroundColor: Color.lerp(a.backgroundColor, b.backgroundColor, t),
|
||||
);
|
||||
|
||||
/// Merges one [ChannelListViewThemeData] with another.
|
||||
ChannelListViewThemeData merge(ChannelListViewThemeData? other) {
|
||||
if (other == null) return this;
|
||||
return copyWith(
|
||||
backgroundColor: other.backgroundColor,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) =>
|
||||
identical(this, other) ||
|
||||
other is ChannelListViewThemeData &&
|
||||
runtimeType == other.runtimeType &&
|
||||
backgroundColor == other.backgroundColor;
|
||||
|
||||
@override
|
||||
int get hashCode => backgroundColor.hashCode;
|
||||
|
||||
@override
|
||||
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
|
||||
super.debugFillProperties(properties);
|
||||
properties.add(ColorProperty('backgroundColor', backgroundColor));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,169 @@
|
||||
import 'package:flutter/foundation.dart';
|
||||
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';
|
||||
|
||||
/// Overrides the default style of [ChannelPreview] descendants.
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [ChannelPreviewThemeData], which is used to configure this theme.
|
||||
class ChannelPreviewTheme extends InheritedTheme {
|
||||
/// Creates a [ChannelPreviewTheme].
|
||||
///
|
||||
/// The [data] parameter must not be null.
|
||||
const ChannelPreviewTheme({
|
||||
Key? key,
|
||||
required this.data,
|
||||
required Widget child,
|
||||
}) : super(key: key, child: child);
|
||||
|
||||
/// The configuration of this theme.
|
||||
final ChannelPreviewThemeData data;
|
||||
|
||||
/// The closest instance of this class that encloses the given context.
|
||||
///
|
||||
/// If there is no enclosing [ChannelPreviewTheme] widget, then
|
||||
/// [StreamChatThemeData.channelPreviewTheme] is used.
|
||||
///
|
||||
/// Typical usage is as follows:
|
||||
///
|
||||
/// ```dart
|
||||
/// final theme = ChannelPreviewTheme.of(context);
|
||||
/// ```
|
||||
static ChannelPreviewThemeData of(BuildContext context) {
|
||||
final channelPreviewTheme =
|
||||
context.dependOnInheritedWidgetOfExactType<ChannelPreviewTheme>();
|
||||
return channelPreviewTheme?.data ??
|
||||
StreamChatTheme.of(context).channelPreviewTheme;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget wrap(BuildContext context, Widget child) =>
|
||||
ChannelPreviewTheme(data: data, child: child);
|
||||
|
||||
@override
|
||||
bool updateShouldNotify(ChannelPreviewTheme oldWidget) =>
|
||||
data != oldWidget.data;
|
||||
}
|
||||
|
||||
/// A style that overrides the default appearance of [ChannelPreview]s when used
|
||||
/// with [ChannelPreviewTheme] or with the overall [StreamChatTheme]'s
|
||||
/// [StreamChatThemeData.channelPreviewTheme].
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [ChannelPreviewTheme], 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({
|
||||
this.titleStyle,
|
||||
this.subtitleStyle,
|
||||
this.lastMessageAtStyle,
|
||||
this.avatarTheme,
|
||||
this.unreadCounterColor,
|
||||
this.indicatorIconSize,
|
||||
});
|
||||
|
||||
/// Theme for title
|
||||
final TextStyle? titleStyle;
|
||||
|
||||
/// Theme for subtitle
|
||||
final TextStyle? subtitleStyle;
|
||||
|
||||
/// Theme of last message at
|
||||
final TextStyle? lastMessageAtStyle;
|
||||
|
||||
/// Avatar theme
|
||||
final AvatarThemeData? avatarTheme;
|
||||
|
||||
/// Unread counter color
|
||||
final Color? unreadCounterColor;
|
||||
|
||||
/// Indicator icon size
|
||||
final double? indicatorIconSize;
|
||||
|
||||
/// Copy with theme
|
||||
ChannelPreviewThemeData copyWith({
|
||||
TextStyle? titleStyle,
|
||||
TextStyle? subtitleStyle,
|
||||
TextStyle? lastMessageAtStyle,
|
||||
AvatarThemeData? avatarTheme,
|
||||
Color? unreadCounterColor,
|
||||
double? indicatorIconSize,
|
||||
}) =>
|
||||
ChannelPreviewThemeData(
|
||||
titleStyle: titleStyle ?? this.titleStyle,
|
||||
subtitleStyle: subtitleStyle ?? this.subtitleStyle,
|
||||
lastMessageAtStyle: lastMessageAtStyle ?? this.lastMessageAtStyle,
|
||||
avatarTheme: avatarTheme ?? this.avatarTheme,
|
||||
unreadCounterColor: unreadCounterColor ?? this.unreadCounterColor,
|
||||
indicatorIconSize: indicatorIconSize ?? this.indicatorIconSize,
|
||||
);
|
||||
|
||||
/// Linearly interpolate one [ChannelPreviewThemeData] to another.
|
||||
ChannelPreviewThemeData lerp(
|
||||
ChannelPreviewThemeData a,
|
||||
ChannelPreviewThemeData b,
|
||||
double t,
|
||||
) =>
|
||||
ChannelPreviewThemeData(
|
||||
avatarTheme:
|
||||
const AvatarThemeData().lerp(a.avatarTheme!, b.avatarTheme!, t),
|
||||
indicatorIconSize: a.indicatorIconSize,
|
||||
lastMessageAtStyle:
|
||||
TextStyle.lerp(a.lastMessageAtStyle, b.lastMessageAtStyle, t),
|
||||
subtitleStyle: TextStyle.lerp(a.subtitleStyle, b.subtitleStyle, t),
|
||||
titleStyle: TextStyle.lerp(a.titleStyle, b.titleStyle, t),
|
||||
unreadCounterColor:
|
||||
Color.lerp(a.unreadCounterColor, b.unreadCounterColor, t),
|
||||
);
|
||||
|
||||
/// Merge with theme
|
||||
ChannelPreviewThemeData merge(ChannelPreviewThemeData? other) {
|
||||
if (other == null) return this;
|
||||
return copyWith(
|
||||
titleStyle: titleStyle?.merge(other.titleStyle) ?? other.titleStyle,
|
||||
subtitleStyle:
|
||||
subtitleStyle?.merge(other.subtitleStyle) ?? other.subtitleStyle,
|
||||
lastMessageAtStyle: lastMessageAtStyle?.merge(other.lastMessageAtStyle) ??
|
||||
other.lastMessageAtStyle,
|
||||
avatarTheme: avatarTheme?.merge(other.avatarTheme) ?? other.avatarTheme,
|
||||
unreadCounterColor: other.unreadCounterColor,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) =>
|
||||
identical(this, other) ||
|
||||
other is ChannelPreviewThemeData &&
|
||||
runtimeType == other.runtimeType &&
|
||||
titleStyle == other.titleStyle &&
|
||||
subtitleStyle == other.subtitleStyle &&
|
||||
lastMessageAtStyle == other.lastMessageAtStyle &&
|
||||
avatarTheme == other.avatarTheme &&
|
||||
unreadCounterColor == other.unreadCounterColor &&
|
||||
indicatorIconSize == other.indicatorIconSize;
|
||||
|
||||
@override
|
||||
int get hashCode =>
|
||||
titleStyle.hashCode ^
|
||||
subtitleStyle.hashCode ^
|
||||
lastMessageAtStyle.hashCode ^
|
||||
avatarTheme.hashCode ^
|
||||
unreadCounterColor.hashCode ^
|
||||
indicatorIconSize.hashCode;
|
||||
|
||||
@override
|
||||
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
|
||||
super.debugFillProperties(properties);
|
||||
properties
|
||||
..add(DiagnosticsProperty('titleStyle', titleStyle))
|
||||
..add(DiagnosticsProperty('subtitleStyle', subtitleStyle))
|
||||
..add(DiagnosticsProperty('lastMessageAtStyle', lastMessageAtStyle))
|
||||
..add(DiagnosticsProperty('avatarTheme', avatarTheme))
|
||||
..add(ColorProperty('unreadCounterColor', unreadCounterColor));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,286 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
/// Theme that holds colors
|
||||
class ColorTheme {
|
||||
/// Initialise with light theme
|
||||
ColorTheme.light({
|
||||
this.textHighEmphasis = const Color(0xff000000),
|
||||
this.textLowEmphasis = const Color(0xff7a7a7a),
|
||||
this.disabled = const Color(0xffdbdbdb),
|
||||
this.borders = const Color(0xffecebeb),
|
||||
this.inputBg = const Color(0xfff2f2f2),
|
||||
this.appBg = const Color(0xfffcfcfc),
|
||||
this.barsBg = const Color(0xffffffff),
|
||||
this.linkBg = const Color(0xffe9f2ff),
|
||||
this.accentPrimary = const Color(0xff005FFF),
|
||||
this.accentError = const Color(0xffFF3842),
|
||||
this.accentInfo = const Color(0xff20E070),
|
||||
this.highlight = const Color(0xfffbf4dd),
|
||||
this.overlay = const Color.fromRGBO(0, 0, 0, 0.2),
|
||||
this.overlayDark = const Color.fromRGBO(0, 0, 0, 0.6),
|
||||
this.bgGradient = const LinearGradient(
|
||||
begin: Alignment.topCenter,
|
||||
end: Alignment.bottomCenter,
|
||||
colors: [Color(0xfff7f7f7), Color(0xfffcfcfc)],
|
||||
stops: [0, 1],
|
||||
),
|
||||
this.borderTop = const Effect(
|
||||
sigmaX: 0, sigmaY: -1, color: Color(0xff000000), blur: 0, alpha: 0.08),
|
||||
this.borderBottom = const Effect(
|
||||
sigmaX: 0, sigmaY: 1, color: Color(0xff000000), blur: 0, alpha: 0.08),
|
||||
this.shadowIconButton = const Effect(
|
||||
sigmaX: 0, sigmaY: 2, color: Color(0xff000000), alpha: 0.5, blur: 4),
|
||||
this.modalShadow = const Effect(
|
||||
sigmaX: 0, sigmaY: 0, color: Color(0xff000000), alpha: 1, blur: 8),
|
||||
}) : brightness = Brightness.light;
|
||||
|
||||
/// Initialise with dark theme
|
||||
ColorTheme.dark({
|
||||
this.textHighEmphasis = const Color(0xffffffff),
|
||||
this.textLowEmphasis = const Color(0xff7a7a7a),
|
||||
this.disabled = const Color(0xff2d2f2f),
|
||||
this.borders = const Color(0xff1c1e22),
|
||||
this.inputBg = const Color(0xff13151b),
|
||||
this.appBg = const Color(0xff070A0D),
|
||||
this.barsBg = const Color(0xff101418),
|
||||
this.linkBg = const Color(0xff00193D),
|
||||
this.accentPrimary = const Color(0xff005FFF),
|
||||
this.accentError = const Color(0xffFF3742),
|
||||
this.accentInfo = const Color(0xff20E070),
|
||||
this.borderTop = const Effect(
|
||||
sigmaX: 0,
|
||||
sigmaY: -1,
|
||||
color: Color(0xff141924),
|
||||
blur: 0,
|
||||
),
|
||||
this.borderBottom = const Effect(
|
||||
sigmaX: 0,
|
||||
sigmaY: 1,
|
||||
color: Color(0xff141924),
|
||||
blur: 0,
|
||||
alpha: 1,
|
||||
),
|
||||
this.shadowIconButton = const Effect(
|
||||
sigmaX: 0,
|
||||
sigmaY: 2,
|
||||
color: Color(0xff000000),
|
||||
alpha: 0.5,
|
||||
blur: 4,
|
||||
),
|
||||
this.modalShadow = const Effect(
|
||||
sigmaX: 0,
|
||||
sigmaY: 0,
|
||||
color: Color(0xff000000),
|
||||
alpha: 1,
|
||||
blur: 8,
|
||||
),
|
||||
this.highlight = const Color(0xff302d22),
|
||||
this.overlay = const Color.fromRGBO(0, 0, 0, 0.4),
|
||||
this.overlayDark = const Color.fromRGBO(255, 255, 255, 0.6),
|
||||
this.bgGradient = const LinearGradient(
|
||||
begin: Alignment.topCenter,
|
||||
end: Alignment.bottomCenter,
|
||||
colors: [
|
||||
Color(0xff101214),
|
||||
Color(0xff070a0d),
|
||||
],
|
||||
stops: [0, 1],
|
||||
),
|
||||
}) : brightness = Brightness.dark;
|
||||
|
||||
///
|
||||
final Color textHighEmphasis;
|
||||
|
||||
///
|
||||
final Color textLowEmphasis;
|
||||
|
||||
///
|
||||
final Color disabled;
|
||||
|
||||
///
|
||||
final Color borders;
|
||||
|
||||
///
|
||||
final Color inputBg;
|
||||
|
||||
///
|
||||
final Color appBg;
|
||||
|
||||
///
|
||||
final Color barsBg;
|
||||
|
||||
///
|
||||
final Color linkBg;
|
||||
|
||||
///
|
||||
final Color accentPrimary;
|
||||
|
||||
///
|
||||
final Color accentError;
|
||||
|
||||
///
|
||||
final Color accentInfo;
|
||||
|
||||
///
|
||||
final Effect borderTop;
|
||||
|
||||
///
|
||||
final Effect borderBottom;
|
||||
|
||||
///
|
||||
final Effect shadowIconButton;
|
||||
|
||||
///
|
||||
final Effect modalShadow;
|
||||
|
||||
///
|
||||
final Color highlight;
|
||||
|
||||
///
|
||||
final Color overlay;
|
||||
|
||||
///
|
||||
final Color overlayDark;
|
||||
|
||||
///
|
||||
final Gradient bgGradient;
|
||||
|
||||
///
|
||||
final Brightness brightness;
|
||||
|
||||
/// Copy with theme
|
||||
ColorTheme copyWith({
|
||||
Brightness brightness = Brightness.light,
|
||||
Color? textHighEmphasis,
|
||||
Color? textLowEmphasis,
|
||||
Color? disabled,
|
||||
Color? borders,
|
||||
Color? inputBg,
|
||||
Color? appBg,
|
||||
Color? barsBg,
|
||||
Color? linkBg,
|
||||
Color? accentPrimary,
|
||||
Color? accentError,
|
||||
Color? accentInfo,
|
||||
Effect? borderTop,
|
||||
Effect? borderBottom,
|
||||
Effect? shadowIconButton,
|
||||
Effect? modalShadow,
|
||||
Color? highlight,
|
||||
Color? overlay,
|
||||
Color? overlayDark,
|
||||
Gradient? bgGradient,
|
||||
}) =>
|
||||
brightness == Brightness.light
|
||||
? ColorTheme.light(
|
||||
textHighEmphasis: textHighEmphasis ?? this.textHighEmphasis,
|
||||
textLowEmphasis: textLowEmphasis ?? this.textLowEmphasis,
|
||||
disabled: disabled ?? this.disabled,
|
||||
borders: borders ?? this.borders,
|
||||
inputBg: inputBg ?? this.inputBg,
|
||||
appBg: appBg ?? this.appBg,
|
||||
barsBg: barsBg ?? this.barsBg,
|
||||
linkBg: linkBg ?? this.linkBg,
|
||||
accentPrimary: accentPrimary ?? this.accentPrimary,
|
||||
accentError: accentError ?? this.accentError,
|
||||
accentInfo: accentInfo ?? this.accentInfo,
|
||||
borderTop: borderTop ?? this.borderTop,
|
||||
borderBottom: borderBottom ?? this.borderBottom,
|
||||
shadowIconButton: shadowIconButton ?? this.shadowIconButton,
|
||||
modalShadow: modalShadow ?? this.modalShadow,
|
||||
highlight: highlight ?? this.highlight,
|
||||
overlay: overlay ?? this.overlay,
|
||||
overlayDark: overlayDark ?? this.overlayDark,
|
||||
bgGradient: bgGradient ?? this.bgGradient,
|
||||
)
|
||||
: ColorTheme.dark(
|
||||
textHighEmphasis: textHighEmphasis ?? this.textHighEmphasis,
|
||||
textLowEmphasis: textLowEmphasis ?? this.textLowEmphasis,
|
||||
disabled: disabled ?? this.disabled,
|
||||
borders: borders ?? this.borders,
|
||||
inputBg: inputBg ?? this.inputBg,
|
||||
appBg: appBg ?? this.appBg,
|
||||
barsBg: barsBg ?? this.barsBg,
|
||||
linkBg: linkBg ?? this.linkBg,
|
||||
accentPrimary: accentPrimary ?? this.accentPrimary,
|
||||
accentError: accentError ?? this.accentError,
|
||||
accentInfo: accentInfo ?? this.accentInfo,
|
||||
borderTop: borderTop ?? this.borderTop,
|
||||
borderBottom: borderBottom ?? this.borderBottom,
|
||||
shadowIconButton: shadowIconButton ?? this.shadowIconButton,
|
||||
modalShadow: modalShadow ?? this.modalShadow,
|
||||
highlight: highlight ?? this.highlight,
|
||||
overlay: overlay ?? this.overlay,
|
||||
overlayDark: overlayDark ?? this.overlayDark,
|
||||
bgGradient: bgGradient ?? this.bgGradient,
|
||||
);
|
||||
|
||||
/// Merge color theme
|
||||
ColorTheme merge(ColorTheme? other) {
|
||||
if (other == null) return this;
|
||||
return copyWith(
|
||||
textHighEmphasis: other.textHighEmphasis,
|
||||
textLowEmphasis: other.textLowEmphasis,
|
||||
disabled: other.disabled,
|
||||
borders: other.borders,
|
||||
inputBg: other.inputBg,
|
||||
appBg: other.appBg,
|
||||
barsBg: other.barsBg,
|
||||
linkBg: other.linkBg,
|
||||
accentPrimary: other.accentPrimary,
|
||||
accentError: other.accentError,
|
||||
accentInfo: other.accentInfo,
|
||||
highlight: other.highlight,
|
||||
overlay: other.overlay,
|
||||
overlayDark: other.overlayDark,
|
||||
bgGradient: other.bgGradient,
|
||||
borderTop: other.borderTop,
|
||||
borderBottom: other.borderBottom,
|
||||
shadowIconButton: other.shadowIconButton,
|
||||
modalShadow: other.modalShadow,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Effect store
|
||||
class Effect {
|
||||
/// Constructor for creating [Effect]
|
||||
const Effect({
|
||||
this.sigmaX,
|
||||
this.sigmaY,
|
||||
this.color,
|
||||
this.alpha,
|
||||
this.blur,
|
||||
});
|
||||
|
||||
///
|
||||
final double? sigmaX;
|
||||
|
||||
///
|
||||
final double? sigmaY;
|
||||
|
||||
///
|
||||
final Color? color;
|
||||
|
||||
///
|
||||
final double? alpha;
|
||||
|
||||
///
|
||||
final double? blur;
|
||||
|
||||
/// Copy with new effect
|
||||
Effect copyWith({
|
||||
double? sigmaX,
|
||||
double? sigmaY,
|
||||
Color? color,
|
||||
double? alpha,
|
||||
double? blur,
|
||||
}) =>
|
||||
Effect(
|
||||
sigmaX: sigmaX ?? this.sigmaX,
|
||||
sigmaY: sigmaY ?? this.sigmaY,
|
||||
color: color ?? this.color,
|
||||
alpha: color as double? ?? this.alpha,
|
||||
blur: blur ?? this.blur,
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,217 @@
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:stream_chat_flutter/src/stream_chat_theme.dart';
|
||||
|
||||
/// Overrides the default style of [GalleryFooter] descendants.
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [GalleryFooterThemeData], which is used to configure this theme.
|
||||
class GalleryFooterTheme extends InheritedTheme {
|
||||
/// Creates an [GalleryFooterTheme].
|
||||
///
|
||||
/// The [data] parameter must not be null.
|
||||
const GalleryFooterTheme({
|
||||
Key? key,
|
||||
required this.data,
|
||||
required Widget child,
|
||||
}) : super(key: key, child: child);
|
||||
|
||||
/// The configuration of this theme.
|
||||
final GalleryFooterThemeData data;
|
||||
|
||||
/// The closest instance of this class that encloses the given context.
|
||||
///
|
||||
/// If there is no enclosing [GalleryFooterTheme] widget, then
|
||||
/// [StreamChatThemeData.galleryFooterTheme] is used.
|
||||
///
|
||||
/// Typical usage is as follows:
|
||||
///
|
||||
/// ```dart
|
||||
/// ImageFooterTheme theme = ImageFooterTheme.of(context);
|
||||
/// ```
|
||||
static GalleryFooterThemeData of(BuildContext context) {
|
||||
final imageFooterTheme =
|
||||
context.dependOnInheritedWidgetOfExactType<GalleryFooterTheme>();
|
||||
return imageFooterTheme?.data ??
|
||||
StreamChatTheme.of(context).galleryFooterTheme;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget wrap(BuildContext context, Widget child) =>
|
||||
GalleryFooterTheme(data: data, child: child);
|
||||
|
||||
@override
|
||||
bool updateShouldNotify(GalleryFooterTheme oldWidget) =>
|
||||
data != oldWidget.data;
|
||||
}
|
||||
|
||||
/// A style that overrides the default appearance of [GalleryFooter]s when used
|
||||
/// with [GalleryFooterTheme] or with the overall [StreamChatTheme]'s
|
||||
/// [StreamChatThemeData.galleryFooterTheme].
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [GalleryFooterTheme], 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({
|
||||
this.backgroundColor,
|
||||
this.shareIconColor,
|
||||
this.titleTextStyle,
|
||||
this.gridIconButtonColor,
|
||||
this.bottomSheetBarrierColor,
|
||||
this.bottomSheetBackgroundColor,
|
||||
this.bottomSheetPhotosTextStyle,
|
||||
this.bottomSheetCloseIconColor,
|
||||
});
|
||||
|
||||
/// The background color for the [GalleryFooter] widget.
|
||||
///
|
||||
/// Defaults to [ColorTheme.barsBg].
|
||||
final Color? backgroundColor;
|
||||
|
||||
/// The color for the "share" icon.
|
||||
///
|
||||
/// Defaults to [ColorTheme.textHighEmphasis].
|
||||
final Color? shareIconColor;
|
||||
|
||||
/// The [TextStyle] to use for the [GalleryFooter] title text.
|
||||
///
|
||||
/// Defaults to [TextTheme.headlineBold].
|
||||
final TextStyle? titleTextStyle;
|
||||
|
||||
/// The color to use for the "grid" icon.
|
||||
///
|
||||
/// Defaults to [ColorTheme.textHighEmphasis].
|
||||
final Color? gridIconButtonColor;
|
||||
|
||||
/// The color to use behind the bottom sheet.
|
||||
///
|
||||
/// Defaults to [ColorTheme.overlay].
|
||||
final Color? bottomSheetBarrierColor;
|
||||
|
||||
/// The background color to use for the bottom sheet.
|
||||
///
|
||||
/// Defaults to [ColorTheme.barsBg].
|
||||
final Color? bottomSheetBackgroundColor;
|
||||
|
||||
/// The [TextStyle] to use for the "photos" text in the bottom sheet.
|
||||
///
|
||||
/// Defaults to [TextTheme.headlineBold].
|
||||
final TextStyle? bottomSheetPhotosTextStyle;
|
||||
|
||||
/// The color to use for the "close" icon.
|
||||
///
|
||||
/// Defaults to [ColorTheme.textHighEmphasis].
|
||||
final Color? bottomSheetCloseIconColor;
|
||||
|
||||
/// Copies this [GalleryFooterThemeData] to another.
|
||||
GalleryFooterThemeData copyWith({
|
||||
Color? backgroundColor,
|
||||
Color? shareIconColor,
|
||||
TextStyle? titleTextStyle,
|
||||
Color? gridIconButtonColor,
|
||||
Color? bottomSheetBarrierColor,
|
||||
Color? bottomSheetBackgroundColor,
|
||||
TextStyle? bottomSheetPhotosTextStyle,
|
||||
Color? bottomSheetCloseIconColor,
|
||||
}) =>
|
||||
GalleryFooterThemeData(
|
||||
backgroundColor: backgroundColor ?? this.backgroundColor,
|
||||
shareIconColor: shareIconColor ?? this.shareIconColor,
|
||||
titleTextStyle: titleTextStyle ?? this.titleTextStyle,
|
||||
gridIconButtonColor: gridIconButtonColor ?? this.gridIconButtonColor,
|
||||
bottomSheetBarrierColor:
|
||||
bottomSheetBarrierColor ?? this.bottomSheetBarrierColor,
|
||||
bottomSheetBackgroundColor:
|
||||
bottomSheetBackgroundColor ?? this.bottomSheetBackgroundColor,
|
||||
bottomSheetPhotosTextStyle:
|
||||
bottomSheetPhotosTextStyle ?? this.bottomSheetPhotosTextStyle,
|
||||
bottomSheetCloseIconColor:
|
||||
bottomSheetCloseIconColor ?? this.bottomSheetCloseIconColor,
|
||||
);
|
||||
|
||||
/// Linearly interpolate between two [GalleryFooter] themes.
|
||||
///
|
||||
/// All the properties must be non-null.
|
||||
GalleryFooterThemeData lerp(
|
||||
GalleryFooterThemeData a,
|
||||
GalleryFooterThemeData b,
|
||||
double t,
|
||||
) =>
|
||||
GalleryFooterThemeData(
|
||||
backgroundColor: Color.lerp(a.backgroundColor, b.backgroundColor, t),
|
||||
shareIconColor: Color.lerp(a.shareIconColor, b.shareIconColor, t),
|
||||
titleTextStyle: TextStyle.lerp(a.titleTextStyle, b.titleTextStyle, t),
|
||||
gridIconButtonColor:
|
||||
Color.lerp(a.gridIconButtonColor, b.gridIconButtonColor, t),
|
||||
bottomSheetBarrierColor:
|
||||
Color.lerp(a.bottomSheetBarrierColor, b.bottomSheetBarrierColor, t),
|
||||
bottomSheetBackgroundColor: Color.lerp(
|
||||
a.bottomSheetBackgroundColor, b.bottomSheetBackgroundColor, t),
|
||||
bottomSheetPhotosTextStyle: TextStyle.lerp(
|
||||
a.bottomSheetPhotosTextStyle, b.bottomSheetPhotosTextStyle, t),
|
||||
bottomSheetCloseIconColor: Color.lerp(
|
||||
a.bottomSheetCloseIconColor, b.bottomSheetCloseIconColor, t),
|
||||
);
|
||||
|
||||
/// Merges one [GalleryFooterThemeData] with another.
|
||||
GalleryFooterThemeData merge(GalleryFooterThemeData? other) {
|
||||
if (other == null) return this;
|
||||
return copyWith(
|
||||
backgroundColor: other.backgroundColor,
|
||||
bottomSheetBarrierColor: other.bottomSheetBarrierColor,
|
||||
bottomSheetBackgroundColor: other.bottomSheetBackgroundColor,
|
||||
bottomSheetCloseIconColor: other.bottomSheetCloseIconColor,
|
||||
bottomSheetPhotosTextStyle: other.bottomSheetPhotosTextStyle,
|
||||
gridIconButtonColor: other.gridIconButtonColor,
|
||||
titleTextStyle: other.titleTextStyle,
|
||||
shareIconColor: other.shareIconColor,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) =>
|
||||
identical(this, other) ||
|
||||
other is GalleryFooterThemeData &&
|
||||
runtimeType == other.runtimeType &&
|
||||
backgroundColor == other.backgroundColor &&
|
||||
shareIconColor == other.shareIconColor &&
|
||||
titleTextStyle == other.titleTextStyle &&
|
||||
gridIconButtonColor == other.gridIconButtonColor &&
|
||||
bottomSheetBarrierColor == other.bottomSheetBarrierColor &&
|
||||
bottomSheetBackgroundColor == other.bottomSheetBackgroundColor &&
|
||||
bottomSheetPhotosTextStyle == other.bottomSheetPhotosTextStyle &&
|
||||
bottomSheetCloseIconColor == other.bottomSheetCloseIconColor;
|
||||
|
||||
@override
|
||||
int get hashCode =>
|
||||
backgroundColor.hashCode ^
|
||||
shareIconColor.hashCode ^
|
||||
titleTextStyle.hashCode ^
|
||||
gridIconButtonColor.hashCode ^
|
||||
bottomSheetBarrierColor.hashCode ^
|
||||
bottomSheetBackgroundColor.hashCode ^
|
||||
bottomSheetPhotosTextStyle.hashCode ^
|
||||
bottomSheetCloseIconColor.hashCode;
|
||||
|
||||
@override
|
||||
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
|
||||
super.debugFillProperties(properties);
|
||||
properties
|
||||
..add(ColorProperty('backgroundColor', backgroundColor))
|
||||
..add(ColorProperty('shareIconColor', shareIconColor))
|
||||
..add(DiagnosticsProperty('titleTextStyle', titleTextStyle))
|
||||
..add(ColorProperty('gridIconButtonColor', gridIconButtonColor))
|
||||
..add(ColorProperty('bottomSheetBarrierColor', bottomSheetBarrierColor))
|
||||
..add(ColorProperty(
|
||||
'bottomSheetBackgroundColor', bottomSheetBackgroundColor))
|
||||
..add(DiagnosticsProperty(
|
||||
'bottomSheetPhotosTextStyle', bottomSheetPhotosTextStyle))
|
||||
..add(ColorProperty(
|
||||
'bottomSheetCloseIconColor', bottomSheetCloseIconColor));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,179 @@
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:stream_chat_flutter/src/stream_chat_theme.dart';
|
||||
|
||||
/// Overrides the default style of [GalleryHeader] descendants.
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [GalleryHeaderThemeData], which is used to configure this theme.
|
||||
class GalleryHeaderTheme extends InheritedTheme {
|
||||
/// Creates a [GalleryHeaderTheme].
|
||||
///
|
||||
/// The [data] parameter must not be null.
|
||||
const GalleryHeaderTheme({
|
||||
Key? key,
|
||||
required this.data,
|
||||
required Widget child,
|
||||
}) : super(key: key, child: child);
|
||||
|
||||
/// The configuration of this theme.
|
||||
final GalleryHeaderThemeData data;
|
||||
|
||||
/// The closest instance of this class that encloses the given context.
|
||||
///
|
||||
/// If there is no enclosing [GalleryHeaderTheme] widget, then
|
||||
/// [StreamChatThemeData.galleryHeaderTheme] is used.
|
||||
///
|
||||
/// Typical usage is as follows:
|
||||
///
|
||||
/// ```dart
|
||||
/// ImageHeaderTheme theme = ImageHeaderTheme.of(context);
|
||||
/// ```
|
||||
static GalleryHeaderThemeData of(BuildContext context) {
|
||||
final galleryHeaderTheme =
|
||||
context.dependOnInheritedWidgetOfExactType<GalleryHeaderTheme>();
|
||||
return galleryHeaderTheme?.data ??
|
||||
StreamChatTheme.of(context).galleryHeaderTheme;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget wrap(BuildContext context, Widget child) =>
|
||||
GalleryHeaderTheme(data: data, child: child);
|
||||
|
||||
@override
|
||||
bool updateShouldNotify(GalleryHeaderTheme oldWidget) =>
|
||||
data != oldWidget.data;
|
||||
}
|
||||
|
||||
/// A style that overrides the default appearance of [GalleryHeader]s when used
|
||||
/// with [GalleryHeaderTheme] or with the overall [StreamChatTheme]'s
|
||||
/// [StreamChatThemeData.galleryHeaderTheme].
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [GalleryHeaderTheme], 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({
|
||||
this.closeButtonColor,
|
||||
this.backgroundColor,
|
||||
this.iconMenuPointColor,
|
||||
this.titleTextStyle,
|
||||
this.subtitleTextStyle,
|
||||
this.bottomSheetBarrierColor,
|
||||
});
|
||||
|
||||
/// The color of the "close" button.
|
||||
///
|
||||
/// Defaults to [ColorTheme.textHighEmphasis].
|
||||
final Color? closeButtonColor;
|
||||
|
||||
/// The background color of the [GalleryHeader] widget.
|
||||
///
|
||||
/// Defaults to [ChannelHeaderTheme.color].
|
||||
final Color? backgroundColor;
|
||||
|
||||
/// Defaults to [ColorTheme.textHighEmphasis].
|
||||
final Color? iconMenuPointColor;
|
||||
|
||||
/// The [TextStyle] to use for the [GalleryHeader] title text.
|
||||
///
|
||||
/// Defaults to [TextTheme.headlineBold].
|
||||
final TextStyle? titleTextStyle;
|
||||
|
||||
/// The [TextStyle] to use for the [GalleryHeader] subtitle text.
|
||||
///
|
||||
/// Defaults to [ChannelPreviewTheme.subtitleStyle].
|
||||
final TextStyle? subtitleTextStyle;
|
||||
|
||||
///
|
||||
final Color? bottomSheetBarrierColor;
|
||||
|
||||
/// Copies this [GalleryHeaderThemeData] to another.
|
||||
GalleryHeaderThemeData copyWith({
|
||||
Color? closeButtonColor,
|
||||
Color? backgroundColor,
|
||||
Color? iconMenuPointColor,
|
||||
TextStyle? titleTextStyle,
|
||||
TextStyle? subtitleTextStyle,
|
||||
Color? bottomSheetBarrierColor,
|
||||
}) =>
|
||||
GalleryHeaderThemeData(
|
||||
closeButtonColor: closeButtonColor ?? this.closeButtonColor,
|
||||
backgroundColor: backgroundColor ?? this.backgroundColor,
|
||||
iconMenuPointColor: iconMenuPointColor ?? this.iconMenuPointColor,
|
||||
titleTextStyle: titleTextStyle ?? this.titleTextStyle,
|
||||
subtitleTextStyle: subtitleTextStyle ?? this.subtitleTextStyle,
|
||||
bottomSheetBarrierColor:
|
||||
bottomSheetBarrierColor ?? this.bottomSheetBarrierColor,
|
||||
);
|
||||
|
||||
/// Linearly interpolate between two [GalleryHeader] themes.
|
||||
///
|
||||
/// All the properties must be non-null.
|
||||
GalleryHeaderThemeData lerp(
|
||||
GalleryHeaderThemeData a,
|
||||
GalleryHeaderThemeData b,
|
||||
double t,
|
||||
) =>
|
||||
GalleryHeaderThemeData(
|
||||
closeButtonColor: Color.lerp(a.closeButtonColor, b.closeButtonColor, t),
|
||||
backgroundColor: Color.lerp(a.backgroundColor, b.backgroundColor, t),
|
||||
iconMenuPointColor:
|
||||
Color.lerp(a.iconMenuPointColor, b.iconMenuPointColor, t),
|
||||
titleTextStyle: TextStyle.lerp(a.titleTextStyle, b.titleTextStyle, t),
|
||||
subtitleTextStyle:
|
||||
TextStyle.lerp(a.subtitleTextStyle, b.subtitleTextStyle, t),
|
||||
bottomSheetBarrierColor:
|
||||
Color.lerp(a.bottomSheetBarrierColor, b.bottomSheetBarrierColor, t),
|
||||
);
|
||||
|
||||
/// Merges one [GalleryHeaderThemeData] with the another
|
||||
GalleryHeaderThemeData merge(GalleryHeaderThemeData? other) {
|
||||
if (other == null) return this;
|
||||
return copyWith(
|
||||
closeButtonColor: other.closeButtonColor,
|
||||
backgroundColor: other.backgroundColor,
|
||||
iconMenuPointColor: other.iconMenuPointColor,
|
||||
titleTextStyle: other.titleTextStyle,
|
||||
subtitleTextStyle: other.subtitleTextStyle,
|
||||
bottomSheetBarrierColor: other.bottomSheetBarrierColor,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) =>
|
||||
identical(this, other) ||
|
||||
other is GalleryHeaderThemeData &&
|
||||
runtimeType == other.runtimeType &&
|
||||
closeButtonColor == other.closeButtonColor &&
|
||||
backgroundColor == other.backgroundColor &&
|
||||
iconMenuPointColor == other.iconMenuPointColor &&
|
||||
titleTextStyle == other.titleTextStyle &&
|
||||
subtitleTextStyle == other.subtitleTextStyle &&
|
||||
bottomSheetBarrierColor == other.bottomSheetBarrierColor;
|
||||
|
||||
@override
|
||||
int get hashCode =>
|
||||
closeButtonColor.hashCode ^
|
||||
backgroundColor.hashCode ^
|
||||
iconMenuPointColor.hashCode ^
|
||||
titleTextStyle.hashCode ^
|
||||
subtitleTextStyle.hashCode ^
|
||||
bottomSheetBarrierColor.hashCode;
|
||||
|
||||
@override
|
||||
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
|
||||
super.debugFillProperties(properties);
|
||||
properties
|
||||
..add(ColorProperty('closeButtonColor', closeButtonColor))
|
||||
..add(ColorProperty('backgroundColor', backgroundColor))
|
||||
..add(ColorProperty('iconMenuPointColor', iconMenuPointColor))
|
||||
..add(DiagnosticsProperty('titleTextStyle', titleTextStyle))
|
||||
..add(DiagnosticsProperty('subtitleTextStyle', subtitleTextStyle))
|
||||
..add(ColorProperty('bottomSheetBarrierColor', bottomSheetBarrierColor));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,237 @@
|
||||
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';
|
||||
|
||||
/// Overrides the default style of [MessageInput] descendants.
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [MessageInputThemeData], which is used to configure this theme.
|
||||
class MessageInputTheme extends InheritedTheme {
|
||||
/// Creates a [MessageInputTheme].
|
||||
///
|
||||
/// The [data] parameter must not be null.
|
||||
const MessageInputTheme({
|
||||
Key? key,
|
||||
required this.data,
|
||||
required Widget child,
|
||||
}) : super(key: key, child: child);
|
||||
|
||||
/// The configuration of this theme.
|
||||
final MessageInputThemeData data;
|
||||
|
||||
/// The closest instance of this class that encloses the given context.
|
||||
///
|
||||
/// If there is no enclosing [MessageInputTheme] widget, then
|
||||
/// [StreamChatThemeData.messageInputTheme] is used.
|
||||
///
|
||||
/// Typical usage is as follows:
|
||||
///
|
||||
/// ```dart
|
||||
/// final theme = MessageInputTheme.of(context);
|
||||
/// ```
|
||||
static MessageInputThemeData of(BuildContext context) {
|
||||
final messageInputTheme =
|
||||
context.dependOnInheritedWidgetOfExactType<MessageInputTheme>();
|
||||
return messageInputTheme?.data ??
|
||||
StreamChatTheme.of(context).messageInputTheme;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget wrap(BuildContext context, Widget child) =>
|
||||
MessageInputTheme(data: data, child: child);
|
||||
|
||||
@override
|
||||
bool updateShouldNotify(MessageInputTheme oldWidget) =>
|
||||
data != oldWidget.data;
|
||||
}
|
||||
|
||||
/// A style that overrides the default appearance of [MessageInput] widgets
|
||||
/// when used with [MessageInputTheme] or with the overall [StreamChatTheme]'s
|
||||
/// [StreamChatThemeData.messageInputTheme].
|
||||
class MessageInputThemeData with Diagnosticable {
|
||||
/// Creates a [MessageInputThemeData].
|
||||
const MessageInputThemeData({
|
||||
this.sendAnimationDuration,
|
||||
this.actionButtonColor,
|
||||
this.sendButtonColor,
|
||||
this.actionButtonIdleColor,
|
||||
this.sendButtonIdleColor,
|
||||
this.inputBackgroundColor,
|
||||
this.inputTextStyle,
|
||||
this.inputDecoration,
|
||||
this.activeBorderGradient,
|
||||
this.idleBorderGradient,
|
||||
this.borderRadius,
|
||||
this.expandButtonColor,
|
||||
});
|
||||
|
||||
/// Duration of the [MessageInput] send button animation
|
||||
final Duration? sendAnimationDuration;
|
||||
|
||||
/// Background color of [MessageInput] send button
|
||||
final Color? sendButtonColor;
|
||||
|
||||
/// Background color of [MessageInput] action buttons
|
||||
final Color? actionButtonColor;
|
||||
|
||||
/// Background color of [MessageInput] send button
|
||||
final Color? sendButtonIdleColor;
|
||||
|
||||
/// Background color of [MessageInput] action buttons
|
||||
final Color? actionButtonIdleColor;
|
||||
|
||||
/// Background color of [MessageInput] expand button
|
||||
final Color? expandButtonColor;
|
||||
|
||||
/// Background color of [MessageInput]
|
||||
final Color? inputBackgroundColor;
|
||||
|
||||
/// TextStyle of [MessageInput]
|
||||
final TextStyle? inputTextStyle;
|
||||
|
||||
/// InputDecoration of [MessageInput]
|
||||
final InputDecoration? inputDecoration;
|
||||
|
||||
/// Border gradient when the [MessageInput] is not focused
|
||||
final Gradient? idleBorderGradient;
|
||||
|
||||
/// Border gradient when the [MessageInput] is focused
|
||||
final Gradient? activeBorderGradient;
|
||||
|
||||
/// Border radius of [MessageInput]
|
||||
final BorderRadius? borderRadius;
|
||||
|
||||
/// Returns a new [MessageInputThemeData] replacing some of its properties
|
||||
MessageInputThemeData copyWith({
|
||||
Duration? sendAnimationDuration,
|
||||
Color? inputBackgroundColor,
|
||||
Color? actionButtonColor,
|
||||
Color? sendButtonColor,
|
||||
Color? actionButtonIdleColor,
|
||||
Color? sendButtonIdleColor,
|
||||
Color? expandButtonColor,
|
||||
TextStyle? inputTextStyle,
|
||||
InputDecoration? inputDecoration,
|
||||
Gradient? activeBorderGradient,
|
||||
Gradient? idleBorderGradient,
|
||||
BorderRadius? borderRadius,
|
||||
}) =>
|
||||
MessageInputThemeData(
|
||||
sendAnimationDuration:
|
||||
sendAnimationDuration ?? this.sendAnimationDuration,
|
||||
inputBackgroundColor: inputBackgroundColor ?? this.inputBackgroundColor,
|
||||
actionButtonColor: actionButtonColor ?? this.actionButtonColor,
|
||||
sendButtonColor: sendButtonColor ?? this.sendButtonColor,
|
||||
actionButtonIdleColor:
|
||||
actionButtonIdleColor ?? this.actionButtonIdleColor,
|
||||
expandButtonColor: expandButtonColor ?? this.expandButtonColor,
|
||||
inputTextStyle: inputTextStyle ?? this.inputTextStyle,
|
||||
sendButtonIdleColor: sendButtonIdleColor ?? this.sendButtonIdleColor,
|
||||
inputDecoration: inputDecoration ?? this.inputDecoration,
|
||||
activeBorderGradient: activeBorderGradient ?? this.activeBorderGradient,
|
||||
idleBorderGradient: idleBorderGradient ?? this.idleBorderGradient,
|
||||
borderRadius: borderRadius ?? this.borderRadius,
|
||||
);
|
||||
|
||||
/// Linearly interpolate from one [MessageInputThemeData] to another.
|
||||
MessageInputThemeData lerp(
|
||||
MessageInputThemeData a,
|
||||
MessageInputThemeData b,
|
||||
double t,
|
||||
) =>
|
||||
MessageInputThemeData(
|
||||
actionButtonColor:
|
||||
Color.lerp(a.actionButtonColor, b.actionButtonColor, t),
|
||||
actionButtonIdleColor:
|
||||
Color.lerp(a.actionButtonIdleColor, b.actionButtonIdleColor, t),
|
||||
activeBorderGradient:
|
||||
Gradient.lerp(a.activeBorderGradient, b.activeBorderGradient, t),
|
||||
borderRadius: BorderRadius.lerp(a.borderRadius, b.borderRadius, t),
|
||||
expandButtonColor:
|
||||
Color.lerp(a.expandButtonColor, b.expandButtonColor, t),
|
||||
idleBorderGradient:
|
||||
Gradient.lerp(a.idleBorderGradient, b.idleBorderGradient, t),
|
||||
inputBackgroundColor:
|
||||
Color.lerp(a.inputBackgroundColor, b.inputBackgroundColor, t),
|
||||
inputTextStyle: TextStyle.lerp(a.inputTextStyle, b.inputTextStyle, t),
|
||||
sendButtonColor: Color.lerp(a.sendButtonColor, b.sendButtonColor, t),
|
||||
sendButtonIdleColor:
|
||||
Color.lerp(a.sendButtonIdleColor, b.sendButtonIdleColor, t),
|
||||
sendAnimationDuration: a.sendAnimationDuration,
|
||||
inputDecoration: a.inputDecoration,
|
||||
);
|
||||
|
||||
/// Merges [this] [MessageInputThemeData] with the [other]
|
||||
MessageInputThemeData merge(MessageInputThemeData? other) {
|
||||
if (other == null) return this;
|
||||
return copyWith(
|
||||
sendAnimationDuration: other.sendAnimationDuration,
|
||||
inputBackgroundColor: other.inputBackgroundColor,
|
||||
actionButtonColor: other.actionButtonColor,
|
||||
actionButtonIdleColor: other.actionButtonIdleColor,
|
||||
sendButtonColor: other.sendButtonColor,
|
||||
sendButtonIdleColor: other.sendButtonIdleColor,
|
||||
inputTextStyle:
|
||||
inputTextStyle?.merge(other.inputTextStyle) ?? other.inputTextStyle,
|
||||
inputDecoration: inputDecoration?.merge(other.inputDecoration) ??
|
||||
other.inputDecoration,
|
||||
activeBorderGradient: other.activeBorderGradient,
|
||||
idleBorderGradient: other.idleBorderGradient,
|
||||
borderRadius: other.borderRadius,
|
||||
expandButtonColor: other.expandButtonColor,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) =>
|
||||
identical(this, other) ||
|
||||
other is MessageInputThemeData &&
|
||||
runtimeType == other.runtimeType &&
|
||||
sendAnimationDuration == other.sendAnimationDuration &&
|
||||
sendButtonColor == other.sendButtonColor &&
|
||||
actionButtonColor == other.actionButtonColor &&
|
||||
sendButtonIdleColor == other.sendButtonIdleColor &&
|
||||
actionButtonIdleColor == other.actionButtonIdleColor &&
|
||||
expandButtonColor == other.expandButtonColor &&
|
||||
inputBackgroundColor == other.inputBackgroundColor &&
|
||||
inputTextStyle == other.inputTextStyle &&
|
||||
inputDecoration == other.inputDecoration &&
|
||||
idleBorderGradient == other.idleBorderGradient &&
|
||||
activeBorderGradient == other.activeBorderGradient &&
|
||||
borderRadius == other.borderRadius;
|
||||
|
||||
@override
|
||||
int get hashCode =>
|
||||
sendAnimationDuration.hashCode ^
|
||||
sendButtonColor.hashCode ^
|
||||
actionButtonColor.hashCode ^
|
||||
sendButtonIdleColor.hashCode ^
|
||||
actionButtonIdleColor.hashCode ^
|
||||
expandButtonColor.hashCode ^
|
||||
inputBackgroundColor.hashCode ^
|
||||
inputTextStyle.hashCode ^
|
||||
inputDecoration.hashCode ^
|
||||
idleBorderGradient.hashCode ^
|
||||
activeBorderGradient.hashCode ^
|
||||
borderRadius.hashCode;
|
||||
|
||||
@override
|
||||
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
|
||||
super.debugFillProperties(properties);
|
||||
properties
|
||||
..add(DiagnosticsProperty('sendAnimationDuration', sendAnimationDuration))
|
||||
..add(ColorProperty('inputBackgroundColor', inputBackgroundColor))
|
||||
..add(ColorProperty('actionButtonColor', actionButtonColor))
|
||||
..add(ColorProperty('actionButtonIdleColor', actionButtonIdleColor))
|
||||
..add(ColorProperty('sendButtonColor', sendButtonColor))
|
||||
..add(ColorProperty('sendButtonIdleColor', sendButtonIdleColor))
|
||||
..add(DiagnosticsProperty('inputTextStyle', inputTextStyle))
|
||||
..add(DiagnosticsProperty('inputDecoration', inputDecoration))
|
||||
..add(DiagnosticsProperty('activeBorderGradient', activeBorderGradient))
|
||||
..add(DiagnosticsProperty('idleBorderGradient', idleBorderGradient))
|
||||
..add(DiagnosticsProperty('borderRadius', borderRadius))
|
||||
..add(ColorProperty('expandButtonColor', expandButtonColor));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:stream_chat_flutter/src/stream_chat_theme.dart';
|
||||
|
||||
/// Overrides the default style of [MessageListView] descendants.
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [MessageListViewThemeData], which is used to configure this theme.
|
||||
class MessageListViewTheme extends InheritedTheme {
|
||||
/// Creates a [MessageListViewTheme].
|
||||
///
|
||||
/// The [data] parameter must not be null.
|
||||
const MessageListViewTheme({
|
||||
Key? key,
|
||||
required this.data,
|
||||
required Widget child,
|
||||
}) : super(key: key, child: child);
|
||||
|
||||
/// The configuration of this theme.
|
||||
final MessageListViewThemeData data;
|
||||
|
||||
/// The closest instance of this class that encloses the given context.
|
||||
///
|
||||
/// If there is no enclosing [MessageListViewTheme] widget, then
|
||||
/// [StreamChatThemeData.messageListViewTheme] is used.
|
||||
///
|
||||
/// Typical usage is as follows:
|
||||
///
|
||||
/// ```dart
|
||||
/// MessageListViewTheme theme = MessageListViewTheme.of(context);
|
||||
/// ```
|
||||
static MessageListViewThemeData of(BuildContext context) {
|
||||
final messageListViewTheme =
|
||||
context.dependOnInheritedWidgetOfExactType<MessageListViewTheme>();
|
||||
return messageListViewTheme?.data ??
|
||||
StreamChatTheme.of(context).messageListViewTheme;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget wrap(BuildContext context, Widget child) =>
|
||||
MessageListViewTheme(data: data, child: child);
|
||||
|
||||
@override
|
||||
bool updateShouldNotify(MessageListViewTheme oldWidget) =>
|
||||
data != oldWidget.data;
|
||||
}
|
||||
|
||||
/// A style that overrides the default appearance of [MessageListView]s when
|
||||
/// used with [MessageListViewTheme] or with the overall [StreamChatTheme]'s
|
||||
/// [StreamChatThemeData.messageListViewTheme].
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [MessageListViewTheme], 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({
|
||||
this.backgroundColor,
|
||||
});
|
||||
|
||||
/// The color of the [MessageListView] background.
|
||||
final Color? backgroundColor;
|
||||
|
||||
/// Copies this [MessageListViewThemeData] to another.
|
||||
MessageListViewThemeData copyWith({
|
||||
Color? backgroundColor,
|
||||
}) =>
|
||||
MessageListViewThemeData(
|
||||
backgroundColor: backgroundColor ?? this.backgroundColor,
|
||||
);
|
||||
|
||||
/// Linearly interpolate between two [MessageListView] themes.
|
||||
///
|
||||
/// All the properties must be non-null.
|
||||
MessageListViewThemeData lerp(
|
||||
MessageListViewThemeData a,
|
||||
MessageListViewThemeData b,
|
||||
double t,
|
||||
) =>
|
||||
MessageListViewThemeData(
|
||||
backgroundColor: Color.lerp(a.backgroundColor, b.backgroundColor, t),
|
||||
);
|
||||
|
||||
/// Merges one [MessageListViewThemeData] with another.
|
||||
MessageListViewThemeData merge(MessageListViewThemeData? other) {
|
||||
if (other == null) return this;
|
||||
return copyWith(
|
||||
backgroundColor: other.backgroundColor,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) =>
|
||||
identical(this, other) ||
|
||||
other is MessageListViewThemeData &&
|
||||
runtimeType == other.runtimeType &&
|
||||
backgroundColor == other.backgroundColor;
|
||||
|
||||
@override
|
||||
int get hashCode => backgroundColor.hashCode;
|
||||
|
||||
@override
|
||||
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
|
||||
super.debugFillProperties(properties);
|
||||
properties.add(ColorProperty('backgroundColor', backgroundColor));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:stream_chat_flutter/src/stream_chat_theme.dart';
|
||||
|
||||
/// Overrides the default style of [MessageSearchListView] descendants.
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [UserListViewThemeData], which is used to configure this theme.
|
||||
class MessageSearchListViewTheme extends InheritedTheme {
|
||||
/// Creates a [UserListViewTheme].
|
||||
///
|
||||
/// The [data] parameter must not be null.
|
||||
const MessageSearchListViewTheme({
|
||||
Key? key,
|
||||
required this.data,
|
||||
required Widget child,
|
||||
}) : super(key: key, child: child);
|
||||
|
||||
/// The configuration of this theme.
|
||||
final MessageSearchListViewThemeData data;
|
||||
|
||||
/// The closest instance of this class that encloses the given context.
|
||||
///
|
||||
/// If there is no enclosing [MessageSearchListView] widget, then
|
||||
/// [StreamChatThemeData.messageSearchListViewTheme] is used.
|
||||
///
|
||||
/// Typical usage is as follows:
|
||||
///
|
||||
/// ```dart
|
||||
/// MessageSearchListViewTheme theme = MessageSearchListViewTheme.of(context);
|
||||
/// ```
|
||||
static MessageSearchListViewThemeData of(BuildContext context) {
|
||||
final messageSearchListViewTheme = context
|
||||
.dependOnInheritedWidgetOfExactType<MessageSearchListViewTheme>();
|
||||
return messageSearchListViewTheme?.data ??
|
||||
StreamChatTheme.of(context).messageSearchListViewTheme;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget wrap(BuildContext context, Widget child) =>
|
||||
MessageSearchListViewTheme(data: data, child: child);
|
||||
|
||||
@override
|
||||
bool updateShouldNotify(MessageSearchListViewTheme oldWidget) =>
|
||||
data != oldWidget.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
|
||||
/// 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({
|
||||
this.backgroundColor,
|
||||
});
|
||||
|
||||
/// The color of the [MessageSearchListView] background.
|
||||
final Color? backgroundColor;
|
||||
|
||||
/// Copies this [MessageSearchListViewThemeData] to another.
|
||||
MessageSearchListViewThemeData copyWith({
|
||||
Color? backgroundColor,
|
||||
}) =>
|
||||
MessageSearchListViewThemeData(
|
||||
backgroundColor: backgroundColor ?? this.backgroundColor,
|
||||
);
|
||||
|
||||
/// Linearly interpolate between two [UserListViewThemeData] themes.
|
||||
///
|
||||
/// All the properties must be non-null.
|
||||
MessageSearchListViewThemeData lerp(
|
||||
MessageSearchListViewThemeData a,
|
||||
MessageSearchListViewThemeData b,
|
||||
double t,
|
||||
) =>
|
||||
MessageSearchListViewThemeData(
|
||||
backgroundColor: Color.lerp(a.backgroundColor, b.backgroundColor, t),
|
||||
);
|
||||
|
||||
/// Merges one [MessageSearchListViewThemeData] with another.
|
||||
MessageSearchListViewThemeData merge(MessageSearchListViewThemeData? other) {
|
||||
if (other == null) return this;
|
||||
return copyWith(
|
||||
backgroundColor: other.backgroundColor,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) =>
|
||||
identical(this, other) ||
|
||||
other is MessageSearchListViewThemeData &&
|
||||
runtimeType == other.runtimeType &&
|
||||
backgroundColor == other.backgroundColor;
|
||||
|
||||
@override
|
||||
int get hashCode => backgroundColor.hashCode;
|
||||
|
||||
@override
|
||||
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
|
||||
super.debugFillProperties(properties);
|
||||
properties.add(ColorProperty('backgroundColor', backgroundColor));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,180 @@
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:stream_chat_flutter/src/theme/avatar_theme.dart';
|
||||
|
||||
/// Class for getting message theme
|
||||
class MessageThemeData with Diagnosticable {
|
||||
/// Creates a [MessageThemeData].
|
||||
const MessageThemeData({
|
||||
this.repliesStyle,
|
||||
this.messageTextStyle,
|
||||
this.messageAuthorStyle,
|
||||
this.messageLinksStyle,
|
||||
this.messageBackgroundColor,
|
||||
this.messageBorderColor,
|
||||
this.reactionsBackgroundColor,
|
||||
this.reactionsBorderColor,
|
||||
this.reactionsMaskColor,
|
||||
this.avatarTheme,
|
||||
this.createdAtStyle,
|
||||
});
|
||||
|
||||
/// Text style for message text
|
||||
final TextStyle? messageTextStyle;
|
||||
|
||||
/// Text style for message author
|
||||
final TextStyle? messageAuthorStyle;
|
||||
|
||||
/// Text style for message links
|
||||
final TextStyle? messageLinksStyle;
|
||||
|
||||
/// Text style for created at text
|
||||
final TextStyle? createdAtStyle;
|
||||
|
||||
/// Text style for replies
|
||||
final TextStyle? repliesStyle;
|
||||
|
||||
/// Color for messageBackgroundColor
|
||||
final Color? messageBackgroundColor;
|
||||
|
||||
/// Color for message border color
|
||||
final Color? messageBorderColor;
|
||||
|
||||
/// Color for reactions
|
||||
final Color? reactionsBackgroundColor;
|
||||
|
||||
/// Colors reaction border
|
||||
final Color? reactionsBorderColor;
|
||||
|
||||
/// Color for reaction mask
|
||||
final Color? reactionsMaskColor;
|
||||
|
||||
/// Theme of the avatar
|
||||
final AvatarThemeData? avatarTheme;
|
||||
|
||||
/// Copy with a theme
|
||||
MessageThemeData copyWith({
|
||||
TextStyle? messageTextStyle,
|
||||
TextStyle? messageAuthorStyle,
|
||||
TextStyle? messageLinksStyle,
|
||||
TextStyle? createdAtStyle,
|
||||
TextStyle? repliesStyle,
|
||||
Color? messageBackgroundColor,
|
||||
Color? messageBorderColor,
|
||||
AvatarThemeData? avatarTheme,
|
||||
Color? reactionsBackgroundColor,
|
||||
Color? reactionsBorderColor,
|
||||
Color? reactionsMaskColor,
|
||||
}) =>
|
||||
MessageThemeData(
|
||||
messageTextStyle: messageTextStyle ?? this.messageTextStyle,
|
||||
messageAuthorStyle: messageAuthorStyle ?? this.messageAuthorStyle,
|
||||
messageLinksStyle: messageLinksStyle ?? this.messageLinksStyle,
|
||||
createdAtStyle: createdAtStyle ?? this.createdAtStyle,
|
||||
messageBackgroundColor:
|
||||
messageBackgroundColor ?? this.messageBackgroundColor,
|
||||
messageBorderColor: messageBorderColor ?? this.messageBorderColor,
|
||||
avatarTheme: avatarTheme ?? this.avatarTheme,
|
||||
repliesStyle: repliesStyle ?? this.repliesStyle,
|
||||
reactionsBackgroundColor:
|
||||
reactionsBackgroundColor ?? this.reactionsBackgroundColor,
|
||||
reactionsBorderColor: reactionsBorderColor ?? this.reactionsBorderColor,
|
||||
reactionsMaskColor: reactionsMaskColor ?? this.reactionsMaskColor,
|
||||
);
|
||||
|
||||
/// 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),
|
||||
createdAtStyle: TextStyle.lerp(a.createdAtStyle, b.createdAtStyle, t),
|
||||
messageAuthorStyle:
|
||||
TextStyle.lerp(a.messageAuthorStyle, b.messageAuthorStyle, t),
|
||||
messageBackgroundColor:
|
||||
Color.lerp(a.messageBackgroundColor, b.messageBackgroundColor, t),
|
||||
messageBorderColor:
|
||||
Color.lerp(a.messageBorderColor, b.messageBorderColor, t),
|
||||
messageLinksStyle:
|
||||
TextStyle.lerp(a.messageLinksStyle, b.messageLinksStyle, t),
|
||||
messageTextStyle:
|
||||
TextStyle.lerp(a.messageTextStyle, b.messageTextStyle, t),
|
||||
reactionsBackgroundColor: Color.lerp(
|
||||
a.reactionsBackgroundColor, b.reactionsBackgroundColor, t),
|
||||
reactionsBorderColor:
|
||||
Color.lerp(a.messageBorderColor, b.reactionsBorderColor, t),
|
||||
reactionsMaskColor:
|
||||
Color.lerp(a.reactionsMaskColor, b.reactionsMaskColor, t),
|
||||
repliesStyle: TextStyle.lerp(a.repliesStyle, b.repliesStyle, t),
|
||||
);
|
||||
|
||||
/// Merge with a theme
|
||||
MessageThemeData merge(MessageThemeData? other) {
|
||||
if (other == null) return this;
|
||||
return copyWith(
|
||||
messageTextStyle: messageTextStyle?.merge(other.messageTextStyle) ??
|
||||
other.messageTextStyle,
|
||||
messageAuthorStyle: messageAuthorStyle?.merge(other.messageAuthorStyle) ??
|
||||
other.messageAuthorStyle,
|
||||
messageLinksStyle: messageLinksStyle?.merge(other.messageLinksStyle) ??
|
||||
other.messageLinksStyle,
|
||||
createdAtStyle:
|
||||
createdAtStyle?.merge(other.createdAtStyle) ?? other.createdAtStyle,
|
||||
repliesStyle:
|
||||
repliesStyle?.merge(other.repliesStyle) ?? other.repliesStyle,
|
||||
messageBackgroundColor: other.messageBackgroundColor,
|
||||
messageBorderColor: other.messageBorderColor,
|
||||
avatarTheme: avatarTheme?.merge(other.avatarTheme) ?? other.avatarTheme,
|
||||
reactionsBackgroundColor: other.reactionsBackgroundColor,
|
||||
reactionsBorderColor: other.reactionsBorderColor,
|
||||
reactionsMaskColor: other.reactionsMaskColor,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) =>
|
||||
identical(this, other) ||
|
||||
other is MessageThemeData &&
|
||||
runtimeType == other.runtimeType &&
|
||||
messageTextStyle == other.messageTextStyle &&
|
||||
messageAuthorStyle == other.messageAuthorStyle &&
|
||||
messageLinksStyle == other.messageLinksStyle &&
|
||||
createdAtStyle == other.createdAtStyle &&
|
||||
repliesStyle == other.repliesStyle &&
|
||||
messageBackgroundColor == other.messageBackgroundColor &&
|
||||
messageBorderColor == other.messageBorderColor &&
|
||||
reactionsBackgroundColor == other.reactionsBackgroundColor &&
|
||||
reactionsBorderColor == other.reactionsBorderColor &&
|
||||
reactionsMaskColor == other.reactionsMaskColor &&
|
||||
avatarTheme == other.avatarTheme;
|
||||
|
||||
@override
|
||||
int get hashCode =>
|
||||
messageTextStyle.hashCode ^
|
||||
messageAuthorStyle.hashCode ^
|
||||
messageLinksStyle.hashCode ^
|
||||
createdAtStyle.hashCode ^
|
||||
repliesStyle.hashCode ^
|
||||
messageBackgroundColor.hashCode ^
|
||||
messageBorderColor.hashCode ^
|
||||
reactionsBackgroundColor.hashCode ^
|
||||
reactionsBorderColor.hashCode ^
|
||||
reactionsMaskColor.hashCode ^
|
||||
avatarTheme.hashCode;
|
||||
|
||||
@override
|
||||
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
|
||||
super.debugFillProperties(properties);
|
||||
properties
|
||||
..add(DiagnosticsProperty('messageTextStyle', messageTextStyle))
|
||||
..add(DiagnosticsProperty('messageAuthorStyle', messageAuthorStyle))
|
||||
..add(DiagnosticsProperty('messageLinksStyle', messageLinksStyle))
|
||||
..add(DiagnosticsProperty('createdAtStyle', createdAtStyle))
|
||||
..add(DiagnosticsProperty('repliesStyle', repliesStyle))
|
||||
..add(ColorProperty('messageBackgroundColor', messageBackgroundColor))
|
||||
..add(ColorProperty('messageBorderColor', messageBorderColor))
|
||||
..add(DiagnosticsProperty('avatarTheme', avatarTheme))
|
||||
..add(ColorProperty('reactionsBackgroundColor', reactionsBackgroundColor))
|
||||
..add(ColorProperty('reactionsBorderColor', reactionsBorderColor))
|
||||
..add(ColorProperty('reactionsMaskColor', reactionsMaskColor));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,163 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
/// Class for holding text theme
|
||||
class TextTheme {
|
||||
/// Initialise light text theme
|
||||
TextTheme.light({
|
||||
this.title = const TextStyle(
|
||||
fontSize: 22,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.black,
|
||||
),
|
||||
this.headlineBold = const TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.black,
|
||||
),
|
||||
this.headline = const TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Colors.black,
|
||||
),
|
||||
this.bodyBold = const TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.black,
|
||||
),
|
||||
this.body = const TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Colors.black,
|
||||
),
|
||||
this.footnoteBold = const TextStyle(
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Colors.black,
|
||||
),
|
||||
this.footnote = const TextStyle(
|
||||
fontSize: 12,
|
||||
color: Colors.black,
|
||||
),
|
||||
this.captionBold = const TextStyle(
|
||||
fontSize: 10,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.black,
|
||||
),
|
||||
});
|
||||
|
||||
/// Initialise with dark theme
|
||||
TextTheme.dark({
|
||||
this.title = const TextStyle(
|
||||
fontSize: 22,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.white,
|
||||
),
|
||||
this.headlineBold = const TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.white,
|
||||
),
|
||||
this.headline = const TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Colors.white,
|
||||
),
|
||||
this.bodyBold = const TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.white,
|
||||
),
|
||||
this.body = const TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Colors.white,
|
||||
),
|
||||
this.footnoteBold = const TextStyle(
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Colors.white,
|
||||
),
|
||||
this.footnote = const TextStyle(
|
||||
fontSize: 12,
|
||||
color: Colors.white,
|
||||
),
|
||||
this.captionBold = const TextStyle(
|
||||
fontSize: 10,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Colors.white,
|
||||
),
|
||||
});
|
||||
|
||||
/// Text theme for title
|
||||
final TextStyle title;
|
||||
|
||||
/// Body Text theme for headline
|
||||
final TextStyle headlineBold;
|
||||
|
||||
/// Text theme for headline
|
||||
final TextStyle headline;
|
||||
|
||||
/// Bold Text theme for body
|
||||
final TextStyle bodyBold;
|
||||
|
||||
/// Text theme body
|
||||
final TextStyle body;
|
||||
|
||||
/// Bold Text theme for footnote
|
||||
final TextStyle footnoteBold;
|
||||
|
||||
/// Text theme for footnote
|
||||
final TextStyle footnote;
|
||||
|
||||
/// Bold Text theme for caption
|
||||
final TextStyle captionBold;
|
||||
|
||||
/// Copy with theme
|
||||
TextTheme copyWith({
|
||||
Brightness brightness = Brightness.light,
|
||||
TextStyle? body,
|
||||
TextStyle? title,
|
||||
TextStyle? headlineBold,
|
||||
TextStyle? headline,
|
||||
TextStyle? bodyBold,
|
||||
TextStyle? footnoteBold,
|
||||
TextStyle? footnote,
|
||||
TextStyle? captionBold,
|
||||
}) =>
|
||||
brightness == Brightness.light
|
||||
? TextTheme.light(
|
||||
body: body ?? this.body,
|
||||
title: title ?? this.title,
|
||||
headlineBold: headlineBold ?? this.headlineBold,
|
||||
headline: headline ?? this.headline,
|
||||
bodyBold: bodyBold ?? this.bodyBold,
|
||||
footnoteBold: footnoteBold ?? this.footnoteBold,
|
||||
footnote: footnote ?? this.footnote,
|
||||
captionBold: captionBold ?? this.captionBold,
|
||||
)
|
||||
: TextTheme.dark(
|
||||
body: body ?? this.body,
|
||||
title: title ?? this.title,
|
||||
headlineBold: headlineBold ?? this.headlineBold,
|
||||
headline: headline ?? this.headline,
|
||||
bodyBold: bodyBold ?? this.bodyBold,
|
||||
footnoteBold: footnoteBold ?? this.footnoteBold,
|
||||
footnote: footnote ?? this.footnote,
|
||||
captionBold: captionBold ?? this.captionBold,
|
||||
);
|
||||
|
||||
/// Merge text theme
|
||||
TextTheme merge(TextTheme? other) {
|
||||
if (other == null) return this;
|
||||
return copyWith(
|
||||
body: body.merge(other.body),
|
||||
title: title.merge(other.title),
|
||||
headlineBold: headlineBold.merge(other.headlineBold),
|
||||
headline: headline.merge(other.headline),
|
||||
bodyBold: bodyBold.merge(other.bodyBold),
|
||||
footnoteBold: footnoteBold.merge(other.footnoteBold),
|
||||
footnote: footnote.merge(other.footnote),
|
||||
captionBold: captionBold.merge(other.captionBold),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
export 'avatar_theme.dart';
|
||||
export 'channel_header_theme.dart';
|
||||
export 'channel_list_header_theme.dart';
|
||||
export 'channel_list_view_theme.dart';
|
||||
export 'channel_preview_theme.dart';
|
||||
export 'color_theme.dart';
|
||||
export 'gallery_footer_theme.dart';
|
||||
export 'gallery_header_theme.dart';
|
||||
export 'message_input_theme.dart';
|
||||
export 'message_list_view_theme.dart';
|
||||
export 'message_search_list_view_theme.dart';
|
||||
export 'message_theme.dart';
|
||||
export 'text_theme.dart';
|
||||
export 'user_list_view_theme.dart';
|
||||
@@ -0,0 +1,111 @@
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:stream_chat_flutter/src/stream_chat_theme.dart';
|
||||
|
||||
/// Overrides the default style of [UserListView] descendants.
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [UserListViewThemeData], which is used to configure this theme.
|
||||
class UserListViewTheme extends InheritedTheme {
|
||||
/// Creates a [UserListViewTheme].
|
||||
///
|
||||
/// The [data] parameter must not be null.
|
||||
const UserListViewTheme({
|
||||
Key? key,
|
||||
required this.data,
|
||||
required Widget child,
|
||||
}) : super(key: key, child: child);
|
||||
|
||||
/// The configuration of this theme.
|
||||
final UserListViewThemeData data;
|
||||
|
||||
/// The closest instance of this class that encloses the given context.
|
||||
///
|
||||
/// If there is no enclosing [UserListViewTheme] widget, then
|
||||
/// [StreamChatThemeData.userListViewTheme] is used.
|
||||
///
|
||||
/// Typical usage is as follows:
|
||||
///
|
||||
/// ```dart
|
||||
/// UserListViewTheme theme = UserListViewTheme.of(context);
|
||||
/// ```
|
||||
static UserListViewThemeData of(BuildContext context) {
|
||||
final userListViewTheme =
|
||||
context.dependOnInheritedWidgetOfExactType<UserListViewTheme>();
|
||||
return userListViewTheme?.data ??
|
||||
StreamChatTheme.of(context).userListViewTheme;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget wrap(BuildContext context, Widget child) =>
|
||||
UserListViewTheme(data: data, child: child);
|
||||
|
||||
@override
|
||||
bool updateShouldNotify(UserListViewTheme oldWidget) =>
|
||||
data != oldWidget.data;
|
||||
}
|
||||
|
||||
/// A style that overrides the default appearance of [UserListView]s when
|
||||
/// used with [UserListViewTheme] or with the overall [StreamChatTheme]'s
|
||||
/// [StreamChatThemeData.userListViewTheme].
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [UserListViewTheme], 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({
|
||||
this.backgroundColor,
|
||||
});
|
||||
|
||||
/// The color of the [ChannelListView] background.
|
||||
final Color? backgroundColor;
|
||||
|
||||
/// Copies this [ChannelListViewThemeData] to another.
|
||||
UserListViewThemeData copyWith({
|
||||
Color? backgroundColor,
|
||||
}) =>
|
||||
UserListViewThemeData(
|
||||
backgroundColor: backgroundColor ?? this.backgroundColor,
|
||||
);
|
||||
|
||||
/// Linearly interpolate between two [UserListViewThemeData] themes.
|
||||
///
|
||||
/// All the properties must be non-null.
|
||||
UserListViewThemeData lerp(
|
||||
UserListViewThemeData a,
|
||||
UserListViewThemeData b,
|
||||
double t,
|
||||
) =>
|
||||
UserListViewThemeData(
|
||||
backgroundColor: Color.lerp(a.backgroundColor, b.backgroundColor, t),
|
||||
);
|
||||
|
||||
/// Merges one [UserListViewThemeData] with another.
|
||||
UserListViewThemeData merge(UserListViewThemeData? other) {
|
||||
if (other == null) return this;
|
||||
return copyWith(
|
||||
backgroundColor: other.backgroundColor,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) =>
|
||||
identical(this, other) ||
|
||||
other is UserListViewThemeData &&
|
||||
runtimeType == other.runtimeType &&
|
||||
backgroundColor == other.backgroundColor;
|
||||
|
||||
@override
|
||||
int get hashCode => backgroundColor.hashCode;
|
||||
|
||||
@override
|
||||
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
|
||||
super.debugFillProperties(properties);
|
||||
properties.add(ColorProperty('backgroundColor', backgroundColor));
|
||||
}
|
||||
}
|
||||
@@ -70,6 +70,7 @@ class ThreadHeader extends StatelessWidget implements PreferredSizeWidget {
|
||||
this.actions,
|
||||
this.onTitleTap,
|
||||
this.showTypingIndicator = true,
|
||||
this.backgroundColor,
|
||||
}) : preferredSize = const Size.fromHeight(kToolbarHeight),
|
||||
super(key: key);
|
||||
|
||||
@@ -102,9 +103,12 @@ class ThreadHeader extends StatelessWidget implements PreferredSizeWidget {
|
||||
/// if a user is typing in this thread
|
||||
final bool showTypingIndicator;
|
||||
|
||||
/// The background color of this [ThreadHeader].
|
||||
final Color? backgroundColor;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final chatThemeData = StreamChatTheme.of(context);
|
||||
final channelHeaderTheme = ChannelHeaderTheme.of(context);
|
||||
|
||||
final defaultSubtitle = subtitle ??
|
||||
Row(
|
||||
@@ -113,12 +117,11 @@ class ThreadHeader extends StatelessWidget implements PreferredSizeWidget {
|
||||
children: [
|
||||
Text(
|
||||
'${context.translations.withText} ',
|
||||
style: chatThemeData.channelTheme.channelHeaderTheme.subtitle,
|
||||
style: channelHeaderTheme.subtitleStyle,
|
||||
),
|
||||
Flexible(
|
||||
child: ChannelName(
|
||||
textStyle:
|
||||
chatThemeData.channelTheme.channelHeaderTheme.subtitle,
|
||||
textStyle: channelHeaderTheme.subtitleStyle,
|
||||
),
|
||||
),
|
||||
],
|
||||
@@ -137,7 +140,7 @@ class ThreadHeader extends StatelessWidget implements PreferredSizeWidget {
|
||||
showUnreads: true,
|
||||
)
|
||||
: const SizedBox()),
|
||||
backgroundColor: chatThemeData.channelTheme.channelHeaderTheme.color,
|
||||
backgroundColor: backgroundColor ?? channelHeaderTheme.color,
|
||||
centerTitle: true,
|
||||
actions: actions,
|
||||
title: InkWell(
|
||||
@@ -151,14 +154,14 @@ class ThreadHeader extends StatelessWidget implements PreferredSizeWidget {
|
||||
title ??
|
||||
Text(
|
||||
context.translations.threadReplyLabel,
|
||||
style: chatThemeData.channelTheme.channelHeaderTheme.title,
|
||||
style: channelHeaderTheme.titleStyle,
|
||||
),
|
||||
const SizedBox(height: 2),
|
||||
if (showTypingIndicator)
|
||||
TypingIndicator(
|
||||
alignment: Alignment.center,
|
||||
channel: StreamChannel.of(context).channel,
|
||||
style: chatThemeData.channelTheme.channelHeaderTheme.subtitle,
|
||||
style: channelHeaderTheme.subtitleStyle,
|
||||
parentId: parent.id,
|
||||
alternativeWidget: defaultSubtitle,
|
||||
)
|
||||
|
||||
@@ -19,6 +19,7 @@ class UserAvatar extends StatelessWidget {
|
||||
this.selected = false,
|
||||
this.selectionColor,
|
||||
this.selectionThickness = 4,
|
||||
this.placeholder,
|
||||
}) : super(key: key);
|
||||
|
||||
/// User whose avatar is to displayed
|
||||
@@ -54,13 +55,17 @@ class UserAvatar extends StatelessWidget {
|
||||
/// Selection thickness around the avatar
|
||||
final double selectionThickness;
|
||||
|
||||
/// The widget that will be built when the user image is loading
|
||||
final Widget Function(BuildContext, User)? placeholder;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final hasImage = user.extraData.containsKey('image') &&
|
||||
user.extraData['image'] != null &&
|
||||
user.extraData['image'] != '';
|
||||
final hasImage = user.image != null && user.image!.isNotEmpty;
|
||||
final streamChatTheme = StreamChatTheme.of(context);
|
||||
|
||||
final placeholder =
|
||||
this.placeholder ?? streamChatTheme.placeholderUserImage;
|
||||
|
||||
Widget avatar = FittedBox(
|
||||
fit: BoxFit.cover,
|
||||
child: ClipRRect(
|
||||
@@ -69,17 +74,16 @@ class UserAvatar extends StatelessWidget {
|
||||
child: Container(
|
||||
constraints: constraints ??
|
||||
streamChatTheme.ownMessageTheme.avatarTheme?.constraints,
|
||||
decoration: BoxDecoration(
|
||||
color: streamChatTheme.colorTheme.accentPrimary,
|
||||
),
|
||||
child: hasImage
|
||||
? CachedNetworkImage(
|
||||
filterQuality: FilterQuality.high,
|
||||
// ignore: cast_nullable_to_non_nullable
|
||||
imageUrl: user.extraData['image'] as String,
|
||||
errorWidget: (_, __, ___) =>
|
||||
streamChatTheme.defaultUserImage(context, user),
|
||||
fit: BoxFit.cover,
|
||||
filterQuality: FilterQuality.high,
|
||||
imageUrl: user.image!,
|
||||
errorWidget: (context, __, ___) =>
|
||||
streamChatTheme.defaultUserImage(context, user),
|
||||
placeholder: placeholder != null
|
||||
? (context, __) => placeholder(context, user)
|
||||
: null,
|
||||
)
|
||||
: streamChatTheme.defaultUserImage(context, user),
|
||||
),
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:stream_chat_flutter/src/theme/themes.dart';
|
||||
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
|
||||
import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart';
|
||||
import 'package:stream_chat_flutter/src/extension.dart';
|
||||
|
||||
@@ -12,6 +12,7 @@ export 'src/channel_preview.dart';
|
||||
export 'src/connection_status_builder.dart';
|
||||
export 'src/date_divider.dart';
|
||||
export 'src/deleted_message.dart';
|
||||
export 'src/extension.dart' show IconButtonX;
|
||||
export 'src/full_screen_media.dart';
|
||||
export 'src/gallery_footer.dart';
|
||||
export 'src/gallery_header.dart';
|
||||
@@ -36,13 +37,12 @@ export 'src/stream_chat_theme.dart';
|
||||
export 'src/stream_neumorphic_button.dart';
|
||||
export 'src/stream_svg_icon.dart';
|
||||
export 'src/system_message.dart';
|
||||
export 'src/theme/themes.dart';
|
||||
export 'src/thread_header.dart';
|
||||
export 'src/typing_indicator.dart';
|
||||
export 'src/unread_indicator.dart';
|
||||
export 'src/user_avatar.dart';
|
||||
export 'src/user_item.dart';
|
||||
export 'src/user_item.dart';
|
||||
export 'src/user_list_view.dart';
|
||||
export 'src/user_list_view.dart';
|
||||
export 'src/utils.dart';
|
||||
export 'src/visible_footnote.dart';
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
name: stream_chat_flutter
|
||||
homepage: https://github.com/GetStream/stream-chat-flutter
|
||||
description: Stream Chat official Flutter SDK. Build your own chat experience using Dart and Flutter.
|
||||
version: 2.1.1
|
||||
version: 2.1.2
|
||||
repository: https://github.com/GetStream/stream-chat-flutter
|
||||
issue_tracker: https://github.com/GetStream/stream-chat-flutter/issues
|
||||
|
||||
|
||||
@@ -48,7 +48,7 @@ void main() {
|
||||
);
|
||||
|
||||
testWidgets(
|
||||
'it should show the the other member image',
|
||||
'it should show the other member image',
|
||||
(tester) async {
|
||||
final client = MockClient();
|
||||
final clientState = MockClientState();
|
||||
@@ -74,9 +74,7 @@ void main() {
|
||||
userId: 'user-id2',
|
||||
user: User(
|
||||
id: 'user-id2',
|
||||
extraData: const {
|
||||
'image': 'testimage',
|
||||
},
|
||||
image: 'testimage',
|
||||
),
|
||||
)
|
||||
]));
|
||||
@@ -85,9 +83,7 @@ void main() {
|
||||
userId: 'user-id2',
|
||||
user: User(
|
||||
id: 'user-id2',
|
||||
extraData: const {
|
||||
'image': 'testimage',
|
||||
},
|
||||
image: 'testimage',
|
||||
),
|
||||
),
|
||||
Member(
|
||||
@@ -98,9 +94,7 @@ void main() {
|
||||
when(() => clientState.usersStream).thenAnswer((i) => Stream.value({
|
||||
'user-id2': User(
|
||||
id: 'user-id2',
|
||||
extraData: const {
|
||||
'image': 'testimage',
|
||||
},
|
||||
image: 'testimage',
|
||||
),
|
||||
}));
|
||||
when(() => channel.extraData).thenReturn({
|
||||
@@ -149,27 +143,21 @@ void main() {
|
||||
userId: 'user-id',
|
||||
user: User(
|
||||
id: 'user-id',
|
||||
extraData: const {
|
||||
'image': 'testimage1',
|
||||
},
|
||||
image: 'testimage1',
|
||||
),
|
||||
),
|
||||
Member(
|
||||
userId: 'user-id2',
|
||||
user: User(
|
||||
id: 'user-id2',
|
||||
extraData: const {
|
||||
'image': 'testimage2',
|
||||
},
|
||||
image: 'testimage2',
|
||||
),
|
||||
),
|
||||
Member(
|
||||
userId: 'user-id3',
|
||||
user: User(
|
||||
id: 'user-id3',
|
||||
extraData: const {
|
||||
'image': 'testimage3',
|
||||
},
|
||||
image: 'testimage3',
|
||||
),
|
||||
),
|
||||
];
|
||||
|
||||
@@ -21,11 +21,11 @@ void main() {
|
||||
client: client,
|
||||
child: const Scaffold(
|
||||
body: DeletedMessage(
|
||||
messageTheme: MessageTheme(
|
||||
createdAt: TextStyle(
|
||||
messageTheme: MessageThemeData(
|
||||
createdAtStyle: TextStyle(
|
||||
color: Colors.black,
|
||||
),
|
||||
messageText: TextStyle(),
|
||||
messageTextStyle: TextStyle(),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
@@ -84,7 +84,7 @@ void main() {
|
||||
final clientState = MockClientState();
|
||||
final channel = MockChannel();
|
||||
final channelState = MockChannelState();
|
||||
const messageTheme = MessageTheme();
|
||||
const messageTheme = MessageThemeData();
|
||||
|
||||
final currentUser = OwnUser(
|
||||
id: 'sahil',
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
|
||||
|
||||
void main() {
|
||||
test('AvatarThemeData copyWith, ==, hashCode basics', () {
|
||||
expect(const AvatarThemeData(), const AvatarThemeData().copyWith());
|
||||
expect(const AvatarThemeData().hashCode,
|
||||
const AvatarThemeData().copyWith().hashCode);
|
||||
});
|
||||
|
||||
group('AvatarThemeData lerps correctly', () {
|
||||
test('Lerp completely', () {
|
||||
expect(
|
||||
const AvatarThemeData()
|
||||
.lerp(_avatarThemeDataControl1, _avatarThemeDataControl2, 1),
|
||||
_avatarThemeDataControl2);
|
||||
});
|
||||
|
||||
test('Lerp halfway', () {
|
||||
expect(
|
||||
const AvatarThemeData()
|
||||
.lerp(_avatarThemeDataControl1, _avatarThemeDataControl2, 0.5),
|
||||
_avatarThemeDataControlMidLerp);
|
||||
});
|
||||
});
|
||||
|
||||
test('Merging two AvatarThemeData results in the latter', () {
|
||||
expect(_avatarThemeDataControl1.merge(_avatarThemeDataControl2),
|
||||
_avatarThemeDataControl2);
|
||||
});
|
||||
}
|
||||
|
||||
const _avatarThemeDataControl1 = AvatarThemeData();
|
||||
|
||||
final _avatarThemeDataControlMidLerp = AvatarThemeData(
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
constraints: const BoxConstraints.tightFor(
|
||||
height: 33,
|
||||
width: 33,
|
||||
),
|
||||
);
|
||||
|
||||
final _avatarThemeDataControl2 = AvatarThemeData(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
constraints: const BoxConstraints.tightFor(
|
||||
height: 34,
|
||||
width: 34,
|
||||
),
|
||||
);
|
||||
@@ -0,0 +1,97 @@
|
||||
import 'package:flutter/material.dart' hide TextTheme;
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
|
||||
|
||||
void main() {
|
||||
test('ChannelHeaderThemeData copyWith, ==, hashCode basics', () {
|
||||
expect(const ChannelHeaderThemeData(),
|
||||
const ChannelHeaderThemeData().copyWith());
|
||||
expect(const ChannelHeaderThemeData().hashCode,
|
||||
const ChannelHeaderThemeData().copyWith().hashCode);
|
||||
});
|
||||
|
||||
group('ChannelHeaderThemeData lerps', () {
|
||||
test(
|
||||
'''Light ChannelHeaderThemeData lerps completely to dark ChannelHeaderThemeData''',
|
||||
() {
|
||||
expect(
|
||||
const ChannelHeaderThemeData()
|
||||
.lerp(_channelThemeControl, _channelThemeControlDark, 1),
|
||||
_channelThemeControlDark);
|
||||
});
|
||||
|
||||
test(
|
||||
'''Light ChannelHeaderThemeData lerps halfway to dark ChannelHeaderThemeData''',
|
||||
() {
|
||||
expect(
|
||||
const ChannelHeaderThemeData()
|
||||
.lerp(_channelThemeControl, _channelThemeControlDark, 0.5),
|
||||
_channelThemeControlMidLerp);
|
||||
});
|
||||
|
||||
test(
|
||||
'''Dark ChannelHeaderThemeData lerps completely to light ChannelHeaderThemeData''',
|
||||
() {
|
||||
expect(
|
||||
const ChannelHeaderThemeData()
|
||||
.lerp(_channelThemeControlDark, _channelThemeControl, 1),
|
||||
_channelThemeControl);
|
||||
});
|
||||
});
|
||||
|
||||
test('Merging dark and light themes results in a dark theme', () {
|
||||
expect(_channelThemeControl.merge(_channelThemeControlDark),
|
||||
_channelThemeControlDark);
|
||||
});
|
||||
}
|
||||
|
||||
final _channelThemeControl = ChannelHeaderThemeData(
|
||||
avatarTheme: AvatarThemeData(
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
constraints: const BoxConstraints.tightFor(
|
||||
height: 40,
|
||||
width: 40,
|
||||
),
|
||||
),
|
||||
color: const Color(0xff101418),
|
||||
titleStyle: TextTheme.light().headlineBold.copyWith(
|
||||
color: const Color(0xffffffff),
|
||||
),
|
||||
subtitleStyle: TextTheme.light().footnote.copyWith(
|
||||
color: const Color(0xff7a7a7a),
|
||||
),
|
||||
);
|
||||
|
||||
final _channelThemeControlMidLerp = ChannelHeaderThemeData(
|
||||
avatarTheme: AvatarThemeData(
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
constraints: const BoxConstraints.tightFor(
|
||||
height: 40,
|
||||
width: 40,
|
||||
),
|
||||
),
|
||||
color: const Color(0xff101418),
|
||||
titleStyle: const TextStyle(
|
||||
color: Color(0xffffffff),
|
||||
fontWeight: FontWeight.bold,
|
||||
fontSize: 16,
|
||||
),
|
||||
subtitleStyle: TextTheme.light().footnote.copyWith(
|
||||
color: const Color(0xff7a7a7a),
|
||||
),
|
||||
);
|
||||
|
||||
final _channelThemeControlDark = ChannelHeaderThemeData(
|
||||
avatarTheme: AvatarThemeData(
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
constraints: const BoxConstraints.tightFor(
|
||||
height: 40,
|
||||
width: 40,
|
||||
),
|
||||
),
|
||||
color: ColorTheme.dark().barsBg,
|
||||
titleStyle: TextTheme.dark().headlineBold,
|
||||
subtitleStyle: TextTheme.dark().footnote.copyWith(
|
||||
color: const Color(0xff7A7A7A),
|
||||
),
|
||||
);
|
||||
@@ -0,0 +1,94 @@
|
||||
import 'package:flutter/material.dart' hide TextTheme;
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
|
||||
|
||||
void main() {
|
||||
test('ChannelListHeaderThemeData copyWith, ==, hashCode basics', () {
|
||||
expect(const ChannelListHeaderThemeData(),
|
||||
const ChannelListHeaderThemeData().copyWith());
|
||||
expect(const ChannelListHeaderThemeData().hashCode,
|
||||
const ChannelListHeaderThemeData().copyWith().hashCode);
|
||||
});
|
||||
|
||||
group('ChannelListHeaderThemeData lerps', () {
|
||||
test(
|
||||
'''Light ChannelListHeaderThemeData lerps completely to dark ChannelListHeaderThemeData''',
|
||||
() {
|
||||
expect(
|
||||
const ChannelListHeaderThemeData().lerp(
|
||||
_channelListHeaderThemeControl,
|
||||
_channelListHeaderThemeControlDark,
|
||||
1),
|
||||
_channelListHeaderThemeControlDark);
|
||||
});
|
||||
|
||||
test(
|
||||
'''Light ChannelListHeaderThemeData lerps halfway to dark ChannelListHeaderThemeData''',
|
||||
() {
|
||||
expect(
|
||||
const ChannelListHeaderThemeData().lerp(
|
||||
_channelListHeaderThemeControl,
|
||||
_channelListHeaderThemeControlDark,
|
||||
0.5),
|
||||
_channelListHeaderThemeControlMidLerp);
|
||||
});
|
||||
|
||||
test(
|
||||
'''Dark ChannelListHeaderThemeData lerps completely to light ChannelListHeaderThemeData''',
|
||||
() {
|
||||
expect(
|
||||
const ChannelListHeaderThemeData().lerp(
|
||||
_channelListHeaderThemeControlDark,
|
||||
_channelListHeaderThemeControl,
|
||||
1),
|
||||
_channelListHeaderThemeControl);
|
||||
});
|
||||
});
|
||||
|
||||
test('Merging dark and light themes results in a dark theme', () {
|
||||
expect(
|
||||
_channelListHeaderThemeControl
|
||||
.merge(_channelListHeaderThemeControlDark),
|
||||
_channelListHeaderThemeControlDark);
|
||||
});
|
||||
}
|
||||
|
||||
final _channelListHeaderThemeControl = ChannelListHeaderThemeData(
|
||||
avatarTheme: AvatarThemeData(
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
constraints: const BoxConstraints.tightFor(
|
||||
height: 40,
|
||||
width: 40,
|
||||
),
|
||||
),
|
||||
color: ColorTheme.light().barsBg,
|
||||
titleStyle: TextTheme.light().headlineBold,
|
||||
);
|
||||
|
||||
final _channelListHeaderThemeControlMidLerp = ChannelListHeaderThemeData(
|
||||
avatarTheme: AvatarThemeData(
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
constraints: const BoxConstraints.tightFor(
|
||||
height: 40,
|
||||
width: 40,
|
||||
),
|
||||
),
|
||||
color: const Color(0xff87898b),
|
||||
titleStyle: const TextStyle(
|
||||
color: Color(0xff7f7f7f),
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
);
|
||||
|
||||
final _channelListHeaderThemeControlDark = ChannelListHeaderThemeData(
|
||||
avatarTheme: AvatarThemeData(
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
constraints: const BoxConstraints.tightFor(
|
||||
height: 40,
|
||||
width: 40,
|
||||
),
|
||||
),
|
||||
color: ColorTheme.dark().barsBg,
|
||||
titleStyle: TextTheme.dark().headlineBold,
|
||||
);
|
||||
+1
-1
@@ -2,7 +2,7 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
|
||||
|
||||
import 'mocks.dart';
|
||||
import '../mocks.dart';
|
||||
|
||||
void main() {
|
||||
test('ChannelListViewThemeData copyWith, ==, hashCode basics', () {
|
||||
@@ -0,0 +1,108 @@
|
||||
import 'package:flutter/material.dart' hide TextTheme;
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
|
||||
|
||||
void main() {
|
||||
test('ChannelPreviewThemeData copyWith, ==, hashCode basics', () {
|
||||
expect(const ChannelPreviewThemeData(),
|
||||
const ChannelPreviewThemeData().copyWith());
|
||||
expect(const ChannelPreviewThemeData().hashCode,
|
||||
const ChannelPreviewThemeData().copyWith().hashCode);
|
||||
});
|
||||
|
||||
group('ChannelPreviewThemeData lerps', () {
|
||||
test(
|
||||
'''Light ChannelPreviewThemeData lerps completely to dark ChannelPreviewThemeData''',
|
||||
() {
|
||||
expect(
|
||||
const ChannelPreviewThemeData().lerp(
|
||||
_channelPreviewThemeControl, _channelPreviewThemeControlDark, 1),
|
||||
_channelPreviewThemeControlDark);
|
||||
});
|
||||
|
||||
test(
|
||||
'''Light ChannelPreviewThemeData lerps halfway to dark ChannelPreviewThemeData''',
|
||||
() {
|
||||
expect(
|
||||
const ChannelPreviewThemeData().lerp(_channelPreviewThemeControl,
|
||||
_channelPreviewThemeControlDark, 0.5),
|
||||
_channelPreviewThemeControlMidLerp);
|
||||
});
|
||||
|
||||
test(
|
||||
'''Dark ChannelPreviewThemeData lerps completely to light ChannelPreviewThemeData''',
|
||||
() {
|
||||
expect(
|
||||
const ChannelPreviewThemeData().lerp(
|
||||
_channelPreviewThemeControlDark, _channelPreviewThemeControl, 1),
|
||||
_channelPreviewThemeControl);
|
||||
});
|
||||
});
|
||||
|
||||
test('Merging dark and light themes results in a dark theme', () {
|
||||
expect(_channelPreviewThemeControl.merge(_channelPreviewThemeControlDark),
|
||||
_channelPreviewThemeControlDark);
|
||||
});
|
||||
}
|
||||
|
||||
final _channelPreviewThemeControl = ChannelPreviewThemeData(
|
||||
unreadCounterColor: ColorTheme.light().accentError,
|
||||
avatarTheme: AvatarThemeData(
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
constraints: const BoxConstraints.tightFor(
|
||||
height: 40,
|
||||
width: 40,
|
||||
),
|
||||
),
|
||||
titleStyle: TextTheme.light().bodyBold,
|
||||
subtitleStyle: TextTheme.light().footnote.copyWith(
|
||||
color: const Color(0xff7A7A7A),
|
||||
),
|
||||
lastMessageAtStyle: TextTheme.light().footnote.copyWith(
|
||||
color: ColorTheme.light().textHighEmphasis.withOpacity(.5),
|
||||
),
|
||||
indicatorIconSize: 16,
|
||||
);
|
||||
|
||||
final _channelPreviewThemeControlMidLerp = ChannelPreviewThemeData(
|
||||
unreadCounterColor: const Color(0xffff3742),
|
||||
avatarTheme: AvatarThemeData(
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
constraints: const BoxConstraints.tightFor(
|
||||
height: 40,
|
||||
width: 40,
|
||||
),
|
||||
),
|
||||
titleStyle: const TextStyle(
|
||||
color: Color(0xff7f7f7f),
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
subtitleStyle: const TextStyle(
|
||||
color: Color(0xff7a7a7a),
|
||||
fontSize: 12,
|
||||
),
|
||||
lastMessageAtStyle: TextTheme.light().footnote.copyWith(
|
||||
color: const Color(0x807f7f7f).withOpacity(.5),
|
||||
),
|
||||
indicatorIconSize: 16,
|
||||
);
|
||||
|
||||
final _channelPreviewThemeControlDark = ChannelPreviewThemeData(
|
||||
unreadCounterColor: ColorTheme.dark().accentError,
|
||||
avatarTheme: AvatarThemeData(
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
constraints: const BoxConstraints.tightFor(
|
||||
height: 40,
|
||||
width: 40,
|
||||
),
|
||||
),
|
||||
titleStyle: TextTheme.dark().bodyBold,
|
||||
subtitleStyle: TextTheme.dark().footnote.copyWith(
|
||||
color: const Color(0xff7A7A7A),
|
||||
),
|
||||
lastMessageAtStyle: TextTheme.dark().footnote.copyWith(
|
||||
color: ColorTheme.dark().textHighEmphasis.withOpacity(.5),
|
||||
),
|
||||
indicatorIconSize: 16,
|
||||
);
|
||||
@@ -0,0 +1,122 @@
|
||||
import 'package:flutter/material.dart' hide TextTheme;
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
|
||||
|
||||
void main() {
|
||||
test('MessageInputThemeData copyWith, ==, hashCode basics', () {
|
||||
expect(const MessageInputThemeData(),
|
||||
const MessageInputThemeData().copyWith());
|
||||
expect(const MessageInputThemeData().hashCode,
|
||||
const MessageInputThemeData().copyWith().hashCode);
|
||||
});
|
||||
|
||||
group('MessageInputThemeData lerps correctly', () {
|
||||
test('Lerp completely from light to dark', () {
|
||||
expect(
|
||||
const MessageInputThemeData().lerp(
|
||||
_messageInputThemeControl, _messageInputThemeControlDark, 1),
|
||||
_messageInputThemeControlDark);
|
||||
});
|
||||
|
||||
test('Lerp halfway from light to dark', () {
|
||||
expect(
|
||||
const MessageInputThemeData().lerp(
|
||||
_messageInputThemeControl, _messageInputThemeControlDark, 0.5),
|
||||
_messageInputThemeControlMidLerp);
|
||||
});
|
||||
|
||||
test('Lerp completely from dark to light', () {
|
||||
expect(
|
||||
const MessageInputThemeData().lerp(
|
||||
_messageInputThemeControlDark, _messageInputThemeControl, 1),
|
||||
_messageInputThemeControl);
|
||||
});
|
||||
});
|
||||
|
||||
test('Merging two MessageInputThemeData results in the latter', () {
|
||||
expect(_messageInputThemeControl.merge(_messageInputThemeControlDark),
|
||||
_messageInputThemeControlDark);
|
||||
});
|
||||
}
|
||||
|
||||
final _messageInputThemeControl = MessageInputThemeData(
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
sendAnimationDuration: const Duration(milliseconds: 300),
|
||||
actionButtonColor: ColorTheme.light().accentPrimary,
|
||||
actionButtonIdleColor: ColorTheme.light().textLowEmphasis,
|
||||
expandButtonColor: ColorTheme.light().accentPrimary,
|
||||
sendButtonColor: ColorTheme.light().accentPrimary,
|
||||
sendButtonIdleColor: ColorTheme.light().disabled,
|
||||
inputBackgroundColor: ColorTheme.light().barsBg,
|
||||
inputTextStyle: TextTheme.light().body,
|
||||
idleBorderGradient: LinearGradient(
|
||||
stops: const [0.0, 1.0],
|
||||
colors: [
|
||||
ColorTheme.light().disabled,
|
||||
ColorTheme.light().disabled,
|
||||
],
|
||||
),
|
||||
activeBorderGradient: LinearGradient(
|
||||
stops: const [0.0, 1.0],
|
||||
colors: [
|
||||
ColorTheme.light().disabled,
|
||||
ColorTheme.light().disabled,
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
final _messageInputThemeControlMidLerp = MessageInputThemeData(
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
sendAnimationDuration: const Duration(milliseconds: 300),
|
||||
inputBackgroundColor: const Color(0xff87898b),
|
||||
actionButtonColor: const Color(0xff005fff),
|
||||
actionButtonIdleColor: const Color(0xff7a7a7a),
|
||||
sendButtonColor: const Color(0xff005fff),
|
||||
sendButtonIdleColor: const Color(0xff848585),
|
||||
expandButtonColor: const Color(0xff005fff),
|
||||
inputTextStyle: const TextStyle(
|
||||
color: Color(0xff7f7f7f),
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
idleBorderGradient: const LinearGradient(
|
||||
stops: [0.0, 1.0],
|
||||
colors: [
|
||||
Color(0xff848585),
|
||||
Color(0xff848585),
|
||||
],
|
||||
),
|
||||
activeBorderGradient: const LinearGradient(
|
||||
stops: [0.0, 1.0],
|
||||
colors: [
|
||||
Color(0xff848585),
|
||||
Color(0xff848585),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
final _messageInputThemeControlDark = MessageInputThemeData(
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
sendAnimationDuration: const Duration(milliseconds: 300),
|
||||
actionButtonColor: ColorTheme.dark().accentPrimary,
|
||||
actionButtonIdleColor: ColorTheme.dark().textLowEmphasis,
|
||||
expandButtonColor: ColorTheme.dark().accentPrimary,
|
||||
sendButtonColor: ColorTheme.dark().accentPrimary,
|
||||
sendButtonIdleColor: ColorTheme.dark().disabled,
|
||||
inputBackgroundColor: ColorTheme.dark().barsBg,
|
||||
inputTextStyle: TextTheme.dark().body,
|
||||
idleBorderGradient: LinearGradient(
|
||||
stops: const [0.0, 1.0],
|
||||
colors: [
|
||||
ColorTheme.dark().disabled,
|
||||
ColorTheme.dark().disabled,
|
||||
],
|
||||
),
|
||||
activeBorderGradient: LinearGradient(
|
||||
stops: const [0.0, 1.0],
|
||||
colors: [
|
||||
ColorTheme.dark().disabled,
|
||||
ColorTheme.dark().disabled,
|
||||
],
|
||||
),
|
||||
);
|
||||
+1
-1
@@ -3,7 +3,7 @@ import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:mocktail/mocktail.dart';
|
||||
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
|
||||
|
||||
import 'mocks.dart';
|
||||
import '../mocks.dart';
|
||||
|
||||
class MockStreamChatClient extends Mock implements StreamChatClient {}
|
||||
|
||||
+1
-1
@@ -2,7 +2,7 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
|
||||
|
||||
import 'mocks.dart';
|
||||
import '../mocks.dart';
|
||||
|
||||
void main() {
|
||||
test('MessageSearchListViewThemeData copyWith, ==, hashCode basics', () {
|
||||
@@ -0,0 +1,90 @@
|
||||
import 'package:flutter/material.dart' hide TextTheme;
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
|
||||
|
||||
void main() {
|
||||
test('MessageThemeData copyWith, ==, hashCode basics', () {
|
||||
expect(const MessageThemeData(), const MessageThemeData().copyWith());
|
||||
expect(const MessageThemeData().hashCode,
|
||||
const MessageThemeData().copyWith().hashCode);
|
||||
});
|
||||
|
||||
group('MessageThemeData lerps', () {
|
||||
test('''Light MessageThemeData lerps completely to dark MessageThemeData''',
|
||||
() {
|
||||
expect(
|
||||
const MessageThemeData()
|
||||
.lerp(_messageThemeControl, _messageThemeControlDark, 1),
|
||||
_messageThemeControlDark);
|
||||
});
|
||||
|
||||
test('''Dark MessageThemeData lerps completely to light MessageThemeData''',
|
||||
() {
|
||||
expect(
|
||||
const MessageThemeData()
|
||||
.lerp(_messageThemeControlDark, _messageThemeControl, 1),
|
||||
_messageThemeControl);
|
||||
});
|
||||
});
|
||||
|
||||
test('Merging dark and light themes results in a dark theme', () {
|
||||
expect(_messageThemeControl.merge(_messageThemeControlDark),
|
||||
_messageThemeControlDark);
|
||||
});
|
||||
}
|
||||
|
||||
final _messageThemeControl = MessageThemeData(
|
||||
messageAuthorStyle: TextTheme.light().footnote.copyWith(
|
||||
color: ColorTheme.light().textLowEmphasis,
|
||||
),
|
||||
messageTextStyle: TextTheme.light().body,
|
||||
createdAtStyle: TextTheme.light().footnote.copyWith(
|
||||
color: ColorTheme.light().textLowEmphasis,
|
||||
),
|
||||
repliesStyle: TextTheme.light().footnoteBold.copyWith(
|
||||
color: ColorTheme.light().accentPrimary,
|
||||
),
|
||||
messageBackgroundColor: ColorTheme.light().disabled,
|
||||
reactionsBackgroundColor: ColorTheme.light().barsBg,
|
||||
reactionsBorderColor: ColorTheme.light().borders,
|
||||
reactionsMaskColor: ColorTheme.light().appBg,
|
||||
messageBorderColor: ColorTheme.light().disabled,
|
||||
avatarTheme: AvatarThemeData(
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
constraints: const BoxConstraints.tightFor(
|
||||
height: 32,
|
||||
width: 32,
|
||||
),
|
||||
),
|
||||
messageLinksStyle: TextStyle(
|
||||
color: ColorTheme.light().accentPrimary,
|
||||
),
|
||||
);
|
||||
|
||||
final _messageThemeControlDark = MessageThemeData(
|
||||
messageAuthorStyle: TextTheme.dark().footnote.copyWith(
|
||||
color: ColorTheme.dark().textLowEmphasis,
|
||||
),
|
||||
messageTextStyle: TextTheme.dark().body,
|
||||
createdAtStyle: TextTheme.dark().footnote.copyWith(
|
||||
color: ColorTheme.dark().textLowEmphasis,
|
||||
),
|
||||
repliesStyle: TextTheme.dark().footnoteBold.copyWith(
|
||||
color: ColorTheme.dark().accentPrimary,
|
||||
),
|
||||
messageBackgroundColor: ColorTheme.dark().disabled,
|
||||
reactionsBackgroundColor: ColorTheme.dark().barsBg,
|
||||
reactionsBorderColor: ColorTheme.dark().borders,
|
||||
reactionsMaskColor: ColorTheme.dark().appBg,
|
||||
messageBorderColor: ColorTheme.dark().disabled,
|
||||
avatarTheme: AvatarThemeData(
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
constraints: const BoxConstraints.tightFor(
|
||||
height: 32,
|
||||
width: 32,
|
||||
),
|
||||
),
|
||||
messageLinksStyle: TextStyle(
|
||||
color: ColorTheme.dark().accentPrimary,
|
||||
),
|
||||
);
|
||||
+1
-1
@@ -2,7 +2,7 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
|
||||
|
||||
import 'mocks.dart';
|
||||
import '../mocks.dart';
|
||||
|
||||
void main() {
|
||||
test('UserListViewThemeData copyWith, ==, hashCode basics', () {
|
||||
@@ -26,7 +26,7 @@ It teaches you how to use this SDK and also shows how to make frequently require
|
||||
## Example App
|
||||
|
||||
This repo includes a fully functional example app with setup instructions.
|
||||
The example is available under the [example](https://github.com/GetStream/stream-chat-flutter-core/tree/master/example) folder.
|
||||
The example is available under the [example](https://github.com/GetStream/stream-chat-flutter/tree/main/packages/stream_chat_flutter_core/example) folder.
|
||||
|
||||
## Add dependency
|
||||
Add this to your package's pubspec.yaml file, use the latest version [](https://pub.dartlang.org/packages/stream_chat_flutter_core)
|
||||
|
||||
@@ -13,13 +13,10 @@ Future<void> main() async {
|
||||
await client.connectUser(
|
||||
User(
|
||||
id: 'cool-shadow-7',
|
||||
extraData: const {
|
||||
'image':
|
||||
'https://getstream.io/random_png/?id=cool-shadow-7&name=Cool+shadow',
|
||||
},
|
||||
image:
|
||||
'https://getstream.io/random_png/?id=cool-shadow-7&name=Cool+shadow',
|
||||
),
|
||||
'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoiY29vbC1zaGFkb3ctNyJ9'
|
||||
'.gkOlCRb1qgy4joHPaxFwPOdXcGvSPvp6QY0S4mpRkVo',
|
||||
'''eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoiY29vbC1zaGFkb3ctNyJ9.gkOlCRb1qgy4joHPaxFwPOdXcGvSPvp6QY0S4mpRkVo''',
|
||||
);
|
||||
|
||||
runApp(
|
||||
@@ -332,21 +329,9 @@ class _MessageScreenState extends State<MessageScreen> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Extensions can be used to add functionality to the SDK. In the examples
|
||||
/// below, we add two simple extensions to the [StreamChatClient] and [Channel].
|
||||
/// Extensions can be used to add functionality to the SDK. In the example
|
||||
/// below, we add a simple extensions to the [StreamChatClient].
|
||||
extension on StreamChatClient {
|
||||
/// Fetches the current user id.
|
||||
String get uid => state.currentUser!.id;
|
||||
}
|
||||
|
||||
extension on Channel {
|
||||
/// Fetches the name of the channel by accessing [extraData] or [cid].
|
||||
String? get name {
|
||||
final _channelName = extraData['name'];
|
||||
if (_channelName != null) {
|
||||
return _channelName as String;
|
||||
} else {
|
||||
return cid;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
## Upcoming
|
||||
|
||||
* Added support for [Spanish](https://github.com/GetStream/stream-chat-flutter/blob/master/packages/stream_chat_localizations/lib/src/stream_chat_localizations_es.dart) locale.
|
||||
|
||||
## 1.0.2
|
||||
|
||||
* Updated stream_chat_flutter dependency
|
||||
* Updated `stream_chat_flutter` dependency
|
||||
|
||||
## 1.0.1
|
||||
|
||||
@@ -8,4 +12,8 @@
|
||||
|
||||
## 1.0.0
|
||||
|
||||
* First release
|
||||
* Initial Release with support for 4 locales
|
||||
- [English](https://github.com/GetStream/stream-chat-flutter/blob/master/packages/stream_chat_localizations/lib/src/stream_chat_localizations_en.dart)
|
||||
- [Hindi](https://github.com/GetStream/stream-chat-flutter/blob/master/packages/stream_chat_localizations/lib/src/stream_chat_localizations_hi.dart)
|
||||
- [Italian](https://github.com/GetStream/stream-chat-flutter/blob/master/packages/stream_chat_localizations/lib/src/stream_chat_localizations_it.dart)
|
||||
- [French](https://github.com/GetStream/stream-chat-flutter/blob/master/packages/stream_chat_localizations/lib/src/stream_chat_localizations_fr.dart)
|
||||
|
||||
@@ -34,6 +34,7 @@ At the moment we support the following languages:
|
||||
- [Hindi](https://github.com/GetStream/stream-chat-flutter/blob/master/packages/stream_chat_localizations/lib/src/stream_chat_localizations_hi.dart)
|
||||
- [Italian](https://github.com/GetStream/stream-chat-flutter/blob/master/packages/stream_chat_localizations/lib/src/stream_chat_localizations_it.dart)
|
||||
- [French](https://github.com/GetStream/stream-chat-flutter/blob/master/packages/stream_chat_localizations/lib/src/stream_chat_localizations_fr.dart)
|
||||
- [Spanish](https://github.com/GetStream/stream-chat-flutter/blob/master/packages/stream_chat_localizations/lib/src/stream_chat_localizations_es.dart)
|
||||
|
||||
More languages will be added in the future. Feel free to [contribute](https://github.com/GetStream/stream-chat-flutter/blob/master/CONTRIBUTING.md) to add more languages.
|
||||
|
||||
@@ -68,6 +69,7 @@ class MyApp extends StatelessWidget {
|
||||
Locale('hi'),
|
||||
Locale('fr'),
|
||||
Locale('it'),
|
||||
Locale('es'),
|
||||
],
|
||||
// Add GlobalStreamChatLocalizations.delegates
|
||||
localizationsDelegates: GlobalStreamChatLocalizations.delegates,
|
||||
@@ -86,13 +88,13 @@ class MyApp extends StatelessWidget {
|
||||
|
||||
### Adding a new language
|
||||
|
||||
To add a new language, you need to create a new class extending `GlobalStreamChatLocalizations` and create a delegate for it adding it to the `delegates` array.
|
||||
To add a new language, create a new class extending `GlobalStreamChatLocalizations` and create a delegate for it, adding it to the `delegates` array.
|
||||
|
||||
Check out [this example](https://github.com/GetStream/stream-chat-flutter/blob/master/packages/stream_chat_localizations/example/lib/add_new_lang.dart) to see how to add a new language.
|
||||
|
||||
### Override existing languages
|
||||
|
||||
To override an existing language, you need to create a new class extending that particular language class and create a delegate for it adding it to the `delegates` array.
|
||||
To override an existing language, create a new class extending that particular language class and create a delegate for it, adding it to the `delegates` array.
|
||||
|
||||
Check out [this example](https://github.com/GetStream/stream-chat-flutter/blob/master/packages/stream_chat_localizations/example/lib/override_lang.dart) to see how to override an existing language.
|
||||
|
||||
@@ -110,6 +112,7 @@ Example:
|
||||
<string>nb</string>
|
||||
<string>fr</string>
|
||||
<string>it</string>
|
||||
<string>es</string>
|
||||
</array>
|
||||
```
|
||||
|
||||
|
||||
@@ -401,8 +401,7 @@ void main() async {
|
||||
/// https://getstream.io/chat/docs/ios_user_setup_and_tokens/
|
||||
await client.connectUser(
|
||||
User(id: 'super-band-9'),
|
||||
'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoic3VwZXItYmFuZC05In0.'
|
||||
'0L6lGoeLwkz0aZRUcpZKsvaXtNEDHBcezVTZ0oPq40A',
|
||||
'''eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoic3VwZXItYmFuZC05In0.0L6lGoeLwkz0aZRUcpZKsvaXtNEDHBcezVTZ0oPq40A''',
|
||||
);
|
||||
|
||||
final channel = client.channel('messaging', id: 'godevs');
|
||||
|
||||
@@ -20,8 +20,7 @@ void main() async {
|
||||
/// https://getstream.io/chat/docs/ios_user_setup_and_tokens/
|
||||
await client.connectUser(
|
||||
User(id: 'super-band-9'),
|
||||
'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoic3VwZXItYmFuZC05In0.'
|
||||
'0L6lGoeLwkz0aZRUcpZKsvaXtNEDHBcezVTZ0oPq40A',
|
||||
'''eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoic3VwZXItYmFuZC05In0.0L6lGoeLwkz0aZRUcpZKsvaXtNEDHBcezVTZ0oPq40A''',
|
||||
);
|
||||
|
||||
final channel = client.channel('messaging', id: 'godevs');
|
||||
|
||||
@@ -45,8 +45,7 @@ void main() async {
|
||||
/// https://getstream.io/chat/docs/ios_user_setup_and_tokens/
|
||||
await client.connectUser(
|
||||
User(id: 'super-band-9'),
|
||||
'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoic3VwZXItYmFuZC05In0.'
|
||||
'0L6lGoeLwkz0aZRUcpZKsvaXtNEDHBcezVTZ0oPq40A',
|
||||
'''eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoic3VwZXItYmFuZC05In0.0L6lGoeLwkz0aZRUcpZKsvaXtNEDHBcezVTZ0oPq40A''',
|
||||
);
|
||||
|
||||
final channel = client.channel('messaging', id: 'godevs');
|
||||
|
||||
@@ -3,6 +3,8 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter_localizations/flutter_localizations.dart';
|
||||
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
|
||||
|
||||
part 'stream_chat_localizations_es.dart';
|
||||
|
||||
part 'stream_chat_localizations_en.dart';
|
||||
|
||||
part 'stream_chat_localizations_fr.dart';
|
||||
@@ -24,6 +26,7 @@ const kStreamChatSupportedLanguages = {
|
||||
'hi',
|
||||
'fr',
|
||||
'it',
|
||||
'es',
|
||||
};
|
||||
|
||||
/// Creates a [GlobalStreamChatLocalizations] instance for the given `locale`.
|
||||
@@ -54,6 +57,8 @@ GlobalStreamChatLocalizations? getStreamChatTranslation(Locale locale) {
|
||||
return const StreamChatLocalizationsFr();
|
||||
case 'it':
|
||||
return const StreamChatLocalizationsIt();
|
||||
case 'es':
|
||||
return const StreamChatLocalizationsEs();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,365 @@
|
||||
part of 'stream_chat_localizations.dart';
|
||||
|
||||
/// The translations for Spanish (`es`).
|
||||
class StreamChatLocalizationsEs extends GlobalStreamChatLocalizations {
|
||||
/// Create an instance of the translation bundle for Spanish.
|
||||
const StreamChatLocalizationsEs({String localeName = 'es'})
|
||||
: super(localeName: localeName);
|
||||
|
||||
@override
|
||||
String get launchUrlError => 'No se pudo abrir la url';
|
||||
|
||||
@override
|
||||
String get loadingUsersError => 'Error de carga del usuario';
|
||||
|
||||
@override
|
||||
String get noUsersLabel => 'No hay usuarios actualmente';
|
||||
|
||||
@override
|
||||
String get retryLabel => 'Inténtelo de nuevo';
|
||||
|
||||
@override
|
||||
String get userLastOnlineText => 'Última vez en línea';
|
||||
|
||||
@override
|
||||
String get userOnlineText => 'En línea';
|
||||
|
||||
@override
|
||||
String userTypingText(Iterable<User> users) {
|
||||
if (users.isEmpty) return '';
|
||||
final first = users.first;
|
||||
if (users.length == 1) {
|
||||
return '${first.name} está escribiendo';
|
||||
}
|
||||
return '${first.name} y ${users.length - 1} están escribiendo';
|
||||
}
|
||||
|
||||
@override
|
||||
String get threadReplyLabel => 'Responder al hilo de discusión';
|
||||
|
||||
@override
|
||||
String get onlyVisibleToYouText => 'Sólo visible para usted';
|
||||
|
||||
@override
|
||||
String threadReplyCountText(int count) =>
|
||||
'$count respuestas al hilo de discusión';
|
||||
|
||||
@override
|
||||
String attachmentsUploadProgressText({
|
||||
required int remaining,
|
||||
required int total,
|
||||
}) =>
|
||||
'Transferencia en curso $remaining/$total ...';
|
||||
|
||||
@override
|
||||
String pinnedByUserText({
|
||||
required User pinnedBy,
|
||||
required User currentUser,
|
||||
}) {
|
||||
final pinnedByCurrentUser = currentUser.id == pinnedBy.id;
|
||||
if (pinnedByCurrentUser) return 'Fijado por ti';
|
||||
return 'Fijado por ${pinnedBy.name}';
|
||||
}
|
||||
|
||||
@override
|
||||
String get emptyMessagesText => 'Actualmente no hay mensajes';
|
||||
|
||||
@override
|
||||
String get genericErrorText => 'Hubo un problema';
|
||||
|
||||
@override
|
||||
String get loadingMessagesError =>
|
||||
'Hubo un error mientras se cargaba el mensaje';
|
||||
|
||||
@override
|
||||
String resultCountText(int count) => '$count resultados';
|
||||
|
||||
@override
|
||||
String get messageDeletedText => 'Este mensaje ha sido borrado.';
|
||||
|
||||
@override
|
||||
String get messageDeletedLabel => 'Mensaje borrado';
|
||||
|
||||
@override
|
||||
String get messageReactionsLabel => 'Reacciones a los mensajes';
|
||||
|
||||
@override
|
||||
String get emptyChatMessagesText => 'Todavía no hay charlas aquí...';
|
||||
|
||||
@override
|
||||
String threadSeparatorText(int replyCount) {
|
||||
if (replyCount == 1) return '1 respuesta';
|
||||
return '$replyCount respuestas';
|
||||
}
|
||||
|
||||
@override
|
||||
String get connectedLabel => 'Conectado';
|
||||
|
||||
@override
|
||||
String get disconnectedLabel => 'Desconectado';
|
||||
|
||||
@override
|
||||
String get reconnectingLabel => 'Reconectando...';
|
||||
|
||||
@override
|
||||
String get alsoSendAsDirectMessageLabel =>
|
||||
'Enviar también como mensaje directo';
|
||||
|
||||
@override
|
||||
String get addACommentOrSendLabel => 'Añadir un comentario o enviar';
|
||||
|
||||
@override
|
||||
String get searchGifLabel => 'Búsqueda de GIFs';
|
||||
|
||||
@override
|
||||
String get writeAMessageLabel => 'Escribir un mensaje';
|
||||
|
||||
@override
|
||||
String get instantCommandsLabel => 'Comandos instantáneos';
|
||||
|
||||
@override
|
||||
String fileTooLargeAfterCompressionError(double limitInMB) =>
|
||||
'El archivo es demasiado grande para descargarlo. '
|
||||
'El tamaño máximo del archivo es de $limitInMB MB. '
|
||||
'Intentamos comprimirlo, pero no fue suficiente.';
|
||||
|
||||
@override
|
||||
String fileTooLargeError(double limitInMB) =>
|
||||
'El archivo es demasiado grande para descargarlo. '
|
||||
'El límite de tamaño de los archivos es de $limitInMB MB.';
|
||||
|
||||
@override
|
||||
String emojiMatchingQueryText(String query) =>
|
||||
'Emoji que corresponde a "$query"';
|
||||
|
||||
@override
|
||||
String get addAFileLabel => 'Añadir un archivo';
|
||||
|
||||
@override
|
||||
String get photoFromCameraLabel => 'Foto de la cámara';
|
||||
|
||||
@override
|
||||
String get uploadAFileLabel => 'Transferir un archivo';
|
||||
|
||||
@override
|
||||
String get uploadAPhotoLabel => 'Subir una foto';
|
||||
|
||||
@override
|
||||
String get uploadAVideoLabel => 'Subir una vídeo';
|
||||
|
||||
@override
|
||||
String get videoFromCameraLabel => 'Vídeo de la cámara';
|
||||
|
||||
@override
|
||||
String get okLabel => 'Vale';
|
||||
|
||||
@override
|
||||
String get somethingWentWrongError => 'Algo ha salido mal';
|
||||
|
||||
@override
|
||||
String get addMoreFilesLabel => 'Añadir más archivos';
|
||||
|
||||
@override
|
||||
String get enablePhotoAndVideoAccessMessage =>
|
||||
'Por favor, permita el acceso a sus fotos'
|
||||
'\ny vídeos para que puedas compartirlos con sus amigos.';
|
||||
|
||||
@override
|
||||
String get allowGalleryAccessMessage => 'Permitir el acceso a su galería';
|
||||
|
||||
@override
|
||||
String get flagMessageLabel => 'Reportar un mensaje';
|
||||
|
||||
@override
|
||||
String get flagMessageQuestion =>
|
||||
'¿Quiere enviar una copia de este mensaje a un'
|
||||
'\nmoderador para una mayor investigación?';
|
||||
|
||||
@override
|
||||
String get flagLabel => 'REPORTAR';
|
||||
|
||||
@override
|
||||
String get cancelLabel => 'CANCELAR';
|
||||
|
||||
@override
|
||||
String get flagMessageSuccessfulLabel => 'Mensaje reportado';
|
||||
|
||||
@override
|
||||
String get flagMessageSuccessfulText =>
|
||||
'Este mensaje ha sido reportado a un moderador.';
|
||||
|
||||
@override
|
||||
String get deleteLabel => 'BORRAR';
|
||||
|
||||
@override
|
||||
String get deleteMessageLabel => 'Borrar el mensaje';
|
||||
|
||||
@override
|
||||
String get deleteMessageQuestion =>
|
||||
'¿Estás seguro de que quieres borrar este\nmensaje de forma permanente?';
|
||||
|
||||
@override
|
||||
String get operationCouldNotBeCompletedText =>
|
||||
'La operación no pudo completarse.';
|
||||
|
||||
@override
|
||||
String get replyLabel => 'Respuesta';
|
||||
|
||||
@override
|
||||
String togglePinUnpinText({required bool pinned}) {
|
||||
if (pinned) return 'Desfijar a la conversación';
|
||||
return 'Fijar a la conversación';
|
||||
}
|
||||
|
||||
@override
|
||||
String toggleDeleteRetryDeleteMessageText({required bool isDeleteFailed}) {
|
||||
if (isDeleteFailed) return 'Reintentar borrar el mensaje';
|
||||
return 'Borrar el mensaje';
|
||||
}
|
||||
|
||||
@override
|
||||
String get copyMessageLabel => 'Copiar el mensaje';
|
||||
|
||||
@override
|
||||
String get editMessageLabel => 'Editar el mensaje';
|
||||
|
||||
@override
|
||||
String toggleResendOrResendEditedMessage({required bool isUpdateFailed}) {
|
||||
if (isUpdateFailed) return 'Reenviar el mensaje modificado';
|
||||
return 'Reenviar';
|
||||
}
|
||||
|
||||
@override
|
||||
String get photosLabel => 'Fotos';
|
||||
|
||||
String _getDay(DateTime dateTime) {
|
||||
final now = DateTime.now();
|
||||
final today = DateTime(now.year, now.month, now.day);
|
||||
final yesterday = DateTime(now.year, now.month, now.day - 1);
|
||||
|
||||
final date = DateTime(dateTime.year, dateTime.month, dateTime.day);
|
||||
|
||||
if (date == today) {
|
||||
return 'hoy';
|
||||
} else if (date == yesterday) {
|
||||
return 'ayer';
|
||||
} else {
|
||||
return 'el ${Jiffy(date).MMMd}';
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
String sentAtText({required DateTime date, required DateTime time}) =>
|
||||
'''Enviado el ${_getDay(date)} a las ${Jiffy(time.toLocal()).format('HH:mm')}''';
|
||||
|
||||
@override
|
||||
String get todayLabel => 'Hoy';
|
||||
|
||||
@override
|
||||
String get yesterdayLabel => 'Ayer';
|
||||
|
||||
@override
|
||||
String get channelIsMutedText => 'El canal está silenciado';
|
||||
|
||||
@override
|
||||
String get noTitleText => 'Sin título';
|
||||
|
||||
@override
|
||||
String get letsStartChattingLabel => '¡Empecemos a charlar!';
|
||||
|
||||
@override
|
||||
String get sendingFirstMessageLabel =>
|
||||
'¿Qué le parece enviar su primer mensaje a un amigo?';
|
||||
|
||||
@override
|
||||
String get startAChatLabel => 'Iniciar una conversación';
|
||||
|
||||
@override
|
||||
String get loadingChannelsError => 'Error al cargar los canales';
|
||||
|
||||
@override
|
||||
String get deleteConversationLabel => 'Borrar la conversación';
|
||||
|
||||
@override
|
||||
String get deleteConversationQuestion =>
|
||||
'¿Estás seguro de que quieres borrar esta conversación?';
|
||||
|
||||
@override
|
||||
String get streamChatLabel => 'Stream Chat';
|
||||
|
||||
@override
|
||||
String get searchingForNetworkText => 'Buscando red';
|
||||
|
||||
@override
|
||||
String get offlineLabel => 'Sin conexión...';
|
||||
|
||||
@override
|
||||
String get tryAgainLabel => 'Inténtelo de nuevo';
|
||||
|
||||
@override
|
||||
String membersCountText(int count) {
|
||||
if (count == 1) return '1 miembro';
|
||||
return '$count miembros';
|
||||
}
|
||||
|
||||
@override
|
||||
String watchersCountText(int count) {
|
||||
if (count == 1) return '1 En línea';
|
||||
return '$count En línea';
|
||||
}
|
||||
|
||||
@override
|
||||
String get viewInfoLabel => 'Ver información';
|
||||
|
||||
@override
|
||||
String get leaveGroupLabel => 'Salir del Grupo';
|
||||
|
||||
@override
|
||||
String get leaveLabel => 'SALIR';
|
||||
|
||||
@override
|
||||
String get leaveConversationLabel => 'Salir de la conversación';
|
||||
|
||||
@override
|
||||
String get leaveConversationQuestion =>
|
||||
'¿Estás seguro de que quiere salir de esta conversación?';
|
||||
|
||||
@override
|
||||
String get showInChatLabel => 'Mostrar en el chat';
|
||||
|
||||
@override
|
||||
String get saveImageLabel => 'Guardar la imagen';
|
||||
|
||||
@override
|
||||
String get saveVideoLabel => 'Guardar el vídeo';
|
||||
|
||||
@override
|
||||
String get uploadErrorLabel => 'ERROR DE TRANSFERENCIA';
|
||||
|
||||
@override
|
||||
String get giphyLabel => 'Giphy';
|
||||
|
||||
@override
|
||||
String get shuffleLabel => 'Mezclar';
|
||||
|
||||
@override
|
||||
String get sendLabel => 'Enviar';
|
||||
|
||||
@override
|
||||
String get withText => 'con';
|
||||
|
||||
@override
|
||||
String get inText => 'en';
|
||||
|
||||
@override
|
||||
String get youText => 'Usted';
|
||||
|
||||
@override
|
||||
String get ofText => 'de';
|
||||
|
||||
@override
|
||||
String get fileText => 'Archivo';
|
||||
|
||||
@override
|
||||
String get replyToMessageLabel => 'Responder al Mensaje';
|
||||
}
|
||||
@@ -31,7 +31,7 @@ class StreamChatLocalizationsFr extends GlobalStreamChatLocalizations {
|
||||
if (users.length == 1) {
|
||||
return "${first.name} est en train d'écrire";
|
||||
}
|
||||
return "${first.name} and ${users.length - 1} sont entrain d'écrire";
|
||||
return "${first.name} et ${users.length - 1} sont entrain d'écrire";
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -49,7 +49,7 @@ class StreamChatLocalizationsFr extends GlobalStreamChatLocalizations {
|
||||
required int remaining,
|
||||
required int total,
|
||||
}) =>
|
||||
'Uploading $remaining/$total ...';
|
||||
'Transfert en cours $remaining/$total ...';
|
||||
|
||||
@override
|
||||
String pinnedByUserText({
|
||||
@@ -206,8 +206,8 @@ class StreamChatLocalizationsFr extends GlobalStreamChatLocalizations {
|
||||
|
||||
@override
|
||||
String togglePinUnpinText({required bool pinned}) {
|
||||
if (pinned) return 'Détacher de la conversation';
|
||||
return 'Attacher à la conversation';
|
||||
if (pinned) return 'Décrocher de la conversation';
|
||||
return 'Épingler à la discussion';
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -311,7 +311,7 @@ class StreamChatLocalizationsFr extends GlobalStreamChatLocalizations {
|
||||
String get viewInfoLabel => 'Voir les informations';
|
||||
|
||||
@override
|
||||
String get leaveGroupLabel => 'Quitter le Group';
|
||||
String get leaveGroupLabel => 'Quitter le Groupe';
|
||||
|
||||
@override
|
||||
String get leaveLabel => 'QUITTER';
|
||||
@@ -324,7 +324,7 @@ class StreamChatLocalizationsFr extends GlobalStreamChatLocalizations {
|
||||
'Etes-vous sûr de vouloir quitter cette conversation ?';
|
||||
|
||||
@override
|
||||
String get showInChatLabel => 'Montrer dans le Chat';
|
||||
String get showInChatLabel => 'Montrer dans la Discussion';
|
||||
|
||||
@override
|
||||
String get saveImageLabel => "Sauvegarder l'image";
|
||||
|
||||
@@ -22,10 +22,8 @@ Future<void> main() async {
|
||||
await client.connectUser(
|
||||
User(
|
||||
id: 'cool-shadow-7',
|
||||
extraData: const {
|
||||
'image':
|
||||
'https://getstream.io/random_png/?id=cool-shadow-7&name=Cool+shadow',
|
||||
},
|
||||
image:
|
||||
'https://getstream.io/random_png/?id=cool-shadow-7&name=Cool+shadow',
|
||||
),
|
||||
'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoiY29vbC1zaGFkb3ctNyJ9.'
|
||||
'gkOlCRb1qgy4joHPaxFwPOdXcGvSPvp6QY0S4mpRkVo',
|
||||
|
||||
Reference in New Issue
Block a user