migrate missing files

This commit is contained in:
Salvatore Giordano
2021-05-10 16:01:20 +02:00
parent 43d5e9063e
commit 435a88cd1b
25 changed files with 1292 additions and 1336 deletions
@@ -44,18 +44,19 @@ class DateDivider extends StatelessWidget {
if (uppercase) dayInfo = dayInfo.toUpperCase(); if (uppercase) dayInfo = dayInfo.toUpperCase();
final chatThemeData = StreamChatTheme.of(context);
return Center( return Center(
child: Container( child: Container(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 1), padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 1),
decoration: BoxDecoration( decoration: BoxDecoration(
color: StreamChatTheme.of(context).colorTheme.overlayDark, color: chatThemeData.colorTheme.overlayDark,
borderRadius: BorderRadius.circular(8), borderRadius: BorderRadius.circular(8),
), ),
child: Text( child: Text(
dayInfo, dayInfo,
style: StreamChatTheme.of(context).textTheme.footnote.copyWith( style: chatThemeData.textTheme.footnote.copyWith(
color: StreamChatTheme.of(context).colorTheme.white, color: chatThemeData.colorTheme.white,
), ),
), ),
), ),
); );
@@ -31,44 +31,41 @@ class DeletedMessage extends StatelessWidget {
final bool reverse; final bool reverse;
@override @override
Widget build(BuildContext context) => Transform( Widget build(BuildContext context) {
transform: Matrix4.rotationY(reverse ? pi : 0), final chatThemeData = StreamChatTheme.of(context);
alignment: Alignment.center, return Transform(
child: Material( transform: Matrix4.rotationY(reverse ? pi : 0),
color: messageTheme.messageBackgroundColor, alignment: Alignment.center,
shape: shape ?? child: Material(
RoundedRectangleBorder( color: messageTheme.messageBackgroundColor,
borderRadius: borderRadiusGeometry ?? BorderRadius.zero, shape: shape ??
side: borderSide ?? RoundedRectangleBorder(
BorderSide( borderRadius: borderRadiusGeometry ?? BorderRadius.zero,
color: Theme.of(context).brightness == Brightness.dark side: borderSide ??
? StreamChatTheme.of(context) BorderSide(
.colorTheme color: Theme.of(context).brightness == Brightness.dark
.white ? chatThemeData.colorTheme.white.withAlpha(24)
.withAlpha(24) : chatThemeData.colorTheme.black.withAlpha(24),
: StreamChatTheme.of(context) ),
.colorTheme
.black
.withAlpha(24),
),
),
child: Padding(
padding: const EdgeInsets.symmetric(
vertical: 8,
horizontal: 16,
), ),
child: Transform( child: Padding(
transform: Matrix4.rotationY(reverse ? pi : 0), padding: const EdgeInsets.symmetric(
alignment: Alignment.center, vertical: 8,
child: Text( horizontal: 16,
'Message deleted', ),
style: messageTheme.messageText?.copyWith( child: Transform(
fontStyle: FontStyle.italic, transform: Matrix4.rotationY(reverse ? pi : 0),
color: messageTheme.createdAt?.color, alignment: Alignment.center,
), child: Text(
'Message deleted',
style: messageTheme.messageText?.copyWith(
fontStyle: FontStyle.italic,
color: messageTheme.createdAt?.color,
), ),
), ),
), ),
), ),
); ),
);
}
} }
@@ -46,18 +46,12 @@ class GroupImage extends StatelessWidget {
onTap: onTap, onTap: onTap,
child: ClipRRect( child: ClipRRect(
borderRadius: borderRadius ?? borderRadius: borderRadius ??
StreamChatTheme.of(context) streamChatTheme.ownMessageTheme.avatarTheme?.borderRadius,
.ownMessageTheme
.avatarTheme
?.borderRadius,
child: Container( child: Container(
constraints: constraints ?? constraints: constraints ??
StreamChatTheme.of(context) streamChatTheme.ownMessageTheme.avatarTheme?.constraints,
.ownMessageTheme
.avatarTheme
?.constraints,
decoration: BoxDecoration( decoration: BoxDecoration(
color: StreamChatTheme.of(context).colorTheme.accentBlue, color: streamChatTheme.colorTheme.accentBlue,
), ),
child: Flex( child: Flex(
direction: Axis.vertical, direction: Axis.vertical,
@@ -125,8 +119,7 @@ class GroupImage extends StatelessWidget {
BorderRadius.zero) + BorderRadius.zero) +
BorderRadius.circular(selectionThickness), BorderRadius.circular(selectionThickness),
child: Container( child: Container(
color: selectionColor ?? color: selectionColor ?? streamChatTheme.colorTheme.accentBlue,
StreamChatTheme.of(context).colorTheme.accentBlue,
height: 64, height: 64,
width: 64, width: 64,
child: Padding( child: Padding(
@@ -77,16 +77,18 @@ class _ImageFooterState extends State<ImageFooter> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final showShareButton = !kIsWeb; final showShareButton = !kIsWeb;
final mediaQueryData = MediaQuery.of(context);
final chatThemeData = StreamChatTheme.of(context);
return SizedBox.fromSize( return SizedBox.fromSize(
size: Size( size: Size(
MediaQuery.of(context).size.width, mediaQueryData.size.width,
MediaQuery.of(context).padding.bottom + widget.preferredSize.height, mediaQueryData.padding.bottom + widget.preferredSize.height,
), ),
child: MediaQuery.removePadding( child: MediaQuery.removePadding(
context: context, context: context,
removeTop: true, removeTop: true,
child: BottomAppBar( child: BottomAppBar(
color: StreamChatTheme.of(context).colorTheme.white, color: chatThemeData.colorTheme.white,
child: Row( child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
@@ -98,7 +100,7 @@ class _ImageFooterState extends State<ImageFooter> {
IconButton( IconButton(
icon: StreamSvgIcon.iconShare( icon: StreamSvgIcon.iconShare(
size: 24, size: 24,
color: StreamChatTheme.of(context).colorTheme.black, color: chatThemeData.colorTheme.black,
), ),
onPressed: () async { onPressed: () async {
final attachment = final attachment =
@@ -134,8 +136,7 @@ class _ImageFooterState extends State<ImageFooter> {
children: <Widget>[ children: <Widget>[
Text( Text(
'${widget.currentPage + 1} of ${widget.totalPages}', '${widget.currentPage + 1} of ${widget.totalPages}',
style: style: chatThemeData.textTheme.headlineBold,
StreamChatTheme.of(context).textTheme.headlineBold,
), ),
], ],
), ),
@@ -143,7 +144,7 @@ class _ImageFooterState extends State<ImageFooter> {
), ),
IconButton( IconButton(
icon: StreamSvgIcon.iconGrid( icon: StreamSvgIcon.iconGrid(
color: StreamChatTheme.of(context).colorTheme.black, color: chatThemeData.colorTheme.black,
), ),
onPressed: () => _showPhotosModal(context), onPressed: () => _showPhotosModal(context),
), ),
@@ -155,10 +156,11 @@ class _ImageFooterState extends State<ImageFooter> {
} }
void _showPhotosModal(context) { void _showPhotosModal(context) {
final chatThemeData = StreamChatTheme.of(context);
showModalBottomSheet( showModalBottomSheet(
context: context, context: context,
barrierColor: StreamChatTheme.of(context).colorTheme.overlay, barrierColor: chatThemeData.colorTheme.overlay,
backgroundColor: StreamChatTheme.of(context).colorTheme.white, backgroundColor: chatThemeData.colorTheme.white,
isScrollControlled: true, isScrollControlled: true,
shape: const RoundedRectangleBorder( shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.only( borderRadius: BorderRadius.only(
@@ -189,9 +191,7 @@ class _ImageFooterState extends State<ImageFooter> {
padding: const EdgeInsets.all(16), padding: const EdgeInsets.all(16),
child: Text( child: Text(
'Photos', 'Photos',
style: StreamChatTheme.of(context) style: chatThemeData.textTheme.headlineBold,
.textTheme
.headlineBold,
), ),
), ),
), ),
@@ -199,7 +199,7 @@ class _ImageFooterState extends State<ImageFooter> {
alignment: Alignment.centerRight, alignment: Alignment.centerRight,
child: IconButton( child: IconButton(
icon: StreamSvgIcon.close( icon: StreamSvgIcon.close(
color: StreamChatTheme.of(context).colorTheme.black, color: chatThemeData.colorTheme.black,
), ),
onPressed: () => Navigator.maybePop(context), onPressed: () => Navigator.maybePop(context),
), ),
@@ -262,9 +262,7 @@ class _ImageFooterState extends State<ImageFooter> {
boxShadow: [ boxShadow: [
BoxShadow( BoxShadow(
blurRadius: 8, blurRadius: 8,
color: StreamChatTheme.of(context) color: chatThemeData.colorTheme.black
.colorTheme
.black
.withOpacity(0.3), .withOpacity(0.3),
), ),
], ],
@@ -50,59 +50,58 @@ class ImageHeader extends StatelessWidget implements PreferredSizeWidget {
final int currentIndex; final int currentIndex;
@override @override
Widget build(BuildContext context) => AppBar( Widget build(BuildContext context) {
brightness: Theme.of(context).brightness, final chatThemeData = StreamChatTheme.of(context);
elevation: 1, return AppBar(
leading: showBackButton brightness: Theme.of(context).brightness,
? IconButton( elevation: 1,
icon: StreamSvgIcon.close( leading: showBackButton
color: StreamChatTheme.of(context).colorTheme.black, ? IconButton(
size: 24, icon: StreamSvgIcon.close(
), color: chatThemeData.colorTheme.black,
onPressed: onBackPressed, size: 24,
)
: const SizedBox(),
backgroundColor:
StreamChatTheme.of(context).channelTheme.channelHeaderTheme.color,
actions: <Widget>[
if (message.type != 'ephemeral')
IconButton(
icon: StreamSvgIcon.iconMenuPoint(
color: StreamChatTheme.of(context).colorTheme.black,
), ),
onPressed: () { onPressed: onBackPressed,
_showMessageActionModalBottomSheet(context); )
}, : const SizedBox(),
backgroundColor: chatThemeData.channelTheme.channelHeaderTheme.color,
actions: <Widget>[
if (message.type != 'ephemeral')
IconButton(
icon: StreamSvgIcon.iconMenuPoint(
color: chatThemeData.colorTheme.black,
), ),
], onPressed: () {
centerTitle: true, _showMessageActionModalBottomSheet(context);
title: message.type != 'ephemeral' },
? InkWell( ),
onTap: onTitleTap, ],
child: SizedBox( centerTitle: true,
height: preferredSize.height, title: message.type != 'ephemeral'
width: preferredSize.width, ? InkWell(
child: Column( onTap: onTitleTap,
mainAxisAlignment: MainAxisAlignment.center, child: SizedBox(
mainAxisSize: MainAxisSize.min, height: preferredSize.height,
children: <Widget>[ width: preferredSize.width,
Text( child: Column(
userName, mainAxisAlignment: MainAxisAlignment.center,
style: mainAxisSize: MainAxisSize.min,
StreamChatTheme.of(context).textTheme.headlineBold, children: <Widget>[
), Text(
Text( userName,
sentAt, style: chatThemeData.textTheme.headlineBold,
style: StreamChatTheme.of(context) ),
.channelPreviewTheme Text(
.subtitle, sentAt,
), style: chatThemeData.channelPreviewTheme.subtitle,
], ),
), ],
), ),
) ),
: const SizedBox(), )
); : const SizedBox(),
);
}
@override @override
final Size preferredSize; final Size preferredSize;
@@ -38,26 +38,29 @@ class InfoTile extends StatelessWidget {
final Color? backgroundColor; final Color? backgroundColor;
@override @override
Widget build(BuildContext context) => PortalEntry( Widget build(BuildContext context) {
visible: showMessage, final chatThemeData = StreamChatTheme.of(context);
portalAnchor: tileAnchor ?? Alignment.topCenter, return PortalEntry(
childAnchor: childAnchor ?? Alignment.bottomCenter, visible: showMessage,
portal: Container( portalAnchor: tileAnchor ?? Alignment.topCenter,
height: 25, childAnchor: childAnchor ?? Alignment.bottomCenter,
color: backgroundColor ?? portal: Container(
StreamChatTheme.of(context).colorTheme.grey.withOpacity(0.9), height: 25,
child: Center( color:
child: Text( backgroundColor ?? chatThemeData.colorTheme.grey.withOpacity(0.9),
message, child: Center(
style: textStyle ?? child: Text(
StreamChatTheme.of(context).textTheme.body.copyWith( message,
color: Colors.white, style: textStyle ??
), chatThemeData.textTheme.body.copyWith(
maxLines: 1, color: Colors.white,
overflow: TextOverflow.ellipsis, ),
), maxLines: 1,
overflow: TextOverflow.ellipsis,
), ),
), ),
child: child, ),
); child: child,
);
}
} }
@@ -56,6 +56,7 @@ class _MediaListViewState extends State<MediaListView> {
position, position,
) { ) {
final media = _media.elementAt(position); final media = _media.elementAt(position);
final chatThemeData = StreamChatTheme.of(context);
return Padding( return Padding(
padding: const EdgeInsets.symmetric(horizontal: 1, vertical: 1), padding: const EdgeInsets.symmetric(horizontal: 1, vertical: 1),
child: InkWell( child: InkWell(
@@ -89,10 +90,8 @@ class _MediaListViewState extends State<MediaListView> {
? 1.0 ? 1.0
: 0.0, : 0.0,
child: Container( child: Container(
color: StreamChatTheme.of(context) color:
.colorTheme chatThemeData.colorTheme.black.withOpacity(0.5),
.black
.withOpacity(0.5),
alignment: Alignment.topRight, alignment: Alignment.topRight,
padding: const EdgeInsets.only( padding: const EdgeInsets.only(
top: 8, top: 8,
@@ -100,13 +99,10 @@ class _MediaListViewState extends State<MediaListView> {
), ),
child: CircleAvatar( child: CircleAvatar(
radius: 12, radius: 12,
backgroundColor: backgroundColor: chatThemeData.colorTheme.white,
StreamChatTheme.of(context).colorTheme.white,
child: StreamSvgIcon.check( child: StreamSvgIcon.check(
size: 24, size: 24,
color: StreamChatTheme.of(context) color: chatThemeData.colorTheme.black,
.colorTheme
.black,
), ),
), ),
), ),
@@ -128,7 +124,7 @@ class _MediaListViewState extends State<MediaListView> {
child: Text( child: Text(
media.videoDuration.format(), media.videoDuration.format(),
style: TextStyle( style: TextStyle(
color: StreamChatTheme.of(context).colorTheme.white, color: chatThemeData.colorTheme.white,
), ),
), ),
), ),
@@ -1,5 +1,4 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:stream_chat_flutter/stream_chat_flutter.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart';
/// This widget is used for showing user tiles for mentions /// This widget is used for showing user tiles for mentions
@@ -32,71 +31,70 @@ class MentionTile extends StatelessWidget {
final Widget? trailing; final Widget? trailing;
@override @override
Widget build(BuildContext context) => SizedBox( Widget build(BuildContext context) {
height: 56, final chatThemeData = StreamChatTheme.of(context);
child: Row( return SizedBox(
children: [ height: 56,
const SizedBox( child: Row(
width: 16, children: [
), const SizedBox(
leading ?? width: 16,
UserAvatar( ),
constraints: BoxConstraints.tight( leading ??
const Size( UserAvatar(
40, constraints: BoxConstraints.tight(
40, const Size(
), 40,
40,
), ),
user: member.user!,
), ),
const SizedBox( user: member.user!,
width: 8, ),
), const SizedBox(
Expanded( width: 8,
child: Align( ),
alignment: Alignment.centerLeft, Expanded(
child: Column( child: Align(
mainAxisAlignment: MainAxisAlignment.center, alignment: Alignment.centerLeft,
crossAxisAlignment: CrossAxisAlignment.start, child: Column(
children: [ mainAxisAlignment: MainAxisAlignment.center,
title ?? crossAxisAlignment: CrossAxisAlignment.start,
Text( children: [
member.user!.name, title ??
maxLines: 1, Text(
overflow: TextOverflow.ellipsis, member.user!.name,
style: StreamChatTheme.of(context).textTheme.bodyBold, maxLines: 1,
overflow: TextOverflow.ellipsis,
style: chatThemeData.textTheme.bodyBold,
),
const SizedBox(
height: 2,
),
subtitle ??
Text(
'@${member.userId}',
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: chatThemeData.textTheme.footnoteBold.copyWith(
color: chatThemeData.colorTheme.grey,
), ),
const SizedBox( ),
height: 2, ],
),
subtitle ??
Text(
'@${member.userId}',
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: StreamChatTheme.of(context)
.textTheme
.footnoteBold
.copyWith(
color:
StreamChatTheme.of(context).colorTheme.grey,
),
),
],
),
), ),
), ),
trailing ?? ),
Padding( trailing ??
padding: const EdgeInsets.only( Padding(
right: 18, padding: const EdgeInsets.only(
left: 8, right: 18,
), left: 8,
child: StreamSvgIcon.mentions(
color: StreamChatTheme.of(context).colorTheme.accentBlue,
),
), ),
], child: StreamSvgIcon.mentions(
), color: chatThemeData.colorTheme.accentBlue,
); ),
),
],
),
);
}
} }
@@ -3,12 +3,12 @@ import 'dart:ui';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:stream_chat_flutter/src/extension.dart';
import 'package:stream_chat_flutter/src/message_action.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/reaction_picker.dart';
import 'package:stream_chat_flutter/src/stream_svg_icon.dart'; import 'package:stream_chat_flutter/src/stream_svg_icon.dart';
import 'package:stream_chat_flutter/src/utils.dart'; import 'package:stream_chat_flutter/src/utils.dart';
import 'package:stream_chat_flutter/stream_chat_flutter.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart';
import 'package:stream_chat_flutter/src/extension.dart';
/// Constructs a modal with actions for a message /// Constructs a modal with actions for a message
class MessageActionsModal extends StatefulWidget { class MessageActionsModal extends StatefulWidget {
@@ -108,7 +108,8 @@ class _MessageActionsModalState extends State<MessageActionsModal> {
Widget build(BuildContext context) => _showMessageOptionsModal(); Widget build(BuildContext context) => _showMessageOptionsModal();
Widget _showMessageOptionsModal() { Widget _showMessageOptionsModal() {
final size = MediaQuery.of(context).size; final mediaQueryData = MediaQuery.of(context);
final size = mediaQueryData.size;
final user = StreamChat.of(context).user; final user = StreamChat.of(context).user;
final roughMaxSize = 2 * size.width / 3; final roughMaxSize = 2 * size.width / 3;
@@ -133,6 +134,7 @@ class _MessageActionsModalState extends State<MessageActionsModal> {
final hasFileAttachment = final hasFileAttachment =
widget.message.attachments.any((it) => it.type == 'file') == true; widget.message.attachments.any((it) => it.type == 'file') == true;
final streamChatThemeData = StreamChatTheme.of(context);
return GestureDetector( return GestureDetector(
behavior: HitTestBehavior.translucent, behavior: HitTestBehavior.translucent,
onTap: () => Navigator.maybePop(context), onTap: () => Navigator.maybePop(context),
@@ -145,7 +147,7 @@ class _MessageActionsModalState extends State<MessageActionsModal> {
sigmaY: 10, sigmaY: 10,
), ),
child: Container( child: Container(
color: StreamChatTheme.of(context).colorTheme.overlay, color: streamChatThemeData.colorTheme.overlay,
), ),
), ),
), ),
@@ -226,11 +228,9 @@ class _MessageActionsModalState extends State<MessageActionsModal> {
left: widget.reverse ? 0 : 40, left: widget.reverse ? 0 : 40,
), ),
child: SizedBox( child: SizedBox(
width: MediaQuery.of(context).size.width * 0.75, width: mediaQueryData.size.width * 0.75,
child: Material( child: Material(
color: StreamChatTheme.of(context) color: streamChatThemeData.colorTheme.whiteSnow,
.colorTheme
.whiteSnow,
clipBehavior: Clip.hardEdge, clipBehavior: Clip.hardEdge,
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16), borderRadius: BorderRadius.circular(16),
@@ -266,9 +266,8 @@ class _MessageActionsModalState extends State<MessageActionsModal> {
].insertBetween( ].insertBetween(
Container( Container(
height: 1, height: 1,
color: StreamChatTheme.of(context) color: streamChatThemeData
.colorTheme .colorTheme.greyWhisper,
.greyWhisper,
), ),
), ),
), ),
@@ -310,11 +309,12 @@ class _MessageActionsModalState extends State<MessageActionsModal> {
void _showFlagDialog() async { void _showFlagDialog() async {
final client = StreamChat.of(context).client; final client = StreamChat.of(context).client;
final streamChatThemeData = StreamChatTheme.of(context);
final answer = await showConfirmationDialog( final answer = await showConfirmationDialog(
context, context,
title: 'Flag Message', title: 'Flag Message',
icon: StreamSvgIcon.flag( icon: StreamSvgIcon.flag(
color: StreamChatTheme.of(context).colorTheme.accentRed, color: streamChatThemeData.colorTheme.accentRed,
size: 24, size: 24,
), ),
question: question:
@@ -324,7 +324,7 @@ class _MessageActionsModalState extends State<MessageActionsModal> {
cancelText: 'CANCEL', cancelText: 'CANCEL',
); );
final theme = StreamChatTheme.of(context); final theme = streamChatThemeData;
if (answer == true) { if (answer == true) {
try { try {
await client.flagMessage(widget.message.id); await client.flagMessage(widget.message.id);
@@ -400,48 +400,54 @@ class _MessageActionsModalState extends State<MessageActionsModal> {
); );
} }
Widget _buildReplyButton(BuildContext context) => InkWell( Widget _buildReplyButton(BuildContext context) {
onTap: () { final streamChatThemeData = StreamChatTheme.of(context);
Navigator.pop(context); return InkWell(
if (widget.onReplyTap != null) { onTap: () {
widget.onReplyTap!(widget.message); Navigator.pop(context);
} if (widget.onReplyTap != null) {
}, widget.onReplyTap!(widget.message);
child: Padding( }
padding: const EdgeInsets.symmetric(vertical: 11, horizontal: 16), },
child: Row( child: Padding(
children: [ padding: const EdgeInsets.symmetric(vertical: 11, horizontal: 16),
StreamSvgIcon.reply( child: Row(
color: StreamChatTheme.of(context).primaryIconTheme.color, children: [
), StreamSvgIcon.reply(
const SizedBox(width: 16), color: streamChatThemeData.primaryIconTheme.color,
Text( ),
'Reply', const SizedBox(width: 16),
style: StreamChatTheme.of(context).textTheme.body, Text(
), 'Reply',
], style: streamChatThemeData.textTheme.body,
), ),
],
), ),
); ),
);
}
Widget _buildFlagButton(BuildContext context) => InkWell( Widget _buildFlagButton(BuildContext context) {
onTap: _showFlagDialog, final streamChatThemeData = StreamChatTheme.of(context);
child: Padding( return InkWell(
padding: const EdgeInsets.symmetric(vertical: 11, horizontal: 16), onTap: _showFlagDialog,
child: Row( child: Padding(
children: [ padding: const EdgeInsets.symmetric(vertical: 11, horizontal: 16),
StreamSvgIcon.iconFlag( child: Row(
color: StreamChatTheme.of(context).primaryIconTheme.color, children: [
), StreamSvgIcon.iconFlag(
const SizedBox(width: 16), color: streamChatThemeData.primaryIconTheme.color,
Text( ),
'Flag Message', const SizedBox(width: 16),
style: StreamChatTheme.of(context).textTheme.body, Text(
), 'Flag Message',
], style: streamChatThemeData.textTheme.body,
), ),
],
), ),
); ),
);
}
Widget _buildDeleteButton(BuildContext context) { Widget _buildDeleteButton(BuildContext context) {
final isDeleteFailed = final isDeleteFailed =
@@ -469,54 +475,61 @@ class _MessageActionsModalState extends State<MessageActionsModal> {
); );
} }
Widget _buildCopyButton(BuildContext context) => InkWell( Widget _buildCopyButton(BuildContext context) {
onTap: () async { final streamChatThemeData = StreamChatTheme.of(context);
widget.onCopyTap?.call(widget.message); return InkWell(
Navigator.pop(context); onTap: () async {
}, widget.onCopyTap?.call(widget.message);
child: Padding( Navigator.pop(context);
padding: const EdgeInsets.symmetric(vertical: 11, horizontal: 16), },
child: Row( child: Padding(
children: [ padding: const EdgeInsets.symmetric(vertical: 11, horizontal: 16),
StreamSvgIcon.copy( child: Row(
size: 24, children: [
color: StreamChatTheme.of(context).primaryIconTheme.color, StreamSvgIcon.copy(
), size: 24,
const SizedBox(width: 16), color: streamChatThemeData.primaryIconTheme.color,
Text( ),
'Copy Message', const SizedBox(width: 16),
style: StreamChatTheme.of(context).textTheme.body, Text(
), 'Copy Message',
], style: streamChatThemeData.textTheme.body,
), ),
],
), ),
); ),
);
}
Widget _buildEditMessage(BuildContext context) => InkWell( Widget _buildEditMessage(BuildContext context) {
onTap: () async { final streamChatThemeData = StreamChatTheme.of(context);
Navigator.pop(context); return InkWell(
_showEditBottomSheet(context); onTap: () async {
}, Navigator.pop(context);
child: Padding( _showEditBottomSheet(context);
padding: const EdgeInsets.symmetric(vertical: 11, horizontal: 16), },
child: Row( child: Padding(
children: [ padding: const EdgeInsets.symmetric(vertical: 11, horizontal: 16),
StreamSvgIcon.edit( child: Row(
color: StreamChatTheme.of(context).primaryIconTheme.color, children: [
), StreamSvgIcon.edit(
const SizedBox(width: 16), color: streamChatThemeData.primaryIconTheme.color,
Text( ),
'Edit Message', const SizedBox(width: 16),
style: StreamChatTheme.of(context).textTheme.body, Text(
), 'Edit Message',
], style: streamChatThemeData.textTheme.body,
), ),
],
), ),
); ),
);
}
Widget _buildResendMessage(BuildContext context) { Widget _buildResendMessage(BuildContext context) {
final isUpdateFailed = final isUpdateFailed =
widget.message.status == MessageSendingStatus.failed_update; widget.message.status == MessageSendingStatus.failed_update;
final streamChatThemeData = StreamChatTheme.of(context);
return InkWell( return InkWell(
onTap: () { onTap: () {
Navigator.pop(context); Navigator.pop(context);
@@ -532,12 +545,12 @@ class _MessageActionsModalState extends State<MessageActionsModal> {
child: Row( child: Row(
children: [ children: [
StreamSvgIcon.circleUp( StreamSvgIcon.circleUp(
color: StreamChatTheme.of(context).colorTheme.accentBlue, color: streamChatThemeData.colorTheme.accentBlue,
), ),
const SizedBox(width: 16), const SizedBox(width: 16),
Text( Text(
isUpdateFailed ? 'Resend Edited Message' : 'Resend', isUpdateFailed ? 'Resend Edited Message' : 'Resend',
style: StreamChatTheme.of(context).textTheme.body, style: streamChatThemeData.textTheme.body,
), ),
], ],
), ),
@@ -547,13 +560,13 @@ class _MessageActionsModalState extends State<MessageActionsModal> {
void _showEditBottomSheet(BuildContext context) { void _showEditBottomSheet(BuildContext context) {
final channel = StreamChannel.of(context).channel; final channel = StreamChannel.of(context).channel;
final streamChatThemeData = StreamChatTheme.of(context);
showModalBottomSheet( showModalBottomSheet(
context: context, context: context,
elevation: 2, elevation: 2,
clipBehavior: Clip.hardEdge, clipBehavior: Clip.hardEdge,
isScrollControlled: true, isScrollControlled: true,
backgroundColor: backgroundColor: streamChatThemeData.messageInputTheme.inputBackground,
StreamChatTheme.of(context).messageInputTheme.inputBackground,
shape: const RoundedRectangleBorder( shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.only( borderRadius: BorderRadius.only(
topLeft: Radius.circular(16), topLeft: Radius.circular(16),
@@ -575,8 +588,7 @@ class _MessageActionsModalState extends State<MessageActionsModal> {
Padding( Padding(
padding: const EdgeInsets.all(8), padding: const EdgeInsets.all(8),
child: StreamSvgIcon.edit( child: StreamSvgIcon.edit(
color: color: streamChatThemeData.colorTheme.greyGainsboro,
StreamChatTheme.of(context).colorTheme.greyGainsboro,
), ),
), ),
const Text( const Text(
@@ -608,27 +620,30 @@ class _MessageActionsModalState extends State<MessageActionsModal> {
); );
} }
Widget _buildThreadReplyButton(BuildContext context) => InkWell( Widget _buildThreadReplyButton(BuildContext context) {
onTap: () { final streamChatThemeData = StreamChatTheme.of(context);
Navigator.pop(context); return InkWell(
if (widget.onThreadReplyTap != null) { onTap: () {
widget.onThreadReplyTap!(widget.message); Navigator.pop(context);
} if (widget.onThreadReplyTap != null) {
}, widget.onThreadReplyTap!(widget.message);
child: Padding( }
padding: const EdgeInsets.symmetric(vertical: 11, horizontal: 16), },
child: Row( child: Padding(
children: [ padding: const EdgeInsets.symmetric(vertical: 11, horizontal: 16),
StreamSvgIcon.thread( child: Row(
color: StreamChatTheme.of(context).primaryIconTheme.color, children: [
), StreamSvgIcon.thread(
const SizedBox(width: 16), color: streamChatThemeData.primaryIconTheme.color,
Text( ),
'Thread Reply', const SizedBox(width: 16),
style: StreamChatTheme.of(context).textTheme.body, Text(
), 'Thread Reply',
], style: streamChatThemeData.textTheme.body,
), ),
],
), ),
); ),
);
}
} }
File diff suppressed because it is too large Load Diff
@@ -6,15 +6,15 @@ import 'package:flutter/material.dart';
import 'package:jiffy/jiffy.dart'; import 'package:jiffy/jiffy.dart';
import 'package:rxdart/rxdart.dart'; import 'package:rxdart/rxdart.dart';
import 'package:scrollable_positioned_list/scrollable_positioned_list.dart'; import 'package:scrollable_positioned_list/scrollable_positioned_list.dart';
import 'package:stream_chat_flutter/src/extension.dart';
import 'package:stream_chat_flutter/src/info_tile.dart'; import 'package:stream_chat_flutter/src/info_tile.dart';
import 'package:stream_chat_flutter/src/message_widget.dart'; 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/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/system_message.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_core/stream_chat_flutter_core.dart';
import 'package:visibility_detector/visibility_detector.dart'; import 'package:visibility_detector/visibility_detector.dart';
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
import 'package:stream_chat_flutter/src/extension.dart';
import 'package:stream_chat_flutter/src/swipeable.dart';
/// Widget builder for message /// Widget builder for message
typedef MessageBuilder = Widget Function( typedef MessageBuilder = Widget Function(
@@ -274,15 +274,15 @@ class _MessageListViewState extends State<MessageListView> {
bool _showScrollToBottom = false; bool _showScrollToBottom = false;
late final ItemPositionsListener _itemPositionListener; late final ItemPositionsListener _itemPositionListener;
int? _messageListLength; int? _messageListLength;
StreamChannelState? streamChannel; late StreamChannelState streamChannel;
int? get _initialIndex { int? get _initialIndex {
if (widget.initialScrollIndex != null) return widget.initialScrollIndex; if (widget.initialScrollIndex != null) return widget.initialScrollIndex;
if (streamChannel!.initialMessageId != null) { if (streamChannel.initialMessageId != null) {
final messages = streamChannel!.channel.state!.messages; final messages = streamChannel.channel.state!.messages;
final totalMessages = messages.length; final totalMessages = messages.length;
final messageIndex = final messageIndex =
messages.indexWhere((e) => e.id == streamChannel!.initialMessageId); messages.indexWhere((e) => e.id == streamChannel.initialMessageId);
final index = totalMessages - messageIndex; final index = totalMessages - messageIndex;
if (index != 0) return index - 1; if (index != 0) return index - 1;
return index; return index;
@@ -295,9 +295,9 @@ class _MessageListViewState extends State<MessageListView> {
return 0; return 0;
} }
bool _isInitialMessage(String id) => streamChannel!.initialMessageId == id; bool _isInitialMessage(String id) => streamChannel.initialMessageId == id;
bool get _upToDate => streamChannel!.channel.state!.isUpToDate; bool get _upToDate => streamChannel.channel.state!.isUpToDate;
bool get _isThreadConversation => widget.parentMessage != null; bool get _isThreadConversation => widget.parentMessage != null;
@@ -316,46 +316,37 @@ class _MessageListViewState extends State<MessageListView> {
final MessageListController _messageListController = MessageListController(); final MessageListController _messageListController = MessageListController();
@override @override
Widget build(BuildContext context) => MessageListCore( Widget build(BuildContext context) {
messageFilter: widget.messageFilter, final chatThemeData = StreamChatTheme.of(context);
loadingBuilder: widget.loadingBuilder ?? return MessageListCore(
(context) => const Center( messageFilter: widget.messageFilter,
child: CircularProgressIndicator(), loadingBuilder: widget.loadingBuilder ??
(context) => const Center(
child: CircularProgressIndicator(),
),
emptyBuilder: widget.emptyBuilder ??
(context) => Center(
child: Text(
'No chats here yet...',
style: chatThemeData.textTheme.footnote.copyWith(
color: chatThemeData.colorTheme.black.withOpacity(.5)),
), ),
emptyBuilder: widget.emptyBuilder ?? ),
(context) => Center( messageListBuilder:
child: Text( widget.messageListBuilder ?? (context, list) => _buildListView(list),
'No chats here yet...', messageListController: _messageListController,
style: StreamChatTheme.of(context) parentMessage: widget.parentMessage,
.textTheme showScrollToBottom: widget.showScrollToBottom,
.footnote errorWidgetBuilder: widget.errorWidgetBuilder ??
.copyWith( (BuildContext context, Object error) => Center(
color: StreamChatTheme.of(context) child: Text(
.colorTheme 'Something went wrong',
.black style: chatThemeData.textTheme.footnote.copyWith(
.withOpacity(.5)), color: chatThemeData.colorTheme.black.withOpacity(.5)),
),
), ),
messageListBuilder: widget.messageListBuilder ?? ),
(context, list) => _buildListView(list), );
messageListController: _messageListController, }
parentMessage: widget.parentMessage,
showScrollToBottom: widget.showScrollToBottom,
errorWidgetBuilder: widget.errorWidgetBuilder ??
(BuildContext context, Object error) => Center(
child: Text(
'Something went wrong',
style: StreamChatTheme.of(context)
.textTheme
.footnote
.copyWith(
color: StreamChatTheme.of(context)
.colorTheme
.black
.withOpacity(.5)),
),
),
);
Widget _buildListView(List<Message> data) { Widget _buildListView(List<Message> data) {
messages = data; messages = data;
@@ -448,10 +439,10 @@ class _MessageListViewState extends State<MessageListView> {
if (i == 0) return const SizedBox(height: 30); if (i == 0) return const SizedBox(height: 30);
if (i == messages.length + 1) { if (i == messages.length + 1) {
final replyCount = widget.parentMessage!.replyCount; final replyCount = widget.parentMessage!.replyCount;
final chatThemeData = StreamChatTheme.of(context);
return Container( return Container(
decoration: BoxDecoration( decoration: BoxDecoration(
gradient: gradient: chatThemeData.colorTheme.bgGradient,
StreamChatTheme.of(context).colorTheme.bgGradient,
), ),
child: Padding( child: Padding(
padding: const EdgeInsets.all(8), padding: const EdgeInsets.all(8),
@@ -459,10 +450,8 @@ class _MessageListViewState extends State<MessageListView> {
// ignore: lines_longer_than_80_chars // ignore: lines_longer_than_80_chars
'$replyCount ${replyCount == 1 ? 'Reply' : 'Replies'}', '$replyCount ${replyCount == 1 ? 'Reply' : 'Replies'}',
textAlign: TextAlign.center, textAlign: TextAlign.center,
style: StreamChatTheme.of(context) style: chatThemeData
.channelTheme .channelTheme.channelHeaderTheme.subtitle,
.channelHeaderTheme
.subtitle,
), ),
), ),
); );
@@ -616,8 +605,8 @@ class _MessageListViewState extends State<MessageListView> {
Widget _buildScrollToBottom() => StreamBuilder<Tuple2<bool, int>>( Widget _buildScrollToBottom() => StreamBuilder<Tuple2<bool, int>>(
stream: Rx.combineLatest2( stream: Rx.combineLatest2(
streamChannel!.channel.state!.isUpToDateStream, streamChannel.channel.state!.isUpToDateStream,
streamChannel!.channel.state!.unreadCountStream, streamChannel.channel.state!.unreadCountStream,
(bool isUpToDate, int unreadCount) => Tuple2(isUpToDate, unreadCount), (bool isUpToDate, int unreadCount) => Tuple2(isUpToDate, unreadCount),
), ),
builder: (_, snapshot) { builder: (_, snapshot) {
@@ -633,8 +622,9 @@ class _MessageListViewState extends State<MessageListView> {
} }
final unreadCount = snapshot.data!.item2; final unreadCount = snapshot.data!.item2;
final showUnreadCount = unreadCount > 0 && final showUnreadCount = unreadCount > 0 &&
streamChannel!.channel.state!.members.any((e) => streamChannel.channel.state!.members.any((e) =>
e.userId == streamChannel!.channel.client.state.user!.id); e.userId == streamChannel.channel.client.state.user!.id);
final chatThemeData = StreamChatTheme.of(context);
return Positioned( return Positioned(
bottom: 8, bottom: 8,
right: 8, right: 8,
@@ -644,15 +634,15 @@ class _MessageListViewState extends State<MessageListView> {
clipBehavior: Clip.none, clipBehavior: Clip.none,
children: [ children: [
FloatingActionButton( FloatingActionButton(
backgroundColor: StreamChatTheme.of(context).colorTheme.white, backgroundColor: chatThemeData.colorTheme.white,
onPressed: () { onPressed: () {
if (unreadCount > 0) { if (unreadCount > 0) {
streamChannel!.channel.markRead(); streamChannel.channel.markRead();
} }
if (!_upToDate) { if (!_upToDate) {
_bottomPaginationActive = false; _bottomPaginationActive = false;
_topPaginationActive = false; _topPaginationActive = false;
streamChannel!.reloadChannel(); streamChannel.reloadChannel();
} else { } else {
setState(() => _showScrollToBottom = false); setState(() => _showScrollToBottom = false);
_scrollController!.scrollTo( _scrollController!.scrollTo(
@@ -663,7 +653,7 @@ class _MessageListViewState extends State<MessageListView> {
} }
}, },
child: StreamSvgIcon.down( child: StreamSvgIcon.down(
color: StreamChatTheme.of(context).colorTheme.black, color: chatThemeData.colorTheme.black,
), ),
), ),
if (showUnreadCount) if (showUnreadCount)
@@ -692,12 +682,12 @@ class _MessageListViewState extends State<MessageListView> {
); );
Widget _buildLoadingIndicator( Widget _buildLoadingIndicator(
StreamChannelState? streamChannel, StreamChannelState streamChannel,
QueryDirection direction, QueryDirection direction,
) { ) {
final stream = direction == QueryDirection.top final stream = direction == QueryDirection.top
? streamChannel!.queryTopMessages ? streamChannel.queryTopMessages
: streamChannel!.queryBottomMessages; : streamChannel.queryBottomMessages;
return StreamBuilder<bool>( return StreamBuilder<bool>(
key: const Key('LOADING-INDICATOR'), key: const Key('LOADING-INDICATOR'),
stream: stream, stream: stream,
@@ -764,7 +754,7 @@ class _MessageListViewState extends State<MessageListView> {
BuildContext context, BuildContext context,
Message message, Message message,
List<Message> messages, List<Message> messages,
StreamChannelState? streamChannel, StreamChannelState streamChannel,
) { ) {
Widget messageWidget; Widget messageWidget;
if (widget.messageBuilder != null) { if (widget.messageBuilder != null) {
@@ -790,7 +780,7 @@ class _MessageListViewState extends State<MessageListView> {
onVisibilityChanged: (visibility) { onVisibilityChanged: (visibility) {
final isVisible = visibility.visibleBounds != Rect.zero; final isVisible = visibility.visibleBounds != Rect.zero;
if (isVisible) { if (isVisible) {
final channel = streamChannel!.channel; final channel = streamChannel.channel;
if (_upToDate && if (_upToDate &&
channel.config?.readEvents == true && channel.config?.readEvents == true &&
channel.state!.unreadCount! > 0) { channel.state!.unreadCount! > 0) {
@@ -811,6 +801,7 @@ class _MessageListViewState extends State<MessageListView> {
final isMyMessage = message.user!.id == StreamChat.of(context).user!.id; final isMyMessage = message.user!.id == StreamChat.of(context).user!.id;
final isOnlyEmoji = message.text!.isOnlyEmoji; final isOnlyEmoji = message.text!.isOnlyEmoji;
final chatThemeData = StreamChatTheme.of(context);
return MessageWidget( return MessageWidget(
showReplyMessage: false, showReplyMessage: false,
showResendMessage: false, showResendMessage: false,
@@ -837,8 +828,8 @@ class _MessageListViewState extends State<MessageListView> {
borderSide: isMyMessage || isOnlyEmoji ? BorderSide.none : null, borderSide: isMyMessage || isOnlyEmoji ? BorderSide.none : null,
showUserAvatar: isMyMessage ? DisplayWidget.gone : DisplayWidget.show, showUserAvatar: isMyMessage ? DisplayWidget.gone : DisplayWidget.show,
messageTheme: isMyMessage messageTheme: isMyMessage
? StreamChatTheme.of(context).ownMessageTheme ? chatThemeData.ownMessageTheme
: StreamChatTheme.of(context).otherMessageTheme, : chatThemeData.otherMessageTheme,
onShowMessage: widget.onShowMessage, onShowMessage: widget.onShowMessage,
onReturnAction: (action) { onReturnAction: (action) {
switch (action) { switch (action) {
@@ -897,7 +888,7 @@ class _MessageListViewState extends State<MessageListView> {
); );
} }
final channel = streamChannel!.channel; final channel = streamChannel.channel;
final readList = channel.state?.read?.where((read) { final readList = channel.state?.read?.where((read) {
if (read.user.id == userId) return false; if (read.user.id == userId) return false;
return read.lastRead.isAfter(message.createdAt) || return read.lastRead.isAfter(message.createdAt) ||
@@ -946,6 +937,7 @@ class _MessageListViewState extends State<MessageListView> {
? BorderSide.none ? BorderSide.none
: null; : null;
final chatThemeData = StreamChatTheme.of(context);
Widget child = MessageWidget( Widget child = MessageWidget(
key: ValueKey<String>('MESSAGE-${message.id}'), key: ValueKey<String>('MESSAGE-${message.id}'),
message: message, message: message,
@@ -970,7 +962,7 @@ class _MessageListViewState extends State<MessageListView> {
if (messages.map((e) => e.id).contains(quotedMessageId)) { if (messages.map((e) => e.id).contains(quotedMessageId)) {
scrollToIndex(); scrollToIndex();
} else { } else {
await streamChannel!.loadChannelAtMessage(quotedMessageId).then((_) { await streamChannel.loadChannelAtMessage(quotedMessageId).then((_) {
WidgetsBinding.instance!.addPostFrameCallback((_) { WidgetsBinding.instance!.addPostFrameCallback((_) {
if (messages.map((e) => e.id).contains(quotedMessageId)) { if (messages.map((e) => e.id).contains(quotedMessageId)) {
scrollToIndex(); scrollToIndex();
@@ -1013,8 +1005,8 @@ class _MessageListViewState extends State<MessageListView> {
horizontal: isOnlyEmoji ? 0 : 16.0, horizontal: isOnlyEmoji ? 0 : 16.0,
), ),
messageTheme: isMyMessage messageTheme: isMyMessage
? StreamChatTheme.of(context).ownMessageTheme ? chatThemeData.ownMessageTheme
: StreamChatTheme.of(context).otherMessageTheme, : chatThemeData.otherMessageTheme,
readList: readList, readList: readList,
allRead: allRead, allRead: allRead,
onShowMessage: widget.onShowMessage, onShowMessage: widget.onShowMessage,
@@ -1054,7 +1046,7 @@ class _MessageListViewState extends State<MessageListView> {
widget.onMessageSwiped?.call(message); widget.onMessageSwiped?.call(message);
}, },
backgroundIcon: StreamSvgIcon.reply( backgroundIcon: StreamSvgIcon.reply(
color: StreamChatTheme.of(context).colorTheme.accentBlue, color: chatThemeData.colorTheme.accentBlue,
), ),
child: child, child: child,
), ),
@@ -1064,7 +1056,7 @@ class _MessageListViewState extends State<MessageListView> {
if (!initialMessageHighlightComplete && if (!initialMessageHighlightComplete &&
widget.highlightInitialMessage && widget.highlightInitialMessage &&
_isInitialMessage(message.id)) { _isInitialMessage(message.id)) {
final colorTheme = StreamChatTheme.of(context).colorTheme; final colorTheme = chatThemeData.colorTheme;
final highlightColor = final highlightColor =
widget.messageHighlightColor ?? colorTheme.highlight; widget.messageHighlightColor ?? colorTheme.highlight;
child = TweenAnimationBuilder<Color?>( child = TweenAnimationBuilder<Color?>(
@@ -1100,14 +1092,23 @@ class _MessageListViewState extends State<MessageListView> {
initialIndex = _initialIndex; initialIndex = _initialIndex;
initialAlignment = _initialAlignment; initialAlignment = _initialAlignment;
_getOnThreadTap();
super.initState();
}
@override
void didChangeDependencies() {
streamChannel = StreamChannel.of(context);
_messageNewListener?.cancel();
_messageNewListener = _messageNewListener =
streamChannel!.channel.on(EventType.messageNew).listen((event) { streamChannel.channel.on(EventType.messageNew).listen((event) {
if (_upToDate) { if (_upToDate) {
_bottomPaginationActive = false; _bottomPaginationActive = false;
_topPaginationActive = false; _topPaginationActive = false;
} }
if (event.message!.user!.id == if (event.message!.user!.id ==
streamChannel!.channel.client.state.user!.id) { streamChannel.channel.client.state.user!.id) {
WidgetsBinding.instance!.addPostFrameCallback((_) { WidgetsBinding.instance!.addPostFrameCallback((_) {
_scrollController?.jumpTo( _scrollController?.jumpTo(
index: 0, index: 0,
@@ -1117,11 +1118,9 @@ class _MessageListViewState extends State<MessageListView> {
}); });
if (_isThreadConversation) { if (_isThreadConversation) {
streamChannel!.getReplies(widget.parentMessage!.id); streamChannel.getReplies(widget.parentMessage!.id);
} }
super.didChangeDependencies();
_getOnThreadTap();
super.initState();
} }
void _getOnThreadTap() { void _getOnThreadTap() {
@@ -1139,12 +1138,12 @@ class _MessageListViewState extends State<MessageListView> {
context, context,
MaterialPageRoute( MaterialPageRoute(
builder: (_) => StreamBuilder<Message>( builder: (_) => StreamBuilder<Message>(
stream: streamChannel!.channel.state!.messagesStream.map( stream: streamChannel.channel.state!.messagesStream.map(
(messages) => (messages) =>
messages!.firstWhere((m) => m.id == message.id)), messages!.firstWhere((m) => m.id == message.id)),
initialData: message, initialData: message,
builder: (_, snapshot) => StreamChannel( builder: (_, snapshot) => StreamChannel(
channel: streamChannel!.channel, channel: streamChannel.channel,
child: widget.threadBuilder!(context, snapshot.data), child: widget.threadBuilder!(context, snapshot.data),
), ),
), ),
@@ -1157,7 +1156,7 @@ class _MessageListViewState extends State<MessageListView> {
@override @override
void dispose() { void dispose() {
if (!_upToDate) { if (!_upToDate) {
streamChannel!.reloadChannel(); streamChannel.reloadChannel();
} }
_messageNewListener?.cancel(); _messageNewListener?.cancel();
super.dispose(); super.dispose();
@@ -1,13 +1,13 @@
import 'dart:ui'; import 'dart:ui';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:stream_chat_flutter/src/extension.dart';
import 'package:stream_chat_flutter/src/reaction_bubble.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/reaction_picker.dart';
import 'package:stream_chat_flutter/src/stream_chat.dart'; import 'package:stream_chat_flutter/src/stream_chat.dart';
import 'package:stream_chat_flutter/src/user_avatar.dart'; import 'package:stream_chat_flutter/src/user_avatar.dart';
import 'package:stream_chat_flutter/stream_chat_flutter.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_core/stream_chat_flutter_core.dart';
import 'package:stream_chat_flutter/src/extension.dart';
/// Modal widget for displaying message reactions /// Modal widget for displaying message reactions
class MessageReactionsModal extends StatelessWidget { class MessageReactionsModal extends StatelessWidget {
@@ -176,8 +176,9 @@ class MessageReactionsModal extends StatelessWidget {
Widget _buildReactionCard(BuildContext context) { Widget _buildReactionCard(BuildContext context) {
final currentUser = StreamChat.of(context).user; final currentUser = StreamChat.of(context).user;
final chatThemeData = StreamChatTheme.of(context);
return Card( return Card(
color: StreamChatTheme.of(context).colorTheme.white, color: chatThemeData.colorTheme.white,
clipBehavior: Clip.hardEdge, clipBehavior: Clip.hardEdge,
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16), borderRadius: BorderRadius.circular(16),
@@ -190,7 +191,7 @@ class MessageReactionsModal extends StatelessWidget {
children: [ children: [
Text( Text(
'Message Reactions', 'Message Reactions',
style: StreamChatTheme.of(context).textTheme.headlineBold, style: chatThemeData.textTheme.headlineBold,
), ),
const SizedBox(height: 16), const SizedBox(height: 16),
Flexible( Flexible(
@@ -220,6 +221,7 @@ class MessageReactionsModal extends StatelessWidget {
BuildContext context, BuildContext context,
) { ) {
final isCurrentUser = reaction.user?.id == currentUser.id; final isCurrentUser = reaction.user?.id == currentUser.id;
final chatThemeData = StreamChatTheme.of(context);
return ConstrainedBox( return ConstrainedBox(
constraints: BoxConstraints.loose(const Size( constraints: BoxConstraints.loose(const Size(
64, 64,
@@ -258,7 +260,7 @@ class MessageReactionsModal extends StatelessWidget {
messageTheme.reactionsBorderColor ?? Colors.transparent, messageTheme.reactionsBorderColor ?? Colors.transparent,
backgroundColor: messageTheme.reactionsBackgroundColor ?? backgroundColor: messageTheme.reactionsBackgroundColor ??
Colors.transparent, Colors.transparent,
maskColor: StreamChatTheme.of(context).colorTheme.white, maskColor: chatThemeData.colorTheme.white,
tailCirclesSpacing: 1, tailCirclesSpacing: 1,
highlightOwnReactions: false, highlightOwnReactions: false,
), ),
@@ -269,7 +271,7 @@ class MessageReactionsModal extends StatelessWidget {
const SizedBox(height: 8), const SizedBox(height: 8),
Text( Text(
reaction.user!.name.split(' ')[0], reaction.user!.name.split(' ')[0],
style: StreamChatTheme.of(context).textTheme.footnoteBold, style: chatThemeData.textTheme.footnoteBold,
textAlign: TextAlign.center, textAlign: TextAlign.center,
), ),
], ],
@@ -35,6 +35,7 @@ class MessageSearchItem extends StatelessWidget {
final channel = getMessageResponse.channel; final channel = getMessageResponse.channel;
final channelName = channel?.extraData['name']; final channelName = channel?.extraData['name'];
final user = message.user!; final user = message.user!;
final chatThemeData = StreamChatTheme.of(context);
return ListTile( return ListTile(
onTap: onTap, onTap: onTap,
leading: UserAvatar( leading: UserAvatar(
@@ -49,21 +50,18 @@ class MessageSearchItem extends StatelessWidget {
children: [ children: [
Text( Text(
user.id == StreamChat.of(context).user?.id ? 'You' : user.name, user.id == StreamChat.of(context).user?.id ? 'You' : user.name,
style: StreamChatTheme.of(context).channelPreviewTheme.title, style: chatThemeData.channelPreviewTheme.title,
), ),
if (channelName != null) ...[ if (channelName != null) ...[
Text( Text(
' in ', ' in ',
style: StreamChatTheme.of(context) style: chatThemeData.channelPreviewTheme.title?.copyWith(
.channelPreviewTheme fontWeight: FontWeight.normal,
.title ),
?.copyWith(
fontWeight: FontWeight.normal,
),
), ),
Text( Text(
channelName as String, channelName as String,
style: StreamChatTheme.of(context).channelPreviewTheme.title, style: chatThemeData.channelPreviewTheme.title,
), ),
], ],
], ],
@@ -121,22 +119,23 @@ class MessageSearchItem extends StatelessWidget {
text = parts.join(' '); text = parts.join(' ');
} }
final chatThemeData = StreamChatTheme.of(context);
return Text.rich( return Text.rich(
_getDisplayText( _getDisplayText(
text!, text!,
message.mentionedUsers, message.mentionedUsers,
message.attachments, message.attachments,
StreamChatTheme.of(context).channelPreviewTheme.subtitle?.copyWith( chatThemeData.channelPreviewTheme.subtitle?.copyWith(
fontStyle: (message.isSystem || message.isDeleted) fontStyle: (message.isSystem || message.isDeleted)
? FontStyle.italic ? FontStyle.italic
: FontStyle.normal, : FontStyle.normal,
), ),
StreamChatTheme.of(context).channelPreviewTheme.subtitle?.copyWith( chatThemeData.channelPreviewTheme.subtitle?.copyWith(
fontStyle: (message.isSystem || message.isDeleted) fontStyle: (message.isSystem || message.isDeleted)
? FontStyle.italic ? FontStyle.italic
: FontStyle.normal, : FontStyle.normal,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
), ),
), ),
maxLines: 1, maxLines: 1,
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
@@ -1,8 +1,8 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:stream_chat_flutter/src/info_tile.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/message_search_item.dart';
import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart';
import 'package:stream_chat_flutter/stream_chat_flutter.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart';
import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart';
/// Callback called when tapping on a user /// Callback called when tapping on a user
typedef MessageSearchItemTapCallback = void Function(GetMessageResponse); typedef MessageSearchItemTapCallback = void Function(GetMessageResponse);
@@ -270,12 +270,13 @@ class _MessageSearchListViewState extends State<MessageSearchListView> {
); );
if (widget.showResultCount) { if (widget.showResultCount) {
final chatThemeData = StreamChatTheme.of(context);
child = Column( child = Column(
children: [ children: [
Container( Container(
width: double.maxFinite, width: double.maxFinite,
decoration: BoxDecoration( decoration: BoxDecoration(
gradient: StreamChatTheme.of(context).colorTheme.bgGradient, gradient: chatThemeData.colorTheme.bgGradient,
), ),
child: Padding( child: Padding(
padding: const EdgeInsets.symmetric( padding: const EdgeInsets.symmetric(
@@ -285,7 +286,7 @@ class _MessageSearchListViewState extends State<MessageSearchListView> {
child: Text( child: Text(
'${items.length} results', '${items.length} results',
style: TextStyle( style: TextStyle(
color: StreamChatTheme.of(context).colorTheme.grey, color: chatThemeData.colorTheme.grey,
), ),
), ),
), ),
@@ -1,8 +1,8 @@
import 'package:collection/collection.dart' show IterableExtension; import 'package:collection/collection.dart' show IterableExtension;
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_markdown/flutter_markdown.dart'; import 'package:flutter_markdown/flutter_markdown.dart';
import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart';
import 'package:stream_chat_flutter/stream_chat_flutter.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart';
import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart';
/// Text widget to display in message /// Text widget to display in message
class MessageText extends StatelessWidget { class MessageText extends StatelessWidget {
@@ -31,6 +31,7 @@ class MessageText extends StatelessWidget {
Widget build(BuildContext context) { Widget build(BuildContext context) {
final text = _replaceMentions(message.text ?? '').replaceAll('\n', '\\\n'); final text = _replaceMentions(message.text ?? '').replaceAll('\n', '\\\n');
final themeData = Theme.of(context);
return MarkdownBody( return MarkdownBody(
data: text, data: text,
onTapLink: ( onTapLink: (
@@ -60,14 +61,14 @@ class MessageText extends StatelessWidget {
} }
}, },
styleSheet: MarkdownStyleSheet.fromTheme( styleSheet: MarkdownStyleSheet.fromTheme(
Theme.of(context).copyWith( themeData.copyWith(
textTheme: Theme.of(context).textTheme.apply( textTheme: themeData.textTheme.apply(
bodyColor: messageTheme.messageText?.color, bodyColor: messageTheme.messageText?.color,
decoration: messageTheme.messageText?.decoration, decoration: messageTheme.messageText?.decoration,
decorationColor: messageTheme.messageText?.decorationColor, decorationColor: messageTheme.messageText?.decorationColor,
decorationStyle: messageTheme.messageText?.decorationStyle, decorationStyle: messageTheme.messageText?.decorationStyle,
fontFamily: messageTheme.messageText?.fontFamily, fontFamily: messageTheme.messageText?.fontFamily,
), ),
), ),
).copyWith( ).copyWith(
a: messageTheme.messageLinks, a: messageTheme.messageLinks,
@@ -8,6 +8,8 @@ import 'package:flutter/rendering.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:flutter_portal/flutter_portal.dart'; import 'package:flutter_portal/flutter_portal.dart';
import 'package:jiffy/jiffy.dart'; import 'package:jiffy/jiffy.dart';
import 'package:stream_chat_flutter/src/extension.dart';
import 'package:stream_chat_flutter/src/image_group.dart';
import 'package:stream_chat_flutter/src/message_action.dart'; import 'package:stream_chat_flutter/src/message_action.dart';
import 'package:stream_chat_flutter/src/message_actions_modal.dart'; 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/message_reactions_modal.dart';
@@ -15,8 +17,6 @@ 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/reaction_bubble.dart';
import 'package:stream_chat_flutter/src/url_attachment.dart'; import 'package:stream_chat_flutter/src/url_attachment.dart';
import 'package:stream_chat_flutter/stream_chat_flutter.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart';
import 'package:stream_chat_flutter/src/image_group.dart';
import 'package:stream_chat_flutter/src/extension.dart';
/// Widget builder for building attachments /// Widget builder for building attachments
typedef AttachmentBuilder = Widget Function( typedef AttachmentBuilder = Widget Function(
@@ -109,6 +109,7 @@ class MessageWidget extends StatefulWidget {
borderRadius: attachmentBorderRadiusGeometry ?? BorderRadius.zero, borderRadius: attachmentBorderRadiusGeometry ?? BorderRadius.zero,
); );
final mediaQueryData = MediaQuery.of(context);
if (attachments.length > 1) { if (attachments.length > 1) {
return Padding( return Padding(
padding: attachmentPadding, padding: attachmentPadding,
@@ -118,8 +119,8 @@ class MessageWidget extends StatefulWidget {
color: messageTheme.messageBackgroundColor, color: messageTheme.messageBackgroundColor,
child: ImageGroup( child: ImageGroup(
size: Size( size: Size(
MediaQuery.of(context).size.width * 0.8, mediaQueryData.size.width * 0.8,
MediaQuery.of(context).size.height * 0.3, mediaQueryData.size.height * 0.3,
), ),
images: attachments, images: attachments,
message: message, message: message,
@@ -142,8 +143,8 @@ class MessageWidget extends StatefulWidget {
message: message, message: message,
messageTheme: messageTheme, messageTheme: messageTheme,
size: Size( size: Size(
MediaQuery.of(context).size.width * 0.8, mediaQueryData.size.width * 0.8,
MediaQuery.of(context).size.height * 0.3, mediaQueryData.size.height * 0.3,
), ),
onShowMessage: onShowMessage, onShowMessage: onShowMessage,
onReturnAction: onReturnAction, onReturnAction: onReturnAction,
@@ -167,24 +168,25 @@ class MessageWidget extends StatefulWidget {
return wrapAttachmentWidget( return wrapAttachmentWidget(
context, context,
Column( Column(
children: attachments children: attachments.map((attachment) {
.map((attachment) => VideoAttachment( final mediaQueryData = MediaQuery.of(context);
attachment: attachment, return VideoAttachment(
messageTheme: messageTheme, attachment: attachment,
size: Size( messageTheme: messageTheme,
MediaQuery.of(context).size.width * 0.8, size: Size(
MediaQuery.of(context).size.height * 0.3, mediaQueryData.size.width * 0.8,
), mediaQueryData.size.height * 0.3,
message: message, ),
onShowMessage: onShowMessage, message: message,
onReturnAction: onReturnAction, onShowMessage: onShowMessage,
onAttachmentTap: onAttachmentTap != null onReturnAction: onReturnAction,
? () { onAttachmentTap: onAttachmentTap != null
onAttachmentTap(message, attachment); ? () {
} onAttachmentTap(message, attachment);
: null, }
)) : null,
.toList(), );
}).toList(),
), ),
border, border,
reverse, reverse,
@@ -200,18 +202,19 @@ class MessageWidget extends StatefulWidget {
return wrapAttachmentWidget( return wrapAttachmentWidget(
context, context,
Column( Column(
children: attachments children: attachments.map((attachment) {
.map((attachment) => GiphyAttachment( final mediaQueryData = MediaQuery.of(context);
attachment: attachment, return GiphyAttachment(
message: message, attachment: attachment,
size: Size( message: message,
MediaQuery.of(context).size.width * 0.8, size: Size(
MediaQuery.of(context).size.height * 0.3, mediaQueryData.size.width * 0.8,
), mediaQueryData.size.height * 0.3,
onShowMessage: onShowMessage, ),
onReturnAction: onReturnAction, onShowMessage: onShowMessage,
)) onReturnAction: onReturnAction,
.toList(), );
}).toList(),
), ),
border, border,
reverse, reverse,
@@ -230,21 +233,24 @@ class MessageWidget extends StatefulWidget {
return Column( return Column(
children: attachments children: attachments
.map<Widget>((attachment) => wrapAttachmentWidget( .map<Widget>((attachment) {
context, final mediaQueryData = MediaQuery.of(context);
FileAttachment( return wrapAttachmentWidget(
message: message, context,
attachment: attachment, FileAttachment(
size: Size( message: message,
MediaQuery.of(context).size.width * 0.8, attachment: attachment,
MediaQuery.of(context).size.height * 0.3, size: Size(
), mediaQueryData.size.width * 0.8,
mediaQueryData.size.height * 0.3,
), ),
border, ),
reverse, border,
attachmentBorderRadiusGeometry as BorderRadius? ?? reverse,
BorderRadius.zero, attachmentBorderRadiusGeometry as BorderRadius? ??
)) BorderRadius.zero,
);
})
.insertBetween(SizedBox( .insertBetween(SizedBox(
height: attachmentPadding.vertical / 2, height: attachmentPadding.vertical / 2,
)) ))
@@ -658,12 +664,13 @@ class _MessageWidgetState extends State<MessageWidget>
widget.onQuotedMessageTap != null widget.onQuotedMessageTap != null
? () => widget.onQuotedMessageTap!(widget.message.quotedMessageId) ? () => widget.onQuotedMessageTap!(widget.message.quotedMessageId)
: null; : null;
final chatThemeData = StreamChatTheme.of(context);
return QuotedMessageWidget( return QuotedMessageWidget(
onTap: onTap, onTap: onTap,
message: widget.message.quotedMessage!, message: widget.message.quotedMessage!,
messageTheme: isMyMessage messageTheme: isMyMessage
? StreamChatTheme.of(context).otherMessageTheme ? chatThemeData.otherMessageTheme
: StreamChatTheme.of(context).ownMessageTheme, : chatThemeData.ownMessageTheme,
reverse: widget.reverse, reverse: widget.reverse,
padding: EdgeInsets.only( padding: EdgeInsets.only(
right: 8, left: 8, top: 8, bottom: hasNonUrlAttachments ? 8 : 0), right: 8, left: 8, top: 8, bottom: hasNonUrlAttachments ? 8 : 0),
@@ -672,6 +679,7 @@ class _MessageWidgetState extends State<MessageWidget>
Widget get _bottomRow { Widget get _bottomRow {
if (isDeleted) { if (isDeleted) {
final chatThemeData = StreamChatTheme.of(context);
return Transform( return Transform(
transform: Matrix4.rotationY(widget.reverse ? pi : 0), transform: Matrix4.rotationY(widget.reverse ? pi : 0),
alignment: Alignment.center, alignment: Alignment.center,
@@ -679,16 +687,14 @@ class _MessageWidgetState extends State<MessageWidget>
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
StreamSvgIcon.eye( StreamSvgIcon.eye(
color: StreamChatTheme.of(context).colorTheme.grey, color: chatThemeData.colorTheme.grey,
size: 16, size: 16,
), ),
const SizedBox(width: 8), const SizedBox(width: 8),
Text( Text(
'Only visible to you', 'Only visible to you',
style: StreamChatTheme.of(context) style: chatThemeData.textTheme.footnote
.textTheme .copyWith(color: chatThemeData.colorTheme.grey),
.footnote
.copyWith(color: StreamChatTheme.of(context).colorTheme.grey),
), ),
], ],
), ),
@@ -41,56 +41,55 @@ class OptionListTile extends StatelessWidget {
final TextStyle? titleTextStyle; final TextStyle? titleTextStyle;
@override @override
Widget build(BuildContext context) => Column( Widget build(BuildContext context) {
children: [ final chatThemeData = StreamChatTheme.of(context);
Container( return Column(
color: separatorColor ?? children: [
StreamChatTheme.of(context).colorTheme.greyGainsboro, Container(
height: 1, color: separatorColor ?? chatThemeData.colorTheme.greyGainsboro,
), height: 1,
Material( ),
color: tileColor ?? StreamChatTheme.of(context).colorTheme.white, Material(
child: SizedBox( color: tileColor ?? chatThemeData.colorTheme.white,
height: 63, child: SizedBox(
child: InkWell( height: 63,
onTap: onTap, child: InkWell(
child: Row( onTap: onTap,
children: [ child: Row(
if (leading != null) Center(child: leading), children: [
if (leading == null) if (leading != null) Center(child: leading),
const SizedBox( if (leading == null)
width: 16, const SizedBox(
), width: 16,
Expanded( ),
flex: 4, Expanded(
child: Text( flex: 4,
title!, child: Text(
style: titleTextStyle ?? title!,
(titleColor == null style: titleTextStyle ??
? StreamChatTheme.of(context).textTheme.bodyBold (titleColor == null
: StreamChatTheme.of(context) ? chatThemeData.textTheme.bodyBold
.textTheme : chatThemeData.textTheme.bodyBold.copyWith(
.bodyBold color: titleColor,
.copyWith( )),
color: titleColor, ),
)), ),
Expanded(
flex: 2,
child: Padding(
padding: const EdgeInsets.only(right: 16),
child: Align(
alignment: Alignment.centerRight,
child: trailing ?? Container(),
), ),
), ),
Expanded( ),
flex: 2, ],
child: Padding(
padding: const EdgeInsets.only(right: 16),
child: Align(
alignment: Alignment.centerRight,
child: trailing ?? Container(),
),
),
),
],
),
), ),
), ),
), ),
], ),
); ],
);
}
} }
@@ -126,6 +126,8 @@ class ReactionBubble extends StatelessWidget {
(r) => r.type == reaction.type, (r) => r.type == reaction.type,
); );
final chatThemeData = StreamChatTheme.of(context);
final userId = StreamChat.of(context).user?.id;
return Padding( return Padding(
padding: const EdgeInsets.symmetric( padding: const EdgeInsets.symmetric(
horizontal: 4, horizontal: 4,
@@ -135,24 +137,16 @@ class ReactionBubble extends StatelessWidget {
assetName: reactionIcon.assetName, assetName: reactionIcon.assetName,
width: 16, width: 16,
height: 16, height: 16,
color: (!highlightOwnReactions || color: (!highlightOwnReactions || reaction.user?.id == userId)
reaction.user?.id == StreamChat.of(context).user?.id) ? chatThemeData.colorTheme.accentBlue
? StreamChatTheme.of(context).colorTheme.accentBlue : chatThemeData.colorTheme.black.withOpacity(.5),
: StreamChatTheme.of(context)
.colorTheme
.black
.withOpacity(.5),
) )
: Icon( : Icon(
Icons.help_outline_rounded, Icons.help_outline_rounded,
size: 16, size: 16,
color: (!highlightOwnReactions || color: (!highlightOwnReactions || reaction.user?.id == userId)
reaction.user?.id == StreamChat.of(context).user?.id) ? chatThemeData.colorTheme.accentBlue
? StreamChatTheme.of(context).colorTheme.accentBlue : chatThemeData.colorTheme.black.withOpacity(.5),
: StreamChatTheme.of(context)
.colorTheme
.black
.withOpacity(.5),
), ),
); );
} }
@@ -2,8 +2,8 @@ import 'dart:math';
import 'package:ezanimation/ezanimation.dart'; import 'package:ezanimation/ezanimation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:stream_chat_flutter/src/stream_svg_icon.dart';
import 'package:stream_chat_flutter/src/extension.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/stream_chat_flutter.dart';
/// ![screenshot](https://raw.githubusercontent.com/GetStream/stream-chat-flutter/master/screenshots/reaction_picker.png) /// ![screenshot](https://raw.githubusercontent.com/GetStream/stream-chat-flutter/master/screenshots/reaction_picker.png)
@@ -33,7 +33,8 @@ class _ReactionPickerState extends State<ReactionPicker>
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final reactionIcons = StreamChatTheme.of(context).reactionIcons; final chatThemeData = StreamChatTheme.of(context);
final reactionIcons = chatThemeData.reactionIcons;
if (animations.isEmpty && reactionIcons.isNotEmpty) { if (animations.isEmpty && reactionIcons.isNotEmpty) {
reactionIcons.forEach((element) { reactionIcons.forEach((element) {
@@ -57,7 +58,7 @@ class _ReactionPickerState extends State<ReactionPicker>
scale: val, scale: val,
child: Material( child: Material(
borderRadius: BorderRadius.circular(24), borderRadius: BorderRadius.circular(24),
color: StreamChatTheme.of(context).colorTheme.white, color: chatThemeData.colorTheme.white,
clipBehavior: Clip.hardEdge, clipBehavior: Clip.hardEdge,
child: Padding( child: Padding(
padding: const EdgeInsets.symmetric( padding: const EdgeInsets.symmetric(
@@ -119,9 +120,8 @@ class _ReactionPickerState extends State<ReactionPicker>
animations[index].value * 24.0, animations[index].value * 24.0,
), ),
color: ownReactionIndex != -1 color: ownReactionIndex != -1
? StreamChatTheme.of(context) ? chatThemeData
.colorTheme .colorTheme.accentBlue
.accentBlue
: Theme.of(context) : Theme.of(context)
.iconTheme .iconTheme
.color! .color!
@@ -97,65 +97,60 @@ class ThreadHeader extends StatelessWidget implements PreferredSizeWidget {
final List<Widget>? actions; final List<Widget>? actions;
@override @override
Widget build(BuildContext context) => AppBar( Widget build(BuildContext context) {
automaticallyImplyLeading: false, final chatThemeData = StreamChatTheme.of(context);
brightness: Theme.of(context).brightness, return AppBar(
elevation: 1, automaticallyImplyLeading: false,
leading: leading ?? brightness: Theme.of(context).brightness,
(showBackButton elevation: 1,
? StreamBackButton( leading: leading ??
cid: StreamChannel.of(context).channel.cid, (showBackButton
onPressed: onBackPressed, ? StreamBackButton(
showUnreads: true, cid: StreamChannel.of(context).channel.cid,
) onPressed: onBackPressed,
: const SizedBox()), showUnreads: true,
backgroundColor: )
StreamChatTheme.of(context).channelTheme.channelHeaderTheme.color, : const SizedBox()),
centerTitle: true, backgroundColor: chatThemeData.channelTheme.channelHeaderTheme.color,
actions: actions, centerTitle: true,
title: InkWell( actions: actions,
onTap: onTitleTap, title: InkWell(
child: SizedBox( onTap: onTitleTap,
height: preferredSize.height, child: SizedBox(
child: Column( height: preferredSize.height,
mainAxisAlignment: MainAxisAlignment.center, child: Column(
children: [ mainAxisAlignment: MainAxisAlignment.center,
title ?? children: [
Text( title ??
'Thread Reply', Text(
style: StreamChatTheme.of(context) 'Thread Reply',
.channelTheme style: chatThemeData.channelTheme.channelHeaderTheme.title,
.channelHeaderTheme ),
.title, const SizedBox(height: 2),
), subtitle ??
const SizedBox(height: 2), Row(
subtitle ?? mainAxisSize: MainAxisSize.min,
Row( mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.min, children: [
mainAxisAlignment: MainAxisAlignment.center, Text(
children: [ 'with ',
Text( style: chatThemeData
'with ', .channelTheme.channelHeaderTheme.subtitle,
style: StreamChatTheme.of(context) ),
.channelTheme Flexible(
.channelHeaderTheme child: ChannelName(
.subtitle, textStyle: chatThemeData
.channelTheme.channelHeaderTheme.subtitle,
), ),
Flexible( ),
child: ChannelName( ],
textStyle: StreamChatTheme.of(context) ),
.channelTheme ],
.channelHeaderTheme
.subtitle,
),
),
],
),
],
),
), ),
), ),
); ),
);
}
@override @override
final Size preferredSize; final Size preferredSize;
@@ -26,91 +26,84 @@ class UrlAttachment extends StatelessWidget {
final EdgeInsets textPadding; final EdgeInsets textPadding;
@override @override
Widget build(BuildContext context) => GestureDetector( Widget build(BuildContext context) {
onTap: () { final chatThemeData = StreamChatTheme.of(context);
launchURL( return GestureDetector(
context, onTap: () {
urlAttachment.ogScrapeUrl, launchURL(
); context,
}, urlAttachment.ogScrapeUrl,
child: Column( );
crossAxisAlignment: CrossAxisAlignment.stretch, },
children: [ child: Column(
if (urlAttachment.imageUrl != null) crossAxisAlignment: CrossAxisAlignment.stretch,
Container( children: [
clipBehavior: Clip.antiAliasWithSaveLayer, if (urlAttachment.imageUrl != null)
margin: const EdgeInsets.symmetric(horizontal: 8), Container(
decoration: BoxDecoration( clipBehavior: Clip.antiAliasWithSaveLayer,
borderRadius: BorderRadius.circular(8), margin: const EdgeInsets.symmetric(horizontal: 8),
), decoration: BoxDecoration(
child: Stack( borderRadius: BorderRadius.circular(8),
children: [ ),
CachedNetworkImage( child: Stack(
width: double.infinity, children: [
imageUrl: urlAttachment.imageUrl!, CachedNetworkImage(
fit: BoxFit.cover, width: double.infinity,
), imageUrl: urlAttachment.imageUrl!,
Positioned( fit: BoxFit.cover,
left: 0, ),
bottom: -1, Positioned(
child: Container( left: 0,
decoration: BoxDecoration( bottom: -1,
borderRadius: const BorderRadius.only( child: Container(
topRight: Radius.circular(16), decoration: BoxDecoration(
), borderRadius: const BorderRadius.only(
color: topRight: Radius.circular(16),
StreamChatTheme.of(context).colorTheme.blueAlice,
), ),
child: Padding( color: chatThemeData.colorTheme.blueAlice,
padding: const EdgeInsets.only( ),
top: 8, child: Padding(
left: 8, padding: const EdgeInsets.only(
right: 8, top: 8,
), left: 8,
child: Text( right: 8,
hostDisplayName, ),
style: StreamChatTheme.of(context) child: Text(
.textTheme hostDisplayName,
.bodyBold style: chatThemeData.textTheme.bodyBold.copyWith(
.copyWith( color: chatThemeData.colorTheme.accentBlue,
color: StreamChatTheme.of(context)
.colorTheme
.accentBlue,
),
), ),
), ),
), ),
), ),
], ),
),
),
Padding(
padding: textPadding,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (urlAttachment.title != null)
Text(
urlAttachment.title!.trim(),
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: StreamChatTheme.of(context)
.textTheme
.body
.copyWith(fontWeight: FontWeight.w700),
),
if (urlAttachment.text != null)
Text(
urlAttachment.text!,
style: StreamChatTheme.of(context)
.textTheme
.body
.copyWith(fontWeight: FontWeight.w400),
),
], ],
), ),
), ),
], Padding(
), padding: textPadding,
); child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (urlAttachment.title != null)
Text(
urlAttachment.title!.trim(),
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: chatThemeData.textTheme.body
.copyWith(fontWeight: FontWeight.w700),
),
if (urlAttachment.text != null)
Text(
urlAttachment.text!,
style: chatThemeData.textTheme.body
.copyWith(fontWeight: FontWeight.w400),
),
],
),
),
],
),
);
}
} }
@@ -1,7 +1,7 @@
import 'package:cached_network_image/cached_network_image.dart'; import 'package:cached_network_image/cached_network_image.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart';
import 'package:stream_chat_flutter/stream_chat_flutter.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart';
import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart';
/// Widget that displays a user avatar /// Widget that displays a user avatar
class UserAvatar extends StatelessWidget { class UserAvatar extends StatelessWidget {
@@ -95,8 +95,7 @@ class UserAvatar extends StatelessWidget {
child: Container( child: Container(
constraints: constraints ?? constraints: constraints ??
streamChatTheme.ownMessageTheme.avatarTheme?.constraints, streamChatTheme.ownMessageTheme.avatarTheme?.constraints,
color: selectionColor ?? color: selectionColor ?? streamChatTheme.colorTheme.accentBlue,
StreamChatTheme.of(context).colorTheme.accentBlue,
child: Padding( child: Padding(
padding: EdgeInsets.all(selectionThickness), padding: EdgeInsets.all(selectionThickness),
child: avatar, child: avatar,
@@ -48,47 +48,52 @@ class UserItem extends StatelessWidget {
final bool showLastOnline; final bool showLastOnline;
@override @override
Widget build(BuildContext context) => ListTile( Widget build(BuildContext context) {
onTap: () { final chatThemeData = StreamChatTheme.of(context);
if (onTap != null) { return ListTile(
onTap!(user); onTap: () {
if (onTap != null) {
onTap!(user);
}
},
onLongPress: () {
if (onLongPress != null) {
onLongPress!(user);
}
},
leading: UserAvatar(
user: user,
onTap: (user) {
if (onImageTap != null) {
onImageTap!(user);
} }
}, },
onLongPress: () { constraints: const BoxConstraints.tightFor(
if (onLongPress != null) { height: 40,
onLongPress!(user); width: 40,
}
},
leading: UserAvatar(
user: user,
onTap: (user) {
if (onImageTap != null) {
onImageTap!(user);
}
},
constraints: const BoxConstraints.tightFor(
height: 40,
width: 40,
),
), ),
trailing: selected ),
? StreamSvgIcon.checkSend( trailing: selected
color: StreamChatTheme.of(context).colorTheme.accentBlue, ? StreamSvgIcon.checkSend(
) color: chatThemeData.colorTheme.accentBlue,
: null, )
title: Text( : null,
user.name, title: Text(
style: StreamChatTheme.of(context).textTheme.bodyBold, user.name,
), style: chatThemeData.textTheme.bodyBold,
subtitle: showLastOnline ? _buildLastActive(context) : null, ),
); subtitle: showLastOnline ? _buildLastActive(context) : null,
);
}
Widget _buildLastActive(context) => Text( Widget _buildLastActive(context) {
user.online == true final chatTheme = StreamChatTheme.of(context);
? 'Online' return Text(
: 'Last online ${Jiffy(user.lastActive).fromNow()}', user.online == true
style: StreamChatTheme.of(context).textTheme.footnote.copyWith( ? 'Online'
color: : 'Last online ${Jiffy(user.lastActive).fromNow()}',
StreamChatTheme.of(context).colorTheme.black.withOpacity(.5)), style: chatTheme.textTheme.footnote
); .copyWith(color: chatTheme.colorTheme.black.withOpacity(.5)),
);
}
} }
@@ -295,21 +295,24 @@ class _UserListViewState extends State<UserListView>
if (i < items.length) { if (i < items.length) {
final item = items[i]; final item = items[i];
return item.when( return item.when(
headerItem: (header) => Container( headerItem: (header) {
key: ValueKey<String>('HEADER-$header'), final chatThemeData = StreamChatTheme.of(context);
color: StreamChatTheme.of(context).colorTheme.black.withOpacity(0.05), return Container(
child: Padding( key: ValueKey<String>('HEADER-$header'),
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 6), color: chatThemeData.colorTheme.black.withOpacity(0.05),
child: Text( child: Padding(
header, padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 6),
style: TextStyle( child: Text(
fontWeight: FontWeight.bold, header,
fontSize: 14.5, style: TextStyle(
color: StreamChatTheme.of(context).colorTheme.grey, fontWeight: FontWeight.bold,
fontSize: 14.5,
color: chatThemeData.colorTheme.grey,
),
), ),
), ),
), );
), },
userItem: (user) { userItem: (user) {
final selected = widget.selectedUsers?.contains(user) ?? false; final selected = widget.selectedUsers?.contains(user) ?? false;
return Container( return Container(
+117 -124
View File
@@ -1,9 +1,9 @@
import 'dart:math'; import 'dart:math';
import 'package:flutter/material.dart'; import 'package:flutter/material.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_core/stream_chat_flutter_core.dart';
import 'package:url_launcher/url_launcher.dart'; import 'package:url_launcher/url_launcher.dart';
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
/// Launch URL /// Launch URL
Future<void> launchURL(BuildContext context, String? url) async { Future<void> launchURL(BuildContext context, String? url) async {
@@ -27,89 +27,81 @@ Future<bool?> showConfirmationDialog(
Widget? icon, Widget? icon,
String? question, String? question,
String? cancelText, String? cancelText,
}) => }) {
showModalBottomSheet( final chatThemeData = StreamChatTheme.of(context);
backgroundColor: StreamChatTheme.of(context).colorTheme.white, return showModalBottomSheet(
context: context, backgroundColor: chatThemeData.colorTheme.white,
shape: const RoundedRectangleBorder( context: context,
borderRadius: BorderRadius.only( shape: const RoundedRectangleBorder(
topLeft: Radius.circular(16), borderRadius: BorderRadius.only(
topRight: Radius.circular(16), topLeft: Radius.circular(16),
)), topRight: Radius.circular(16),
builder: (context) { )),
final effect = StreamChatTheme.of(context).colorTheme.borderTop; builder: (context) {
return SafeArea( final effect = chatThemeData.colorTheme.borderTop;
child: Column( return SafeArea(
mainAxisSize: MainAxisSize.min, child: Column(
children: [ mainAxisSize: MainAxisSize.min,
const SizedBox(height: 26), children: [
if (icon != null) icon, const SizedBox(height: 26),
const SizedBox(height: 26), if (icon != null) icon,
const SizedBox(height: 26),
Text(
title,
style: chatThemeData.textTheme.headlineBold,
),
const SizedBox(height: 7),
if (question != null)
Text( Text(
title, question,
style: StreamChatTheme.of(context).textTheme.headlineBold, textAlign: TextAlign.center,
), ),
const SizedBox(height: 7), const SizedBox(height: 36),
if (question != null) Container(
Text( color: effect.color!.withOpacity(effect.alpha ?? 1),
question, height: 1,
textAlign: TextAlign.center, ),
), Row(
const SizedBox(height: 36), children: [
Container( if (cancelText != null)
color: effect.color!.withOpacity(effect.alpha ?? 1),
height: 1,
),
Row(
children: [
if (cancelText != null)
Flexible(
child: Container(
alignment: Alignment.center,
child: TextButton(
onPressed: () {
Navigator.of(context).pop(false);
},
child: Text(
cancelText,
style: StreamChatTheme.of(context)
.textTheme
.bodyBold
.copyWith(
color: StreamChatTheme.of(context)
.colorTheme
.black
.withOpacity(0.5)),
),
),
),
),
Flexible( Flexible(
child: Container( child: Container(
alignment: Alignment.center, alignment: Alignment.center,
child: TextButton( child: TextButton(
onPressed: () { onPressed: () {
Navigator.pop(context, true); Navigator.of(context).pop(false);
}, },
child: Text( child: Text(
okText, cancelText,
style: StreamChatTheme.of(context) style: chatThemeData.textTheme.bodyBold.copyWith(
.textTheme color: chatThemeData.colorTheme.black
.bodyBold .withOpacity(0.5)),
.copyWith(
color: StreamChatTheme.of(context)
.colorTheme
.accentRed),
), ),
), ),
), ),
), ),
], Flexible(
), child: Container(
], alignment: Alignment.center,
), child: TextButton(
); onPressed: () {
}); Navigator.pop(context, true);
},
child: Text(
okText,
style: chatThemeData.textTheme.bodyBold.copyWith(
color: chatThemeData.colorTheme.accentRed),
),
),
),
),
],
),
],
),
);
});
}
/// Shows info dialog /// Shows info dialog
Future<bool?> showInfoDialog( Future<bool?> showInfoDialog(
@@ -119,63 +111,64 @@ Future<bool?> showInfoDialog(
Widget? icon, Widget? icon,
String? details, String? details,
StreamChatThemeData? theme, StreamChatThemeData? theme,
}) => }) {
showModalBottomSheet( final chatThemeData = StreamChatTheme.of(context);
backgroundColor: theme?.colorTheme.white ?? return showModalBottomSheet(
StreamChatTheme.of(context).colorTheme.white, backgroundColor: theme?.colorTheme.white ?? chatThemeData.colorTheme.white,
context: context, context: context,
shape: const RoundedRectangleBorder( shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.only( borderRadius: BorderRadius.only(
topLeft: Radius.circular(16), topLeft: Radius.circular(16),
topRight: Radius.circular(16), topRight: Radius.circular(16),
)), )),
builder: (context) => SafeArea( builder: (context) => SafeArea(
child: Column( child: Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
const SizedBox( const SizedBox(
height: 26, height: 26,
), ),
if (icon != null) icon, if (icon != null) icon,
const SizedBox( const SizedBox(
height: 26, height: 26,
), ),
Text( Text(
title, title,
style: theme?.textTheme.headlineBold ?? style: theme?.textTheme.headlineBold ??
StreamChatTheme.of(context).textTheme.headlineBold, chatThemeData.textTheme.headlineBold,
), ),
const SizedBox( const SizedBox(
height: 7, height: 7,
), ),
if (details != null) Text(details), if (details != null) Text(details),
const SizedBox( const SizedBox(
height: 36, height: 36,
), ),
Container( Container(
color: theme?.colorTheme.black.withOpacity(.08) ?? color: theme?.colorTheme.black.withOpacity(.08) ??
StreamChatTheme.of(context).colorTheme.black.withOpacity(.08), chatThemeData.colorTheme.black.withOpacity(.08),
height: 1, height: 1,
), ),
Center( Center(
child: TextButton( child: TextButton(
onPressed: () { onPressed: () {
Navigator.of(context).pop(); Navigator.of(context).pop();
}, },
child: Text( child: Text(
okText, okText,
style: TextStyle( style: TextStyle(
color: theme?.colorTheme.black.withOpacity(0.5) ?? color: theme?.colorTheme.black.withOpacity(0.5) ??
StreamChatTheme.of(context).colorTheme.accentBlue, chatThemeData.colorTheme.accentBlue,
fontWeight: FontWeight.w400, fontWeight: FontWeight.w400,
),
), ),
), ),
), ),
], ),
), ],
), ),
); ),
);
}
/// Get random png with initials /// Get random png with initials
String getRandomPicUrl(User user) => String getRandomPicUrl(User user) =>