refactor message widget
This commit is contained in:
+61
-61
@@ -1,61 +1,61 @@
|
|||||||
include: package:pedantic/analysis_options.yaml
|
#include: package:pedantic/analysis_options.yaml
|
||||||
|
#
|
||||||
analyzer:
|
#analyzer:
|
||||||
exclude:
|
# exclude:
|
||||||
- lib/**/*.g.dart
|
# - lib/**/*.g.dart
|
||||||
- example/*
|
# - example/*
|
||||||
|
#
|
||||||
linter:
|
#linter:
|
||||||
rules:
|
# rules:
|
||||||
# these rules are documented on and in the same order as
|
# # these rules are documented on and in the same order as
|
||||||
# the Dart Lint rules page to make maintenance easier
|
# # the Dart Lint rules page to make maintenance easier
|
||||||
# https://github.com/dart-lang/linter/blob/master/example/all.yaml
|
# # https://github.com/dart-lang/linter/blob/master/example/all.yaml
|
||||||
# - always_declare_return_types
|
# # - always_declare_return_types
|
||||||
# - always_specify_types
|
# # - always_specify_types
|
||||||
# - annotate_overrides
|
# # - annotate_overrides
|
||||||
# - avoid_as
|
# # - avoid_as
|
||||||
- avoid_empty_else
|
# - avoid_empty_else
|
||||||
- avoid_init_to_null
|
# - avoid_init_to_null
|
||||||
- avoid_return_types_on_setters
|
# - avoid_return_types_on_setters
|
||||||
- avoid_web_libraries_in_flutter
|
# - avoid_web_libraries_in_flutter
|
||||||
- await_only_futures
|
# - await_only_futures
|
||||||
- camel_case_types
|
# - camel_case_types
|
||||||
- cancel_subscriptions
|
# - cancel_subscriptions
|
||||||
- close_sinks
|
# - close_sinks
|
||||||
# - comment_references # we do not presume as to what people want to reference in their dartdocs
|
# # - comment_references # we do not presume as to what people want to reference in their dartdocs
|
||||||
# - constant_identifier_names # https://github.com/dart-lang/linter/issues/204
|
# # - constant_identifier_names # https://github.com/dart-lang/linter/issues/204
|
||||||
- control_flow_in_finally
|
# - control_flow_in_finally
|
||||||
- empty_constructor_bodies
|
# - empty_constructor_bodies
|
||||||
- empty_statements
|
# - empty_statements
|
||||||
- hash_and_equals
|
# - hash_and_equals
|
||||||
- implementation_imports
|
# - implementation_imports
|
||||||
# - invariant_booleans
|
# # - invariant_booleans
|
||||||
# - iterable_contains_unrelated_type
|
# # - iterable_contains_unrelated_type
|
||||||
- library_names
|
# - library_names
|
||||||
# - library_prefixes
|
# # - library_prefixes
|
||||||
# - list_remove_unrelated_type
|
# # - list_remove_unrelated_type
|
||||||
# - literal_only_boolean_expressions
|
# # - literal_only_boolean_expressions
|
||||||
- non_constant_identifier_names
|
# - non_constant_identifier_names
|
||||||
# - one_member_abstracts
|
# # - one_member_abstracts
|
||||||
# - only_throw_errors
|
# # - only_throw_errors
|
||||||
# - overridden_fields
|
# # - overridden_fields
|
||||||
# - package_api_docs
|
## - package_api_docs
|
||||||
- package_names
|
# - package_names
|
||||||
- package_prefixed_library_names
|
# - package_prefixed_library_names
|
||||||
- prefer_is_not_empty
|
# - prefer_is_not_empty
|
||||||
# - prefer_mixin # https://github.com/dart-lang/language/issues/32
|
# # - prefer_mixin # https://github.com/dart-lang/language/issues/32
|
||||||
- public_member_api_docs
|
# - public_member_api_docs
|
||||||
- slash_for_doc_comments
|
# - slash_for_doc_comments
|
||||||
# - sort_constructors_first
|
# # - sort_constructors_first
|
||||||
# - sort_unnamed_constructors_first
|
# # - sort_unnamed_constructors_first
|
||||||
# - super_goes_last # no longer needed w/ Dart 2
|
# # - super_goes_last # no longer needed w/ Dart 2
|
||||||
- test_types_in_equals
|
# - test_types_in_equals
|
||||||
- throw_in_finally
|
# - throw_in_finally
|
||||||
# - type_annotate_public_apis # subset of always_specify_types
|
# # - type_annotate_public_apis # subset of always_specify_types
|
||||||
- type_init_formals
|
# - type_init_formals
|
||||||
# - unawaited_futures
|
# # - unawaited_futures
|
||||||
- unnecessary_brace_in_string_interps
|
# - unnecessary_brace_in_string_interps
|
||||||
- unnecessary_getters_setters
|
# - unnecessary_getters_setters
|
||||||
- unnecessary_statements
|
# - unnecessary_statements
|
||||||
- unrelated_type_equality_checks
|
# - unrelated_type_equality_checks
|
||||||
- valid_regexps
|
# - valid_regexps
|
||||||
|
|||||||
@@ -90,7 +90,12 @@ class ChannelPage extends StatelessWidget {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _messageBuilder(context, message, index) {
|
Widget _messageBuilder(
|
||||||
|
BuildContext context,
|
||||||
|
MessageDetails details,
|
||||||
|
List<Message> messages,
|
||||||
|
) {
|
||||||
|
final message = details.message;
|
||||||
final isCurrentUser = StreamChat.of(context).user.id == message.user.id;
|
final isCurrentUser = StreamChat.of(context).user.id == message.user.id;
|
||||||
final textAlign = isCurrentUser ? TextAlign.right : TextAlign.left;
|
final textAlign = isCurrentUser ? TextAlign.right : TextAlign.left;
|
||||||
final color = isCurrentUser ? Colors.blueGrey : Colors.blue;
|
final color = isCurrentUser ? Colors.blueGrey : Colors.blue;
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
|
import 'package:flutter/cupertino.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_apns/apns.dart';
|
import 'package:flutter_apns/apns.dart';
|
||||||
import 'package:flutter_local_notifications/flutter_local_notifications.dart'
|
import 'package:flutter_local_notifications/flutter_local_notifications.dart'
|
||||||
@@ -66,9 +67,11 @@ void _initNotifications(Client client) {
|
|||||||
void main() async {
|
void main() async {
|
||||||
final client = Client(
|
final client = Client(
|
||||||
's2dxdhpxd94g',
|
's2dxdhpxd94g',
|
||||||
logLevel: Level.INFO,
|
logLevel: Level.SEVERE,
|
||||||
showLocalNotification: Platform.isAndroid ? showLocalNotification : null,
|
showLocalNotification: Platform.isAndroid ? showLocalNotification : null,
|
||||||
backgroundKeepAlive: Duration.zero,
|
backgroundKeepAlive: Duration.zero,
|
||||||
|
persistenceEnabled: true,
|
||||||
|
// baseURL: 'chat-us-east-staging.stream-io-api.com',
|
||||||
);
|
);
|
||||||
|
|
||||||
await client.setUser(
|
await client.setUser(
|
||||||
@@ -108,11 +111,11 @@ class ChannelListPage extends StatelessWidget {
|
|||||||
return Scaffold(
|
return Scaffold(
|
||||||
body: ChannelsBloc(
|
body: ChannelsBloc(
|
||||||
child: ChannelListView(
|
child: ChannelListView(
|
||||||
filter: {
|
// filter: {
|
||||||
'members': {
|
// 'members': {
|
||||||
'\$in': [StreamChat.of(context).user.id],
|
// '\$in': [StreamChat.of(context).user.id],
|
||||||
}
|
// }
|
||||||
},
|
// },
|
||||||
sort: [SortOption('last_message_at')],
|
sort: [SortOption('last_message_at')],
|
||||||
pagination: PaginationParams(
|
pagination: PaginationParams(
|
||||||
limit: 20,
|
limit: 20,
|
||||||
@@ -139,8 +142,6 @@ class ChannelPage extends StatelessWidget {
|
|||||||
child: Stack(
|
child: Stack(
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
MessageListView(
|
MessageListView(
|
||||||
showOtherMessageUsername: true,
|
|
||||||
showVideoFullScreen: false,
|
|
||||||
threadBuilder: (_, parentMessage) {
|
threadBuilder: (_, parentMessage) {
|
||||||
return ThreadPage(
|
return ThreadPage(
|
||||||
parent: parentMessage,
|
parent: parentMessage,
|
||||||
|
|||||||
@@ -22,43 +22,33 @@ class AttachmentTitle extends StatelessWidget {
|
|||||||
launchURL(context, attachment.titleLink);
|
launchURL(context, attachment.titleLink);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
child: Container(
|
child: Padding(
|
||||||
constraints: BoxConstraints.loose(
|
padding: const EdgeInsets.all(8.0),
|
||||||
Size(
|
child: Column(
|
||||||
MediaQuery.of(context).size.width * 0.7,
|
mainAxisSize: MainAxisSize.min,
|
||||||
500,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
),
|
children: <Widget>[
|
||||||
),
|
Text(
|
||||||
child: Padding(
|
attachment.title,
|
||||||
padding: const EdgeInsets.all(8.0),
|
overflow: TextOverflow.ellipsis,
|
||||||
child: Column(
|
style: messageTheme.messageText.copyWith(
|
||||||
mainAxisSize: MainAxisSize.min,
|
color: StreamChatTheme.of(context).accentColor,
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
fontWeight: FontWeight.bold,
|
||||||
children: <Widget>[
|
|
||||||
Text(
|
|
||||||
attachment.title,
|
|
||||||
overflow: TextOverflow.ellipsis,
|
|
||||||
style: messageTheme.messageText.copyWith(
|
|
||||||
color: StreamChatTheme.of(context).accentColor,
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
if (attachment.titleLink != null ||
|
),
|
||||||
attachment.ogScrapeUrl != null)
|
if (attachment.titleLink != null || attachment.ogScrapeUrl != null)
|
||||||
Text(
|
Text(
|
||||||
Uri.parse(attachment.titleLink ?? attachment.ogScrapeUrl)
|
Uri.parse(attachment.titleLink ?? attachment.ogScrapeUrl)
|
||||||
.authority
|
.authority
|
||||||
.split('.')
|
.split('.')
|
||||||
.reversed
|
.reversed
|
||||||
.take(2)
|
.take(2)
|
||||||
.toList()
|
.toList()
|
||||||
.reversed
|
.reversed
|
||||||
.join('.'),
|
.join('.'),
|
||||||
overflow: TextOverflow.ellipsis,
|
style: messageTheme.createdAt,
|
||||||
style: messageTheme.createdAt,
|
),
|
||||||
),
|
],
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ class DateDivider extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: const EdgeInsets.only(top: 24.0),
|
padding: const EdgeInsets.symmetric(vertical: 12.0),
|
||||||
child: Row(
|
child: Row(
|
||||||
crossAxisAlignment: CrossAxisAlignment.center,
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
|
|||||||
@@ -5,30 +5,19 @@ class DeletedMessage extends StatelessWidget {
|
|||||||
const DeletedMessage({
|
const DeletedMessage({
|
||||||
Key key,
|
Key key,
|
||||||
@required this.messageTheme,
|
@required this.messageTheme,
|
||||||
this.alignment,
|
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
final MessageTheme messageTheme;
|
final MessageTheme messageTheme;
|
||||||
final Alignment alignment;
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Align(
|
return Text(
|
||||||
alignment: alignment,
|
'This message was deleted...',
|
||||||
child: Padding(
|
style: messageTheme.messageText.copyWith(
|
||||||
padding: const EdgeInsets.symmetric(
|
fontStyle: FontStyle.italic,
|
||||||
horizontal: 14,
|
color: Theme.of(context).brightness == Brightness.dark
|
||||||
vertical: 14,
|
? Colors.white
|
||||||
),
|
: Colors.black,
|
||||||
child: Text(
|
|
||||||
'This message was deleted...',
|
|
||||||
style: messageTheme.messageText.copyWith(
|
|
||||||
fontStyle: FontStyle.italic,
|
|
||||||
color: Theme.of(context).brightness == Brightness.dark
|
|
||||||
? Colors.white
|
|
||||||
: Colors.black,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,10 +26,9 @@ class ImageAttachment extends StatelessWidget {
|
|||||||
attachment: attachment,
|
attachment: attachment,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return Column(
|
return Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
Stack(
|
Stack(
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
@@ -42,7 +41,6 @@ class ImageAttachment extends StatelessWidget {
|
|||||||
return GestureDetector(
|
return GestureDetector(
|
||||||
child: Image(
|
child: Image(
|
||||||
image: provider,
|
image: provider,
|
||||||
width: MediaQuery.of(context).size.width * 0.7,
|
|
||||||
fit: BoxFit.cover,
|
fit: BoxFit.cover,
|
||||||
),
|
),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
|
|||||||
@@ -0,0 +1,209 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:stream_chat/stream_chat.dart';
|
||||||
|
import 'package:stream_chat_flutter/src/reaction_picker.dart';
|
||||||
|
import 'package:stream_chat_flutter/src/stream_channel.dart';
|
||||||
|
|
||||||
|
import '../stream_chat_flutter.dart';
|
||||||
|
import 'message_input.dart';
|
||||||
|
import 'stream_chat.dart';
|
||||||
|
|
||||||
|
class MessageActionsBottomSheet extends StatelessWidget {
|
||||||
|
final Widget Function(BuildContext, Message) editMessageInputBuilder;
|
||||||
|
final void Function(Message) onThreadTap;
|
||||||
|
final Message message;
|
||||||
|
final bool showReactions;
|
||||||
|
final bool showDeleteMessage;
|
||||||
|
final bool showEditMessage;
|
||||||
|
final bool showReply;
|
||||||
|
final Map<String, String> reactionToEmoji = const {
|
||||||
|
'love': '❤️️',
|
||||||
|
'haha': '😂',
|
||||||
|
'like': '👍',
|
||||||
|
'sad': '😕',
|
||||||
|
'angry': '😡',
|
||||||
|
'wow': '😲',
|
||||||
|
};
|
||||||
|
|
||||||
|
const MessageActionsBottomSheet({
|
||||||
|
Key key,
|
||||||
|
this.message,
|
||||||
|
this.showReactions,
|
||||||
|
this.showDeleteMessage,
|
||||||
|
this.showEditMessage,
|
||||||
|
this.onThreadTap,
|
||||||
|
this.showReply,
|
||||||
|
this.editMessageInputBuilder,
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final channel = StreamChannel.of(context).channel;
|
||||||
|
return SafeArea(
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
|
children: <Widget>[
|
||||||
|
if (showReactions &&
|
||||||
|
(message.status == MessageSendingStatus.SENT ||
|
||||||
|
message.status == null))
|
||||||
|
ReactionPicker(
|
||||||
|
channel: channel,
|
||||||
|
reactionToEmoji: reactionToEmoji,
|
||||||
|
message: message,
|
||||||
|
),
|
||||||
|
if (showDeleteMessage) _buildDeleteButton(context),
|
||||||
|
if (showEditMessage) _buildEditMessage(context),
|
||||||
|
if (showReply &&
|
||||||
|
(message.status == MessageSendingStatus.SENT ||
|
||||||
|
message.status == null) &&
|
||||||
|
message.parentId == null)
|
||||||
|
_buildReplyButton(context),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
FlatButton _buildDeleteButton(BuildContext context) {
|
||||||
|
return FlatButton(
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(16.0),
|
||||||
|
child: Text(
|
||||||
|
'Delete message',
|
||||||
|
style:
|
||||||
|
Theme.of(context).textTheme.headline5.copyWith(color: Colors.red),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.pop(context);
|
||||||
|
StreamChat.of(context).client.deleteMessage(
|
||||||
|
message,
|
||||||
|
StreamChannel.of(context).channel.cid,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
FlatButton _buildEditMessage(BuildContext context) {
|
||||||
|
return FlatButton(
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(16.0),
|
||||||
|
child: Text(
|
||||||
|
'Edit message',
|
||||||
|
style: Theme.of(context).textTheme.headline5,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
onPressed: () async {
|
||||||
|
Navigator.pop(context);
|
||||||
|
_showEditBottomSheet(context);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _showEditBottomSheet(BuildContext context) {
|
||||||
|
final channel = StreamChannel.of(context).channel;
|
||||||
|
showModalBottomSheet(
|
||||||
|
context: context,
|
||||||
|
elevation: 2,
|
||||||
|
clipBehavior: Clip.hardEdge,
|
||||||
|
isScrollControlled: true,
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.only(
|
||||||
|
topLeft: Radius.circular(32),
|
||||||
|
topRight: Radius.circular(32),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
builder: (context) {
|
||||||
|
return StreamChannel(
|
||||||
|
channel: channel,
|
||||||
|
child: Flex(
|
||||||
|
direction: Axis.vertical,
|
||||||
|
mainAxisAlignment: MainAxisAlignment.end,
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: <Widget>[
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.only(
|
||||||
|
top: 16.0,
|
||||||
|
left: 16.0,
|
||||||
|
right: 16.0,
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: <Widget>[
|
||||||
|
Text(
|
||||||
|
'Edit message',
|
||||||
|
style: Theme.of(context).textTheme.headline6,
|
||||||
|
),
|
||||||
|
Container(
|
||||||
|
height: 30,
|
||||||
|
padding: const EdgeInsets.all(2.0),
|
||||||
|
child: AspectRatio(
|
||||||
|
aspectRatio: 1,
|
||||||
|
child: RawMaterialButton(
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(4),
|
||||||
|
),
|
||||||
|
elevation: 0,
|
||||||
|
highlightElevation: 0,
|
||||||
|
focusElevation: 0,
|
||||||
|
disabledElevation: 0,
|
||||||
|
hoverElevation: 0,
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
},
|
||||||
|
fillColor:
|
||||||
|
Theme.of(context).brightness == Brightness.dark
|
||||||
|
? Colors.white.withOpacity(.1)
|
||||||
|
: Colors.black.withOpacity(.1),
|
||||||
|
padding: EdgeInsets.all(4),
|
||||||
|
child: Icon(
|
||||||
|
Icons.close,
|
||||||
|
size: 15,
|
||||||
|
color: StreamChatTheme.of(context)
|
||||||
|
.primaryIconTheme
|
||||||
|
.color,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Padding(
|
||||||
|
padding: EdgeInsets.only(
|
||||||
|
bottom: MediaQuery.of(context).viewInsets.bottom,
|
||||||
|
),
|
||||||
|
child: editMessageInputBuilder != null
|
||||||
|
? editMessageInputBuilder(context, message)
|
||||||
|
: MessageInput(
|
||||||
|
editMessage: message,
|
||||||
|
onMessageSent: (_) {
|
||||||
|
FocusScope.of(context).unfocus();
|
||||||
|
Navigator.pop(context);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
FlatButton _buildReplyButton(BuildContext context) {
|
||||||
|
return FlatButton(
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(16.0),
|
||||||
|
child: Text(
|
||||||
|
'Start a thread',
|
||||||
|
style: Theme.of(context).textTheme.headline5,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.pop(context);
|
||||||
|
if (onThreadTap != null) {
|
||||||
|
onThreadTap(message);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -861,7 +861,6 @@ class MessageInputState extends State<MessageInput> {
|
|||||||
Message message;
|
Message message;
|
||||||
if (widget.editMessage != null) {
|
if (widget.editMessage != null) {
|
||||||
message = widget.editMessage.copyWith(
|
message = widget.editMessage.copyWith(
|
||||||
parentId: widget.parentMessage?.id,
|
|
||||||
text: text,
|
text: text,
|
||||||
attachments: _getAttachments(attachments).toList(),
|
attachments: _getAttachments(attachments).toList(),
|
||||||
mentionedUsers:
|
mentionedUsers:
|
||||||
|
|||||||
+152
-123
@@ -3,19 +3,46 @@ import 'dart:async';
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:jiffy/jiffy.dart';
|
import 'package:jiffy/jiffy.dart';
|
||||||
import 'package:stream_chat/stream_chat.dart';
|
import 'package:stream_chat/stream_chat.dart';
|
||||||
|
import 'package:stream_chat_flutter/src/message_widget.dart';
|
||||||
import 'package:visibility_detector/visibility_detector.dart';
|
import 'package:visibility_detector/visibility_detector.dart';
|
||||||
|
|
||||||
import '../stream_chat_flutter.dart';
|
import '../stream_chat_flutter.dart';
|
||||||
import 'date_divider.dart';
|
import 'date_divider.dart';
|
||||||
import 'message_widget.dart';
|
|
||||||
import 'stream_channel.dart';
|
import 'stream_channel.dart';
|
||||||
|
|
||||||
typedef MessageBuilder = Widget Function(
|
typedef MessageBuilder = Widget Function(
|
||||||
BuildContext, Message, List<Message>, int index);
|
BuildContext,
|
||||||
typedef ParentMessageBuilder = Widget Function(BuildContext, Message);
|
MessageDetails,
|
||||||
|
List<Message>,
|
||||||
|
);
|
||||||
|
typedef ParentMessageBuilder = Widget Function(
|
||||||
|
BuildContext,
|
||||||
|
Message,
|
||||||
|
);
|
||||||
typedef ThreadBuilder = Widget Function(BuildContext context, Message parent);
|
typedef ThreadBuilder = Widget Function(BuildContext context, Message parent);
|
||||||
typedef ThreadTapCallback = void Function(Message, Widget);
|
typedef ThreadTapCallback = void Function(Message, Widget);
|
||||||
|
|
||||||
|
class MessageDetails {
|
||||||
|
bool isMyMessage;
|
||||||
|
bool isLastUser;
|
||||||
|
bool isNextUser;
|
||||||
|
Message message;
|
||||||
|
int index;
|
||||||
|
|
||||||
|
MessageDetails(
|
||||||
|
BuildContext context,
|
||||||
|
this.message,
|
||||||
|
List<Message> messages,
|
||||||
|
this.index,
|
||||||
|
) {
|
||||||
|
isMyMessage = message.user.id == StreamChat.of(context).user.id;
|
||||||
|
isLastUser = index + 1 < messages.length &&
|
||||||
|
message.user.id == messages[index + 1]?.user?.id;
|
||||||
|
isNextUser =
|
||||||
|
index - 1 >= 0 && message.user.id == messages[index - 1]?.user?.id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// 
|
/// 
|
||||||
/// 
|
/// 
|
||||||
///
|
///
|
||||||
@@ -65,15 +92,7 @@ class MessageListView extends StatefulWidget {
|
|||||||
this.parentMessage,
|
this.parentMessage,
|
||||||
this.threadBuilder,
|
this.threadBuilder,
|
||||||
this.onThreadTap,
|
this.onThreadTap,
|
||||||
this.showOtherMessageUsername = false,
|
|
||||||
this.showVideoFullScreen = true,
|
|
||||||
this.onMentionTap,
|
|
||||||
this.onUserAvatarTap,
|
|
||||||
this.onMessageActions,
|
|
||||||
this.attachmentBuilders,
|
|
||||||
this.dateDividerBuilder,
|
this.dateDividerBuilder,
|
||||||
this.showAvatar = true,
|
|
||||||
this.editMessageInputBuilder,
|
|
||||||
this.scrollPhysics = const AlwaysScrollableScrollPhysics(),
|
this.scrollPhysics = const AlwaysScrollableScrollPhysics(),
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
@@ -93,38 +112,12 @@ class MessageListView extends StatefulWidget {
|
|||||||
/// Parent message in case of a thread
|
/// Parent message in case of a thread
|
||||||
final Message parentMessage;
|
final Message parentMessage;
|
||||||
|
|
||||||
/// If true show the other users username next to the timestamp of the message
|
|
||||||
final bool showOtherMessageUsername;
|
|
||||||
|
|
||||||
/// True if the video player will allow fullscreen mode
|
|
||||||
final bool showVideoFullScreen;
|
|
||||||
|
|
||||||
/// Function called on message mention tap
|
|
||||||
final void Function(User) onMentionTap;
|
|
||||||
|
|
||||||
/// Function called on User Avatar tap
|
|
||||||
final void Function(User) onUserAvatarTap;
|
|
||||||
|
|
||||||
/// Function called on message long press
|
|
||||||
final Function(BuildContext, Message) onMessageActions;
|
|
||||||
|
|
||||||
/// Map that defines a builder for an attachment type
|
|
||||||
final Map<String, AttachmentBuilder> attachmentBuilders;
|
|
||||||
|
|
||||||
/// Builder used to render date dividers
|
/// Builder used to render date dividers
|
||||||
final Widget Function(DateTime) dateDividerBuilder;
|
final Widget Function(DateTime) dateDividerBuilder;
|
||||||
|
|
||||||
/// if true shows the user avatar
|
|
||||||
final bool showAvatar;
|
|
||||||
|
|
||||||
|
|
||||||
/// Builder used to build the message input to edit a message
|
|
||||||
final Widget Function(BuildContext, Message) editMessageInputBuilder;
|
|
||||||
|
|
||||||
/// The ScrollPhysics used by the ListView
|
/// The ScrollPhysics used by the ListView
|
||||||
final ScrollPhysics scrollPhysics;
|
final ScrollPhysics scrollPhysics;
|
||||||
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
_MessageListViewState createState() => _MessageListViewState();
|
_MessageListViewState createState() => _MessageListViewState();
|
||||||
}
|
}
|
||||||
@@ -171,24 +164,7 @@ class _MessageListViewState extends State<MessageListView> {
|
|||||||
return Column(
|
return Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
MessageWidget(
|
buildParentMessage(widget.parentMessage),
|
||||||
key: ValueKey<String>(
|
|
||||||
'PARENT-MESSAGE-${widget.parentMessage.id}'),
|
|
||||||
previousMessage: null,
|
|
||||||
message: widget.parentMessage,
|
|
||||||
nextMessage: null,
|
|
||||||
onThreadTap: _onThreadTap,
|
|
||||||
isParent: true,
|
|
||||||
showVideoFullScreen: widget.showVideoFullScreen,
|
|
||||||
showOtherMessageUsername:
|
|
||||||
widget.showOtherMessageUsername,
|
|
||||||
onMentionTap: widget.onMentionTap,
|
|
||||||
onUserAvatarTap: widget.onUserAvatarTap,
|
|
||||||
onMessageActions: widget.onMessageActions,
|
|
||||||
attachmentBuilders: widget.attachmentBuilders,
|
|
||||||
showAvatar: widget.showAvatar,
|
|
||||||
editMessageInputBuilder: widget.editMessageInputBuilder,
|
|
||||||
),
|
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 32),
|
padding: const EdgeInsets.symmetric(horizontal: 32),
|
||||||
child: Container(
|
child: Container(
|
||||||
@@ -212,50 +188,40 @@ class _MessageListViewState extends State<MessageListView> {
|
|||||||
return _buildLoadingIndicator(streamChannel);
|
return _buildLoadingIndicator(streamChannel);
|
||||||
}
|
}
|
||||||
final message = _messages[i];
|
final message = _messages[i];
|
||||||
|
|
||||||
final previousMessage =
|
|
||||||
i < _messages.length - 1 ? _messages[i + 1] : null;
|
|
||||||
final nextMessage = i > 0 ? _messages[i - 1] : null;
|
final nextMessage = i > 0 ? _messages[i - 1] : null;
|
||||||
|
|
||||||
Widget messageWidget;
|
Widget messageWidget;
|
||||||
|
|
||||||
if (i == 0) {
|
if (i == 0) {
|
||||||
messageWidget = _buildBottomMessage(
|
messageWidget = _buildBottomMessage(
|
||||||
streamChannel,
|
|
||||||
previousMessage,
|
|
||||||
message,
|
|
||||||
context,
|
context,
|
||||||
|
message,
|
||||||
|
_messages,
|
||||||
|
streamChannel,
|
||||||
);
|
);
|
||||||
} else if (i == _messages.length - 1) {
|
} else if (i == _messages.length - 1) {
|
||||||
messageWidget = _buildTopMessage(
|
messageWidget = _buildTopMessage(
|
||||||
message,
|
|
||||||
nextMessage,
|
|
||||||
streamChannel,
|
|
||||||
context,
|
context,
|
||||||
|
message,
|
||||||
|
_messages,
|
||||||
|
streamChannel,
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
if (widget.messageBuilder != null) {
|
if (widget.messageBuilder != null) {
|
||||||
messageWidget = Builder(
|
messageWidget = Builder(
|
||||||
key: ValueKey<String>('MESSAGE-${message.id}'),
|
key: ValueKey<String>('MESSAGE-${message.id}'),
|
||||||
builder: (_) =>
|
builder: (_) => widget.messageBuilder(
|
||||||
widget.messageBuilder(context, message, _messages, i),
|
context,
|
||||||
|
MessageDetails(
|
||||||
|
context,
|
||||||
|
message,
|
||||||
|
_messages,
|
||||||
|
i,
|
||||||
|
),
|
||||||
|
_messages),
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
messageWidget = MessageWidget(
|
messageWidget = buildMessage(message, _messages, i);
|
||||||
key: ValueKey<String>('MESSAGE-${message.id}'),
|
|
||||||
previousMessage: previousMessage,
|
|
||||||
message: message,
|
|
||||||
nextMessage: nextMessage,
|
|
||||||
onThreadTap: _onThreadTap,
|
|
||||||
showOtherMessageUsername: widget.showOtherMessageUsername,
|
|
||||||
showVideoFullScreen: widget.showVideoFullScreen,
|
|
||||||
onMentionTap: widget.onMentionTap,
|
|
||||||
onUserAvatarTap: widget.onUserAvatarTap,
|
|
||||||
onMessageActions: widget.onMessageActions,
|
|
||||||
attachmentBuilders: widget.attachmentBuilders,
|
|
||||||
showAvatar: widget.showAvatar,
|
|
||||||
editMessageInputBuilder: widget.editMessageInputBuilder,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -319,38 +285,28 @@ class _MessageListViewState extends State<MessageListView> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildTopMessage(
|
Widget _buildTopMessage(
|
||||||
Message message,
|
|
||||||
Message nextMessage,
|
|
||||||
StreamChannelState streamChannelState,
|
|
||||||
BuildContext context,
|
BuildContext context,
|
||||||
|
Message message,
|
||||||
|
List<Message> messages,
|
||||||
|
StreamChannelState streamChannel,
|
||||||
) {
|
) {
|
||||||
Widget messageWidget;
|
Widget messageWidget;
|
||||||
if (widget.messageBuilder != null) {
|
if (widget.messageBuilder != null) {
|
||||||
messageWidget = Builder(
|
messageWidget = Builder(
|
||||||
key: ValueKey<String>('MESSAGE-${message.id}'),
|
key: ValueKey<String>('TOP-MESSAGE'),
|
||||||
builder: (_) => widget.messageBuilder(
|
builder: (_) => widget.messageBuilder(
|
||||||
context,
|
context,
|
||||||
message,
|
MessageDetails(
|
||||||
|
context,
|
||||||
|
message,
|
||||||
|
_messages,
|
||||||
|
_messages.length - 1,
|
||||||
|
),
|
||||||
_messages,
|
_messages,
|
||||||
_messages.length - 1,
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
messageWidget = MessageWidget(
|
messageWidget = buildMessage(message, messages, _messages.length - 1);
|
||||||
key: ValueKey<String>('MESSAGE-${message.id}'),
|
|
||||||
previousMessage: null,
|
|
||||||
message: message,
|
|
||||||
nextMessage: nextMessage,
|
|
||||||
onThreadTap: _onThreadTap,
|
|
||||||
showVideoFullScreen: widget.showVideoFullScreen,
|
|
||||||
showOtherMessageUsername: widget.showOtherMessageUsername,
|
|
||||||
onMentionTap: widget.onMentionTap,
|
|
||||||
onUserAvatarTap: widget.onUserAvatarTap,
|
|
||||||
onMessageActions: widget.onMessageActions,
|
|
||||||
attachmentBuilders: widget.attachmentBuilders,
|
|
||||||
showAvatar: widget.showAvatar,
|
|
||||||
editMessageInputBuilder: widget.editMessageInputBuilder,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return VisibilityDetector(
|
return VisibilityDetector(
|
||||||
@@ -359,7 +315,7 @@ class _MessageListViewState extends State<MessageListView> {
|
|||||||
onVisibilityChanged: (visibility) {
|
onVisibilityChanged: (visibility) {
|
||||||
final topIsVisible = visibility.visibleBounds != Rect.zero;
|
final topIsVisible = visibility.visibleBounds != Rect.zero;
|
||||||
if (topIsVisible && !_topWasVisible) {
|
if (topIsVisible && !_topWasVisible) {
|
||||||
streamChannelState.queryMessages();
|
streamChannel.queryMessages();
|
||||||
}
|
}
|
||||||
_topWasVisible = topIsVisible;
|
_topWasVisible = topIsVisible;
|
||||||
},
|
},
|
||||||
@@ -367,33 +323,28 @@ class _MessageListViewState extends State<MessageListView> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildBottomMessage(
|
Widget _buildBottomMessage(
|
||||||
StreamChannelState streamChannel,
|
|
||||||
Message previousMessage,
|
|
||||||
Message message,
|
|
||||||
BuildContext context,
|
BuildContext context,
|
||||||
|
Message message,
|
||||||
|
List<Message> messages,
|
||||||
|
StreamChannelState streamChannel,
|
||||||
) {
|
) {
|
||||||
Widget messageWidget;
|
Widget messageWidget;
|
||||||
if (widget.messageBuilder != null) {
|
if (widget.messageBuilder != null) {
|
||||||
messageWidget = Builder(
|
messageWidget = Builder(
|
||||||
key: ValueKey<String>('MESSAGE-${message.id}'),
|
key: ValueKey<String>('BOTTOM-MESSAGE'),
|
||||||
builder: (_) => widget.messageBuilder(context, message, _messages, 0),
|
builder: (_) => widget.messageBuilder(
|
||||||
|
context,
|
||||||
|
MessageDetails(
|
||||||
|
context,
|
||||||
|
message,
|
||||||
|
_messages,
|
||||||
|
0,
|
||||||
|
),
|
||||||
|
_messages,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
messageWidget = MessageWidget(
|
messageWidget = buildMessage(message, messages, 0);
|
||||||
key: ValueKey<String>('MESSAGE-${message.id}'),
|
|
||||||
previousMessage: previousMessage,
|
|
||||||
message: message,
|
|
||||||
nextMessage: null,
|
|
||||||
onThreadTap: _onThreadTap,
|
|
||||||
showVideoFullScreen: widget.showVideoFullScreen,
|
|
||||||
showOtherMessageUsername: widget.showOtherMessageUsername,
|
|
||||||
onMentionTap: widget.onMentionTap,
|
|
||||||
onUserAvatarTap: widget.onUserAvatarTap,
|
|
||||||
onMessageActions: widget.onMessageActions,
|
|
||||||
attachmentBuilders: widget.attachmentBuilders,
|
|
||||||
showAvatar: widget.showAvatar,
|
|
||||||
editMessageInputBuilder: widget.editMessageInputBuilder,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return VisibilityDetector(
|
return VisibilityDetector(
|
||||||
@@ -410,6 +361,84 @@ class _MessageListViewState extends State<MessageListView> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Widget buildParentMessage(
|
||||||
|
Message message,
|
||||||
|
) {
|
||||||
|
final isMyMessage = message.user.id == StreamChat.of(context).user.id;
|
||||||
|
|
||||||
|
return MessageWidget(
|
||||||
|
showReplyIndicator: false,
|
||||||
|
message: message,
|
||||||
|
reverse: isMyMessage,
|
||||||
|
showUsername: !isMyMessage,
|
||||||
|
padding: EdgeInsets.only(
|
||||||
|
top: 8.0,
|
||||||
|
left: 8.0,
|
||||||
|
right: 8.0,
|
||||||
|
bottom: 16.0,
|
||||||
|
),
|
||||||
|
showSendingIndicator: DisplayWidget.hide,
|
||||||
|
onThreadTap: _onThreadTap,
|
||||||
|
showEditMessage: false,
|
||||||
|
showDeleteMessage: false,
|
||||||
|
borderRadiusGeometry: BorderRadius.only(
|
||||||
|
topLeft: Radius.circular(16),
|
||||||
|
bottomLeft: Radius.circular(2),
|
||||||
|
topRight: Radius.circular(16),
|
||||||
|
bottomRight: Radius.circular(16),
|
||||||
|
),
|
||||||
|
borderSide: isMyMessage ? BorderSide.none : null,
|
||||||
|
showUserAvatar: DisplayWidget.show,
|
||||||
|
messageTheme: isMyMessage
|
||||||
|
? StreamChatTheme.of(context).ownMessageTheme
|
||||||
|
: StreamChatTheme.of(context).otherMessageTheme,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget buildMessage(
|
||||||
|
Message message,
|
||||||
|
List<Message> messages,
|
||||||
|
int index,
|
||||||
|
) {
|
||||||
|
final isMyMessage = message.user.id == StreamChat.of(context).user.id;
|
||||||
|
final isLastUser = index + 1 < messages.length &&
|
||||||
|
message.user.id == messages[index + 1]?.user?.id;
|
||||||
|
final isNextUser =
|
||||||
|
index - 1 >= 0 && message.user.id == messages[index - 1]?.user?.id;
|
||||||
|
|
||||||
|
return MessageWidget(
|
||||||
|
message: message,
|
||||||
|
reverse: isMyMessage,
|
||||||
|
showReactions: !message.isDeleted,
|
||||||
|
padding: EdgeInsets.only(
|
||||||
|
left: 8.0,
|
||||||
|
right: 8.0,
|
||||||
|
bottom: index == 0 ? 30 : (isLastUser ? 5 : 10),
|
||||||
|
),
|
||||||
|
showUsername: !isMyMessage && !isNextUser,
|
||||||
|
showSendingIndicator: isMyMessage &&
|
||||||
|
(index == 0 || message.status != MessageSendingStatus.SENT)
|
||||||
|
? DisplayWidget.show
|
||||||
|
: DisplayWidget.hide,
|
||||||
|
showTimestamp: !isNextUser,
|
||||||
|
showEditMessage: isMyMessage,
|
||||||
|
showDeleteMessage: isMyMessage,
|
||||||
|
borderSide: isMyMessage ? BorderSide.none : null,
|
||||||
|
onThreadTap: _onThreadTap,
|
||||||
|
attachmentBorderRadiusGeometry: BorderRadius.circular(16),
|
||||||
|
borderRadiusGeometry: BorderRadius.only(
|
||||||
|
topLeft: Radius.circular(isLastUser ? 2 : 16),
|
||||||
|
bottomLeft: Radius.circular(2),
|
||||||
|
topRight: Radius.circular(16),
|
||||||
|
bottomRight: Radius.circular(16),
|
||||||
|
),
|
||||||
|
showUserAvatar: isNextUser ? DisplayWidget.hide : DisplayWidget.show,
|
||||||
|
messageTheme: isMyMessage
|
||||||
|
? StreamChatTheme.of(context).ownMessageTheme
|
||||||
|
: StreamChatTheme.of(context).otherMessageTheme,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
StreamSubscription _streamListener;
|
StreamSubscription _streamListener;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|||||||
@@ -0,0 +1,64 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_markdown/flutter_markdown.dart';
|
||||||
|
import 'package:stream_chat/stream_chat.dart';
|
||||||
|
|
||||||
|
import 'stream_chat_theme.dart';
|
||||||
|
import 'utils.dart';
|
||||||
|
|
||||||
|
class MessageText extends StatelessWidget {
|
||||||
|
const MessageText({
|
||||||
|
Key key,
|
||||||
|
@required this.message,
|
||||||
|
@required this.messageTheme,
|
||||||
|
this.onMentionTap,
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
|
final Message message;
|
||||||
|
final void Function(User) onMentionTap;
|
||||||
|
final MessageTheme messageTheme;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final text = _replaceMentions(message.text);
|
||||||
|
return MarkdownBody(
|
||||||
|
data: text,
|
||||||
|
onTapLink: (link) {
|
||||||
|
if (link.startsWith('@')) {
|
||||||
|
final mentionedUser = message.mentionedUsers.firstWhere(
|
||||||
|
(u) => '@${u.name.replaceAll(' ', '')}' == link,
|
||||||
|
orElse: () => null,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (onMentionTap != null) {
|
||||||
|
onMentionTap(mentionedUser);
|
||||||
|
} else {
|
||||||
|
print('tap on ${mentionedUser.name}');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
launchURL(context, link);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
styleSheet: MarkdownStyleSheet.fromTheme(
|
||||||
|
Theme.of(context).copyWith(
|
||||||
|
textTheme: Theme.of(context).textTheme.apply(
|
||||||
|
bodyColor: messageTheme.messageText.color,
|
||||||
|
decoration: messageTheme.messageText.decoration,
|
||||||
|
decorationColor: messageTheme.messageText.decorationColor,
|
||||||
|
decorationStyle: messageTheme.messageText.decorationStyle,
|
||||||
|
fontFamily: messageTheme.messageText.fontFamily,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
).copyWith(
|
||||||
|
p: messageTheme.messageText,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
String _replaceMentions(String text) {
|
||||||
|
message.mentionedUsers?.forEach((u) {
|
||||||
|
text = text.replaceAll(
|
||||||
|
'@${u.name}', '[@${u.name}](@${u.name.replaceAll(' ', '')})');
|
||||||
|
});
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
}
|
||||||
+583
-853
File diff suppressed because it is too large
Load Diff
@@ -24,47 +24,50 @@ class ReactionPicker extends StatelessWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Row(
|
return Container(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
color: Colors.black87,
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
child: Row(
|
||||||
mainAxisSize: MainAxisSize.min,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: reactionToEmoji.keys.map((reactionType) {
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
final ownReactionIndex = message.ownReactions
|
mainAxisSize: MainAxisSize.min,
|
||||||
?.indexWhere((reaction) => reaction.type == reactionType) ??
|
children: reactionToEmoji.keys.map((reactionType) {
|
||||||
-1;
|
final ownReactionIndex = message.ownReactions
|
||||||
return Column(
|
?.indexWhere((reaction) => reaction.type == reactionType) ??
|
||||||
mainAxisSize: MainAxisSize.min,
|
-1;
|
||||||
mainAxisAlignment: MainAxisAlignment.start,
|
return Column(
|
||||||
children: <Widget>[
|
mainAxisSize: MainAxisSize.min,
|
||||||
IconButton(
|
mainAxisAlignment: MainAxisAlignment.start,
|
||||||
iconSize: size,
|
children: <Widget>[
|
||||||
icon: Text(
|
IconButton(
|
||||||
reactionToEmoji[reactionType],
|
iconSize: size,
|
||||||
style: TextStyle(
|
icon: Text(
|
||||||
fontSize: size - 10,
|
reactionToEmoji[reactionType],
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: size - 10,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
|
onPressed: () {
|
||||||
|
if (ownReactionIndex != -1) {
|
||||||
|
removeReaction(
|
||||||
|
context, message.ownReactions[ownReactionIndex]);
|
||||||
|
} else {
|
||||||
|
sendReaction(context, reactionType);
|
||||||
|
}
|
||||||
|
},
|
||||||
),
|
),
|
||||||
onPressed: () {
|
ownReactionIndex != -1
|
||||||
if (ownReactionIndex != -1) {
|
? Padding(
|
||||||
removeReaction(
|
padding: const EdgeInsets.only(bottom: 4.0),
|
||||||
context, message.ownReactions[ownReactionIndex]);
|
child: Text(
|
||||||
} else {
|
message.ownReactions[ownReactionIndex].score.toString(),
|
||||||
sendReaction(context, reactionType);
|
style: TextStyle(color: Colors.white),
|
||||||
}
|
),
|
||||||
},
|
)
|
||||||
),
|
: SizedBox(),
|
||||||
ownReactionIndex != -1
|
],
|
||||||
? Padding(
|
);
|
||||||
padding: const EdgeInsets.only(bottom: 4.0),
|
}).toList(),
|
||||||
child: Text(
|
),
|
||||||
message.ownReactions[ownReactionIndex].score.toString(),
|
|
||||||
style: TextStyle(color: Colors.white),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
: SizedBox(),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
}).toList(),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -47,6 +47,7 @@ class ReplyIndicator extends StatelessWidget {
|
|||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.symmetric(vertical: 2.0),
|
padding: const EdgeInsets.symmetric(vertical: 2.0),
|
||||||
child: Row(
|
child: Row(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: row,
|
children: row,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -13,53 +13,38 @@ class SendingIndicator extends StatelessWidget {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
if (message.status == MessageSendingStatus.SENT || message.status == null) {
|
if (message.status == MessageSendingStatus.SENT || message.status == null) {
|
||||||
return Padding(
|
return CircleAvatar(
|
||||||
padding: const EdgeInsets.symmetric(
|
radius: 4,
|
||||||
horizontal: 1.0,
|
backgroundColor: StreamChatTheme.of(context).accentColor,
|
||||||
),
|
child: Icon(
|
||||||
child: CircleAvatar(
|
Icons.done,
|
||||||
radius: 4,
|
color: Colors.white,
|
||||||
backgroundColor: StreamChatTheme.of(context).accentColor,
|
size: 4,
|
||||||
child: Icon(
|
|
||||||
Icons.done,
|
|
||||||
color: Colors.white,
|
|
||||||
size: 4,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (message.status == MessageSendingStatus.SENDING ||
|
if (message.status == MessageSendingStatus.SENDING ||
|
||||||
message.status == MessageSendingStatus.UPDATING) {
|
message.status == MessageSendingStatus.UPDATING) {
|
||||||
return Padding(
|
return CircleAvatar(
|
||||||
padding: const EdgeInsets.symmetric(
|
radius: 4,
|
||||||
horizontal: 1.0,
|
backgroundColor: Colors.grey,
|
||||||
),
|
child: Icon(
|
||||||
child: CircleAvatar(
|
Icons.access_time,
|
||||||
radius: 4,
|
size: 4,
|
||||||
backgroundColor: Colors.grey,
|
color: Colors.white,
|
||||||
child: Icon(
|
|
||||||
Icons.access_time,
|
|
||||||
size: 4,
|
|
||||||
color: Colors.white,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (message.status == MessageSendingStatus.FAILED ||
|
if (message.status == MessageSendingStatus.FAILED ||
|
||||||
message.status == MessageSendingStatus.FAILED_UPDATE ||
|
message.status == MessageSendingStatus.FAILED_UPDATE ||
|
||||||
message.status == MessageSendingStatus.FAILED_DELETE) {
|
message.status == MessageSendingStatus.FAILED_DELETE) {
|
||||||
return Padding(
|
return CircleAvatar(
|
||||||
padding: const EdgeInsets.symmetric(
|
radius: 4,
|
||||||
horizontal: 1.0,
|
backgroundColor: Color(0xffd0021B).withOpacity(.1),
|
||||||
),
|
child: Icon(
|
||||||
child: CircleAvatar(
|
Icons.error_outline,
|
||||||
radius: 4,
|
size: 4,
|
||||||
backgroundColor: Color(0xffd0021B).withOpacity(.1),
|
color: Colors.white,
|
||||||
child: Icon(
|
|
||||||
Icons.error_outline,
|
|
||||||
size: 4,
|
|
||||||
color: Colors.white,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ export 'src/giphy_attachment.dart';
|
|||||||
export 'src/image_attachment.dart';
|
export 'src/image_attachment.dart';
|
||||||
export 'src/message_input.dart';
|
export 'src/message_input.dart';
|
||||||
export 'src/message_list_view.dart';
|
export 'src/message_list_view.dart';
|
||||||
|
export 'src/message_text.dart';
|
||||||
export 'src/message_widget.dart';
|
export 'src/message_widget.dart';
|
||||||
export 'src/reaction_picker.dart';
|
export 'src/reaction_picker.dart';
|
||||||
export 'src/reply_indicator.dart';
|
export 'src/reply_indicator.dart';
|
||||||
|
|||||||
+5
-4
@@ -12,18 +12,19 @@ dependencies:
|
|||||||
photo_view: ^0.9.2
|
photo_view: ^0.9.2
|
||||||
rxdart: ^0.24.0
|
rxdart: ^0.24.0
|
||||||
jiffy: ^3.0.1
|
jiffy: ^3.0.1
|
||||||
cached_network_image: ^2.2.0
|
flutter_portal: ^0.1.0
|
||||||
|
cached_network_image: ^2.2.0+1
|
||||||
flutter_markdown: ^0.3.5
|
flutter_markdown: ^0.3.5
|
||||||
url_launcher: ^5.4.5
|
url_launcher: ^5.4.5
|
||||||
video_player: ^0.10.9+1
|
video_player: ^0.10.10
|
||||||
chewie: ^0.9.10
|
chewie: ^0.9.10
|
||||||
file_picker: ^1.8.0+2
|
file_picker: ^1.8.0+2
|
||||||
image_picker: ^0.6.5+3
|
image_picker: ^0.6.6+1
|
||||||
flutter_keyboard_visibility: ^2.0.0
|
flutter_keyboard_visibility: ^2.0.0
|
||||||
stream_chat:
|
stream_chat:
|
||||||
path: ../stream_chat_dart
|
path: ../stream_chat_dart
|
||||||
mime: ^0.9.6+3
|
mime: ^0.9.6+3
|
||||||
visibility_detector: ^0.1.4
|
visibility_detector: ^0.1.5
|
||||||
http_parser: ^3.1.4
|
http_parser: ^3.1.4
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
|
|||||||
Reference in New Issue
Block a user