From 0a384f3c450736c342ca1271784fe1d6b624b84a Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Fri, 3 Sep 2021 16:34:47 +0530 Subject: [PATCH 01/70] changed over to the new overlays --- .../stream_chat_flutter/lib/overlays.dart | 140 +++++++++ .../lib/src/message_input.dart | 282 +++++++----------- 2 files changed, 255 insertions(+), 167 deletions(-) create mode 100644 packages/stream_chat_flutter/lib/overlays.dart diff --git a/packages/stream_chat_flutter/lib/overlays.dart b/packages/stream_chat_flutter/lib/overlays.dart new file mode 100644 index 00000000..01210fec --- /dev/null +++ b/packages/stream_chat_flutter/lib/overlays.dart @@ -0,0 +1,140 @@ +import 'package:flutter/material.dart'; + +class AnchoredOverlay extends StatelessWidget { + final bool showOverlay; + final Widget Function(BuildContext, Offset anchor) overlayBuilder; + final Widget child; + + AnchoredOverlay({ + this.showOverlay = false, + required this.overlayBuilder, + required this.child, + }); + + @override + Widget build(BuildContext context) { + return new Container( + child: new LayoutBuilder( + builder: (BuildContext context, BoxConstraints constraints) { + return new OverlayBuilder( + showOverlay: showOverlay, + overlayBuilder: (BuildContext overlayContext) { + RenderBox box = context.findRenderObject() as RenderBox; + final center = + box.size.center(box.localToGlobal(const Offset(0.0, 0.0))); + return overlayBuilder(overlayContext, center); + }, + child: child, + ); + }), + ); + } +} + +class OverlayBuilder extends StatefulWidget { + final bool showOverlay; + final Widget Function(BuildContext) overlayBuilder; + final Widget child; + + OverlayBuilder({ + this.showOverlay = false, + required this.overlayBuilder, + required this.child, + }); + + @override + _OverlayBuilderState createState() => new _OverlayBuilderState(); +} + +class _OverlayBuilderState extends State { + OverlayEntry? overlayEntry; + + @override + void initState() { + super.initState(); + + if (widget.showOverlay) { + WidgetsBinding.instance?.addPostFrameCallback((_) => showOverlay()); + } + } + + @override + void didUpdateWidget(OverlayBuilder oldWidget) { + super.didUpdateWidget(oldWidget); + WidgetsBinding.instance + ?.addPostFrameCallback((_) => syncWidgetAndOverlay()); + } + + @override + void reassemble() { + super.reassemble(); + WidgetsBinding.instance + ?.addPostFrameCallback((_) => syncWidgetAndOverlay()); + } + + @override + void dispose() { + if (isShowingOverlay()) { + hideOverlay(); + } + + super.dispose(); + } + + bool isShowingOverlay() => overlayEntry != null; + + void showOverlay() { + overlayEntry = new OverlayEntry( + builder: widget.overlayBuilder, + ); + if (overlayEntry != null) { + addToOverlay(overlayEntry!); + } + } + + void addToOverlay(OverlayEntry entry) async { + print('addToOverlay'); + Overlay.of(context)?.insert(entry); + } + + void hideOverlay() { + print('hideOverlay'); + overlayEntry?.remove(); + overlayEntry = null; + } + + void syncWidgetAndOverlay() { + if (isShowingOverlay() && !widget.showOverlay) { + hideOverlay(); + } else if (!isShowingOverlay() && widget.showOverlay) { + showOverlay(); + } + } + + @override + Widget build(BuildContext context) { + return widget.child; + } +} + +class CenterAbout extends StatelessWidget { + final Offset position; + final Widget child; + + CenterAbout({ + required this.position, + required this.child, + }); + + @override + Widget build(BuildContext context) { + return new Positioned( + top: position.dy, + left: position.dx, + child: new FractionalTranslation( + translation: const Offset(-0.5, -0.5), + child: child, + ), + ); + } +} \ No newline at end of file diff --git a/packages/stream_chat_flutter/lib/src/message_input.dart b/packages/stream_chat_flutter/lib/src/message_input.dart index 651a204c..c51cab9c 100644 --- a/packages/stream_chat_flutter/lib/src/message_input.dart +++ b/packages/stream_chat_flutter/lib/src/message_input.dart @@ -11,6 +11,7 @@ import 'package:flutter_svg/flutter_svg.dart'; import 'package:image_picker/image_picker.dart'; import 'package:photo_manager/photo_manager.dart'; import 'package:shimmer/shimmer.dart'; +import 'package:stream_chat_flutter/overlays.dart'; import 'package:stream_chat_flutter/src/emoji/emoji.dart'; import 'package:stream_chat_flutter/src/extension.dart'; import 'package:stream_chat_flutter/src/media_list_view.dart'; @@ -318,7 +319,7 @@ class MessageInputState extends State { late final FocusNode _focusNode; bool _inputEnabled = true; bool _commandEnabled = false; - OverlayEntry? _commandsOverlay, _mentionsOverlay, _emojiOverlay; + Widget? _commandsOverlay, _mentionsOverlay, _emojiOverlay; late Iterable _emojiNames; Command? _chosenCommand; @@ -469,7 +470,21 @@ class MessageInputState extends State { child: child, ); } - return child; + return AnchoredOverlay( + showOverlay: _commandsOverlay != null || + _emojiOverlay != null || + _mentionsOverlay != null, + overlayBuilder: (context, offset) { + if (_commandsOverlay != null) { + return _buildCommandsOverlayEntry(); + } else if (_emojiOverlay != null) { + return _buildEmojiOverlay(); + } else { + return _buildMentionsOverlayEntry(); + } + }, + child: child, + ); } Flex _buildTextField(BuildContext context) => Flex( @@ -811,11 +826,8 @@ class MessageInputState extends State { 1); }); - _commandsOverlay?.remove(); _commandsOverlay = null; - _mentionsOverlay?.remove(); _mentionsOverlay = null; - _emojiOverlay?.remove(); _emojiOverlay = null; _checkCommands(s.trim(), context); @@ -861,9 +873,9 @@ class MessageInputState extends State { } else { _emojiOverlay = _buildEmojiOverlay(); - if (_emojiOverlay != null) { - Overlay.of(context)!.insert(_emojiOverlay!); - } + // if (_emojiOverlay != null) { + // Overlay.of(context)!.insert(_emojiOverlay!); + // } } } } @@ -877,9 +889,6 @@ class MessageInputState extends State { .last .contains('@')) { _mentionsOverlay = _buildMentionsOverlayEntry(); - if (_mentionsOverlay != null) { - Overlay.of(context)!.insert(_mentionsOverlay!); - } } } @@ -899,18 +908,14 @@ class MessageInputState extends State { setState(() { _commandEnabled = true; }); - _commandsOverlay?.remove(); _commandsOverlay = null; } else { _commandsOverlay = _buildCommandsOverlayEntry(); - if (_commandsOverlay != null) { - Overlay.of(context)!.insert(_commandsOverlay!); - } } } } - OverlayEntry? _buildCommandsOverlayEntry() { + Widget _buildCommandsOverlayEntry() { final text = textEditingController.text.trimLeft(); final commands = StreamChannel.of(context) .channel @@ -920,15 +925,7 @@ class MessageInputState extends State { .toList() ?? []; - if (commands.isEmpty) { - return null; - } - - // ignore: cast_nullable_to_non_nullable - final renderBox = context.findRenderObject() as RenderBox; - final size = renderBox.size; - - final child = Padding( + return Padding( padding: const EdgeInsets.all(8), child: Card( elevation: 2, @@ -1020,22 +1017,6 @@ class MessageInputState extends State { ), ), ); - return OverlayEntry( - builder: (context) => Positioned( - bottom: size.height + MediaQuery.of(context).viewInsets.bottom, - left: 0, - right: 0, - child: TweenAnimationBuilder( - tween: Tween(begin: 0, end: 1), - duration: const Duration(milliseconds: 300), - curve: Curves.easeInOutExpo, - builder: (context, val, child) => Transform.scale( - scale: val, - child: child, - ), - child: child, - ), - )); } Widget _buildFilePickerSection() { @@ -1323,7 +1304,7 @@ class MessageInputState extends State { } } - OverlayEntry? _buildMentionsOverlayEntry() { + Widget _buildMentionsOverlayEntry() { final splits = textEditingController.text .substring(0, textEditingController.value.selection.start) .split('@'); @@ -1343,10 +1324,6 @@ class MessageInputState extends State { .toList() ?? []; - if (members.isEmpty) { - return null; - } - // ignore: cast_nullable_to_non_nullable final renderBox = context.findRenderObject() as RenderBox; final size = renderBox.size; @@ -1396,7 +1373,6 @@ class MessageInputState extends State { ), ); _debounce!.cancel(); - _mentionsOverlay?.remove(); _mentionsOverlay = null; }, child: widget.mentionsTileBuilder != null @@ -1414,133 +1390,119 @@ class MessageInputState extends State { ), ), ); - return OverlayEntry( - builder: (context) => Positioned( - bottom: size.height + MediaQuery.of(context).viewInsets.bottom, - left: 0, - right: 0, - child: TweenAnimationBuilder( - tween: Tween(begin: 0, end: 1), - duration: const Duration(milliseconds: 300), - curve: Curves.easeInOutExpo, - builder: (context, val, child) => Transform.scale( - scale: val, - child: child, - ), + return Positioned( + bottom: size.height + MediaQuery.of(context).viewInsets.bottom, + left: 0, + right: 0, + child: TweenAnimationBuilder( + tween: Tween(begin: 0, end: 1), + duration: const Duration(milliseconds: 300), + curve: Curves.easeInOutExpo, + builder: (context, val, child) => Transform.scale( + scale: val, child: child, ), + child: child, ), ); } - OverlayEntry? _buildEmojiOverlay() { + Widget _buildEmojiOverlay() { final splits = textEditingController.text .substring(0, textEditingController.value.selection.start) .split(':'); final query = splits.last.toLowerCase(); - if (query.isEmpty) { - return null; - } - final emojis = _emojiNames .where((e) => e.contains(query)) .map(Emoji.byName) .where((e) => e != null); - if (emojis.isEmpty) { - return null; - } - // ignore: cast_nullable_to_non_nullable final renderBox = context.findRenderObject() as RenderBox; final size = renderBox.size; - return OverlayEntry( - builder: (context) => Positioned( - bottom: size.height + MediaQuery.of(context).viewInsets.bottom, - left: 0, - right: 0, - child: Card( - margin: const EdgeInsets.all(8), - elevation: 2, - color: _streamChatTheme.colorTheme.barsBg, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(8), - ), - clipBehavior: Clip.hardEdge, - child: Container( - constraints: BoxConstraints.loose(const Size.fromHeight(200)), - decoration: BoxDecoration( - boxShadow: const [ - BoxShadow( - spreadRadius: -8, - blurRadius: 5, - offset: Offset(0, -4), - ), - ], - color: _streamChatTheme.colorTheme.barsBg, - ), - child: ListView.builder( - padding: const EdgeInsets.all(0), - shrinkWrap: true, - itemCount: emojis.length + 1, - itemBuilder: (context, i) { - if (i == 0) { - return Padding( - padding: const EdgeInsets.only(left: 8, top: 8), - child: Row( - children: [ - Padding( - padding: - const EdgeInsets.symmetric(horizontal: 8), - child: StreamSvgIcon.smile( - color: _streamChatTheme - .colorTheme.accentPrimary, - ), - ), - Flexible( - child: Text( - context.translations.emojiMatchingQueryText( - query, - ), - style: TextStyle( - color: _streamChatTheme - .colorTheme.textHighEmphasis - .withOpacity(.5), - ), - ), - ) - ], - ), - ); - } - - final emoji = emojis.elementAt(i - 1)!; - final themeData = Theme.of(context); - return ListTile( - title: SubstringHighlight( - text: - // ignore: lines_longer_than_80_chars - "${emoji.char} ${emoji.name!.replaceAll('_', ' ')}", - term: query, - textStyleHighlight: - themeData.textTheme.headline6!.copyWith( - fontSize: 14.5, - fontWeight: FontWeight.bold, - ), - textStyle: themeData.textTheme.headline6!.copyWith( - fontSize: 14.5, - ), - ), - onTap: () { - _chooseEmoji(splits, emoji); - }, - ); - }), - ), + return Positioned( + bottom: size.height + MediaQuery.of(context).viewInsets.bottom, + left: 0, + right: 0, + child: Card( + margin: const EdgeInsets.all(8), + elevation: 2, + color: _streamChatTheme.colorTheme.barsBg, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(8), + ), + clipBehavior: Clip.hardEdge, + child: Container( + constraints: BoxConstraints.loose(const Size.fromHeight(200)), + decoration: BoxDecoration( + boxShadow: const [ + BoxShadow( + spreadRadius: -8, + blurRadius: 5, + offset: Offset(0, -4), ), - )); + ], + color: _streamChatTheme.colorTheme.barsBg, + ), + child: ListView.builder( + padding: const EdgeInsets.all(0), + shrinkWrap: true, + itemCount: emojis.length + 1, + itemBuilder: (context, i) { + if (i == 0) { + return Padding( + padding: const EdgeInsets.only(left: 8, top: 8), + child: Row( + children: [ + Padding( + padding: const EdgeInsets.symmetric(horizontal: 8), + child: StreamSvgIcon.smile( + color: _streamChatTheme.colorTheme.accentPrimary, + ), + ), + Flexible( + child: Text( + context.translations.emojiMatchingQueryText( + query, + ), + style: TextStyle( + color: _streamChatTheme.colorTheme.textHighEmphasis + .withOpacity(.5), + ), + ), + ) + ], + ), + ); + } + + final emoji = emojis.elementAt(i - 1)!; + final themeData = Theme.of(context); + return ListTile( + title: SubstringHighlight( + text: + // ignore: lines_longer_than_80_chars + "${emoji.char} ${emoji.name!.replaceAll('_', ' ')}", + term: query, + textStyleHighlight: themeData.textTheme.headline6!.copyWith( + fontSize: 14.5, + fontWeight: FontWeight.bold, + ), + textStyle: themeData.textTheme.headline6!.copyWith( + fontSize: 14.5, + ), + ), + onTap: () { + _chooseEmoji(splits, emoji); + }, + ); + }, + ), + ), + ), + ); } void _chooseEmoji(List splits, Emoji emoji) { @@ -1555,7 +1517,6 @@ class MessageInputState extends State { ), ); - _emojiOverlay?.remove(); _emojiOverlay = null; } @@ -1565,7 +1526,6 @@ class MessageInputState extends State { _chosenCommand = c; _commandEnabled = true; }); - _commandsOverlay?.remove(); _commandsOverlay = null; } @@ -1778,13 +1738,9 @@ class MessageInputState extends State { if (_commandsOverlay == null) { setState(() { _commandsOverlay = _buildCommandsOverlayEntry(); - if (_commandsOverlay != null) { - Overlay.of(context)!.insert(_commandsOverlay!); - } }); } else { setState(() { - _commandsOverlay?.remove(); _commandsOverlay = null; }); } @@ -1809,11 +1765,8 @@ class MessageInputState extends State { ), splashRadius: 24, onPressed: () async { - _emojiOverlay?.remove(); _emojiOverlay = null; - _commandsOverlay?.remove(); _commandsOverlay = null; - _mentionsOverlay?.remove(); _mentionsOverlay = null; if (_openFilePickerSection) { @@ -2100,9 +2053,7 @@ class MessageInputState extends State { _commandEnabled = false; }); - _commandsOverlay?.remove(); _commandsOverlay = null; - _mentionsOverlay?.remove(); _mentionsOverlay = null; Message message; @@ -2246,9 +2197,6 @@ class MessageInputState extends State { @override void dispose() { - _commandsOverlay?.remove(); - _emojiOverlay?.remove(); - _mentionsOverlay?.remove(); _keyboardListener?.cancel(); textEditingController.dispose(); _stopSlowMode(); From ae2e8b740fe76b7ea5615ad60212d84420e4b9ba Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Mon, 6 Sep 2021 16:46:15 +0530 Subject: [PATCH 02/70] corrected overlay positions --- .../lib/src/message_input.dart | 182 +++++++++--------- .../lib/{ => src}/overlays.dart | 39 ++-- 2 files changed, 112 insertions(+), 109 deletions(-) rename packages/stream_chat_flutter/lib/{ => src}/overlays.dart (79%) diff --git a/packages/stream_chat_flutter/lib/src/message_input.dart b/packages/stream_chat_flutter/lib/src/message_input.dart index c51cab9c..cf118b7f 100644 --- a/packages/stream_chat_flutter/lib/src/message_input.dart +++ b/packages/stream_chat_flutter/lib/src/message_input.dart @@ -11,7 +11,7 @@ import 'package:flutter_svg/flutter_svg.dart'; import 'package:image_picker/image_picker.dart'; import 'package:photo_manager/photo_manager.dart'; import 'package:shimmer/shimmer.dart'; -import 'package:stream_chat_flutter/overlays.dart'; +import 'package:stream_chat_flutter/src/overlays.dart'; import 'package:stream_chat_flutter/src/emoji/emoji.dart'; import 'package:stream_chat_flutter/src/extension.dart'; import 'package:stream_chat_flutter/src/media_list_view.dart'; @@ -475,13 +475,20 @@ class MessageInputState extends State { _emojiOverlay != null || _mentionsOverlay != null, overlayBuilder: (context, offset) { + late Widget child; + if (_commandsOverlay != null) { - return _buildCommandsOverlayEntry(); + child = _buildCommandsOverlayEntry(); } else if (_emojiOverlay != null) { - return _buildEmojiOverlay(); + child = _buildEmojiOverlay(); } else { - return _buildMentionsOverlayEntry(); + child = _buildMentionsOverlayEntry(); } + + return CenterAbout( + position: Offset(offset.dx, offset.dy), + child: child, + ); }, child: child, ); @@ -924,9 +931,10 @@ class MessageInputState extends State { .where((c) => c.name.contains(text.replaceFirst('/', ''))) .toList() ?? []; + final size = MediaQuery.of(context).size; return Padding( - padding: const EdgeInsets.all(8), + padding: const EdgeInsets.all(4), child: Card( elevation: 2, shape: RoundedRectangleBorder( @@ -935,7 +943,7 @@ class MessageInputState extends State { color: _streamChatTheme.colorTheme.barsBg, clipBehavior: Clip.hardEdge, child: Container( - constraints: BoxConstraints.loose(const Size.fromHeight(400)), + constraints: BoxConstraints.loose(Size(size.width - 16, 400)), decoration: BoxDecoration( color: _streamChatTheme.colorTheme.barsBg, borderRadius: BorderRadius.circular(8)), @@ -1336,7 +1344,7 @@ class MessageInputState extends State { ), clipBehavior: Clip.hardEdge, child: Container( - constraints: BoxConstraints.loose(const Size.fromHeight(240)), + constraints: BoxConstraints.loose(Size(size.width - 16, 200)), decoration: BoxDecoration( color: _streamChatTheme.colorTheme.barsBg, ), @@ -1390,20 +1398,15 @@ class MessageInputState extends State { ), ), ); - return Positioned( - bottom: size.height + MediaQuery.of(context).viewInsets.bottom, - left: 0, - right: 0, - child: TweenAnimationBuilder( - tween: Tween(begin: 0, end: 1), - duration: const Duration(milliseconds: 300), - curve: Curves.easeInOutExpo, - builder: (context, val, child) => Transform.scale( - scale: val, - child: child, - ), + return TweenAnimationBuilder( + tween: Tween(begin: 0, end: 1), + duration: const Duration(milliseconds: 300), + curve: Curves.easeInOutExpo, + builder: (context, val, child) => Transform.scale( + scale: val, child: child, ), + child: child, ); } @@ -1422,84 +1425,79 @@ class MessageInputState extends State { final renderBox = context.findRenderObject() as RenderBox; final size = renderBox.size; - return Positioned( - bottom: size.height + MediaQuery.of(context).viewInsets.bottom, - left: 0, - right: 0, - child: Card( - margin: const EdgeInsets.all(8), - elevation: 2, - color: _streamChatTheme.colorTheme.barsBg, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(8), + return Card( + margin: const EdgeInsets.all(8), + elevation: 2, + color: _streamChatTheme.colorTheme.barsBg, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(8), + ), + clipBehavior: Clip.hardEdge, + child: Container( + constraints: BoxConstraints.loose(Size(size.width - 16, 200)), + decoration: BoxDecoration( + boxShadow: const [ + BoxShadow( + spreadRadius: -8, + blurRadius: 5, + offset: Offset(0, -4), + ), + ], + color: _streamChatTheme.colorTheme.barsBg, ), - clipBehavior: Clip.hardEdge, - child: Container( - constraints: BoxConstraints.loose(const Size.fromHeight(200)), - decoration: BoxDecoration( - boxShadow: const [ - BoxShadow( - spreadRadius: -8, - blurRadius: 5, - offset: Offset(0, -4), - ), - ], - color: _streamChatTheme.colorTheme.barsBg, - ), - child: ListView.builder( - padding: const EdgeInsets.all(0), - shrinkWrap: true, - itemCount: emojis.length + 1, - itemBuilder: (context, i) { - if (i == 0) { - return Padding( - padding: const EdgeInsets.only(left: 8, top: 8), - child: Row( - children: [ - Padding( - padding: const EdgeInsets.symmetric(horizontal: 8), - child: StreamSvgIcon.smile( - color: _streamChatTheme.colorTheme.accentPrimary, + child: ListView.builder( + padding: const EdgeInsets.all(0), + shrinkWrap: true, + itemCount: emojis.length + 1, + itemBuilder: (context, i) { + if (i == 0) { + return Padding( + padding: const EdgeInsets.only(left: 8, top: 8), + child: Row( + children: [ + Padding( + padding: const EdgeInsets.symmetric(horizontal: 8), + child: StreamSvgIcon.smile( + color: _streamChatTheme.colorTheme.accentPrimary, + ), + ), + Flexible( + child: Text( + context.translations.emojiMatchingQueryText( + query, + ), + style: TextStyle( + color: _streamChatTheme.colorTheme.textHighEmphasis + .withOpacity(.5), ), ), - Flexible( - child: Text( - context.translations.emojiMatchingQueryText( - query, - ), - style: TextStyle( - color: _streamChatTheme.colorTheme.textHighEmphasis - .withOpacity(.5), - ), - ), - ) - ], - ), - ); - } - - final emoji = emojis.elementAt(i - 1)!; - final themeData = Theme.of(context); - return ListTile( - title: SubstringHighlight( - text: - // ignore: lines_longer_than_80_chars - "${emoji.char} ${emoji.name!.replaceAll('_', ' ')}", - term: query, - textStyleHighlight: themeData.textTheme.headline6!.copyWith( - fontSize: 14.5, - fontWeight: FontWeight.bold, - ), - textStyle: themeData.textTheme.headline6!.copyWith( - fontSize: 14.5, - ), + ) + ], ), - onTap: () { - _chooseEmoji(splits, emoji); - }, ); - }, - ), + } + + final emoji = emojis.elementAt(i - 1)!; + final themeData = Theme.of(context); + return ListTile( + title: SubstringHighlight( + text: + // ignore: lines_longer_than_80_chars + "${emoji.char} ${emoji.name!.replaceAll('_', ' ')}", + term: query, + textStyleHighlight: themeData.textTheme.headline6!.copyWith( + fontSize: 14.5, + fontWeight: FontWeight.bold, + ), + textStyle: themeData.textTheme.headline6!.copyWith( + fontSize: 14.5, + ), + ), + onTap: () { + _chooseEmoji(splits, emoji); + }, + ); + }, ), ), ); diff --git a/packages/stream_chat_flutter/lib/overlays.dart b/packages/stream_chat_flutter/lib/src/overlays.dart similarity index 79% rename from packages/stream_chat_flutter/lib/overlays.dart rename to packages/stream_chat_flutter/lib/src/overlays.dart index 01210fec..65956cb9 100644 --- a/packages/stream_chat_flutter/lib/overlays.dart +++ b/packages/stream_chat_flutter/lib/src/overlays.dart @@ -16,17 +16,17 @@ class AnchoredOverlay extends StatelessWidget { return new Container( child: new LayoutBuilder( builder: (BuildContext context, BoxConstraints constraints) { - return new OverlayBuilder( - showOverlay: showOverlay, - overlayBuilder: (BuildContext overlayContext) { - RenderBox box = context.findRenderObject() as RenderBox; - final center = + return new OverlayBuilder( + showOverlay: showOverlay, + overlayBuilder: (BuildContext overlayContext) { + RenderBox box = context.findRenderObject() as RenderBox; + final center = box.size.center(box.localToGlobal(const Offset(0.0, 0.0))); - return overlayBuilder(overlayContext, center); - }, - child: child, - ); - }), + return overlayBuilder(overlayContext, center); + }, + child: child, + ); + }), ); } } @@ -84,7 +84,7 @@ class _OverlayBuilderState extends State { bool isShowingOverlay() => overlayEntry != null; void showOverlay() { - overlayEntry = new OverlayEntry( + overlayEntry = OverlayEntry( builder: widget.overlayBuilder, ); if (overlayEntry != null) { @@ -128,13 +128,18 @@ class CenterAbout extends StatelessWidget { @override Widget build(BuildContext context) { - return new Positioned( - top: position.dy, + var size = MediaQuery.of(context).size; + + return Positioned( + bottom: size.height - position.dy, left: position.dx, - child: new FractionalTranslation( - translation: const Offset(-0.5, -0.5), - child: child, + child: FractionalTranslation( + translation: const Offset(-0.5, 0.0), + child: Padding( + padding: const EdgeInsets.only(bottom: 32.0), + child: child, + ), ), ); } -} \ No newline at end of file +} From 0af7b1a0e61be679bb63875858ce1dd27990c097 Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Tue, 7 Sep 2021 14:31:48 +0530 Subject: [PATCH 03/70] added new overlays and docs --- .../lib/src/message_input.dart | 23 +++- .../stream_chat_flutter/lib/src/overlays.dart | 103 +++++++++++------- 2 files changed, 80 insertions(+), 46 deletions(-) diff --git a/packages/stream_chat_flutter/lib/src/message_input.dart b/packages/stream_chat_flutter/lib/src/message_input.dart index cf118b7f..60fe49a1 100644 --- a/packages/stream_chat_flutter/lib/src/message_input.dart +++ b/packages/stream_chat_flutter/lib/src/message_input.dart @@ -475,7 +475,7 @@ class MessageInputState extends State { _emojiOverlay != null || _mentionsOverlay != null, overlayBuilder: (context, offset) { - late Widget child; + late Widget? child; if (_commandsOverlay != null) { child = _buildCommandsOverlayEntry(); @@ -487,7 +487,7 @@ class MessageInputState extends State { return CenterAbout( position: Offset(offset.dx, offset.dy), - child: child, + child: child ?? const SizedBox(), ); }, child: child, @@ -922,7 +922,7 @@ class MessageInputState extends State { } } - Widget _buildCommandsOverlayEntry() { + Widget? _buildCommandsOverlayEntry() { final text = textEditingController.text.trimLeft(); final commands = StreamChannel.of(context) .channel @@ -931,6 +931,11 @@ class MessageInputState extends State { .where((c) => c.name.contains(text.replaceFirst('/', ''))) .toList() ?? []; + + if (commands.isEmpty) { + return null; + } + final size = MediaQuery.of(context).size; return Padding( @@ -1312,7 +1317,7 @@ class MessageInputState extends State { } } - Widget _buildMentionsOverlayEntry() { + Widget? _buildMentionsOverlayEntry() { final splits = textEditingController.text .substring(0, textEditingController.value.selection.start) .split('@'); @@ -1332,6 +1337,10 @@ class MessageInputState extends State { .toList() ?? []; + if (members.isEmpty) { + return null; + } + // ignore: cast_nullable_to_non_nullable final renderBox = context.findRenderObject() as RenderBox; final size = renderBox.size; @@ -1410,7 +1419,7 @@ class MessageInputState extends State { ); } - Widget _buildEmojiOverlay() { + Widget? _buildEmojiOverlay() { final splits = textEditingController.text .substring(0, textEditingController.value.selection.start) .split(':'); @@ -1421,6 +1430,10 @@ class MessageInputState extends State { .map(Emoji.byName) .where((e) => e != null); + if (emojis.isEmpty) { + return null; + } + // ignore: cast_nullable_to_non_nullable final renderBox = context.findRenderObject() as RenderBox; final size = renderBox.size; diff --git a/packages/stream_chat_flutter/lib/src/overlays.dart b/packages/stream_chat_flutter/lib/src/overlays.dart index 65956cb9..ce6a9a4e 100644 --- a/packages/stream_chat_flutter/lib/src/overlays.dart +++ b/packages/stream_chat_flutter/lib/src/overlays.dart @@ -1,49 +1,66 @@ import 'package:flutter/material.dart'; +/// Class to create an overlay anchored to a child widget class AnchoredOverlay extends StatelessWidget { - final bool showOverlay; - final Widget Function(BuildContext, Offset anchor) overlayBuilder; - final Widget child; - - AnchoredOverlay({ + /// Constructor for creating an [AnchoredOverlay] + const AnchoredOverlay({ + Key? key, this.showOverlay = false, required this.overlayBuilder, required this.child, - }); + }) : super(key: key); + + /// Show or hide overlay + final bool showOverlay; + + /// Builder for creating overlay widget + final Widget Function(BuildContext, Offset anchor) overlayBuilder; + + /// Child to which overlay is anchored + final Widget child; @override - Widget build(BuildContext context) { - return new Container( - child: new LayoutBuilder( - builder: (BuildContext context, BoxConstraints constraints) { - return new OverlayBuilder( - showOverlay: showOverlay, - overlayBuilder: (BuildContext overlayContext) { - RenderBox box = context.findRenderObject() as RenderBox; - final center = - box.size.center(box.localToGlobal(const Offset(0.0, 0.0))); - return overlayBuilder(overlayContext, center); - }, - child: child, - ); - }), - ); - } + Widget build(BuildContext context) => SizedBox( + child: LayoutBuilder( + builder: (BuildContext context, BoxConstraints constraints) => + OverlayBuilder( + showOverlay: showOverlay, + overlayBuilder: (BuildContext overlayContext) { + final box = context.findRenderObject() as RenderBox?; + if (box != null) { + final center = box.size + .center(box.localToGlobal(const Offset(0, 0))); + return overlayBuilder(overlayContext, center); + } else { + return const SizedBox(); + } + }, + child: child, + )), + ); } +/// Class for creating a declarative-ish overlay class OverlayBuilder extends StatefulWidget { - final bool showOverlay; - final Widget Function(BuildContext) overlayBuilder; - final Widget child; - - OverlayBuilder({ + /// Constructor for creating an [OverlayBuilder] + const OverlayBuilder({ + Key? key, this.showOverlay = false, required this.overlayBuilder, required this.child, - }); + }) : super(key: key); + + /// Show or hide overlay + final bool showOverlay; + + /// Builder for creating overlay widget + final Widget Function(BuildContext) overlayBuilder; + + /// Child to which overlay is anchored + final Widget child; @override - _OverlayBuilderState createState() => new _OverlayBuilderState(); + _OverlayBuilderState createState() => _OverlayBuilderState(); } class _OverlayBuilderState extends State { @@ -112,31 +129,35 @@ class _OverlayBuilderState extends State { } @override - Widget build(BuildContext context) { - return widget.child; - } + Widget build(BuildContext context) => widget.child; } +/// Class to position Overlay class CenterAbout extends StatelessWidget { - final Offset position; - final Widget child; - - CenterAbout({ + /// Constructor for creating an [CenterAbout] + const CenterAbout({ + Key? key, required this.position, required this.child, - }); + }) : super(key: key); + + /// Position of child + final Offset position; + + /// Child widget to which overlay is attached + final Widget child; @override Widget build(BuildContext context) { - var size = MediaQuery.of(context).size; + final size = MediaQuery.of(context).size; return Positioned( bottom: size.height - position.dy, left: position.dx, child: FractionalTranslation( - translation: const Offset(-0.5, 0.0), + translation: const Offset(-0.5, 0), child: Padding( - padding: const EdgeInsets.only(bottom: 32.0), + padding: const EdgeInsets.only(bottom: 32), child: child, ), ), From 14a3e12afcb3af02f70470ce2f21ce6bd1284f0b Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Tue, 7 Sep 2021 14:33:06 +0530 Subject: [PATCH 04/70] added changelog --- packages/stream_chat_flutter/CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/stream_chat_flutter/CHANGELOG.md b/packages/stream_chat_flutter/CHANGELOG.md index 02e2b214..0ee6f5d5 100644 --- a/packages/stream_chat_flutter/CHANGELOG.md +++ b/packages/stream_chat_flutter/CHANGELOG.md @@ -58,6 +58,7 @@ breakdown: - Fixed date dividers position/alignment in non reversed `MessageListView`. - Fixed `MessageListView` not opening to the right initialMessage if `StreamChannel.initialMessageId` is set. - Fixed null check errors when accessing `message.text` in `MessageWidget` and `MessageListView`; this occurred when sending a message with no text. +- MessageInput can now be placed in any position on the screen. ## 2.1.2 From 1170a3cb484fe9c3f5d103abe0d208ce66051f97 Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Tue, 7 Sep 2021 14:57:55 +0530 Subject: [PATCH 05/70] removed comment --- packages/stream_chat_flutter/lib/src/message_input.dart | 4 ---- 1 file changed, 4 deletions(-) diff --git a/packages/stream_chat_flutter/lib/src/message_input.dart b/packages/stream_chat_flutter/lib/src/message_input.dart index 059075ff..48bcf19c 100644 --- a/packages/stream_chat_flutter/lib/src/message_input.dart +++ b/packages/stream_chat_flutter/lib/src/message_input.dart @@ -882,10 +882,6 @@ class MessageInputState extends State { _chooseEmoji(splits.sublist(0, splits.length - 1), emoji); } else { _emojiOverlay = _buildEmojiOverlay(); - - // if (_emojiOverlay != null) { - // Overlay.of(context)!.insert(_emojiOverlay!); - // } } } } From b3fa3aea454d2c03f4e85ebc72f3331d14eee8d6 Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Wed, 8 Sep 2021 16:04:27 +0530 Subject: [PATCH 06/70] added new overlays and replaced impls in message_input.dart --- .../lib/src/commands_overlay.dart | 204 +++++++++ .../lib/src/emoji_overlay.dart | 115 +++++ .../lib/src/mentions_overlay.dart | 104 +++++ .../lib/src/message_input.dart | 403 ++---------------- 4 files changed, 457 insertions(+), 369 deletions(-) create mode 100644 packages/stream_chat_flutter/lib/src/commands_overlay.dart create mode 100644 packages/stream_chat_flutter/lib/src/emoji_overlay.dart create mode 100644 packages/stream_chat_flutter/lib/src/mentions_overlay.dart diff --git a/packages/stream_chat_flutter/lib/src/commands_overlay.dart b/packages/stream_chat_flutter/lib/src/commands_overlay.dart new file mode 100644 index 00000000..8e236c35 --- /dev/null +++ b/packages/stream_chat_flutter/lib/src/commands_overlay.dart @@ -0,0 +1,204 @@ +import 'package:flutter/material.dart'; +import 'package:stream_chat_flutter/stream_chat_flutter.dart'; +import 'package:stream_chat_flutter/src/extension.dart'; + +class CommandsOverlay extends StatelessWidget { + final BuildContext context; + final String text; + final ValueChanged onCommandResult; + + const CommandsOverlay( + this.context, { + required this.text, + required this.onCommandResult, + Key? key, + }) : super(key: key); + + @override + Widget build(BuildContext otherContext) { + var _streamChatTheme = StreamChatTheme.of(context); + final commands = StreamChannel.of(context) + .channel + .config + ?.commands + .where((c) => c.name.contains(text.replaceFirst('/', ''))) + .toList() ?? + []; + + if (commands.isEmpty) { + return const SizedBox(); + } + + final size = MediaQuery.of(context).size; + + return Padding( + padding: const EdgeInsets.all(4), + child: Card( + elevation: 2, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(8), + ), + color: _streamChatTheme.colorTheme.barsBg, + clipBehavior: Clip.hardEdge, + child: Container( + constraints: BoxConstraints.loose(Size(size.width - 16, 400)), + decoration: BoxDecoration( + color: _streamChatTheme.colorTheme.barsBg, + borderRadius: BorderRadius.circular(8)), + child: ListView( + padding: const EdgeInsets.all(0), + shrinkWrap: true, + children: [ + if (commands.isNotEmpty) + Padding( + padding: const EdgeInsets.only(top: 8), + child: Row( + children: [ + Padding( + padding: const EdgeInsets.symmetric( + horizontal: 8, + ), + child: StreamSvgIcon.lightning( + color: _streamChatTheme.colorTheme.accentPrimary, + ), + ), + Text( + context.translations.instantCommandsLabel, + style: TextStyle( + color: _streamChatTheme.colorTheme.textHighEmphasis + .withOpacity(.5), + ), + ) + ], + ), + ), + const SizedBox( + height: 10, + ), + ...commands + .map( + (c) => InkWell( + onTap: () { + onCommandResult(c); + }, + child: SizedBox( + height: 40, + child: Row( + children: [ + const SizedBox( + width: 16, + ), + _buildCommandIcon(_streamChatTheme, c.name), + const SizedBox( + width: 8, + ), + Text.rich( + TextSpan( + text: c.name.capitalize(), + style: const TextStyle( + fontWeight: FontWeight.bold), + children: [ + TextSpan( + text: ' /${c.name} ${c.args}', + style: _streamChatTheme.textTheme.body + .copyWith( + // ignore: lines_longer_than_80_chars + color: _streamChatTheme + // ignore: lines_longer_than_80_chars + .colorTheme + .textLowEmphasis, + ), + ), + ], + ), + ), + ], + ), + ), + ), + ) + .toList(), + ], + ), + ), + ), + ); + } + + Widget _buildCommandIcon( + StreamChatThemeData _streamChatTheme, String iconType) { + switch (iconType) { + case 'giphy': + return CircleAvatar( + radius: 12, + child: StreamSvgIcon.giphyIcon( + size: 24, + ), + ); + case 'ban': + return CircleAvatar( + backgroundColor: _streamChatTheme.colorTheme.accentPrimary, + radius: 12, + child: StreamSvgIcon.iconUserDelete( + size: 16, + color: Colors.white, + ), + ); + case 'flag': + return CircleAvatar( + backgroundColor: _streamChatTheme.colorTheme.accentPrimary, + radius: 12, + child: StreamSvgIcon.flag( + size: 14, + color: Colors.white, + ), + ); + case 'imgur': + return CircleAvatar( + backgroundColor: _streamChatTheme.colorTheme.accentPrimary, + radius: 12, + child: ClipOval( + child: StreamSvgIcon.imgur( + size: 24, + ), + ), + ); + case 'mute': + return CircleAvatar( + backgroundColor: _streamChatTheme.colorTheme.accentPrimary, + radius: 12, + child: StreamSvgIcon.mute( + size: 16, + color: Colors.white, + ), + ); + case 'unban': + return CircleAvatar( + backgroundColor: _streamChatTheme.colorTheme.accentPrimary, + radius: 12, + child: StreamSvgIcon.userAdd( + size: 16, + color: Colors.white, + ), + ); + case 'unmute': + return CircleAvatar( + backgroundColor: _streamChatTheme.colorTheme.accentPrimary, + radius: 12, + child: StreamSvgIcon.volumeUp( + size: 16, + color: Colors.white, + ), + ); + default: + return CircleAvatar( + backgroundColor: _streamChatTheme.colorTheme.accentPrimary, + radius: 12, + child: StreamSvgIcon.lightning( + size: 16, + color: Colors.white, + ), + ); + } + } +} diff --git a/packages/stream_chat_flutter/lib/src/emoji_overlay.dart b/packages/stream_chat_flutter/lib/src/emoji_overlay.dart new file mode 100644 index 00000000..292e8230 --- /dev/null +++ b/packages/stream_chat_flutter/lib/src/emoji_overlay.dart @@ -0,0 +1,115 @@ +import 'package:flutter/material.dart'; +import 'package:stream_chat_flutter/stream_chat_flutter.dart'; +import 'package:stream_chat_flutter/src/emoji/emoji.dart'; +import 'package:stream_chat_flutter/src/extension.dart'; +import 'package:substring_highlight/substring_highlight.dart'; + +class EmojiOverlay extends StatelessWidget { + final BuildContext context; + final String query; + final ValueChanged onEmojiResult; + + const EmojiOverlay( + this.context, { + required this.query, + required this.onEmojiResult, + Key? key, + }) : super(key: key); + + @override + Widget build(BuildContext otherContext) { + var _streamChatTheme = StreamChatTheme.of(context); + var _emojiNames = + Emoji.all().where((it) => it.name != null).map((e) => e.name!); + + final emojis = _emojiNames + .where((e) => e.contains(query)) + .map(Emoji.byName) + .where((e) => e != null); + + if (emojis.isEmpty) { + return const SizedBox(); + } + + // ignore: cast_nullable_to_non_nullable + final renderBox = context.findRenderObject() as RenderBox; + final size = renderBox.size; + + return Card( + margin: const EdgeInsets.all(8), + elevation: 2, + color: _streamChatTheme.colorTheme.barsBg, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(8), + ), + clipBehavior: Clip.hardEdge, + child: Container( + constraints: BoxConstraints.loose(Size(size.width - 16, 200)), + decoration: BoxDecoration( + boxShadow: const [ + BoxShadow( + spreadRadius: -8, + blurRadius: 5, + offset: Offset(0, -4), + ), + ], + color: _streamChatTheme.colorTheme.barsBg, + ), + child: ListView.builder( + padding: const EdgeInsets.all(0), + shrinkWrap: true, + itemCount: emojis.length + 1, + itemBuilder: (context, i) { + if (i == 0) { + return Padding( + padding: const EdgeInsets.only(left: 8, top: 8), + child: Row( + children: [ + Padding( + padding: const EdgeInsets.symmetric(horizontal: 8), + child: StreamSvgIcon.smile( + color: _streamChatTheme.colorTheme.accentPrimary, + ), + ), + Flexible( + child: Text( + context.translations.emojiMatchingQueryText( + query, + ), + style: TextStyle( + color: _streamChatTheme.colorTheme.textHighEmphasis + .withOpacity(.5), + ), + ), + ) + ], + ), + ); + } + + final emoji = emojis.elementAt(i - 1)!; + final themeData = Theme.of(context); + return ListTile( + title: SubstringHighlight( + text: + // ignore: lines_longer_than_80_chars + "${emoji.char} ${emoji.name!.replaceAll('_', ' ')}", + term: query, + textStyleHighlight: themeData.textTheme.headline6!.copyWith( + fontSize: 14.5, + fontWeight: FontWeight.bold, + ), + textStyle: themeData.textTheme.headline6!.copyWith( + fontSize: 14.5, + ), + ), + onTap: () { + onEmojiResult(emoji); + }, + ); + }, + ), + ), + ); + } +} diff --git a/packages/stream_chat_flutter/lib/src/mentions_overlay.dart b/packages/stream_chat_flutter/lib/src/mentions_overlay.dart new file mode 100644 index 00000000..83dd6ab2 --- /dev/null +++ b/packages/stream_chat_flutter/lib/src/mentions_overlay.dart @@ -0,0 +1,104 @@ +import 'package:flutter/material.dart'; +import 'package:stream_chat_flutter/src/extension.dart'; +import 'package:stream_chat_flutter/stream_chat_flutter.dart'; + +class MentionsOverlay extends StatelessWidget { + final BuildContext context; + final String query; + final ValueChanged onMentionResult; + + /// Customize the tile for the mentions overlay + final MentionTileBuilder? mentionsTileBuilder; + + const MentionsOverlay( + this.context, { + required this.query, + required this.onMentionResult, + this.mentionsTileBuilder, + Key? key, + }) : super(key: key); + + @override + Widget build(BuildContext otherContext) { + var _streamChatTheme = StreamChatTheme.of(context); + + Future>? queryMembers; + + final channelState = StreamChannel.of(context); + if (query.isNotEmpty) { + queryMembers = channelState.channel + .queryMembers(filter: Filter.autoComplete('name', query)) + .then((res) => res.members); + } + + final members = channelState.channel.state?.members + .where((m) => m.user?.name.toLowerCase().contains(query) == true) + .toList() ?? + []; + + if (members.isEmpty) { + return const SizedBox(); + } + + // ignore: cast_nullable_to_non_nullable + final renderBox = context.findRenderObject() as RenderBox; + final size = renderBox.size; + final child = Card( + margin: const EdgeInsets.all(8), + elevation: 2, + color: _streamChatTheme.colorTheme.barsBg, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(8), + ), + clipBehavior: Clip.hardEdge, + child: Container( + constraints: BoxConstraints.loose(Size(size.width - 16, 200)), + decoration: BoxDecoration( + color: _streamChatTheme.colorTheme.barsBg, + ), + child: FutureBuilder>( + future: queryMembers ?? Future.value(members), + initialData: members, + builder: (context, snapshot) => ListView( + padding: const EdgeInsets.all(0), + shrinkWrap: true, + children: [ + const SizedBox( + height: 8, + ), + ...snapshot.data! + .where((it) => it.user != null) + .map( + (m) => Material( + color: _streamChatTheme.colorTheme.barsBg, + child: InkWell( + onTap: () { + onMentionResult(m.user); + }, + child: mentionsTileBuilder != null + ? mentionsTileBuilder!(context, m) + : MentionTile(m), + ), + ), + ) + .toList(), + const SizedBox( + height: 8, + ), + ], + ), + ), + ), + ); + return TweenAnimationBuilder( + tween: Tween(begin: 0, end: 1), + duration: const Duration(milliseconds: 300), + curve: Curves.easeInOutExpo, + builder: (context, val, child) => Transform.scale( + scale: val, + child: child, + ), + child: child, + ); + } +} diff --git a/packages/stream_chat_flutter/lib/src/message_input.dart b/packages/stream_chat_flutter/lib/src/message_input.dart index 48bcf19c..f150d09d 100644 --- a/packages/stream_chat_flutter/lib/src/message_input.dart +++ b/packages/stream_chat_flutter/lib/src/message_input.dart @@ -11,6 +11,9 @@ import 'package:flutter_svg/flutter_svg.dart'; import 'package:image_picker/image_picker.dart'; import 'package:photo_manager/photo_manager.dart'; import 'package:shimmer/shimmer.dart'; +import 'package:stream_chat_flutter/src/commands_overlay.dart'; +import 'package:stream_chat_flutter/src/emoji_overlay.dart'; +import 'package:stream_chat_flutter/src/mentions_overlay.dart'; import 'package:stream_chat_flutter/src/overlays.dart'; import 'package:stream_chat_flutter/src/emoji/emoji.dart'; import 'package:stream_chat_flutter/src/extension.dart'; @@ -23,7 +26,6 @@ import 'package:stream_chat_flutter/src/video_service.dart'; import 'package:stream_chat_flutter/src/video_thumbnail_image.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; -import 'package:substring_highlight/substring_highlight.dart'; import 'package:video_compress/video_compress.dart'; export 'package:video_compress/video_compress.dart' show VideoQuality; @@ -323,7 +325,6 @@ class MessageInputState extends State { bool _inputEnabled = true; bool _commandEnabled = false; Widget? _commandsOverlay, _mentionsOverlay, _emojiOverlay; - late Iterable _emojiNames; Command? _chosenCommand; bool _actionsShrunk = false; @@ -347,8 +348,6 @@ class MessageInputState extends State { void initState() { super.initState(); _focusNode = widget.focusNode ?? FocusNode(); - _emojiNames = - Emoji.all().where((it) => it.name != null).map((e) => e.name!); if (!kIsWeb) { _keyboardListener = @@ -478,7 +477,7 @@ class MessageInputState extends State { _emojiOverlay != null || _mentionsOverlay != null, overlayBuilder: (context, offset) { - late Widget? child; + late Widget child; if (_commandsOverlay != null) { child = _buildCommandsOverlayEntry(); @@ -490,7 +489,7 @@ class MessageInputState extends State { return CenterAbout( position: Offset(offset.dx, offset.dy), - child: child ?? const SizedBox(), + child: child, ); }, child: child, @@ -921,114 +920,10 @@ class MessageInputState extends State { } } - Widget? _buildCommandsOverlayEntry() { + Widget _buildCommandsOverlayEntry() { final text = textEditingController.text.trimLeft(); - final commands = StreamChannel.of(context) - .channel - .config - ?.commands - .where((c) => c.name.contains(text.replaceFirst('/', ''))) - .toList() ?? - []; - if (commands.isEmpty) { - return null; - } - - final size = MediaQuery.of(context).size; - - return Padding( - padding: const EdgeInsets.all(4), - child: Card( - elevation: 2, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(8), - ), - color: _streamChatTheme.colorTheme.barsBg, - clipBehavior: Clip.hardEdge, - child: Container( - constraints: BoxConstraints.loose(Size(size.width - 16, 400)), - decoration: BoxDecoration( - color: _streamChatTheme.colorTheme.barsBg, - borderRadius: BorderRadius.circular(8)), - child: ListView( - padding: const EdgeInsets.all(0), - shrinkWrap: true, - children: [ - if (commands.isNotEmpty) - Padding( - padding: const EdgeInsets.only(top: 8), - child: Row( - children: [ - Padding( - padding: const EdgeInsets.symmetric( - horizontal: 8, - ), - child: StreamSvgIcon.lightning( - color: _streamChatTheme.colorTheme.accentPrimary, - ), - ), - Text( - context.translations.instantCommandsLabel, - style: TextStyle( - color: _streamChatTheme.colorTheme.textHighEmphasis - .withOpacity(.5), - ), - ) - ], - ), - ), - const SizedBox( - height: 10, - ), - ...commands - .map( - (c) => InkWell( - onTap: () { - _setCommand(c); - }, - child: SizedBox( - height: 40, - child: Row( - children: [ - const SizedBox( - width: 16, - ), - _buildCommandIcon(c.name), - const SizedBox( - width: 8, - ), - Text.rich( - TextSpan( - text: c.name.capitalize(), - style: const TextStyle( - fontWeight: FontWeight.bold), - children: [ - TextSpan( - text: ' /${c.name} ${c.args}', - style: _streamChatTheme.textTheme.body - .copyWith( - // ignore: lines_longer_than_80_chars - color: _streamChatTheme - // ignore: lines_longer_than_80_chars - .colorTheme - .textLowEmphasis, - ), - ), - ], - ), - ), - ], - ), - ), - ), - ) - .toList(), - ], - ), - ), - ), - ); + return CommandsOverlay(context, text: text, onCommandResult: _setCommand); } Widget _buildFilePickerSection() { @@ -1240,278 +1135,48 @@ class MessageInputState extends State { }); } - Widget _buildCommandIcon(String iconType) { - switch (iconType) { - case 'giphy': - return CircleAvatar( - radius: 12, - child: StreamSvgIcon.giphyIcon( - size: 24, - ), - ); - case 'ban': - return CircleAvatar( - backgroundColor: _streamChatTheme.colorTheme.accentPrimary, - radius: 12, - child: StreamSvgIcon.iconUserDelete( - size: 16, - color: Colors.white, - ), - ); - case 'flag': - return CircleAvatar( - backgroundColor: _streamChatTheme.colorTheme.accentPrimary, - radius: 12, - child: StreamSvgIcon.flag( - size: 14, - color: Colors.white, - ), - ); - case 'imgur': - return CircleAvatar( - backgroundColor: _streamChatTheme.colorTheme.accentPrimary, - radius: 12, - child: ClipOval( - child: StreamSvgIcon.imgur( - size: 24, - ), - ), - ); - case 'mute': - return CircleAvatar( - backgroundColor: _streamChatTheme.colorTheme.accentPrimary, - radius: 12, - child: StreamSvgIcon.mute( - size: 16, - color: Colors.white, - ), - ); - case 'unban': - return CircleAvatar( - backgroundColor: _streamChatTheme.colorTheme.accentPrimary, - radius: 12, - child: StreamSvgIcon.userAdd( - size: 16, - color: Colors.white, - ), - ); - case 'unmute': - return CircleAvatar( - backgroundColor: _streamChatTheme.colorTheme.accentPrimary, - radius: 12, - child: StreamSvgIcon.volumeUp( - size: 16, - color: Colors.white, - ), - ); - default: - return CircleAvatar( - backgroundColor: _streamChatTheme.colorTheme.accentPrimary, - radius: 12, - child: StreamSvgIcon.lightning( - size: 16, - color: Colors.white, - ), - ); - } - } - - Widget? _buildMentionsOverlayEntry() { + Widget _buildMentionsOverlayEntry() { final splits = textEditingController.text .substring(0, textEditingController.value.selection.start) .split('@'); final query = splits.last.toLowerCase(); - Future>? queryMembers; + return MentionsOverlay(context, query: query, onMentionResult: (user) { + if (user != null) { + _mentionedUsers.add(user); + } - final channelState = StreamChannel.of(context); - if (query.isNotEmpty) { - queryMembers = channelState.channel - .queryMembers(filter: Filter.autoComplete('name', query)) - .then((res) => res.members); - } + splits[splits.length - 1] = user!.name; + final rejoin = splits.join('@'); - final members = channelState.channel.state?.members - .where((m) => m.user?.name.toLowerCase().contains(query) == true) - .toList() ?? - []; - - if (members.isEmpty) { - return null; - } - - // ignore: cast_nullable_to_non_nullable - final renderBox = context.findRenderObject() as RenderBox; - final size = renderBox.size; - final child = Card( - margin: const EdgeInsets.all(8), - elevation: 2, - color: _streamChatTheme.colorTheme.barsBg, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(8), - ), - clipBehavior: Clip.hardEdge, - child: Container( - constraints: BoxConstraints.loose(Size(size.width - 16, 200)), - decoration: BoxDecoration( - color: _streamChatTheme.colorTheme.barsBg, + textEditingController.value = TextEditingValue( + text: rejoin + + textEditingController.text + .substring(textEditingController.selection.start), + selection: TextSelection.collapsed( + offset: rejoin.length, ), - child: FutureBuilder>( - future: queryMembers ?? Future.value(members), - initialData: members, - builder: (context, snapshot) => ListView( - padding: const EdgeInsets.all(0), - shrinkWrap: true, - children: [ - const SizedBox( - height: 8, - ), - ...snapshot.data! - .where((it) => it.user != null) - .map( - (m) => Material( - color: _streamChatTheme.colorTheme.barsBg, - child: InkWell( - onTap: () { - if (m.user != null) { - _mentionedUsers.add(m.user!); - } - - splits[splits.length - 1] = m.user!.name; - final rejoin = splits.join('@'); - - textEditingController.value = TextEditingValue( - text: rejoin + - textEditingController.text.substring( - textEditingController.selection.start), - selection: TextSelection.collapsed( - offset: rejoin.length, - ), - ); - _debounce!.cancel(); - _mentionsOverlay = null; - }, - child: widget.mentionsTileBuilder != null - ? widget.mentionsTileBuilder!(context, m) - : MentionTile(m), - ), - ), - ) - .toList(), - const SizedBox( - height: 8, - ), - ], - ), - ), - ), - ); - return TweenAnimationBuilder( - tween: Tween(begin: 0, end: 1), - duration: const Duration(milliseconds: 300), - curve: Curves.easeInOutExpo, - builder: (context, val, child) => Transform.scale( - scale: val, - child: child, - ), - child: child, - ); + ); + _debounce!.cancel(); + setState(() { + _mentionsOverlay = null; + }); + }); } - Widget? _buildEmojiOverlay() { + Widget _buildEmojiOverlay() { final splits = textEditingController.text .substring(0, textEditingController.value.selection.start) .split(':'); + final query = splits.last.toLowerCase(); - final emojis = _emojiNames - .where((e) => e.contains(query)) - .map(Emoji.byName) - .where((e) => e != null); - - if (emojis.isEmpty) { - return null; - } - - // ignore: cast_nullable_to_non_nullable - final renderBox = context.findRenderObject() as RenderBox; - final size = renderBox.size; - - return Card( - margin: const EdgeInsets.all(8), - elevation: 2, - color: _streamChatTheme.colorTheme.barsBg, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(8), - ), - clipBehavior: Clip.hardEdge, - child: Container( - constraints: BoxConstraints.loose(Size(size.width - 16, 200)), - decoration: BoxDecoration( - boxShadow: const [ - BoxShadow( - spreadRadius: -8, - blurRadius: 5, - offset: Offset(0, -4), - ), - ], - color: _streamChatTheme.colorTheme.barsBg, - ), - child: ListView.builder( - padding: const EdgeInsets.all(0), - shrinkWrap: true, - itemCount: emojis.length + 1, - itemBuilder: (context, i) { - if (i == 0) { - return Padding( - padding: const EdgeInsets.only(left: 8, top: 8), - child: Row( - children: [ - Padding( - padding: const EdgeInsets.symmetric(horizontal: 8), - child: StreamSvgIcon.smile( - color: _streamChatTheme.colorTheme.accentPrimary, - ), - ), - Flexible( - child: Text( - context.translations.emojiMatchingQueryText( - query, - ), - style: TextStyle( - color: _streamChatTheme.colorTheme.textHighEmphasis - .withOpacity(.5), - ), - ), - ) - ], - ), - ); - } - - final emoji = emojis.elementAt(i - 1)!; - final themeData = Theme.of(context); - return ListTile( - title: SubstringHighlight( - text: - // ignore: lines_longer_than_80_chars - "${emoji.char} ${emoji.name!.replaceAll('_', ' ')}", - term: query, - textStyleHighlight: themeData.textTheme.headline6!.copyWith( - fontSize: 14.5, - fontWeight: FontWeight.bold, - ), - textStyle: themeData.textTheme.headline6!.copyWith( - fontSize: 14.5, - ), - ), - onTap: () { - _chooseEmoji(splits, emoji); - }, - ); - }, - ), - ), + return EmojiOverlay( + context, + query: query, + onEmojiResult: (emoji) { + _chooseEmoji(splits, emoji); + }, ); } From 2f65a0d480387d1cc27536fcd9d3bb0d24cd6c0c Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Wed, 8 Sep 2021 16:12:29 +0530 Subject: [PATCH 07/70] added docs --- .../lib/src/commands_overlay.dart | 17 +++++++++---- .../lib/src/emoji_overlay.dart | 19 ++++++++++----- .../lib/src/mentions_overlay.dart | 24 ++++++++++++------- 3 files changed, 40 insertions(+), 20 deletions(-) diff --git a/packages/stream_chat_flutter/lib/src/commands_overlay.dart b/packages/stream_chat_flutter/lib/src/commands_overlay.dart index 8e236c35..aec35da9 100644 --- a/packages/stream_chat_flutter/lib/src/commands_overlay.dart +++ b/packages/stream_chat_flutter/lib/src/commands_overlay.dart @@ -2,11 +2,9 @@ import 'package:flutter/material.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; import 'package:stream_chat_flutter/src/extension.dart'; +/// Overlay for displaying commands that can be used class CommandsOverlay extends StatelessWidget { - final BuildContext context; - final String text; - final ValueChanged onCommandResult; - + /// Constructor for creating a [CommandsOverlay] const CommandsOverlay( this.context, { required this.text, @@ -14,9 +12,18 @@ class CommandsOverlay extends StatelessWidget { Key? key, }) : super(key: key); + /// Context of the upper tree + final BuildContext context; + + /// Query for searching commands + final String text; + + /// Callback called when a command is selected + final ValueChanged onCommandResult; + @override Widget build(BuildContext otherContext) { - var _streamChatTheme = StreamChatTheme.of(context); + final _streamChatTheme = StreamChatTheme.of(context); final commands = StreamChannel.of(context) .channel .config diff --git a/packages/stream_chat_flutter/lib/src/emoji_overlay.dart b/packages/stream_chat_flutter/lib/src/emoji_overlay.dart index 292e8230..c3768851 100644 --- a/packages/stream_chat_flutter/lib/src/emoji_overlay.dart +++ b/packages/stream_chat_flutter/lib/src/emoji_overlay.dart @@ -4,11 +4,9 @@ import 'package:stream_chat_flutter/src/emoji/emoji.dart'; import 'package:stream_chat_flutter/src/extension.dart'; import 'package:substring_highlight/substring_highlight.dart'; +/// Overlay for displaying emoji that can be used class EmojiOverlay extends StatelessWidget { - final BuildContext context; - final String query; - final ValueChanged onEmojiResult; - + /// Constructor for creating a [EmojiOverlay] const EmojiOverlay( this.context, { required this.query, @@ -16,10 +14,19 @@ class EmojiOverlay extends StatelessWidget { Key? key, }) : super(key: key); + /// Context of the upper tree + final BuildContext context; + + /// Query for searching emoji + final String query; + + /// Callback called when an emoji is selected + final ValueChanged onEmojiResult; + @override Widget build(BuildContext otherContext) { - var _streamChatTheme = StreamChatTheme.of(context); - var _emojiNames = + final _streamChatTheme = StreamChatTheme.of(context); + final _emojiNames = Emoji.all().where((it) => it.name != null).map((e) => e.name!); final emojis = _emojiNames diff --git a/packages/stream_chat_flutter/lib/src/mentions_overlay.dart b/packages/stream_chat_flutter/lib/src/mentions_overlay.dart index 83dd6ab2..823e920d 100644 --- a/packages/stream_chat_flutter/lib/src/mentions_overlay.dart +++ b/packages/stream_chat_flutter/lib/src/mentions_overlay.dart @@ -1,15 +1,9 @@ import 'package:flutter/material.dart'; -import 'package:stream_chat_flutter/src/extension.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; +/// Overlay for displaying users that can be mentioned class MentionsOverlay extends StatelessWidget { - final BuildContext context; - final String query; - final ValueChanged onMentionResult; - - /// Customize the tile for the mentions overlay - final MentionTileBuilder? mentionsTileBuilder; - + /// Constructor for creating a [MentionsOverlay] const MentionsOverlay( this.context, { required this.query, @@ -18,9 +12,21 @@ class MentionsOverlay extends StatelessWidget { Key? key, }) : super(key: key); + /// Context of the upper tree + final BuildContext context; + + /// Query for searching users + final String query; + + /// Callback called when a user is selected + final ValueChanged onMentionResult; + + /// Customize the tile for the mentions overlay + final MentionTileBuilder? mentionsTileBuilder; + @override Widget build(BuildContext otherContext) { - var _streamChatTheme = StreamChatTheme.of(context); + final _streamChatTheme = StreamChatTheme.of(context); Future>? queryMembers; From 19472027cc20e3f4a6421aca60da704321c5765a Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Thu, 9 Sep 2021 17:01:36 +0200 Subject: [PATCH 08/70] refactor(ui): remove context and add size property to EmojiOverlay --- .../core/http/interceptor/auth_interceptor.dart | 4 +++- .../example/ios/Flutter/AppFrameworkInfo.plist | 2 +- .../lib/src/emoji_overlay.dart | 16 ++++++---------- .../lib/src/message_input.dart | 4 +++- 4 files changed, 13 insertions(+), 13 deletions(-) diff --git a/packages/stream_chat/lib/src/core/http/interceptor/auth_interceptor.dart b/packages/stream_chat/lib/src/core/http/interceptor/auth_interceptor.dart index 9947eefa..37f618f5 100644 --- a/packages/stream_chat/lib/src/core/http/interceptor/auth_interceptor.dart +++ b/packages/stream_chat/lib/src/core/http/interceptor/auth_interceptor.dart @@ -38,7 +38,9 @@ class AuthInterceptor extends Interceptor { 'Authorization': token.rawValue, 'stream-auth-type': token.authType.raw, }; - options..queryParameters.addAll(params)..headers.addAll(headers); + options + ..queryParameters.addAll(params) + ..headers.addAll(headers); return handler.next(options); } diff --git a/packages/stream_chat_flutter/example/ios/Flutter/AppFrameworkInfo.plist b/packages/stream_chat_flutter/example/ios/Flutter/AppFrameworkInfo.plist index 6b4c0f78..f2872cf4 100644 --- a/packages/stream_chat_flutter/example/ios/Flutter/AppFrameworkInfo.plist +++ b/packages/stream_chat_flutter/example/ios/Flutter/AppFrameworkInfo.plist @@ -21,6 +21,6 @@ CFBundleVersion 1.0 MinimumOSVersion - 8.0 + 9.0 diff --git a/packages/stream_chat_flutter/lib/src/emoji_overlay.dart b/packages/stream_chat_flutter/lib/src/emoji_overlay.dart index c3768851..211b18b1 100644 --- a/packages/stream_chat_flutter/lib/src/emoji_overlay.dart +++ b/packages/stream_chat_flutter/lib/src/emoji_overlay.dart @@ -7,15 +7,15 @@ import 'package:substring_highlight/substring_highlight.dart'; /// Overlay for displaying emoji that can be used class EmojiOverlay extends StatelessWidget { /// Constructor for creating a [EmojiOverlay] - const EmojiOverlay( - this.context, { + const EmojiOverlay({ required this.query, required this.onEmojiResult, + required this.size, Key? key, }) : super(key: key); - /// Context of the upper tree - final BuildContext context; + /// The size of the overlay + final Size size; /// Query for searching emoji final String query; @@ -24,7 +24,7 @@ class EmojiOverlay extends StatelessWidget { final ValueChanged onEmojiResult; @override - Widget build(BuildContext otherContext) { + Widget build(BuildContext context) { final _streamChatTheme = StreamChatTheme.of(context); final _emojiNames = Emoji.all().where((it) => it.name != null).map((e) => e.name!); @@ -38,10 +38,6 @@ class EmojiOverlay extends StatelessWidget { return const SizedBox(); } - // ignore: cast_nullable_to_non_nullable - final renderBox = context.findRenderObject() as RenderBox; - final size = renderBox.size; - return Card( margin: const EdgeInsets.all(8), elevation: 2, @@ -51,7 +47,7 @@ class EmojiOverlay extends StatelessWidget { ), clipBehavior: Clip.hardEdge, child: Container( - constraints: BoxConstraints.loose(Size(size.width - 16, 200)), + constraints: BoxConstraints.loose(size), decoration: BoxDecoration( boxShadow: const [ BoxShadow( diff --git a/packages/stream_chat_flutter/lib/src/message_input.dart b/packages/stream_chat_flutter/lib/src/message_input.dart index f150d09d..54cc7bae 100644 --- a/packages/stream_chat_flutter/lib/src/message_input.dart +++ b/packages/stream_chat_flutter/lib/src/message_input.dart @@ -1170,9 +1170,11 @@ class MessageInputState extends State { .split(':'); final query = splits.last.toLowerCase(); + // ignore: cast_nullable_to_non_nullable + final renderObject = context.findRenderObject() as RenderBox; return EmojiOverlay( - context, + size: Size(renderObject.size.width - 16, 200), query: query, onEmojiResult: (emoji) { _chooseEmoji(splits, emoji); From 377789c4210f918d508c468cbcb771f6235522cc Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Fri, 10 Sep 2021 10:52:59 +0200 Subject: [PATCH 09/70] refactor(ui): remove context from Commands and Mentions overlays --- .../{overlays.dart => anchored_overlay.dart} | 2 - .../lib/src/commands_overlay.dart | 25 ++++---- .../lib/src/emoji_overlay.dart | 2 +- .../lib/src/mentions_overlay.dart | 24 ++++---- .../lib/src/message_input.dart | 57 ++++++++++++------- 5 files changed, 61 insertions(+), 49 deletions(-) rename packages/stream_chat_flutter/lib/src/{overlays.dart => anchored_overlay.dart} (98%) diff --git a/packages/stream_chat_flutter/lib/src/overlays.dart b/packages/stream_chat_flutter/lib/src/anchored_overlay.dart similarity index 98% rename from packages/stream_chat_flutter/lib/src/overlays.dart rename to packages/stream_chat_flutter/lib/src/anchored_overlay.dart index ce6a9a4e..d79e19df 100644 --- a/packages/stream_chat_flutter/lib/src/overlays.dart +++ b/packages/stream_chat_flutter/lib/src/anchored_overlay.dart @@ -110,12 +110,10 @@ class _OverlayBuilderState extends State { } void addToOverlay(OverlayEntry entry) async { - print('addToOverlay'); Overlay.of(context)?.insert(entry); } void hideOverlay() { - print('hideOverlay'); overlayEntry?.remove(); overlayEntry = null; } diff --git a/packages/stream_chat_flutter/lib/src/commands_overlay.dart b/packages/stream_chat_flutter/lib/src/commands_overlay.dart index aec35da9..ffaec928 100644 --- a/packages/stream_chat_flutter/lib/src/commands_overlay.dart +++ b/packages/stream_chat_flutter/lib/src/commands_overlay.dart @@ -1,33 +1,34 @@ import 'package:flutter/material.dart'; -import 'package:stream_chat_flutter/stream_chat_flutter.dart'; import 'package:stream_chat_flutter/src/extension.dart'; +import 'package:stream_chat_flutter/stream_chat_flutter.dart'; /// Overlay for displaying commands that can be used class CommandsOverlay extends StatelessWidget { /// Constructor for creating a [CommandsOverlay] - const CommandsOverlay( - this.context, { + const CommandsOverlay({ required this.text, required this.onCommandResult, + required this.size, + required this.channel, Key? key, }) : super(key: key); - /// Context of the upper tree - final BuildContext context; + /// The size of the overlay + final Size size; /// Query for searching commands final String text; + /// The channel to search for users + final Channel channel; + /// Callback called when a command is selected final ValueChanged onCommandResult; @override - Widget build(BuildContext otherContext) { + Widget build(BuildContext context) { final _streamChatTheme = StreamChatTheme.of(context); - final commands = StreamChannel.of(context) - .channel - .config - ?.commands + final commands = channel.config?.commands .where((c) => c.name.contains(text.replaceFirst('/', ''))) .toList() ?? []; @@ -36,8 +37,6 @@ class CommandsOverlay extends StatelessWidget { return const SizedBox(); } - final size = MediaQuery.of(context).size; - return Padding( padding: const EdgeInsets.all(4), child: Card( @@ -48,7 +47,7 @@ class CommandsOverlay extends StatelessWidget { color: _streamChatTheme.colorTheme.barsBg, clipBehavior: Clip.hardEdge, child: Container( - constraints: BoxConstraints.loose(Size(size.width - 16, 400)), + constraints: BoxConstraints.loose(size), decoration: BoxDecoration( color: _streamChatTheme.colorTheme.barsBg, borderRadius: BorderRadius.circular(8)), diff --git a/packages/stream_chat_flutter/lib/src/emoji_overlay.dart b/packages/stream_chat_flutter/lib/src/emoji_overlay.dart index 211b18b1..c37dd687 100644 --- a/packages/stream_chat_flutter/lib/src/emoji_overlay.dart +++ b/packages/stream_chat_flutter/lib/src/emoji_overlay.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; -import 'package:stream_chat_flutter/stream_chat_flutter.dart'; import 'package:stream_chat_flutter/src/emoji/emoji.dart'; import 'package:stream_chat_flutter/src/extension.dart'; +import 'package:stream_chat_flutter/stream_chat_flutter.dart'; import 'package:substring_highlight/substring_highlight.dart'; /// Overlay for displaying emoji that can be used diff --git a/packages/stream_chat_flutter/lib/src/mentions_overlay.dart b/packages/stream_chat_flutter/lib/src/mentions_overlay.dart index 823e920d..57d286d9 100644 --- a/packages/stream_chat_flutter/lib/src/mentions_overlay.dart +++ b/packages/stream_chat_flutter/lib/src/mentions_overlay.dart @@ -4,20 +4,24 @@ import 'package:stream_chat_flutter/stream_chat_flutter.dart'; /// Overlay for displaying users that can be mentioned class MentionsOverlay extends StatelessWidget { /// Constructor for creating a [MentionsOverlay] - const MentionsOverlay( - this.context, { + const MentionsOverlay({ required this.query, required this.onMentionResult, + required this.size, + required this.channel, this.mentionsTileBuilder, Key? key, }) : super(key: key); - /// Context of the upper tree - final BuildContext context; + /// The size of the overlay + final Size size; /// Query for searching users final String query; + /// The channel to search for users + final Channel channel; + /// Callback called when a user is selected final ValueChanged onMentionResult; @@ -25,19 +29,18 @@ class MentionsOverlay extends StatelessWidget { final MentionTileBuilder? mentionsTileBuilder; @override - Widget build(BuildContext otherContext) { + Widget build(BuildContext context) { final _streamChatTheme = StreamChatTheme.of(context); Future>? queryMembers; - final channelState = StreamChannel.of(context); if (query.isNotEmpty) { - queryMembers = channelState.channel + queryMembers = channel .queryMembers(filter: Filter.autoComplete('name', query)) .then((res) => res.members); } - final members = channelState.channel.state?.members + final members = channel.state?.members .where((m) => m.user?.name.toLowerCase().contains(query) == true) .toList() ?? []; @@ -46,9 +49,6 @@ class MentionsOverlay extends StatelessWidget { return const SizedBox(); } - // ignore: cast_nullable_to_non_nullable - final renderBox = context.findRenderObject() as RenderBox; - final size = renderBox.size; final child = Card( margin: const EdgeInsets.all(8), elevation: 2, @@ -58,7 +58,7 @@ class MentionsOverlay extends StatelessWidget { ), clipBehavior: Clip.hardEdge, child: Container( - constraints: BoxConstraints.loose(Size(size.width - 16, 200)), + constraints: BoxConstraints.loose(size), decoration: BoxDecoration( color: _streamChatTheme.colorTheme.barsBg, ), diff --git a/packages/stream_chat_flutter/lib/src/message_input.dart b/packages/stream_chat_flutter/lib/src/message_input.dart index 54cc7bae..a6795a9e 100644 --- a/packages/stream_chat_flutter/lib/src/message_input.dart +++ b/packages/stream_chat_flutter/lib/src/message_input.dart @@ -14,7 +14,7 @@ import 'package:shimmer/shimmer.dart'; import 'package:stream_chat_flutter/src/commands_overlay.dart'; import 'package:stream_chat_flutter/src/emoji_overlay.dart'; import 'package:stream_chat_flutter/src/mentions_overlay.dart'; -import 'package:stream_chat_flutter/src/overlays.dart'; +import 'package:stream_chat_flutter/src/anchored_overlay.dart'; import 'package:stream_chat_flutter/src/emoji/emoji.dart'; import 'package:stream_chat_flutter/src/extension.dart'; import 'package:stream_chat_flutter/src/media_list_view.dart'; @@ -923,7 +923,14 @@ class MessageInputState extends State { Widget _buildCommandsOverlayEntry() { final text = textEditingController.text.trimLeft(); - return CommandsOverlay(context, text: text, onCommandResult: _setCommand); +// ignore: cast_nullable_to_non_nullable + final renderObject = context.findRenderObject() as RenderBox; + return CommandsOverlay( + channel: StreamChannel.of(context).channel, + size: Size(renderObject.size.width - 16, 400), + text: text, + onCommandResult: _setCommand, + ); } Widget _buildFilePickerSection() { @@ -1141,27 +1148,35 @@ class MessageInputState extends State { .split('@'); final query = splits.last.toLowerCase(); - return MentionsOverlay(context, query: query, onMentionResult: (user) { - if (user != null) { - _mentionedUsers.add(user); - } + // ignore: cast_nullable_to_non_nullable + final renderObject = context.findRenderObject() as RenderBox; - splits[splits.length - 1] = user!.name; - final rejoin = splits.join('@'); + return MentionsOverlay( + channel: StreamChannel.of(context).channel, + size: Size(renderObject.size.width - 16, 400), + query: query, + onMentionResult: (user) { + if (user != null) { + _mentionedUsers.add(user); + } - textEditingController.value = TextEditingValue( - text: rejoin + - textEditingController.text - .substring(textEditingController.selection.start), - selection: TextSelection.collapsed( - offset: rejoin.length, - ), - ); - _debounce!.cancel(); - setState(() { - _mentionsOverlay = null; - }); - }); + splits[splits.length - 1] = user!.name; + final rejoin = splits.join('@'); + + textEditingController.value = TextEditingValue( + text: rejoin + + textEditingController.text + .substring(textEditingController.selection.start), + selection: TextSelection.collapsed( + offset: rejoin.length, + ), + ); + _debounce!.cancel(); + setState(() { + _mentionsOverlay = null; + }); + }, + ); } Widget _buildEmojiOverlay() { From 88d1210a8d24bc21592fcf97908bb038f3d80de7 Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Fri, 10 Sep 2021 18:04:45 +0530 Subject: [PATCH 10/70] fix gradient border radius --- .../stream_chat_flutter/lib/src/user_avatar.dart | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/packages/stream_chat_flutter/lib/src/user_avatar.dart b/packages/stream_chat_flutter/lib/src/user_avatar.dart index f9a1fd02..a923011c 100644 --- a/packages/stream_chat_flutter/lib/src/user_avatar.dart +++ b/packages/stream_chat_flutter/lib/src/user_avatar.dart @@ -66,6 +66,12 @@ class UserAvatar extends StatelessWidget { final placeholder = this.placeholder ?? streamChatTheme.placeholderUserImage; + final backupGradientAvatar = ClipRRect( + borderRadius: borderRadius ?? + streamChatTheme.ownMessageTheme.avatarTheme?.borderRadius, + child: streamChatTheme.defaultUserImage(context, user), + ); + Widget avatar = FittedBox( fit: BoxFit.cover, child: Container( @@ -76,8 +82,7 @@ class UserAvatar extends StatelessWidget { fit: BoxFit.cover, filterQuality: FilterQuality.high, imageUrl: user.image!, - errorWidget: (context, __, ___) => - streamChatTheme.defaultUserImage(context, user), + errorWidget: (context, __, ___) => backupGradientAvatar, placeholder: placeholder != null ? (context, __) => placeholder(context, user) : null, @@ -91,11 +96,7 @@ class UserAvatar extends StatelessWidget { ), ), ) - : ClipRRect( - borderRadius: borderRadius ?? - streamChatTheme.ownMessageTheme.avatarTheme?.borderRadius, - child: streamChatTheme.defaultUserImage(context, user), - ), + : backupGradientAvatar, ), ); From eb3d356407cd483456d5ae72a10b36f25d26556f Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Mon, 13 Sep 2021 15:16:04 +0530 Subject: [PATCH 11/70] general fixes --- .../lib/src/message_input.dart | 26 +++++++++---------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/packages/stream_chat_flutter/lib/src/message_input.dart b/packages/stream_chat_flutter/lib/src/message_input.dart index a6795a9e..296fb889 100644 --- a/packages/stream_chat_flutter/lib/src/message_input.dart +++ b/packages/stream_chat_flutter/lib/src/message_input.dart @@ -477,19 +477,19 @@ class MessageInputState extends State { _emojiOverlay != null || _mentionsOverlay != null, overlayBuilder: (context, offset) { - late Widget child; + late Widget? child; if (_commandsOverlay != null) { - child = _buildCommandsOverlayEntry(); + child = _commandsOverlay; } else if (_emojiOverlay != null) { - child = _buildEmojiOverlay(); + child = _emojiOverlay; } else { - child = _buildMentionsOverlayEntry(); + child = _mentionsOverlay; } return CenterAbout( position: Offset(offset.dx, offset.dy), - child: child, + child: child!, ); }, child: child, @@ -827,14 +827,6 @@ class MessageInputState extends State { .keyStroke(widget.parentMessage?.id) .catchError((e) {}); - setState(() { - _actionsShrunk = s.trim().isNotEmpty && - ((widget.actions?.length ?? 0) + - (widget.showCommandsButton ? 1 : 0) + - (widget.disableAttachments ? 0 : 1) > - 1); - }); - _commandsOverlay = null; _mentionsOverlay = null; _emojiOverlay = null; @@ -844,6 +836,14 @@ class MessageInputState extends State { _checkMentions(s, context); _checkEmoji(s, context); + + setState(() { + _actionsShrunk = s.trim().isNotEmpty && + ((widget.actions?.length ?? 0) + + (widget.showCommandsButton ? 1 : 0) + + (widget.disableAttachments ? 0 : 1) > + 1); + }); }, ); } From 145685be4b7a3704de8b3d7195f9f83acd5fa939 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Thu, 16 Sep 2021 16:15:41 +0200 Subject: [PATCH 12/70] wip --- .../stream_chat_flutter/example/lib/main.dart | 2 +- .../lib/src/message_input.dart | 120 ++++++++++-------- 2 files changed, 67 insertions(+), 55 deletions(-) diff --git a/packages/stream_chat_flutter/example/lib/main.dart b/packages/stream_chat_flutter/example/lib/main.dart index 3d292b09..e980b0cf 100644 --- a/packages/stream_chat_flutter/example/lib/main.dart +++ b/packages/stream_chat_flutter/example/lib/main.dart @@ -11,7 +11,7 @@ void main() async { /// from your project dashboard. final client = StreamChatClient( 's2dxdhpxd94g', - logLevel: Level.INFO, + logLevel: Level.OFF, ); /// Set the current user and connect the websocket. In a production diff --git a/packages/stream_chat_flutter/lib/src/message_input.dart b/packages/stream_chat_flutter/lib/src/message_input.dart index 296fb889..4cd6d7ee 100644 --- a/packages/stream_chat_flutter/lib/src/message_input.dart +++ b/packages/stream_chat_flutter/lib/src/message_input.dart @@ -324,7 +324,9 @@ class MessageInputState extends State { late final FocusNode _focusNode; bool _inputEnabled = true; bool _commandEnabled = false; - Widget? _commandsOverlay, _mentionsOverlay, _emojiOverlay; + bool _showCommandsOverlay = false; + bool _showEmojiOverlay = false; + bool _showMentionsOverlay = false; Command? _chosenCommand; bool _actionsShrunk = false; @@ -473,26 +475,33 @@ class MessageInputState extends State { ); } return AnchoredOverlay( - showOverlay: _commandsOverlay != null || - _emojiOverlay != null || - _mentionsOverlay != null, - overlayBuilder: (context, offset) { - late Widget? child; - - if (_commandsOverlay != null) { - child = _commandsOverlay; - } else if (_emojiOverlay != null) { - child = _emojiOverlay; - } else { - child = _mentionsOverlay; - } - - return CenterAbout( + showOverlay: _showMentionsOverlay, + overlayBuilder: (context, offset) => CenterAbout( + position: Offset(offset.dx, offset.dy), + child: _buildMentionsOverlayEntry(), + ), + child: AnchoredOverlay( + showOverlay: _showCommandsOverlay, + overlayBuilder: (context, offset) => CenterAbout( position: Offset(offset.dx, offset.dy), - child: child!, - ); - }, - child: child, + child: _buildCommandsOverlayEntry(), + ), + child: AnchoredOverlay( + showOverlay: textEditingController.text.isNotEmpty && + textEditingController.selection.baseOffset > 0 && + textEditingController.text + .substring( + 0, + textEditingController.selection.baseOffset, + ) + .contains(':'), + overlayBuilder: (context, offset) => CenterAbout( + position: Offset(offset.dx, offset.dy), + child: _buildEmojiOverlay(), + ), + child: child, + ), + ), ); } @@ -827,10 +836,6 @@ class MessageInputState extends State { .keyStroke(widget.parentMessage?.id) .catchError((e) {}); - _commandsOverlay = null; - _mentionsOverlay = null; - _emojiOverlay = null; - _checkCommands(s.trim(), context); _checkMentions(s, context); @@ -840,8 +845,8 @@ class MessageInputState extends State { setState(() { _actionsShrunk = s.trim().isNotEmpty && ((widget.actions?.length ?? 0) + - (widget.showCommandsButton ? 1 : 0) + - (widget.disableAttachments ? 0 : 1) > + (widget.showCommandsButton ? 1 : 0) + + (widget.disableAttachments ? 0 : 1) > 1); }); }, @@ -879,8 +884,6 @@ class MessageInputState extends State { if (textToSelection.endsWith(':') && emoji != null) { _chooseEmoji(splits.sublist(0, splits.length - 1), emoji); - } else { - _emojiOverlay = _buildEmojiOverlay(); } } } @@ -893,7 +896,15 @@ class MessageInputState extends State { .split(' ') .last .contains('@')) { - _mentionsOverlay = _buildMentionsOverlayEntry(); + if (!_showMentionsOverlay) { + setState(() { + _showMentionsOverlay = true; + }); + } + } else if (_showMentionsOverlay) { + setState(() { + _showMentionsOverlay = false; + }); } } @@ -910,20 +921,29 @@ class MessageInputState extends State { if (matchedCommandsList.length == 1) { _chosenCommand = matchedCommandsList[0]; textEditingController.clear(); + + if (_showCommandsOverlay) { + setState(() { + _commandEnabled = true; + _showCommandsOverlay = false; + }); + } + } else if (!_showCommandsOverlay) { setState(() { - _commandEnabled = true; + _showCommandsOverlay = true; }); - _commandsOverlay = null; - } else { - _commandsOverlay = _buildCommandsOverlayEntry(); } + } else if (_showCommandsOverlay) { + setState(() { + _showCommandsOverlay = false; + }); } } Widget _buildCommandsOverlayEntry() { final text = textEditingController.text.trimLeft(); -// ignore: cast_nullable_to_non_nullable + // ignore: cast_nullable_to_non_nullable final renderObject = context.findRenderObject() as RenderBox; return CommandsOverlay( channel: StreamChannel.of(context).channel, @@ -1143,6 +1163,10 @@ class MessageInputState extends State { } Widget _buildMentionsOverlayEntry() { + if (textEditingController.value.selection.start < 0) { + return const Offstage(); + } + final splits = textEditingController.text .substring(0, textEditingController.value.selection.start) .split('@'); @@ -1173,7 +1197,7 @@ class MessageInputState extends State { ); _debounce!.cancel(); setState(() { - _mentionsOverlay = null; + _showMentionsOverlay = false; }); }, ); @@ -1208,8 +1232,6 @@ class MessageInputState extends State { offset: rejoin.length, ), ); - - _emojiOverlay = null; } void _setCommand(Command c) { @@ -1217,8 +1239,8 @@ class MessageInputState extends State { setState(() { _chosenCommand = c; _commandEnabled = true; + _showCommandsOverlay = false; }); - _commandsOverlay = null; } Widget _buildReplyToMessage() { @@ -1411,7 +1433,7 @@ class MessageInputState extends State { icon: StreamSvgIcon.lightning( color: s.isNotEmpty ? _streamChatTheme.colorTheme.disabled - : (_commandsOverlay != null + : (_showCommandsOverlay ? _messageInputTheme.actionButtonColor : _messageInputTheme.actionButtonIdleColor), ), @@ -1427,15 +1449,9 @@ class MessageInputState extends State { await Future.delayed(const Duration(milliseconds: 300)); } - if (_commandsOverlay == null) { - setState(() { - _commandsOverlay = _buildCommandsOverlayEntry(); - }); - } else { - setState(() { - _commandsOverlay = null; - }); - } + setState(() { + _showCommandsOverlay = !_showCommandsOverlay; + }); }, ); @@ -1457,9 +1473,8 @@ class MessageInputState extends State { ), splashRadius: 24, onPressed: () async { - _emojiOverlay = null; - _commandsOverlay = null; - _mentionsOverlay = null; + _showCommandsOverlay = false; + _showMentionsOverlay = false; if (_openFilePickerSection) { setState(() => _openFilePickerSection = false); @@ -1745,9 +1760,6 @@ class MessageInputState extends State { _commandEnabled = false; }); - _commandsOverlay = null; - _mentionsOverlay = null; - Message message; if (widget.editMessage != null) { message = widget.editMessage!.copyWith( From f156c2b91b8a516193e18104355f0ae9ae84be7e Mon Sep 17 00:00:00 2001 From: Salih Guler Date: Fri, 17 Sep 2021 09:51:33 +0300 Subject: [PATCH 13/70] fix: Fix the wrong call on byShortName feat: Add hashCode and equals override to Emoji class. --- .../lib/src/emoji/emoji.dart | 53 ++++++++++--- .../test/src/emoji_test.dart | 79 +++++++++++++++++++ 2 files changed, 119 insertions(+), 13 deletions(-) create mode 100644 packages/stream_chat_flutter/test/src/emoji_test.dart diff --git a/packages/stream_chat_flutter/lib/src/emoji/emoji.dart b/packages/stream_chat_flutter/lib/src/emoji/emoji.dart index 38af85af..36717000 100644 --- a/packages/stream_chat_flutter/lib/src/emoji/emoji.dart +++ b/packages/stream_chat_flutter/lib/src/emoji/emoji.dart @@ -26,7 +26,8 @@ // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -import 'package:collection/collection.dart' show IterableExtension; +import 'package:collection/collection.dart' + show IterableExtension, ListEquality; /// All Groups enum EmojiGroup { @@ -114240,7 +114241,6 @@ final emojiRegex = RegExp( class Emoji { static const variationSelector16 = 65039; static const ZWJ = 8205; - final String? name; final String? char; final String? shortName; @@ -114252,14 +114252,39 @@ class Emoji { /// Emoji class. /// [name] of emoji. [char] and character of emoji. [shortName] and a digest name of emoji, [emojiGroup] is emoji's group and [emojiSubgroup] is emoji's subgroup. [keywords] list of keywords for emoji. [modifiable] `true` if emoji has skin. - Emoji( - {this.name, - this.char, - this.shortName, - this.emojiGroup, - this.emojiSubgroup, - this.keywords = const [], - this.modifiable = false}); + Emoji({ + this.name, + this.char, + this.shortName, + this.emojiGroup, + this.emojiSubgroup, + this.keywords = const [], + this.modifiable = false, + }); + + @override + bool operator ==(Object other) => + identical(this, other) || + other is Emoji && + runtimeType == other.runtimeType && + name == other.name && + char == other.char && + shortName == other.shortName && + emojiGroup == other.emojiGroup && + emojiSubgroup == other.emojiSubgroup && + const ListEquality().equals(keywords, other.keywords) && + modifiable == other.modifiable; + + @override + int get hashCode => Object.hash( + name.hashCode, + char.hashCode, + shortName.hashCode, + emojiGroup.hashCode, + emojiSubgroup.hashCode, + keywords.hashCode, + modifiable.hashCode, + ); /// Runes of Emoji Character List get charRunes { @@ -114339,9 +114364,11 @@ class Emoji { return _emojis.firstWhereOrNull((Emoji emoji) => emoji.name == name); } - /// Returns Emoji by [name] as short name. - static Emoji? byShortName(String name) { - return _emojis.firstWhereOrNull((Emoji emoji) => emoji.char == name); + /// Returns Emoji by [shortName] as short name. + static Emoji? byShortName(String shortName) { + return _emojis.firstWhereOrNull( + (Emoji emoji) => emoji.shortName == shortName, + ); } /// Returns list of Emojis in a same [group] diff --git a/packages/stream_chat_flutter/test/src/emoji_test.dart b/packages/stream_chat_flutter/test/src/emoji_test.dart new file mode 100644 index 00000000..61a0ba58 --- /dev/null +++ b/packages/stream_chat_flutter/test/src/emoji_test.dart @@ -0,0 +1,79 @@ +import 'package:stream_chat_flutter/src/emoji/emoji.dart'; +import 'package:test/test.dart'; + +void main() { + group('src/emoji', () { + test('${Emoji.byShortName} should bring the correct emoji', () { + final resultEmoji = Emoji.byShortName('smiley'); + expect(resultEmoji, _mockEmoji); + }); + }); +} + +final _mockEmoji = Emoji( + name: 'grinning face with big eyes', + char: '\u{1F603}', + shortName: 'smiley', + emojiGroup: EmojiGroup.smileysEmotion, + emojiSubgroup: EmojiSubgroup.faceSmiling, + keywords: [ + 'face', + 'mouth', + 'open', + 'smile', + 'uc6', + 'smiley', + 'happy', + 'silly', + 'laugh', + 'good', + 'smile', + 'teeth', + 'fun', + 'smileys', + 'mood', + 'emotion', + 'emotions', + 'emotional', + 'hooray', + 'cheek', + 'cheeky', + 'excited', + 'feliz', + 'heureux', + 'cheerful', + 'delighted', + 'ecstatic', + 'elated', + 'glad', + 'joy', + 'merry', + 'funny', + 'laughing', + 'lol', + 'rofl', + 'lmao', + 'lmfao', + 'hilarious', + 'ha', + 'haha', + 'chuckle', + 'comedy', + 'giggle', + 'hehe', + 'joyful', + 'laugh out loud', + 'rire', + 'tee hee', + 'jaja', + 'good job', + 'nice', + 'well done', + 'bravo', + 'congratulations', + 'congrats', + 'smiles', + 'dentist', + ':-D', + '=D' + ]); From 92f5153b6b7632dbbacdeba9b3adb687a5f385a6 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Mon, 20 Sep 2021 11:33:42 +0200 Subject: [PATCH 14/70] refactor(ui): refactor overlays --- .../stream_chat/lib/src/client/channel.dart | 11 ++- .../lib/src/anchored_overlay.dart | 87 +++++++++++++------ .../lib/src/attachment/giphy_attachment.dart | 3 +- .../lib/src/mentions_overlay.dart | 13 +-- .../lib/src/message_input.dart | 81 ++++++++--------- .../lib/src/message_widget.dart | 19 ++-- packages/stream_chat_flutter/pubspec.yaml | 1 - 7 files changed, 125 insertions(+), 90 deletions(-) diff --git a/packages/stream_chat/lib/src/client/channel.dart b/packages/stream_chat/lib/src/client/channel.dart index c2c40018..decc1c7d 100644 --- a/packages/stream_chat/lib/src/client/channel.dart +++ b/packages/stream_chat/lib/src/client/channel.dart @@ -1081,10 +1081,10 @@ class Channel { // remove regular message if present if (oldIndex != -1) { final oldMessage = state!.messages[oldIndex]; - state!.updateChannelState(state!._channelState.copyWith( - messages: state?.messages?..remove(oldMessage), + state!._channelState = state!._channelState.copyWith( + messages: [...(state!.messages..remove(oldMessage))], channel: state?._channelState.channel, - )); + ); } else { // remove thread message if present // also reduces total reply count @@ -1773,9 +1773,8 @@ class ChannelClientState { List get messages => _channelState.messages; /// Channel message list as a stream. - Stream> get messagesStream => channelStateStream - .map((cs) => cs.messages) - .distinct(const ListEquality().equals); + Stream> get messagesStream => + channelStateStream.map((cs) => cs.messages); /// Channel pinned message list. List get pinnedMessages => _channelState.pinnedMessages.toList(); diff --git a/packages/stream_chat_flutter/lib/src/anchored_overlay.dart b/packages/stream_chat_flutter/lib/src/anchored_overlay.dart index d79e19df..540fe2ec 100644 --- a/packages/stream_chat_flutter/lib/src/anchored_overlay.dart +++ b/packages/stream_chat_flutter/lib/src/anchored_overlay.dart @@ -1,43 +1,62 @@ +import 'package:collection/collection.dart'; import 'package:flutter/material.dart'; +import 'package:flutter/scheduler.dart'; -/// Class to create an overlay anchored to a child widget -class AnchoredOverlay extends StatelessWidget { - /// Constructor for creating an [AnchoredOverlay] - const AnchoredOverlay({ - Key? key, - this.showOverlay = false, +/// Options for building an [Overlay] +class OverlayOption { + /// Constructor for creating an [OverlayOption]. + OverlayOption({ + required this.showOverlay, required this.overlayBuilder, - required this.child, - }) : super(key: key); + }); /// Show or hide overlay final bool showOverlay; /// Builder for creating overlay widget final Widget Function(BuildContext, Offset anchor) overlayBuilder; +} + +/// Class to create an overlay anchored to a child widget +class AnchoredOverlay extends StatelessWidget { + /// Constructor for creating an [AnchoredOverlay] + const AnchoredOverlay({ + Key? key, + required this.overlayOptions, + required this.child, + }) : super(key: key); + + /// Builder for creating overlay widget + final List overlayOptions; /// Child to which overlay is anchored final Widget child; @override - Widget build(BuildContext context) => SizedBox( - child: LayoutBuilder( - builder: (BuildContext context, BoxConstraints constraints) => - OverlayBuilder( - showOverlay: showOverlay, - overlayBuilder: (BuildContext overlayContext) { - final box = context.findRenderObject() as RenderBox?; - if (box != null) { - final center = box.size - .center(box.localToGlobal(const Offset(0, 0))); - return overlayBuilder(overlayContext, center); - } else { - return const SizedBox(); - } - }, - child: child, - )), - ); + Widget build(BuildContext context) { + final overlay = + overlayOptions.firstWhereOrNull((option) => option.showOverlay); + return SizedBox( + child: LayoutBuilder( + builder: (BuildContext context, BoxConstraints constraints) => + OverlayBuilder( + showOverlay: overlay != null, + overlayBuilder: (BuildContext overlayContext) { + final box = context.findRenderObject() as RenderBox?; + if (box != null) { + final center = + box.size.center(box.localToGlobal(const Offset(0, 0))); + return overlay?.overlayBuilder(overlayContext, center) ?? + const Offstage(); + } else { + return const Offstage(); + } + }, + child: child, + ), + ), + ); + } } /// Class for creating a declarative-ish overlay @@ -63,18 +82,31 @@ class OverlayBuilder extends StatefulWidget { _OverlayBuilderState createState() => _OverlayBuilderState(); } -class _OverlayBuilderState extends State { +class _OverlayBuilderState extends State + with WidgetsBindingObserver { OverlayEntry? overlayEntry; @override void initState() { super.initState(); + WidgetsBinding.instance!.addObserver(this); + if (widget.showOverlay) { WidgetsBinding.instance?.addPostFrameCallback((_) => showOverlay()); } } + @override + void didChangeMetrics() { + super.didChangeMetrics(); + + SchedulerBinding.instance!.addPostFrameCallback((_) { + hideOverlay(); + syncWidgetAndOverlay(); + }); + } + @override void didUpdateWidget(OverlayBuilder oldWidget) { super.didUpdateWidget(oldWidget); @@ -91,6 +123,7 @@ class _OverlayBuilderState extends State { @override void dispose() { + WidgetsBinding.instance!.removeObserver(this); if (isShowingOverlay()) { hideOverlay(); } diff --git a/packages/stream_chat_flutter/lib/src/attachment/giphy_attachment.dart b/packages/stream_chat_flutter/lib/src/attachment/giphy_attachment.dart index e165fc6d..dacf7fbe 100644 --- a/packages/stream_chat_flutter/lib/src/attachment/giphy_attachment.dart +++ b/packages/stream_chat_flutter/lib/src/attachment/giphy_attachment.dart @@ -2,10 +2,10 @@ import 'package:cached_network_image/cached_network_image.dart'; import 'package:flutter/material.dart'; import 'package:shimmer/shimmer.dart'; import 'package:stream_chat_flutter/src/attachment/attachment_widget.dart'; +import 'package:stream_chat_flutter/src/extension.dart'; import 'package:stream_chat_flutter/src/visible_footnote.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; -import 'package:stream_chat_flutter/src/extension.dart'; /// Widget for showing a GIF attachment class GiphyAttachment extends AttachmentWidget { @@ -48,6 +48,7 @@ class GiphyAttachment extends AttachmentWidget { } Widget _buildSendingAttachment(BuildContext context, String imageUrl) { + print('sending attachment GIPHY $imageUrl'); final streamChannel = StreamChannel.of(context); return Column( mainAxisSize: MainAxisSize.min, diff --git a/packages/stream_chat_flutter/lib/src/mentions_overlay.dart b/packages/stream_chat_flutter/lib/src/mentions_overlay.dart index 57d286d9..b51f8261 100644 --- a/packages/stream_chat_flutter/lib/src/mentions_overlay.dart +++ b/packages/stream_chat_flutter/lib/src/mentions_overlay.dart @@ -49,7 +49,7 @@ class MentionsOverlay extends StatelessWidget { return const SizedBox(); } - final child = Card( + return Card( margin: const EdgeInsets.all(8), elevation: 2, color: _streamChatTheme.colorTheme.barsBg, @@ -96,15 +96,6 @@ class MentionsOverlay extends StatelessWidget { ), ), ); - return TweenAnimationBuilder( - tween: Tween(begin: 0, end: 1), - duration: const Duration(milliseconds: 300), - curve: Curves.easeInOutExpo, - builder: (context, val, child) => Transform.scale( - scale: val, - child: child, - ), - child: child, - ); + ; } } diff --git a/packages/stream_chat_flutter/lib/src/message_input.dart b/packages/stream_chat_flutter/lib/src/message_input.dart index 65c23804..2eca6ee7 100644 --- a/packages/stream_chat_flutter/lib/src/message_input.dart +++ b/packages/stream_chat_flutter/lib/src/message_input.dart @@ -6,18 +6,17 @@ import 'package:file_picker/file_picker.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; -import 'package:flutter_keyboard_visibility/flutter_keyboard_visibility.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:image_picker/image_picker.dart'; import 'package:photo_manager/photo_manager.dart'; import 'package:shimmer/shimmer.dart'; -import 'package:stream_chat_flutter/src/commands_overlay.dart'; -import 'package:stream_chat_flutter/src/emoji_overlay.dart'; -import 'package:stream_chat_flutter/src/mentions_overlay.dart'; import 'package:stream_chat_flutter/src/anchored_overlay.dart'; +import 'package:stream_chat_flutter/src/commands_overlay.dart'; import 'package:stream_chat_flutter/src/emoji/emoji.dart'; +import 'package:stream_chat_flutter/src/emoji_overlay.dart'; import 'package:stream_chat_flutter/src/extension.dart'; import 'package:stream_chat_flutter/src/media_list_view.dart'; +import 'package:stream_chat_flutter/src/mentions_overlay.dart'; import 'package:stream_chat_flutter/src/message_list_view.dart'; import 'package:stream_chat_flutter/src/quoted_message_widget.dart'; import 'package:stream_chat_flutter/src/stream_chat_theme.dart'; @@ -334,8 +333,6 @@ class MessageInputState extends State { bool _openFilePickerSection = false; int _filePickerIndex = 0; - final _keyboardVisibilityController = KeyboardVisibilityController(); - /// The editing controller passed to the input TextField late final TextEditingController textEditingController; @@ -351,15 +348,6 @@ class MessageInputState extends State { super.initState(); _focusNode = widget.focusNode ?? FocusNode(); - if (!kIsWeb) { - _keyboardListener = - _keyboardVisibilityController.onChange.listen((visible) { - if (_focusNode.hasFocus) { - _onChanged(context, textEditingController.text); - } - }); - } - textEditingController = widget.textEditingController ?? TextEditingController(); if (widget.editMessage != null || widget.initialMessage != null) { @@ -474,20 +462,26 @@ class MessageInputState extends State { child: child, ); } + return AnchoredOverlay( - showOverlay: _showMentionsOverlay, - overlayBuilder: (context, offset) => CenterAbout( - position: Offset(offset.dx, offset.dy), - child: _buildMentionsOverlayEntry(), - ), - child: AnchoredOverlay( - showOverlay: _showCommandsOverlay, - overlayBuilder: (context, offset) => CenterAbout( - position: Offset(offset.dx, offset.dy), - child: _buildCommandsOverlayEntry(), + overlayOptions: [ + OverlayOption( + overlayBuilder: (context, offset) => CenterAbout( + position: Offset(offset.dx, offset.dy), + child: _buildMentionsOverlayEntry(), + ), + showOverlay: _showMentionsOverlay, ), - child: AnchoredOverlay( - showOverlay: textEditingController.text.isNotEmpty && + OverlayOption( + showOverlay: _showCommandsOverlay, + overlayBuilder: (context, offset) => CenterAbout( + position: Offset(offset.dx, offset.dy), + child: _buildCommandsOverlayEntry(), + ), + ), + OverlayOption( + showOverlay: _focusNode.hasFocus && + textEditingController.text.isNotEmpty && textEditingController.selection.baseOffset > 0 && textEditingController.text .substring( @@ -499,9 +493,9 @@ class MessageInputState extends State { position: Offset(offset.dx, offset.dy), child: _buildEmojiOverlay(), ), - child: child, ), - ), + ], + child: child, ); } @@ -817,7 +811,10 @@ class MessageInputState extends State { String? _previousValue; - void _onChanged(BuildContext context, String s) { + void _onChanged( + BuildContext context, + String s, + ) { if (s == _previousValue) { return; } @@ -830,10 +827,13 @@ class MessageInputState extends State { if (!mounted) { return; } - StreamChannel.of(context) - .channel - .keyStroke(widget.parentMessage?.id) - .catchError((e) {}); + + if (s.isNotEmpty) { + StreamChannel.of(context) + .channel + .keyStroke(widget.parentMessage?.id) + .catchError((e) {}); + } _checkCommands(s.trim(), context); @@ -999,7 +999,9 @@ class MessageInputState extends State { } return AnimatedContainer( - duration: const Duration(milliseconds: 300), + duration: _openFilePickerSection + ? const Duration(milliseconds: 300) + : const Duration(), curve: Curves.easeOut, height: _openFilePickerSection ? _kMinMediaPickerSize : 0, child: SingleChildScrollView( @@ -1215,8 +1217,12 @@ class MessageInputState extends State { } Widget _buildEmojiOverlay() { + if (textEditingController.value.selection.baseOffset < 0) { + return const Offstage(); + } + final splits = textEditingController.text - .substring(0, textEditingController.value.selection.start) + .substring(0, textEditingController.value.selection.baseOffset) .split(':'); final query = splits.last.toLowerCase(); @@ -1837,8 +1843,6 @@ class MessageInputState extends State { } } - StreamSubscription? _keyboardListener; - void _showErrorAlert(String description) { showModalBottomSheet( backgroundColor: _streamChatTheme.colorTheme.barsBg, @@ -1912,7 +1916,6 @@ class MessageInputState extends State { @override void dispose() { - _keyboardListener?.cancel(); textEditingController.dispose(); _stopSlowMode(); super.dispose(); diff --git a/packages/stream_chat_flutter/lib/src/message_widget.dart b/packages/stream_chat_flutter/lib/src/message_widget.dart index 01177603..fc56303f 100644 --- a/packages/stream_chat_flutter/lib/src/message_widget.dart +++ b/packages/stream_chat_flutter/lib/src/message_widget.dart @@ -580,11 +580,12 @@ class _MessageWidgetState extends State bool get isOnlyEmoji => widget.message.text?.isOnlyEmoji == true; - bool get hasNonUrlAttachments => - widget.message.attachments.where((it) => it.titleLink == null).isNotEmpty; + bool get hasNonUrlAttachments => widget.message.attachments + .where((it) => it.titleLink == null || it.type == 'giphy') + .isNotEmpty; - bool get hasUrlAttachments => - widget.message.attachments.any((it) => it.titleLink != null) == true; + bool get hasUrlAttachments => widget.message.attachments + .any((it) => it.titleLink != null && it.type != 'giphy'); bool get showBottomRow => showThreadReplyIndicator || @@ -603,6 +604,9 @@ class _MessageWidgetState extends State @override Widget build(BuildContext context) { super.build(context); + if (widget.message.isEphemeral) { + print('EPHEMERAL'); + } final avatarWidth = widget.messageTheme.avatarTheme?.constraints.maxWidth ?? 40; final bottomRowPadding = @@ -1153,10 +1157,15 @@ class _MessageWidgetState extends State } Widget _parseAttachments() { + if (widget.message.isEphemeral) { + print('EPHEMERAL'); + } final attachmentGroups = >{}; widget.message.attachments - .where((element) => element.titleLink == null && element.type != null) + .where((element) => + (element.titleLink == null && element.type != null) || + element.type == 'giphy') .forEach((e) { if (attachmentGroups[e.type] == null) { attachmentGroups[e.type!] = []; diff --git a/packages/stream_chat_flutter/pubspec.yaml b/packages/stream_chat_flutter/pubspec.yaml index 907e9a7f..54888873 100644 --- a/packages/stream_chat_flutter/pubspec.yaml +++ b/packages/stream_chat_flutter/pubspec.yaml @@ -19,7 +19,6 @@ dependencies: file_picker: ^3.0.1 flutter: sdk: flutter - flutter_keyboard_visibility: ^5.0.1 flutter_markdown: ^0.6.1 flutter_portal: ^0.4.0 flutter_slidable: ^0.6.0 From 76671197f72e4e88754427c3e6bc8a32c69a44ad Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Mon, 20 Sep 2021 11:35:50 +0200 Subject: [PATCH 15/70] refactor(ui): fix sendAction --- packages/stream_chat/lib/src/client/channel.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/stream_chat/lib/src/client/channel.dart b/packages/stream_chat/lib/src/client/channel.dart index decc1c7d..96bd1838 100644 --- a/packages/stream_chat/lib/src/client/channel.dart +++ b/packages/stream_chat/lib/src/client/channel.dart @@ -1082,7 +1082,7 @@ class Channel { if (oldIndex != -1) { final oldMessage = state!.messages[oldIndex]; state!._channelState = state!._channelState.copyWith( - messages: [...(state!.messages..remove(oldMessage))], + messages: state!.messages..remove(oldMessage), channel: state?._channelState.channel, ); } else { From 8730c1b05514603c44ca9c2d9fc6efa9462d9f50 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Mon, 20 Sep 2021 11:36:28 +0200 Subject: [PATCH 16/70] fix(ui): fix example logs --- packages/stream_chat_flutter/example/lib/main.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/stream_chat_flutter/example/lib/main.dart b/packages/stream_chat_flutter/example/lib/main.dart index e980b0cf..3d292b09 100644 --- a/packages/stream_chat_flutter/example/lib/main.dart +++ b/packages/stream_chat_flutter/example/lib/main.dart @@ -11,7 +11,7 @@ void main() async { /// from your project dashboard. final client = StreamChatClient( 's2dxdhpxd94g', - logLevel: Level.OFF, + logLevel: Level.INFO, ); /// Set the current user and connect the websocket. In a production From d07b7c6cfdf3d2a265406739d373effeeea6bae8 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Mon, 20 Sep 2021 11:47:08 +0200 Subject: [PATCH 17/70] docs(ui): add code docs --- packages/stream_chat_flutter/lib/src/anchored_overlay.dart | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/stream_chat_flutter/lib/src/anchored_overlay.dart b/packages/stream_chat_flutter/lib/src/anchored_overlay.dart index 540fe2ec..ef06cda0 100644 --- a/packages/stream_chat_flutter/lib/src/anchored_overlay.dart +++ b/packages/stream_chat_flutter/lib/src/anchored_overlay.dart @@ -26,7 +26,8 @@ class AnchoredOverlay extends StatelessWidget { required this.child, }) : super(key: key); - /// Builder for creating overlay widget + /// List of overlay options + /// The first one which showOverlay is true will be used final List overlayOptions; /// Child to which overlay is anchored From bba1e817d398ff4f50bfad1bcdeebc8edd59b50b Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Mon, 20 Sep 2021 11:59:21 +0200 Subject: [PATCH 18/70] fix(ui): remove dead code --- .../lib/src/attachment/giphy_attachment.dart | 1 - packages/stream_chat_flutter/lib/src/message_widget.dart | 6 ------ 2 files changed, 7 deletions(-) diff --git a/packages/stream_chat_flutter/lib/src/attachment/giphy_attachment.dart b/packages/stream_chat_flutter/lib/src/attachment/giphy_attachment.dart index dacf7fbe..8272a962 100644 --- a/packages/stream_chat_flutter/lib/src/attachment/giphy_attachment.dart +++ b/packages/stream_chat_flutter/lib/src/attachment/giphy_attachment.dart @@ -48,7 +48,6 @@ class GiphyAttachment extends AttachmentWidget { } Widget _buildSendingAttachment(BuildContext context, String imageUrl) { - print('sending attachment GIPHY $imageUrl'); final streamChannel = StreamChannel.of(context); return Column( mainAxisSize: MainAxisSize.min, diff --git a/packages/stream_chat_flutter/lib/src/message_widget.dart b/packages/stream_chat_flutter/lib/src/message_widget.dart index fc56303f..6731e15a 100644 --- a/packages/stream_chat_flutter/lib/src/message_widget.dart +++ b/packages/stream_chat_flutter/lib/src/message_widget.dart @@ -604,9 +604,6 @@ class _MessageWidgetState extends State @override Widget build(BuildContext context) { super.build(context); - if (widget.message.isEphemeral) { - print('EPHEMERAL'); - } final avatarWidth = widget.messageTheme.avatarTheme?.constraints.maxWidth ?? 40; final bottomRowPadding = @@ -1157,9 +1154,6 @@ class _MessageWidgetState extends State } Widget _parseAttachments() { - if (widget.message.isEphemeral) { - print('EPHEMERAL'); - } final attachmentGroups = >{}; widget.message.attachments From a4efbbaa9f74a82e1b88fc1d569e068502bc4e71 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Mon, 20 Sep 2021 12:07:08 +0200 Subject: [PATCH 19/70] feat(ui): add customOverlays to `MessageInput` --- packages/stream_chat_flutter/CHANGELOG.md | 6 +++++- packages/stream_chat_flutter/lib/src/message_input.dart | 5 ++++- packages/stream_chat_flutter/lib/stream_chat_flutter.dart | 1 + 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/packages/stream_chat_flutter/CHANGELOG.md b/packages/stream_chat_flutter/CHANGELOG.md index 4fa15914..581101e6 100644 --- a/packages/stream_chat_flutter/CHANGELOG.md +++ b/packages/stream_chat_flutter/CHANGELOG.md @@ -5,11 +5,16 @@ - [[#668]](https://github.com/GetStream/stream-chat-flutter/issues/668): Fix `MessageInput` rendering errors in case there are no actions available to show. - [[#349]](https://github.com/GetStream/stream-chat-flutter/issues/349): Fix `MessageInput` attachment render overflow error. +- `MessageInput` overlays now follow the `MessageInput` focus. 🔄 Changed - Animation curves changed from default `Curves.linear` to `Curves.easeOut` and `Curves.easeIn` for attachment controls. +✅ Added + +- Added `MessageInput.customOverlays` property to add custom overlays to the `MessageInput`. + ## 2.2.1 ⚠️ Deprecated @@ -109,7 +114,6 @@ upgraded with some goodies like `lerp` functions. Here's the full naming breakdo - Fixed `MessageListView` not opening to the right initialMessage if `StreamChannel.initialMessageId` is set. - Fixed null check errors when accessing `message.text` in `MessageWidget` and `MessageListView`; this occurred when sending a message with no text. -- MessageInput can now be placed in any position on the screen. ## 2.1.2 diff --git a/packages/stream_chat_flutter/lib/src/message_input.dart b/packages/stream_chat_flutter/lib/src/message_input.dart index 2eca6ee7..6257a2c8 100644 --- a/packages/stream_chat_flutter/lib/src/message_input.dart +++ b/packages/stream_chat_flutter/lib/src/message_input.dart @@ -192,6 +192,7 @@ class MessageInput extends StatefulWidget { this.onAttachmentLimitExceed, this.attachmentButtonBuilder, this.commandButtonBuilder, + this.customOverlays = const [], }) : assert( initialMessage == null || editMessage == null, "Can't provide both `initialMessage` and `editMessage`", @@ -222,6 +223,9 @@ class MessageInput extends StatefulWidget { /// Use this to transform the message final FutureOr Function(Message)? preMessageSending; + /// List of custom overlays + final List customOverlays; + /// Parent message in case of a thread final Message? parentMessage; @@ -324,7 +328,6 @@ class MessageInputState extends State { bool _inputEnabled = true; bool _commandEnabled = false; bool _showCommandsOverlay = false; - bool _showEmojiOverlay = false; bool _showMentionsOverlay = false; Command? _chosenCommand; diff --git a/packages/stream_chat_flutter/lib/stream_chat_flutter.dart b/packages/stream_chat_flutter/lib/stream_chat_flutter.dart index 42188fa0..694293a1 100644 --- a/packages/stream_chat_flutter/lib/stream_chat_flutter.dart +++ b/packages/stream_chat_flutter/lib/stream_chat_flutter.dart @@ -1,6 +1,7 @@ export 'package:jiffy/jiffy.dart'; export 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; +export 'src/anchored_overlay.dart'; export 'src/attachment/attachment.dart'; export 'src/back_button.dart'; export 'src/channel_avatar.dart'; From 829c7042cceab550a65966c294c4884c7350bf52 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Mon, 20 Sep 2021 13:35:49 +0200 Subject: [PATCH 20/70] fix(ui): fix ios --- .../lib/src/anchored_overlay.dart | 16 ++++++++++++---- .../lib/src/message_input.dart | 7 ++++--- 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/packages/stream_chat_flutter/lib/src/anchored_overlay.dart b/packages/stream_chat_flutter/lib/src/anchored_overlay.dart index ef06cda0..9a7f45ee 100644 --- a/packages/stream_chat_flutter/lib/src/anchored_overlay.dart +++ b/packages/stream_chat_flutter/lib/src/anchored_overlay.dart @@ -98,14 +98,22 @@ class _OverlayBuilderState extends State } } + EdgeInsets? _previousInsets; + @override void didChangeMetrics() { super.didChangeMetrics(); - SchedulerBinding.instance!.addPostFrameCallback((_) { - hideOverlay(); - syncWidgetAndOverlay(); - }); + final newInsets = MediaQuery.of(context).viewPadding; + + if (newInsets.bottom != _previousInsets?.bottom) { + WidgetsBinding.instance!.addPostFrameCallback((_) { + hideOverlay(); + syncWidgetAndOverlay(); + }); + + _previousInsets = newInsets; + } } @override diff --git a/packages/stream_chat_flutter/lib/src/message_input.dart b/packages/stream_chat_flutter/lib/src/message_input.dart index 6257a2c8..837e88c4 100644 --- a/packages/stream_chat_flutter/lib/src/message_input.dart +++ b/packages/stream_chat_flutter/lib/src/message_input.dart @@ -470,7 +470,7 @@ class MessageInputState extends State { overlayOptions: [ OverlayOption( overlayBuilder: (context, offset) => CenterAbout( - position: Offset(offset.dx, offset.dy), + position: offset, child: _buildMentionsOverlayEntry(), ), showOverlay: _showMentionsOverlay, @@ -478,7 +478,7 @@ class MessageInputState extends State { OverlayOption( showOverlay: _showCommandsOverlay, overlayBuilder: (context, offset) => CenterAbout( - position: Offset(offset.dx, offset.dy), + position: offset, child: _buildCommandsOverlayEntry(), ), ), @@ -493,10 +493,11 @@ class MessageInputState extends State { ) .contains(':'), overlayBuilder: (context, offset) => CenterAbout( - position: Offset(offset.dx, offset.dy), + position: offset, child: _buildEmojiOverlay(), ), ), + ...widget.customOverlays, ], child: child, ); From e10da9b245866fa0299ed34138eb8d6564298f2a Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Mon, 20 Sep 2021 17:33:12 +0530 Subject: [PATCH 21/70] ref(llc): update `lastSyncedAt` only after calling sync. Signed-off-by: xsahil03x --- packages/stream_chat/lib/src/client/client.dart | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/packages/stream_chat/lib/src/client/client.dart b/packages/stream_chat/lib/src/client/client.dart index 9487e6e7..15b88ce2 100644 --- a/packages/stream_chat/lib/src/client/client.dart +++ b/packages/stream_chat/lib/src/client/client.dart @@ -406,10 +406,6 @@ class StreamChatClient { if (event.type == EventType.healthCheck) { return _handleHealthCheckEvent(event); } - if (!event.isLocal && _synced) { - _lastSyncedAt = event.createdAt; - _chatPersistenceClient?.updateLastSyncAt(event.createdAt); - } state.updateUser(event.user); return _eventController.add(event); } @@ -1325,6 +1321,7 @@ class StreamChatClient { // resetting state state.dispose(); state = ClientState(this); + _lastSyncedAt = null; // resetting credentials _tokenManager.reset(); From 9774ee336b820a72c7c363b8dd92a7f89a81d560 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Mon, 20 Sep 2021 15:54:04 +0200 Subject: [PATCH 22/70] fix(ui): fix `MessageListView` header and footer while reverse: false --- .../lib/src/message_list_view.dart | 36 ++++++++++++------- 1 file changed, 24 insertions(+), 12 deletions(-) diff --git a/packages/stream_chat_flutter/lib/src/message_list_view.dart b/packages/stream_chat_flutter/lib/src/message_list_view.dart index 61053106..77938032 100644 --- a/packages/stream_chat_flutter/lib/src/message_list_view.dart +++ b/packages/stream_chat_flutter/lib/src/message_list_view.dart @@ -494,14 +494,18 @@ class _MessageListViewState extends State { return _buildThreadSeparator(); } if (i == itemCount - 3) { - if (widget.headerBuilder == null) { + if (widget.reverse + ? widget.headerBuilder == null + : widget.footerBuilder == null) { if (_isThreadConversation) return const Offstage(); return const SizedBox(height: 52); } return const SizedBox(height: 8); } if (i == 0) { - if (widget.footerBuilder == null) { + if (widget.reverse + ? widget.footerBuilder == null + : widget.headerBuilder == null) { return const SizedBox(height: 30); } return const SizedBox(height: 8); @@ -560,8 +564,13 @@ class _MessageListViewState extends State { } if (i == itemCount - 2) { - return widget.headerBuilder?.call(context) ?? - const Offstage(); + if (widget.reverse) { + return widget.headerBuilder?.call(context) ?? + const Offstage(); + } else { + return widget.footerBuilder?.call(context) ?? + const Offstage(); + } } if (i == itemCount - 3) { @@ -579,8 +588,13 @@ class _MessageListViewState extends State { } if (i == 0) { - return widget.footerBuilder?.call(context) ?? - const Offstage(); + if (widget.reverse) { + return widget.footerBuilder?.call(context) ?? + const Offstage(); + } else { + return widget.headerBuilder?.call(context) ?? + const Offstage(); + } } const bottomMessageIndex = 2; // 1 -> loader // 0 -> footer @@ -1199,12 +1213,10 @@ class _MessageListViewState extends State { initialAlignment = _initialAlignment; WidgetsBinding.instance!.addPostFrameCallback((timeStamp) { - if (_scrollController?.isAttached == true) { - _scrollController?.jumpTo( - index: initialIndex, - alignment: initialAlignment, - ); - } + _scrollController?.jumpTo( + index: initialIndex, + alignment: initialAlignment, + ); }); _messageNewListener = From 0f0344ddf8c3d7941660b4c4756b70dbe3509c2c Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Mon, 20 Sep 2021 15:54:15 +0200 Subject: [PATCH 23/70] chore(ui): update changelog --- packages/stream_chat_flutter/CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/stream_chat_flutter/CHANGELOG.md b/packages/stream_chat_flutter/CHANGELOG.md index 7f9af105..1c39ec47 100644 --- a/packages/stream_chat_flutter/CHANGELOG.md +++ b/packages/stream_chat_flutter/CHANGELOG.md @@ -6,6 +6,7 @@ there are no actions available to show. - [[#349]](https://github.com/GetStream/stream-chat-flutter/issues/349): Fix `MessageInput` attachment render overflow error. - [[#674]](https://github.com/GetStream/stream-chat-flutter/issues/674): Check scrollController is attached before calling jump in MessageListView. +- Fixed `MessageListView` header and footer when `reverse: false`. 🔄 Changed From 6e8c8624b1f1ae4fb502367561e359df3087cb28 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Mon, 20 Sep 2021 16:00:32 +0200 Subject: [PATCH 24/70] fix(ui): remove default padding in date divider --- .../lib/src/message_list_view.dart | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/stream_chat_flutter/lib/src/message_list_view.dart b/packages/stream_chat_flutter/lib/src/message_list_view.dart index 61053106..cf39a97a 100644 --- a/packages/stream_chat_flutter/lib/src/message_list_view.dart +++ b/packages/stream_chat_flutter/lib/src/message_list_view.dart @@ -525,13 +525,13 @@ class _MessageListViewState extends State { ? widget.dateDividerBuilder!( nextMessage.createdAt.toLocal(), ) - : DateDivider( - dateTime: nextMessage.createdAt.toLocal(), + : Padding( + padding: const EdgeInsets.symmetric(vertical: 12), + child: DateDivider( + dateTime: nextMessage.createdAt.toLocal(), + ), ); - return Padding( - padding: const EdgeInsets.symmetric(vertical: 12), - child: divider, - ); + return divider; } final timeDiff = Jiffy(nextMessage.createdAt.toLocal()).diff( From 98ae2dd2ce0158c4c98eafb3f4e5d6e29ca63040 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Mon, 20 Sep 2021 16:01:13 +0200 Subject: [PATCH 25/70] chore(ui): update changelog --- packages/stream_chat_flutter/CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/stream_chat_flutter/CHANGELOG.md b/packages/stream_chat_flutter/CHANGELOG.md index 7f9af105..614b47f1 100644 --- a/packages/stream_chat_flutter/CHANGELOG.md +++ b/packages/stream_chat_flutter/CHANGELOG.md @@ -10,6 +10,7 @@ 🔄 Changed - Animation curves changed from default `Curves.linear` to `Curves.easeOut` and `Curves.easeIn` for attachment controls. +- Removed default padding in `DateDivider` in `MessageListView` ## 2.2.1 From 3da495a87230a237a74df0631bdb1cb33cb1cf7f Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Tue, 21 Sep 2021 09:46:46 +0200 Subject: [PATCH 26/70] refactor(ui): use portal --- packages/stream_chat_flutter/CHANGELOG.md | 4 - .../lib/src/anchored_overlay.dart | 206 ------------------ .../lib/src/mentions_overlay.dart | 2 +- .../lib/src/message_input.dart | 70 +++--- .../lib/stream_chat_flutter.dart | 1 - 5 files changed, 31 insertions(+), 252 deletions(-) delete mode 100644 packages/stream_chat_flutter/lib/src/anchored_overlay.dart diff --git a/packages/stream_chat_flutter/CHANGELOG.md b/packages/stream_chat_flutter/CHANGELOG.md index d4a755a5..c500cb5e 100644 --- a/packages/stream_chat_flutter/CHANGELOG.md +++ b/packages/stream_chat_flutter/CHANGELOG.md @@ -12,10 +12,6 @@ - Animation curves changed from default `Curves.linear` to `Curves.easeOut` and `Curves.easeIn` for attachment controls. -✅ Added - -- Added `MessageInput.customOverlays` property to add custom overlays to the `MessageInput`. - ## 2.2.1 ⚠️ Deprecated diff --git a/packages/stream_chat_flutter/lib/src/anchored_overlay.dart b/packages/stream_chat_flutter/lib/src/anchored_overlay.dart deleted file mode 100644 index 9a7f45ee..00000000 --- a/packages/stream_chat_flutter/lib/src/anchored_overlay.dart +++ /dev/null @@ -1,206 +0,0 @@ -import 'package:collection/collection.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter/scheduler.dart'; - -/// Options for building an [Overlay] -class OverlayOption { - /// Constructor for creating an [OverlayOption]. - OverlayOption({ - required this.showOverlay, - required this.overlayBuilder, - }); - - /// Show or hide overlay - final bool showOverlay; - - /// Builder for creating overlay widget - final Widget Function(BuildContext, Offset anchor) overlayBuilder; -} - -/// Class to create an overlay anchored to a child widget -class AnchoredOverlay extends StatelessWidget { - /// Constructor for creating an [AnchoredOverlay] - const AnchoredOverlay({ - Key? key, - required this.overlayOptions, - required this.child, - }) : super(key: key); - - /// List of overlay options - /// The first one which showOverlay is true will be used - final List overlayOptions; - - /// Child to which overlay is anchored - final Widget child; - - @override - Widget build(BuildContext context) { - final overlay = - overlayOptions.firstWhereOrNull((option) => option.showOverlay); - return SizedBox( - child: LayoutBuilder( - builder: (BuildContext context, BoxConstraints constraints) => - OverlayBuilder( - showOverlay: overlay != null, - overlayBuilder: (BuildContext overlayContext) { - final box = context.findRenderObject() as RenderBox?; - if (box != null) { - final center = - box.size.center(box.localToGlobal(const Offset(0, 0))); - return overlay?.overlayBuilder(overlayContext, center) ?? - const Offstage(); - } else { - return const Offstage(); - } - }, - child: child, - ), - ), - ); - } -} - -/// Class for creating a declarative-ish overlay -class OverlayBuilder extends StatefulWidget { - /// Constructor for creating an [OverlayBuilder] - const OverlayBuilder({ - Key? key, - this.showOverlay = false, - required this.overlayBuilder, - required this.child, - }) : super(key: key); - - /// Show or hide overlay - final bool showOverlay; - - /// Builder for creating overlay widget - final Widget Function(BuildContext) overlayBuilder; - - /// Child to which overlay is anchored - final Widget child; - - @override - _OverlayBuilderState createState() => _OverlayBuilderState(); -} - -class _OverlayBuilderState extends State - with WidgetsBindingObserver { - OverlayEntry? overlayEntry; - - @override - void initState() { - super.initState(); - - WidgetsBinding.instance!.addObserver(this); - - if (widget.showOverlay) { - WidgetsBinding.instance?.addPostFrameCallback((_) => showOverlay()); - } - } - - EdgeInsets? _previousInsets; - - @override - void didChangeMetrics() { - super.didChangeMetrics(); - - final newInsets = MediaQuery.of(context).viewPadding; - - if (newInsets.bottom != _previousInsets?.bottom) { - WidgetsBinding.instance!.addPostFrameCallback((_) { - hideOverlay(); - syncWidgetAndOverlay(); - }); - - _previousInsets = newInsets; - } - } - - @override - void didUpdateWidget(OverlayBuilder oldWidget) { - super.didUpdateWidget(oldWidget); - WidgetsBinding.instance - ?.addPostFrameCallback((_) => syncWidgetAndOverlay()); - } - - @override - void reassemble() { - super.reassemble(); - WidgetsBinding.instance - ?.addPostFrameCallback((_) => syncWidgetAndOverlay()); - } - - @override - void dispose() { - WidgetsBinding.instance!.removeObserver(this); - if (isShowingOverlay()) { - hideOverlay(); - } - - super.dispose(); - } - - bool isShowingOverlay() => overlayEntry != null; - - void showOverlay() { - overlayEntry = OverlayEntry( - builder: widget.overlayBuilder, - ); - if (overlayEntry != null) { - addToOverlay(overlayEntry!); - } - } - - void addToOverlay(OverlayEntry entry) async { - Overlay.of(context)?.insert(entry); - } - - void hideOverlay() { - overlayEntry?.remove(); - overlayEntry = null; - } - - void syncWidgetAndOverlay() { - if (isShowingOverlay() && !widget.showOverlay) { - hideOverlay(); - } else if (!isShowingOverlay() && widget.showOverlay) { - showOverlay(); - } - } - - @override - Widget build(BuildContext context) => widget.child; -} - -/// Class to position Overlay -class CenterAbout extends StatelessWidget { - /// Constructor for creating an [CenterAbout] - const CenterAbout({ - Key? key, - required this.position, - required this.child, - }) : super(key: key); - - /// Position of child - final Offset position; - - /// Child widget to which overlay is attached - final Widget child; - - @override - Widget build(BuildContext context) { - final size = MediaQuery.of(context).size; - - return Positioned( - bottom: size.height - position.dy, - left: position.dx, - child: FractionalTranslation( - translation: const Offset(-0.5, 0), - child: Padding( - padding: const EdgeInsets.only(bottom: 32), - child: child, - ), - ), - ); - } -} diff --git a/packages/stream_chat_flutter/lib/src/mentions_overlay.dart b/packages/stream_chat_flutter/lib/src/mentions_overlay.dart index b51f8261..41b25d83 100644 --- a/packages/stream_chat_flutter/lib/src/mentions_overlay.dart +++ b/packages/stream_chat_flutter/lib/src/mentions_overlay.dart @@ -83,7 +83,7 @@ class MentionsOverlay extends StatelessWidget { }, child: mentionsTileBuilder != null ? mentionsTileBuilder!(context, m) - : MentionTile(m), + : MentionTile(m, key: ValueKey(m.user?.id)), ), ), ) diff --git a/packages/stream_chat_flutter/lib/src/message_input.dart b/packages/stream_chat_flutter/lib/src/message_input.dart index 837e88c4..f8c83d02 100644 --- a/packages/stream_chat_flutter/lib/src/message_input.dart +++ b/packages/stream_chat_flutter/lib/src/message_input.dart @@ -6,11 +6,11 @@ import 'package:file_picker/file_picker.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_portal/flutter_portal.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:image_picker/image_picker.dart'; import 'package:photo_manager/photo_manager.dart'; import 'package:shimmer/shimmer.dart'; -import 'package:stream_chat_flutter/src/anchored_overlay.dart'; import 'package:stream_chat_flutter/src/commands_overlay.dart'; import 'package:stream_chat_flutter/src/emoji/emoji.dart'; import 'package:stream_chat_flutter/src/emoji_overlay.dart'; @@ -192,7 +192,6 @@ class MessageInput extends StatefulWidget { this.onAttachmentLimitExceed, this.attachmentButtonBuilder, this.commandButtonBuilder, - this.customOverlays = const [], }) : assert( initialMessage == null || editMessage == null, "Can't provide both `initialMessage` and `editMessage`", @@ -223,9 +222,6 @@ class MessageInput extends StatefulWidget { /// Use this to transform the message final FutureOr Function(Message)? preMessageSending; - /// List of custom overlays - final List customOverlays; - /// Parent message in case of a thread final Message? parentMessage; @@ -466,40 +462,32 @@ class MessageInputState extends State { ); } - return AnchoredOverlay( - overlayOptions: [ - OverlayOption( - overlayBuilder: (context, offset) => CenterAbout( - position: offset, - child: _buildMentionsOverlayEntry(), - ), - showOverlay: _showMentionsOverlay, + return PortalEntry( + portal: _buildCommandsOverlayEntry(), + visible: _showCommandsOverlay, + childAnchor: Alignment.topCenter, + portalAnchor: Alignment.bottomCenter, + child: PortalEntry( + portal: _buildEmojiOverlay(), + visible: _focusNode.hasFocus && + textEditingController.text.isNotEmpty && + textEditingController.selection.baseOffset > 0 && + textEditingController.text + .substring( + 0, + textEditingController.selection.baseOffset, + ) + .contains(':'), + childAnchor: Alignment.topCenter, + portalAnchor: Alignment.bottomCenter, + child: PortalEntry( + portal: _buildMentionsOverlayEntry(), + visible: _showMentionsOverlay, + childAnchor: Alignment.topCenter, + portalAnchor: Alignment.bottomCenter, + child: child, ), - OverlayOption( - showOverlay: _showCommandsOverlay, - overlayBuilder: (context, offset) => CenterAbout( - position: offset, - child: _buildCommandsOverlayEntry(), - ), - ), - OverlayOption( - showOverlay: _focusNode.hasFocus && - textEditingController.text.isNotEmpty && - textEditingController.selection.baseOffset > 0 && - textEditingController.text - .substring( - 0, - textEditingController.selection.baseOffset, - ) - .contains(':'), - overlayBuilder: (context, offset) => CenterAbout( - position: offset, - child: _buildEmojiOverlay(), - ), - ), - ...widget.customOverlays, - ], - child: child, + ), ); } @@ -946,8 +934,10 @@ class MessageInputState extends State { Widget _buildCommandsOverlayEntry() { final text = textEditingController.text.trimLeft(); - // ignore: cast_nullable_to_non_nullable - final renderObject = context.findRenderObject() as RenderBox; + final renderObject = context.findRenderObject() as RenderBox?; + if (renderObject == null) { + return const Offstage(); + } return CommandsOverlay( channel: StreamChannel.of(context).channel, size: Size(renderObject.size.width - 16, 400), diff --git a/packages/stream_chat_flutter/lib/stream_chat_flutter.dart b/packages/stream_chat_flutter/lib/stream_chat_flutter.dart index 694293a1..42188fa0 100644 --- a/packages/stream_chat_flutter/lib/stream_chat_flutter.dart +++ b/packages/stream_chat_flutter/lib/stream_chat_flutter.dart @@ -1,7 +1,6 @@ export 'package:jiffy/jiffy.dart'; export 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; -export 'src/anchored_overlay.dart'; export 'src/attachment/attachment.dart'; export 'src/back_button.dart'; export 'src/channel_avatar.dart'; From e0b23653b798ed017487a2988044308653d67814 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Tue, 21 Sep 2021 14:40:25 +0200 Subject: [PATCH 27/70] fix(ui): fix review --- packages/stream_chat_flutter/lib/src/mentions_overlay.dart | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/stream_chat_flutter/lib/src/mentions_overlay.dart b/packages/stream_chat_flutter/lib/src/mentions_overlay.dart index 41b25d83..63c43585 100644 --- a/packages/stream_chat_flutter/lib/src/mentions_overlay.dart +++ b/packages/stream_chat_flutter/lib/src/mentions_overlay.dart @@ -96,6 +96,5 @@ class MentionsOverlay extends StatelessWidget { ), ), ); - ; } } From 403d0e62a090d7b916d8fa705b0c54661ce26375 Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Tue, 21 Sep 2021 18:25:21 +0530 Subject: [PATCH 28/70] fix(llc): Connecting user without providing `name` uses `id` instead for setting `user.name`. Signed-off-by: xsahil03x --- packages/stream_chat/CHANGELOG.md | 5 ++++- .../lib/src/core/models/own_user.dart | 4 ---- .../test/src/core/models/own_user_test.dart | 21 +++++++++++++++++++ 3 files changed, 25 insertions(+), 5 deletions(-) diff --git a/packages/stream_chat/CHANGELOG.md b/packages/stream_chat/CHANGELOG.md index 9c925f88..e0c0f693 100644 --- a/packages/stream_chat/CHANGELOG.md +++ b/packages/stream_chat/CHANGELOG.md @@ -16,13 +16,16 @@ - Added support for `next`, `previous` value pagination in `client.search` , [read more.](https://getstream.io/chat/docs/other-rest/search/#pagination) - `Attachment` class now has a `fileSize` and `mimeType` property. Setting a `file` will also set the `file_size` - , `mime_type` key on `extraData`, so `attachment.fileSize`, `attachment.mimetype` and `attachment.extraData['file_size']` + , `mime_type` key on `extraData`, so `attachment.fileSize`, `attachment.mimetype` + and `attachment.extraData['file_size']` , `attachment.extraData['mime_type]` is same respectively. 🐞 Fixed - [[#659]](https://github.com/GetStream/stream-chat-flutter/issues/659) Fixed unread count not updating correctly. - Fix `Filter.empty()` json encoding. +- [[#700]](https://github.com/GetStream/stream-chat-flutter/issues/700) Connecting user without providing `name` + uses `id` instead for setting `user.name`. ## 2.2.1 diff --git a/packages/stream_chat/lib/src/core/models/own_user.dart b/packages/stream_chat/lib/src/core/models/own_user.dart index 07c6cf30..eb64eed3 100644 --- a/packages/stream_chat/lib/src/core/models/own_user.dart +++ b/packages/stream_chat/lib/src/core/models/own_user.dart @@ -54,8 +54,6 @@ class OwnUser extends User { factory OwnUser.fromUser(User user) => OwnUser( id: user.id, role: user.role, - name: user.name, - image: user.image, createdAt: user.createdAt, updatedAt: user.updatedAt, lastActive: user.lastActive, @@ -116,8 +114,6 @@ class OwnUser extends User { return copyWith( id: other.id, role: other.role, - name: other.name, - image: other.image, banned: other.banned, channelMutes: other.channelMutes, createdAt: other.createdAt, diff --git a/packages/stream_chat/test/src/core/models/own_user_test.dart b/packages/stream_chat/test/src/core/models/own_user_test.dart index e1d12ada..ea84185f 100644 --- a/packages/stream_chat/test/src/core/models/own_user_test.dart +++ b/packages/stream_chat/test/src/core/models/own_user_test.dart @@ -175,5 +175,26 @@ void main() { expect(newUser.teams, ['team1', 'team2']); expect(newUser.language, 'fr'); }); + + test( + 'fromUser should not override name with id if not available in extraData', + () { + final user = User(id: 'test-id'); + expect(user.id, 'test-id'); + expect(user.name, 'test-id'); + + final encodedUser = user.toJson(); + expect(encodedUser['id'], 'test-id'); + expect(encodedUser['name'], null); + + final ownUser = OwnUser.fromUser(user); + expect(user.id, 'test-id'); + expect(user.name, 'test-id'); + + final encodedOwnUser = ownUser.toJson(); + expect(encodedOwnUser['id'], 'test-id'); + expect(encodedOwnUser['name'], null); + }, + ); }); } From 371fff385122d87ab3d26e00bf1e5a9e0142b04e Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Tue, 21 Sep 2021 15:48:57 +0200 Subject: [PATCH 29/70] fix(ui): add default loading builder in photoview --- packages/stream_chat_flutter/lib/src/full_screen_media.dart | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/stream_chat_flutter/lib/src/full_screen_media.dart b/packages/stream_chat_flutter/lib/src/full_screen_media.dart index bef2a531..a959229c 100644 --- a/packages/stream_chat_flutter/lib/src/full_screen_media.dart +++ b/packages/stream_chat_flutter/lib/src/full_screen_media.dart @@ -5,11 +5,11 @@ import 'package:cached_network_image/cached_network_image.dart'; import 'package:chewie/chewie.dart'; import 'package:flutter/material.dart'; import 'package:photo_view/photo_view.dart'; +import 'package:stream_chat_flutter/src/extension.dart'; import 'package:stream_chat_flutter/src/gallery_footer.dart'; import 'package:stream_chat_flutter/src/gallery_header.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; import 'package:video_player/video_player.dart'; -import 'package:stream_chat_flutter/src/extension.dart'; /// Return action for coming back from pages enum ReturnActionType { @@ -112,6 +112,8 @@ class _FullScreenMediaState extends State attachment.assetUrl ?? attachment.thumbUrl; return PhotoView( + loadingBuilder: (context, image) => + const Offstage(), imageProvider: (imageUrl == null && attachment.localUri != null && attachment.file?.bytes != null) From 152adfc8b0cbfa0346700ccadc221cea172af929 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Tue, 21 Sep 2021 16:44:03 +0200 Subject: [PATCH 30/70] refactor(ui): added `MultiPortal` and `customPortalOptions` --- packages/stream_chat_flutter/CHANGELOG.md | 4 ++ .../lib/src/message_input.dart | 53 +++++++++------- .../stream_chat_flutter/lib/src/overlays.dart | 63 +++++++++++++++++++ 3 files changed, 96 insertions(+), 24 deletions(-) create mode 100644 packages/stream_chat_flutter/lib/src/overlays.dart diff --git a/packages/stream_chat_flutter/CHANGELOG.md b/packages/stream_chat_flutter/CHANGELOG.md index c500cb5e..e7bbf104 100644 --- a/packages/stream_chat_flutter/CHANGELOG.md +++ b/packages/stream_chat_flutter/CHANGELOG.md @@ -12,6 +12,10 @@ - Animation curves changed from default `Curves.linear` to `Curves.easeOut` and `Curves.easeIn` for attachment controls. +✅ Added + +- Added `MessageInput.customPortalOptions` property to add custom overlays to the `MessageInput`. + ## 2.2.1 ⚠️ Deprecated diff --git a/packages/stream_chat_flutter/lib/src/message_input.dart b/packages/stream_chat_flutter/lib/src/message_input.dart index f8c83d02..1c8f8f37 100644 --- a/packages/stream_chat_flutter/lib/src/message_input.dart +++ b/packages/stream_chat_flutter/lib/src/message_input.dart @@ -6,7 +6,6 @@ import 'package:file_picker/file_picker.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; -import 'package:flutter_portal/flutter_portal.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:image_picker/image_picker.dart'; import 'package:photo_manager/photo_manager.dart'; @@ -18,6 +17,7 @@ import 'package:stream_chat_flutter/src/extension.dart'; import 'package:stream_chat_flutter/src/media_list_view.dart'; import 'package:stream_chat_flutter/src/mentions_overlay.dart'; import 'package:stream_chat_flutter/src/message_list_view.dart'; +import 'package:stream_chat_flutter/src/overlays.dart'; import 'package:stream_chat_flutter/src/quoted_message_widget.dart'; import 'package:stream_chat_flutter/src/stream_chat_theme.dart'; import 'package:stream_chat_flutter/src/stream_svg_icon.dart'; @@ -192,12 +192,16 @@ class MessageInput extends StatefulWidget { this.onAttachmentLimitExceed, this.attachmentButtonBuilder, this.commandButtonBuilder, + this.customPortalOptions = const [], }) : assert( initialMessage == null || editMessage == null, "Can't provide both `initialMessage` and `editMessage`", ), super(key: key); + /// List of portal options for showing overlays + final List customPortalOptions; + /// Message to edit final Message? editMessage; @@ -462,32 +466,33 @@ class MessageInputState extends State { ); } - return PortalEntry( - portal: _buildCommandsOverlayEntry(), - visible: _showCommandsOverlay, + return MultiPortal( childAnchor: Alignment.topCenter, portalAnchor: Alignment.bottomCenter, - child: PortalEntry( - portal: _buildEmojiOverlay(), - visible: _focusNode.hasFocus && - textEditingController.text.isNotEmpty && - textEditingController.selection.baseOffset > 0 && - textEditingController.text - .substring( - 0, - textEditingController.selection.baseOffset, - ) - .contains(':'), - childAnchor: Alignment.topCenter, - portalAnchor: Alignment.bottomCenter, - child: PortalEntry( - portal: _buildMentionsOverlayEntry(), - visible: _showMentionsOverlay, - childAnchor: Alignment.topCenter, - portalAnchor: Alignment.bottomCenter, - child: child, + portalOptions: [ + PortalOptions( + visible: _showCommandsOverlay, + widget: _buildCommandsOverlayEntry(), ), - ), + PortalOptions( + visible: _focusNode.hasFocus && + textEditingController.text.isNotEmpty && + textEditingController.selection.baseOffset > 0 && + textEditingController.text + .substring( + 0, + textEditingController.selection.baseOffset, + ) + .contains(':'), + widget: _buildEmojiOverlay(), + ), + PortalOptions( + visible: _showMentionsOverlay, + widget: _buildMentionsOverlayEntry(), + ), + ...widget.customPortalOptions, + ], + child: child, ); } diff --git a/packages/stream_chat_flutter/lib/src/overlays.dart b/packages/stream_chat_flutter/lib/src/overlays.dart new file mode 100644 index 00000000..59328429 --- /dev/null +++ b/packages/stream_chat_flutter/lib/src/overlays.dart @@ -0,0 +1,63 @@ +import 'package:collection/collection.dart'; +import 'package:flutter/widgets.dart'; +import 'package:flutter_portal/flutter_portal.dart'; + +/// Class that contains the parameters for building a portal entry +class PortalOptions { + /// Constructs a new portal options object + /// [visible] - the visibility of the portal + /// [widget] - the widget to be displayed + PortalOptions({ + required this.visible, + required this.widget, + }); + + /// the visibility of the portal + final bool visible; + + /// the widget to be displayed + final Widget widget; +} + +/// Widget that renders a single portal widget from a list of [portalOptions] +/// It shows the first one that is visible +class MultiPortal extends StatelessWidget { + /// Constructs a new MultiPortal widget + /// [portalOptions] - the list of portal options + /// [portalAnchor] - the anchor relative to the portal + /// [childAnchor] - the anchor relative to the child + /// [child] - the child widget + const MultiPortal({ + Key? key, + required this.portalOptions, + required this.child, + required this.portalAnchor, + required this.childAnchor, + }) : super(key: key); + + /// The list of portal options + final List portalOptions; + + /// The child widget + final Widget child; + + /// The anchor relative to the portal + final Alignment? portalAnchor; + + /// The anchor relative to the child + final Alignment? childAnchor; + + @override + Widget build(BuildContext context) { + final visibleOverlay = + portalOptions.firstWhereOrNull((element) => element.visible); + + return PortalEntry( + childAnchor: childAnchor, + portalAnchor: portalAnchor, + visible: visibleOverlay != null, + portal: visibleOverlay?.widget, + child: child, + ); + } +} From 9a1cb38e2a1a215a321d70fd2aa4fdf2329c7c6f Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Wed, 22 Sep 2021 09:24:36 +0200 Subject: [PATCH 31/70] refactor(ui): rename portal to overlays --- .../lib/src/message_input.dart | 20 +++++----- .../stream_chat_flutter/lib/src/overlays.dart | 40 +++++++++---------- 2 files changed, 30 insertions(+), 30 deletions(-) diff --git a/packages/stream_chat_flutter/lib/src/message_input.dart b/packages/stream_chat_flutter/lib/src/message_input.dart index 1c8f8f37..9cd84aa2 100644 --- a/packages/stream_chat_flutter/lib/src/message_input.dart +++ b/packages/stream_chat_flutter/lib/src/message_input.dart @@ -192,15 +192,15 @@ class MessageInput extends StatefulWidget { this.onAttachmentLimitExceed, this.attachmentButtonBuilder, this.commandButtonBuilder, - this.customPortalOptions = const [], + this.customOverlays = const [], }) : assert( initialMessage == null || editMessage == null, "Can't provide both `initialMessage` and `editMessage`", ), super(key: key); - /// List of portal options for showing overlays - final List customPortalOptions; + /// List of options for showing overlays + final List customOverlays; /// Message to edit final Message? editMessage; @@ -466,15 +466,15 @@ class MessageInputState extends State { ); } - return MultiPortal( + return MultiOverlay( childAnchor: Alignment.topCenter, - portalAnchor: Alignment.bottomCenter, - portalOptions: [ - PortalOptions( + overlayAnchor: Alignment.bottomCenter, + overlayOptions: [ + OverlayOptions( visible: _showCommandsOverlay, widget: _buildCommandsOverlayEntry(), ), - PortalOptions( + OverlayOptions( visible: _focusNode.hasFocus && textEditingController.text.isNotEmpty && textEditingController.selection.baseOffset > 0 && @@ -486,11 +486,11 @@ class MessageInputState extends State { .contains(':'), widget: _buildEmojiOverlay(), ), - PortalOptions( + OverlayOptions( visible: _showMentionsOverlay, widget: _buildMentionsOverlayEntry(), ), - ...widget.customPortalOptions, + ...widget.customOverlays, ], child: child, ); diff --git a/packages/stream_chat_flutter/lib/src/overlays.dart b/packages/stream_chat_flutter/lib/src/overlays.dart index 59328429..8d36be50 100644 --- a/packages/stream_chat_flutter/lib/src/overlays.dart +++ b/packages/stream_chat_flutter/lib/src/overlays.dart @@ -2,47 +2,47 @@ import 'package:collection/collection.dart'; import 'package:flutter/widgets.dart'; import 'package:flutter_portal/flutter_portal.dart'; -/// Class that contains the parameters for building a portal entry -class PortalOptions { - /// Constructs a new portal options object - /// [visible] - the visibility of the portal +/// Class that contains the parameters for building an overlay entry +class OverlayOptions { + /// Constructs a new overlay options object + /// [visible] - the visibility of the overlay /// [widget] - the widget to be displayed - PortalOptions({ + OverlayOptions({ required this.visible, required this.widget, }); - /// the visibility of the portal + /// the visibility of the overlay final bool visible; /// the widget to be displayed final Widget widget; } -/// Widget that renders a single portal widget from a list of [portalOptions] +/// Widget that renders a single overlay widget from a list of [overlayOptions] /// It shows the first one that is visible -class MultiPortal extends StatelessWidget { - /// Constructs a new MultiPortal widget - /// [portalOptions] - the list of portal options - /// [portalAnchor] - the anchor relative to the portal +class MultiOverlay extends StatelessWidget { + /// Constructs a new MultiOverlay widget + /// [overlayOptions] - the list of overlay options + /// [overlayAnchor] - the anchor relative to the overlay /// [childAnchor] - the anchor relative to the child /// [child] - the child widget - const MultiPortal({ + const MultiOverlay({ Key? key, - required this.portalOptions, + required this.overlayOptions, required this.child, - required this.portalAnchor, + required this.overlayAnchor, required this.childAnchor, }) : super(key: key); - /// The list of portal options - final List portalOptions; + /// The list of overlay options + final List overlayOptions; /// The child widget final Widget child; - /// The anchor relative to the portal - final Alignment? portalAnchor; + /// The anchor relative to the overlay + final Alignment? overlayAnchor; /// The anchor relative to the child final Alignment? childAnchor; @@ -50,11 +50,11 @@ class MultiPortal extends StatelessWidget { @override Widget build(BuildContext context) { final visibleOverlay = - portalOptions.firstWhereOrNull((element) => element.visible); + overlayOptions.firstWhereOrNull((element) => element.visible); return PortalEntry( childAnchor: childAnchor, - portalAnchor: portalAnchor, + portalAnchor: overlayAnchor, visible: visibleOverlay != null, portal: visibleOverlay?.widget, child: child, From 403bec53659ce80e1f259c05538d7376adb9d473 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Wed, 22 Sep 2021 09:46:47 +0200 Subject: [PATCH 32/70] fix(ui): add missing code --- .../stream_chat_flutter/lib/src/message_list_view.dart | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/packages/stream_chat_flutter/lib/src/message_list_view.dart b/packages/stream_chat_flutter/lib/src/message_list_view.dart index 77938032..44063487 100644 --- a/packages/stream_chat_flutter/lib/src/message_list_view.dart +++ b/packages/stream_chat_flutter/lib/src/message_list_view.dart @@ -1213,10 +1213,12 @@ class _MessageListViewState extends State { initialAlignment = _initialAlignment; WidgetsBinding.instance!.addPostFrameCallback((timeStamp) { - _scrollController?.jumpTo( - index: initialIndex, - alignment: initialAlignment, - ); + if (_scrollController?.isAttached == true) { + _scrollController?.jumpTo( + index: initialIndex, + alignment: initialAlignment, + ); + } }); _messageNewListener = From f032a72d00649ca78f0c92776019976dfda2f27a Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Wed, 22 Sep 2021 10:30:23 +0200 Subject: [PATCH 33/70] chore(ui,core,llc,persistance,localization): remove analysis warnings due to flutter 2.5 --- packages/stream_chat/lib/src/client/client.dart | 9 --------- packages/stream_chat/lib/src/client/retry_queue.dart | 2 +- .../lib/src/core/models/channel_model.dart | 2 +- .../stream_chat/lib/src/core/models/reaction.dart | 2 +- packages/stream_chat/lib/src/ws/websocket.dart | 4 ++-- .../test/src/core/api/responses_test.dart | 2 +- .../test/src/core/models/filter_test.dart | 2 +- .../test/src/core/models/serialization_test.dart | 2 +- .../test/src/core/util/extension_test.dart | 2 +- packages/stream_chat/test/src/fakes.dart | 2 +- packages/stream_chat/test/src/ws/websocket_test.dart | 4 ++-- .../stream_chat_flutter/example/lib/split_view.dart | 4 +--- .../example/lib/tutorial_part_2.dart | 4 +--- .../example/lib/tutorial_part_3.dart | 4 +--- .../example/lib/tutorial_part_4.dart | 4 +--- .../example/lib/tutorial_part_5.dart | 4 +--- .../example/lib/tutorial_part_6.dart | 4 +--- .../attachment/attachment_upload_state_builder.dart | 2 +- .../lib/src/attachment/file_attachment.dart | 5 ++--- .../lib/src/attachment/giphy_attachment.dart | 2 +- .../lib/src/channel_bottom_sheet.dart | 2 +- .../stream_chat_flutter/lib/src/channel_header.dart | 9 ++++++--- .../stream_chat_flutter/lib/src/channel_info.dart | 2 +- .../lib/src/channel_list_header.dart | 7 +++++-- .../lib/src/channel_list_view.dart | 2 +- .../stream_chat_flutter/lib/src/channel_name.dart | 2 +- .../stream_chat_flutter/lib/src/date_divider.dart | 2 +- .../stream_chat_flutter/lib/src/gallery_footer.dart | 2 +- .../stream_chat_flutter/lib/src/gallery_header.dart | 7 +++++-- .../lib/src/message_reactions_modal.dart | 2 +- .../lib/src/message_search_item.dart | 2 +- .../lib/src/message_search_list_view.dart | 2 +- .../stream_chat_flutter/lib/src/stream_chat.dart | 4 +++- .../lib/src/stream_chat_theme.dart | 2 +- .../stream_chat_flutter/lib/src/thread_header.dart | 9 ++++++--- .../lib/src/typing_indicator.dart | 2 +- packages/stream_chat_flutter/lib/src/user_item.dart | 2 +- .../stream_chat_flutter/lib/src/user_list_view.dart | 2 +- packages/stream_chat_flutter/lib/src/utils.dart | 2 +- .../lib/src/visible_footnote.dart | 2 +- .../test/src/theme/gallery_footer_theme_test.dart | 2 +- .../test/src/theme/gallery_header_theme_test.dart | 2 +- .../test/channel_list_core_test.dart | 12 ++++++------ .../test/message_search_list_core_test.dart | 6 +++--- .../test/user_list_core_test.dart | 10 +++++----- .../test/override_test.dart | 2 +- .../lib/src/db/shared/native_db.dart | 3 +-- .../lib/src/db/shared/web_db.dart | 3 +-- .../test/src/converter/list_coverter_test.dart | 2 +- .../test/src/converter/map_converter_test.dart | 2 +- .../message_sending_status_converter_test.dart | 2 +- .../test/src/dao/user_dao_test.dart | 2 +- .../test/src/db/moor_chat_database_test.dart | 2 +- .../test/src/mapper/channel_mapper_test.dart | 3 ++- .../test/src/mapper/member_mapper_test.dart | 2 +- .../test/src/mapper/message_mapper_test.dart | 2 +- .../test/src/mapper/pinned_message_mapper_test.dart | 2 +- .../mapper/pinned_message_reaction_mapper_test.dart | 2 +- .../test/src/mapper/reaction_mapper_test.dart | 2 +- .../test/src/mapper/read_mapper_test.dart | 2 +- .../test/src/mapper/user_mapper_test.dart | 3 ++- 61 files changed, 96 insertions(+), 104 deletions(-) diff --git a/packages/stream_chat/lib/src/client/client.dart b/packages/stream_chat/lib/src/client/client.dart index 15b88ce2..ae6716fd 100644 --- a/packages/stream_chat/lib/src/client/client.dart +++ b/packages/stream_chat/lib/src/client/client.dart @@ -143,9 +143,6 @@ class StreamChatClient { late final RetryPolicy _retryPolicy; - /// sync state of the channels present inside state, defaults to false - bool _synced = false; - /// the last dateTime at the which all the channels were synced DateTime? _lastSyncedAt; @@ -434,8 +431,6 @@ class StreamChatClient { type: EventType.connectionRecovered, online: true, )); - } else { - _synced = false; } } @@ -460,13 +455,11 @@ class StreamChatClient { Future sync({List? cids, DateTime? lastSyncAt}) async { cids ??= await _chatPersistenceClient?.getChannelCids(); if (cids == null || cids.isEmpty) { - _synced = true; return; } lastSyncAt ??= await _chatPersistenceClient?.getLastSyncAt(); if (lastSyncAt == null) { - _synced = true; return; } @@ -484,12 +477,10 @@ class StreamChatClient { handleEvent(event); } - _synced = true; final now = DateTime.now(); _lastSyncedAt = now; _chatPersistenceClient?.updateLastSyncAt(now); } catch (e, stk) { - _synced = false; logger.severe('Error during sync', e, stk); } } diff --git a/packages/stream_chat/lib/src/client/retry_queue.dart b/packages/stream_chat/lib/src/client/retry_queue.dart index 4c44433f..c441b5fb 100644 --- a/packages/stream_chat/lib/src/client/retry_queue.dart +++ b/packages/stream_chat/lib/src/client/retry_queue.dart @@ -6,8 +6,8 @@ import 'package:rxdart/rxdart.dart'; import 'package:stream_chat/src/client/channel.dart'; import 'package:stream_chat/src/client/retry_policy.dart'; import 'package:stream_chat/src/core/error/error.dart'; -import 'package:stream_chat/src/event_type.dart'; import 'package:stream_chat/src/core/models/message.dart'; +import 'package:stream_chat/src/event_type.dart'; import 'package:stream_chat/stream_chat.dart'; /// The retry queue associated to a channel diff --git a/packages/stream_chat/lib/src/core/models/channel_model.dart b/packages/stream_chat/lib/src/core/models/channel_model.dart index a4e6db79..31ac4923 100644 --- a/packages/stream_chat/lib/src/core/models/channel_model.dart +++ b/packages/stream_chat/lib/src/core/models/channel_model.dart @@ -1,7 +1,7 @@ import 'package:json_annotation/json_annotation.dart'; import 'package:stream_chat/src/core/models/channel_config.dart'; -import 'package:stream_chat/src/core/util/serializer.dart'; import 'package:stream_chat/src/core/models/user.dart'; +import 'package:stream_chat/src/core/util/serializer.dart'; part 'channel_model.g.dart'; diff --git a/packages/stream_chat/lib/src/core/models/reaction.dart b/packages/stream_chat/lib/src/core/models/reaction.dart index 57d0b48d..2b0ffbf6 100644 --- a/packages/stream_chat/lib/src/core/models/reaction.dart +++ b/packages/stream_chat/lib/src/core/models/reaction.dart @@ -1,6 +1,6 @@ import 'package:json_annotation/json_annotation.dart'; -import 'package:stream_chat/src/core/util/serializer.dart'; import 'package:stream_chat/src/core/models/user.dart'; +import 'package:stream_chat/src/core/util/serializer.dart'; part 'reaction.g.dart'; diff --git a/packages/stream_chat/lib/src/ws/websocket.dart b/packages/stream_chat/lib/src/ws/websocket.dart index b26d811e..05382fb7 100644 --- a/packages/stream_chat/lib/src/ws/websocket.dart +++ b/packages/stream_chat/lib/src/ws/websocket.dart @@ -6,15 +6,15 @@ import 'package:logging/logging.dart'; import 'package:meta/meta.dart'; import 'package:rxdart/rxdart.dart'; import 'package:stream_chat/src/core/error/error.dart'; -import 'package:stream_chat/src/ws/connection_status.dart'; import 'package:stream_chat/src/core/http/token.dart'; import 'package:stream_chat/src/core/http/token_manager.dart'; import 'package:stream_chat/src/core/models/event.dart'; import 'package:stream_chat/src/core/models/user.dart'; import 'package:stream_chat/src/event_type.dart'; +import 'package:stream_chat/src/ws/connection_status.dart'; import 'package:stream_chat/src/ws/timer_helper.dart'; -import 'package:web_socket_channel/web_socket_channel.dart'; import 'package:web_socket_channel/status.dart' as status; +import 'package:web_socket_channel/web_socket_channel.dart'; /// Typedef which exposes an [Event] as the only parameter. typedef EventHandler = void Function(Event); diff --git a/packages/stream_chat/test/src/core/api/responses_test.dart b/packages/stream_chat/test/src/core/api/responses_test.dart index 77e7abc3..a61d825b 100644 --- a/packages/stream_chat/test/src/core/api/responses_test.dart +++ b/packages/stream_chat/test/src/core/api/responses_test.dart @@ -1,6 +1,5 @@ import 'dart:convert'; -import 'package:test/test.dart'; import 'package:stream_chat/src/core/api/responses.dart'; import 'package:stream_chat/src/core/models/device.dart'; import 'package:stream_chat/src/core/models/member.dart'; @@ -8,6 +7,7 @@ import 'package:stream_chat/src/core/models/message.dart'; import 'package:stream_chat/src/core/models/reaction.dart'; import 'package:stream_chat/src/core/models/read.dart'; import 'package:stream_chat/stream_chat.dart'; +import 'package:test/test.dart'; void main() { group('src/api/responses', () { diff --git a/packages/stream_chat/test/src/core/models/filter_test.dart b/packages/stream_chat/test/src/core/models/filter_test.dart index e396c2b3..c0a8ce8f 100644 --- a/packages/stream_chat/test/src/core/models/filter_test.dart +++ b/packages/stream_chat/test/src/core/models/filter_test.dart @@ -1,7 +1,7 @@ import 'dart:convert'; -import 'package:test/test.dart'; import 'package:stream_chat/src/core/models/filter.dart'; +import 'package:test/test.dart'; void main() { group('operators', () { diff --git a/packages/stream_chat/test/src/core/models/serialization_test.dart b/packages/stream_chat/test/src/core/models/serialization_test.dart index 605b5b58..63b1428f 100644 --- a/packages/stream_chat/test/src/core/models/serialization_test.dart +++ b/packages/stream_chat/test/src/core/models/serialization_test.dart @@ -1,5 +1,5 @@ -import 'package:test/test.dart'; import 'package:stream_chat/src/core/util/serializer.dart'; +import 'package:test/test.dart'; void main() { group('src/models/serialization', () { diff --git a/packages/stream_chat/test/src/core/util/extension_test.dart b/packages/stream_chat/test/src/core/util/extension_test.dart index 98f9f784..ae3c08d8 100644 --- a/packages/stream_chat/test/src/core/util/extension_test.dart +++ b/packages/stream_chat/test/src/core/util/extension_test.dart @@ -1,5 +1,5 @@ -import 'package:test/test.dart'; import 'package:stream_chat/src/core/util/extension.dart'; +import 'package:test/test.dart'; void main() { test('`.withNullifyer` converts the type into non-nullable', () { diff --git a/packages/stream_chat/test/src/fakes.dart b/packages/stream_chat/test/src/fakes.dart index 8d3f6d37..3dcd8ebd 100644 --- a/packages/stream_chat/test/src/fakes.dart +++ b/packages/stream_chat/test/src/fakes.dart @@ -6,11 +6,11 @@ import 'package:rxdart/rxdart.dart'; import 'package:stream_chat/src/core/api/channel_api.dart'; import 'package:stream_chat/src/core/api/device_api.dart'; import 'package:stream_chat/src/core/api/general_api.dart'; +import 'package:stream_chat/src/core/api/guest_api.dart'; import 'package:stream_chat/src/core/api/message_api.dart'; import 'package:stream_chat/src/core/api/moderation_api.dart'; import 'package:stream_chat/src/core/api/stream_chat_api.dart'; import 'package:stream_chat/src/core/api/user_api.dart'; -import 'package:stream_chat/src/core/api/guest_api.dart'; import 'package:stream_chat/src/core/http/token.dart'; import 'package:stream_chat/src/core/http/token_manager.dart'; import 'package:stream_chat/src/ws/websocket.dart'; diff --git a/packages/stream_chat/test/src/ws/websocket_test.dart b/packages/stream_chat/test/src/ws/websocket_test.dart index 94033bf0..5519b2b3 100644 --- a/packages/stream_chat/test/src/ws/websocket_test.dart +++ b/packages/stream_chat/test/src/ws/websocket_test.dart @@ -1,11 +1,11 @@ import 'dart:async'; import 'dart:convert'; +import 'package:mocktail/mocktail.dart'; import 'package:stream_chat/src/core/http/token_manager.dart'; +import 'package:stream_chat/src/ws/websocket.dart'; import 'package:stream_chat/stream_chat.dart'; import 'package:test/test.dart'; -import 'package:mocktail/mocktail.dart'; -import 'package:stream_chat/src/ws/websocket.dart'; import 'package:web_socket_channel/web_socket_channel.dart'; import '../fakes.dart'; diff --git a/packages/stream_chat_flutter/example/lib/split_view.dart b/packages/stream_chat_flutter/example/lib/split_view.dart index 52c0e885..1fd3ccb6 100644 --- a/packages/stream_chat_flutter/example/lib/split_view.dart +++ b/packages/stream_chat_flutter/example/lib/split_view.dart @@ -106,9 +106,7 @@ class ChannelListPage extends StatelessWidget { [StreamChat.of(context).currentUser!.id], ), sort: const [SortOption('last_message_at')], - pagination: const PaginationParams( - limit: 20, - ), + limit: 20, ), ), ); diff --git a/packages/stream_chat_flutter/example/lib/tutorial_part_2.dart b/packages/stream_chat_flutter/example/lib/tutorial_part_2.dart index 5fa9cfdb..6abc260e 100644 --- a/packages/stream_chat_flutter/example/lib/tutorial_part_2.dart +++ b/packages/stream_chat_flutter/example/lib/tutorial_part_2.dart @@ -83,9 +83,7 @@ class ChannelListPage extends StatelessWidget { [StreamChat.of(context).currentUser!.id], ), sort: const [SortOption('last_message_at')], - pagination: const PaginationParams( - limit: 20, - ), + limit: 20, channelWidget: const ChannelPage(), ), ), diff --git a/packages/stream_chat_flutter/example/lib/tutorial_part_3.dart b/packages/stream_chat_flutter/example/lib/tutorial_part_3.dart index 824c0efa..538dbd68 100644 --- a/packages/stream_chat_flutter/example/lib/tutorial_part_3.dart +++ b/packages/stream_chat_flutter/example/lib/tutorial_part_3.dart @@ -85,9 +85,7 @@ class ChannelListPage extends StatelessWidget { ), channelPreviewBuilder: _channelPreviewBuilder, // sort: [SortOption('last_message_at')], - pagination: const PaginationParams( - limit: 20, - ), + limit: 20, channelWidget: const ChannelPage(), ), ), diff --git a/packages/stream_chat_flutter/example/lib/tutorial_part_4.dart b/packages/stream_chat_flutter/example/lib/tutorial_part_4.dart index df94cf06..5a05e4f9 100644 --- a/packages/stream_chat_flutter/example/lib/tutorial_part_4.dart +++ b/packages/stream_chat_flutter/example/lib/tutorial_part_4.dart @@ -69,9 +69,7 @@ class ChannelListPage extends StatelessWidget { [StreamChat.of(context).currentUser!.id], ), sort: const [SortOption('last_message_at')], - pagination: const PaginationParams( - limit: 20, - ), + limit: 20, channelWidget: const ChannelPage(), ), ), diff --git a/packages/stream_chat_flutter/example/lib/tutorial_part_5.dart b/packages/stream_chat_flutter/example/lib/tutorial_part_5.dart index b9089d6a..86ac2e1f 100644 --- a/packages/stream_chat_flutter/example/lib/tutorial_part_5.dart +++ b/packages/stream_chat_flutter/example/lib/tutorial_part_5.dart @@ -75,9 +75,7 @@ class ChannelListPage extends StatelessWidget { [StreamChat.of(context).currentUser!.id], ), sort: const [SortOption('last_message_at')], - pagination: const PaginationParams( - limit: 20, - ), + limit: 20, channelWidget: const ChannelPage(), ), ), diff --git a/packages/stream_chat_flutter/example/lib/tutorial_part_6.dart b/packages/stream_chat_flutter/example/lib/tutorial_part_6.dart index b64281cb..2fbdcbee 100644 --- a/packages/stream_chat_flutter/example/lib/tutorial_part_6.dart +++ b/packages/stream_chat_flutter/example/lib/tutorial_part_6.dart @@ -102,9 +102,7 @@ class ChannelListPage extends StatelessWidget { [StreamChat.of(context).currentUser!.id], ), sort: const [SortOption('last_message_at')], - pagination: const PaginationParams( - limit: 20, - ), + limit: 20, channelWidget: const ChannelPage(), ), ), diff --git a/packages/stream_chat_flutter/lib/src/attachment/attachment_upload_state_builder.dart b/packages/stream_chat_flutter/lib/src/attachment/attachment_upload_state_builder.dart index af46515e..1a0ba989 100644 --- a/packages/stream_chat_flutter/lib/src/attachment/attachment_upload_state_builder.dart +++ b/packages/stream_chat_flutter/lib/src/attachment/attachment_upload_state_builder.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; +import 'package:stream_chat_flutter/src/extension.dart'; import 'package:stream_chat_flutter/src/upload_progress_indicator.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; -import 'package:stream_chat_flutter/src/extension.dart'; /// Widget to build in progress typedef InProgressBuilder = Widget Function(BuildContext, int, int); diff --git a/packages/stream_chat_flutter/lib/src/attachment/file_attachment.dart b/packages/stream_chat_flutter/lib/src/attachment/file_attachment.dart index 6045539f..4d2e8825 100644 --- a/packages/stream_chat_flutter/lib/src/attachment/file_attachment.dart +++ b/packages/stream_chat_flutter/lib/src/attachment/file_attachment.dart @@ -1,15 +1,14 @@ import 'package:cached_network_image/cached_network_image.dart'; import 'package:flutter/material.dart'; import 'package:shimmer/shimmer.dart'; +import 'package:stream_chat_flutter/src/attachment/attachment_widget.dart'; +import 'package:stream_chat_flutter/src/extension.dart'; import 'package:stream_chat_flutter/src/stream_chat_theme.dart'; import 'package:stream_chat_flutter/src/stream_svg_icon.dart'; import 'package:stream_chat_flutter/src/upload_progress_indicator.dart'; import 'package:stream_chat_flutter/src/utils.dart'; import 'package:stream_chat_flutter/src/video_thumbnail_image.dart'; import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; -import 'package:stream_chat_flutter/src/extension.dart'; - -import 'package:stream_chat_flutter/src/attachment/attachment_widget.dart'; /// Widget for displaying file attachments class FileAttachment extends AttachmentWidget { diff --git a/packages/stream_chat_flutter/lib/src/attachment/giphy_attachment.dart b/packages/stream_chat_flutter/lib/src/attachment/giphy_attachment.dart index e165fc6d..8272a962 100644 --- a/packages/stream_chat_flutter/lib/src/attachment/giphy_attachment.dart +++ b/packages/stream_chat_flutter/lib/src/attachment/giphy_attachment.dart @@ -2,10 +2,10 @@ import 'package:cached_network_image/cached_network_image.dart'; import 'package:flutter/material.dart'; import 'package:shimmer/shimmer.dart'; import 'package:stream_chat_flutter/src/attachment/attachment_widget.dart'; +import 'package:stream_chat_flutter/src/extension.dart'; import 'package:stream_chat_flutter/src/visible_footnote.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; -import 'package:stream_chat_flutter/src/extension.dart'; /// Widget for showing a GIF attachment class GiphyAttachment extends AttachmentWidget { diff --git a/packages/stream_chat_flutter/lib/src/channel_bottom_sheet.dart b/packages/stream_chat_flutter/lib/src/channel_bottom_sheet.dart index 5723234d..f4be36e1 100644 --- a/packages/stream_chat_flutter/lib/src/channel_bottom_sheet.dart +++ b/packages/stream_chat_flutter/lib/src/channel_bottom_sheet.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; import 'package:stream_chat_flutter/src/channel_info.dart'; -import 'package:stream_chat_flutter/stream_chat_flutter.dart'; import 'package:stream_chat_flutter/src/extension.dart'; +import 'package:stream_chat_flutter/stream_chat_flutter.dart'; /// Bottom Sheet with options class ChannelBottomSheet extends StatefulWidget { diff --git a/packages/stream_chat_flutter/lib/src/channel_header.dart b/packages/stream_chat_flutter/lib/src/channel_header.dart index 1c12b132..576c0da3 100644 --- a/packages/stream_chat_flutter/lib/src/channel_header.dart +++ b/packages/stream_chat_flutter/lib/src/channel_header.dart @@ -1,12 +1,13 @@ import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; import 'package:stream_chat_flutter/src/back_button.dart'; import 'package:stream_chat_flutter/src/channel_info.dart'; import 'package:stream_chat_flutter/src/channel_name.dart'; +import 'package:stream_chat_flutter/src/extension.dart'; import 'package:stream_chat_flutter/src/info_tile.dart'; import 'package:stream_chat_flutter/src/stream_chat_theme.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; -import 'package:stream_chat_flutter/src/extension.dart'; /// ![screenshot](https://raw.githubusercontent.com/GetStream/stream-chat-flutter/master/packages/stream_chat_flutter/screenshots/channel_header.png) /// ![screenshot](https://raw.githubusercontent.com/GetStream/stream-chat-flutter/master/packages/stream_chat_flutter/screenshots/channel_header_paint.png) @@ -141,8 +142,10 @@ class ChannelHeader extends StatelessWidget implements PreferredSizeWidget { showMessage: showConnectionStateTile && showStatus, message: statusString, child: AppBar( - textTheme: Theme.of(context).textTheme, - brightness: Theme.of(context).brightness, + titleTextStyle: Theme.of(context).appBarTheme.titleTextStyle, + systemOverlayStyle: Theme.of(context).brightness == Brightness.dark + ? SystemUiOverlayStyle.dark + : SystemUiOverlayStyle.light, elevation: 1, leading: leadingWidget, backgroundColor: backgroundColor ?? channelHeaderTheme.color, diff --git a/packages/stream_chat_flutter/lib/src/channel_info.dart b/packages/stream_chat_flutter/lib/src/channel_info.dart index de9d74b9..6bd61dad 100644 --- a/packages/stream_chat_flutter/lib/src/channel_info.dart +++ b/packages/stream_chat_flutter/lib/src/channel_info.dart @@ -1,8 +1,8 @@ import 'package:collection/collection.dart' show IterableExtension; import 'package:flutter/material.dart'; import 'package:jiffy/jiffy.dart'; -import 'package:stream_chat_flutter/stream_chat_flutter.dart'; import 'package:stream_chat_flutter/src/extension.dart'; +import 'package:stream_chat_flutter/stream_chat_flutter.dart'; /// Widget which shows channel info class ChannelInfo extends StatelessWidget { diff --git a/packages/stream_chat_flutter/lib/src/channel_list_header.dart b/packages/stream_chat_flutter/lib/src/channel_list_header.dart index 4ce8b0d2..d95e439d 100644 --- a/packages/stream_chat_flutter/lib/src/channel_list_header.dart +++ b/packages/stream_chat_flutter/lib/src/channel_list_header.dart @@ -1,6 +1,7 @@ import 'dart:ui'; import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:stream_chat_flutter/src/extension.dart'; import 'package:stream_chat_flutter/src/stream_neumorphic_button.dart'; @@ -125,8 +126,10 @@ class ChannelListHeader extends StatelessWidget implements PreferredSizeWidget { showMessage: showConnectionStateTile && showStatus, message: statusString, child: AppBar( - textTheme: Theme.of(context).textTheme, - brightness: Theme.of(context).brightness, + titleTextStyle: Theme.of(context).appBarTheme.titleTextStyle, + systemOverlayStyle: Theme.of(context).brightness == Brightness.dark + ? SystemUiOverlayStyle.dark + : SystemUiOverlayStyle.light, elevation: 1, backgroundColor: backgroundColor ?? channelListHeaderThemeData.color, diff --git a/packages/stream_chat_flutter/lib/src/channel_list_view.dart b/packages/stream_chat_flutter/lib/src/channel_list_view.dart index 0f06e269..96b27823 100644 --- a/packages/stream_chat_flutter/lib/src/channel_list_view.dart +++ b/packages/stream_chat_flutter/lib/src/channel_list_view.dart @@ -4,11 +4,11 @@ import 'package:flutter/material.dart'; import 'package:flutter_slidable/flutter_slidable.dart'; import 'package:shimmer/shimmer.dart'; import 'package:stream_chat_flutter/src/channel_bottom_sheet.dart'; +import 'package:stream_chat_flutter/src/extension.dart'; import 'package:stream_chat_flutter/src/stream_svg_icon.dart'; import 'package:stream_chat_flutter/src/theme/themes.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; -import 'package:stream_chat_flutter/src/extension.dart'; /// Callback called when tapping on a channel typedef ChannelTapCallback = void Function(Channel, Widget?); diff --git a/packages/stream_chat_flutter/lib/src/channel_name.dart b/packages/stream_chat_flutter/lib/src/channel_name.dart index 918aa076..258893c8 100644 --- a/packages/stream_chat_flutter/lib/src/channel_name.dart +++ b/packages/stream_chat_flutter/lib/src/channel_name.dart @@ -1,8 +1,8 @@ import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; +import 'package:stream_chat_flutter/src/extension.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; -import 'package:stream_chat_flutter/src/extension.dart'; /// It shows the current [Channel] name using a [Text] widget. /// diff --git a/packages/stream_chat_flutter/lib/src/date_divider.dart b/packages/stream_chat_flutter/lib/src/date_divider.dart index 3d85299c..7c977f60 100644 --- a/packages/stream_chat_flutter/lib/src/date_divider.dart +++ b/packages/stream_chat_flutter/lib/src/date_divider.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; import 'package:jiffy/jiffy.dart'; -import 'package:stream_chat_flutter/src/stream_chat_theme.dart'; import 'package:stream_chat_flutter/src/extension.dart'; +import 'package:stream_chat_flutter/src/stream_chat_theme.dart'; /// It shows a date divider depending on the date difference class DateDivider extends StatelessWidget { diff --git a/packages/stream_chat_flutter/lib/src/gallery_footer.dart b/packages/stream_chat_flutter/lib/src/gallery_footer.dart index 2b2858f8..eaa03fb7 100644 --- a/packages/stream_chat_flutter/lib/src/gallery_footer.dart +++ b/packages/stream_chat_flutter/lib/src/gallery_footer.dart @@ -7,12 +7,12 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:path_provider/path_provider.dart'; import 'package:share_plus/share_plus.dart'; +import 'package:stream_chat_flutter/src/extension.dart'; import 'package:stream_chat_flutter/src/stream_chat_theme.dart'; import 'package:stream_chat_flutter/src/theme/themes.dart'; import 'package:stream_chat_flutter/src/video_thumbnail_image.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; -import 'package:stream_chat_flutter/src/extension.dart'; /// Footer widget for media display class GalleryFooter extends StatefulWidget implements PreferredSizeWidget { diff --git a/packages/stream_chat_flutter/lib/src/gallery_header.dart b/packages/stream_chat_flutter/lib/src/gallery_header.dart index 74eeb4ee..09f79016 100644 --- a/packages/stream_chat_flutter/lib/src/gallery_header.dart +++ b/packages/stream_chat_flutter/lib/src/gallery_header.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; import 'package:stream_chat_flutter/src/attachment_actions_modal.dart'; import 'package:stream_chat_flutter/src/stream_chat_theme.dart'; import 'package:stream_chat_flutter/src/stream_svg_icon.dart'; @@ -58,8 +59,10 @@ class GalleryHeader extends StatelessWidget implements PreferredSizeWidget { Widget build(BuildContext context) { final galleryHeaderThemeData = GalleryHeaderTheme.of(context); return AppBar( - textTheme: Theme.of(context).textTheme, - brightness: Theme.of(context).brightness, + titleTextStyle: Theme.of(context).appBarTheme.titleTextStyle, + systemOverlayStyle: Theme.of(context).brightness == Brightness.dark + ? SystemUiOverlayStyle.dark + : SystemUiOverlayStyle.light, elevation: 1, leading: showBackButton ? IconButton( diff --git a/packages/stream_chat_flutter/lib/src/message_reactions_modal.dart b/packages/stream_chat_flutter/lib/src/message_reactions_modal.dart index d2c69e4b..e9f2e55e 100644 --- a/packages/stream_chat_flutter/lib/src/message_reactions_modal.dart +++ b/packages/stream_chat_flutter/lib/src/message_reactions_modal.dart @@ -1,6 +1,7 @@ import 'dart:ui'; 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_picker.dart'; import 'package:stream_chat_flutter/src/stream_chat.dart'; @@ -8,7 +9,6 @@ import 'package:stream_chat_flutter/src/theme/themes.dart'; import 'package:stream_chat_flutter/src/user_avatar.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; -import 'package:stream_chat_flutter/src/extension.dart'; /// Modal widget for displaying message reactions class MessageReactionsModal extends StatelessWidget { diff --git a/packages/stream_chat_flutter/lib/src/message_search_item.dart b/packages/stream_chat_flutter/lib/src/message_search_item.dart index ab41532c..89d43531 100644 --- a/packages/stream_chat_flutter/lib/src/message_search_item.dart +++ b/packages/stream_chat_flutter/lib/src/message_search_item.dart @@ -1,8 +1,8 @@ import 'package:flutter/material.dart'; import 'package:jiffy/jiffy.dart'; +import 'package:stream_chat_flutter/src/extension.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; -import 'package:stream_chat_flutter/src/extension.dart'; /// It shows the current [Message] preview. /// diff --git a/packages/stream_chat_flutter/lib/src/message_search_list_view.dart b/packages/stream_chat_flutter/lib/src/message_search_list_view.dart index ab1a9178..5c8c2bc6 100644 --- a/packages/stream_chat_flutter/lib/src/message_search_list_view.dart +++ b/packages/stream_chat_flutter/lib/src/message_search_list_view.dart @@ -1,10 +1,10 @@ import 'package:flutter/material.dart'; +import 'package:stream_chat_flutter/src/extension.dart'; import 'package:stream_chat_flutter/src/info_tile.dart'; import 'package:stream_chat_flutter/src/message_search_item.dart'; import 'package:stream_chat_flutter/src/theme/themes.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; -import 'package:stream_chat_flutter/src/extension.dart'; /// Callback called when tapping on a user typedef MessageSearchItemTapCallback = void Function(GetMessageResponse); diff --git a/packages/stream_chat_flutter/lib/src/stream_chat.dart b/packages/stream_chat_flutter/lib/src/stream_chat.dart index f185d288..af617d34 100644 --- a/packages/stream_chat_flutter/lib/src/stream_chat.dart +++ b/packages/stream_chat_flutter/lib/src/stream_chat.dart @@ -100,7 +100,9 @@ class StreamChatState extends State { return Theme( data: materialTheme.copyWith( primaryIconTheme: streamTheme.primaryIconTheme, - accentColor: streamTheme.colorTheme.accentPrimary, + colorScheme: materialTheme.colorScheme.copyWith( + secondary: streamTheme.colorTheme.accentPrimary, + ), ), child: StreamChatCore( client: client, diff --git a/packages/stream_chat_flutter/lib/src/stream_chat_theme.dart b/packages/stream_chat_flutter/lib/src/stream_chat_theme.dart index f7a05d7e..0e50f132 100644 --- a/packages/stream_chat_flutter/lib/src/stream_chat_theme.dart +++ b/packages/stream_chat_flutter/lib/src/stream_chat_theme.dart @@ -131,7 +131,7 @@ class StreamChatThemeData { final defaultTheme = StreamChatThemeData(brightness: theme.brightness); final customizedTheme = StreamChatThemeData.fromColorAndTextTheme( defaultTheme.colorTheme.copyWith( - accentPrimary: theme.accentColor, + accentPrimary: theme.colorScheme.secondary, ), defaultTheme.textTheme, ); diff --git a/packages/stream_chat_flutter/lib/src/thread_header.dart b/packages/stream_chat_flutter/lib/src/thread_header.dart index 55a270fc..60bfed89 100644 --- a/packages/stream_chat_flutter/lib/src/thread_header.dart +++ b/packages/stream_chat_flutter/lib/src/thread_header.dart @@ -1,8 +1,9 @@ import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:stream_chat_flutter/src/extension.dart'; import 'package:stream_chat_flutter/src/stream_chat_theme.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; -import 'package:stream_chat_flutter/src/extension.dart'; /// ![screenshot](https://raw.githubusercontent.com/GetStream/stream-chat-flutter/master/packages/stream_chat_flutter/screenshots/thread_header.png) /// ![screenshot](https://raw.githubusercontent.com/GetStream/stream-chat-flutter/master/packages/stream_chat_flutter/screenshots/thread_header_paint.png) @@ -129,8 +130,10 @@ class ThreadHeader extends StatelessWidget implements PreferredSizeWidget { return AppBar( automaticallyImplyLeading: false, - textTheme: Theme.of(context).textTheme, - brightness: Theme.of(context).brightness, + titleTextStyle: Theme.of(context).appBarTheme.titleTextStyle, + systemOverlayStyle: Theme.of(context).brightness == Brightness.dark + ? SystemUiOverlayStyle.dark + : SystemUiOverlayStyle.light, elevation: 1, leading: leading ?? (showBackButton diff --git a/packages/stream_chat_flutter/lib/src/typing_indicator.dart b/packages/stream_chat_flutter/lib/src/typing_indicator.dart index 48752f07..5d425e58 100644 --- a/packages/stream_chat_flutter/lib/src/typing_indicator.dart +++ b/packages/stream_chat_flutter/lib/src/typing_indicator.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; import 'package:lottie/lottie.dart'; -import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; import 'package:stream_chat_flutter/src/extension.dart'; +import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; /// Widget to show the current list of typing users class TypingIndicator extends StatelessWidget { diff --git a/packages/stream_chat_flutter/lib/src/user_item.dart b/packages/stream_chat_flutter/lib/src/user_item.dart index a55693aa..a2622385 100644 --- a/packages/stream_chat_flutter/lib/src/user_item.dart +++ b/packages/stream_chat_flutter/lib/src/user_item.dart @@ -1,10 +1,10 @@ import 'package:flutter/material.dart'; import 'package:jiffy/jiffy.dart'; +import 'package:stream_chat_flutter/src/extension.dart'; import 'package:stream_chat_flutter/src/stream_svg_icon.dart'; import 'package:stream_chat_flutter/src/user_list_view.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; -import 'package:stream_chat_flutter/src/extension.dart'; /// /// It shows the current [User] preview. diff --git a/packages/stream_chat_flutter/lib/src/user_list_view.dart b/packages/stream_chat_flutter/lib/src/user_list_view.dart index b946f800..98cf6621 100644 --- a/packages/stream_chat_flutter/lib/src/user_list_view.dart +++ b/packages/stream_chat_flutter/lib/src/user_list_view.dart @@ -1,8 +1,8 @@ import 'package:flutter/material.dart'; +import 'package:stream_chat_flutter/src/extension.dart'; import 'package:stream_chat_flutter/src/theme/themes.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; -import 'package:stream_chat_flutter/src/extension.dart'; /// Callback called when tapping on a user typedef UserTapCallback = void Function(User, Widget?); diff --git a/packages/stream_chat_flutter/lib/src/utils.dart b/packages/stream_chat_flutter/lib/src/utils.dart index 4dc9fee7..252bd87b 100644 --- a/packages/stream_chat_flutter/lib/src/utils.dart +++ b/packages/stream_chat_flutter/lib/src/utils.dart @@ -1,10 +1,10 @@ import 'dart:async'; import 'package:flutter/material.dart'; +import 'package:stream_chat_flutter/src/extension.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; import 'package:url_launcher/url_launcher.dart'; -import 'package:stream_chat_flutter/src/extension.dart'; /// Launch URL Future launchURL(BuildContext context, String url) async { diff --git a/packages/stream_chat_flutter/lib/src/visible_footnote.dart b/packages/stream_chat_flutter/lib/src/visible_footnote.dart index 8d16ce41..18bb976d 100644 --- a/packages/stream_chat_flutter/lib/src/visible_footnote.dart +++ b/packages/stream_chat_flutter/lib/src/visible_footnote.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; +import 'package:stream_chat_flutter/src/extension.dart'; import 'package:stream_chat_flutter/src/stream_chat_theme.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; -import 'package:stream_chat_flutter/src/extension.dart'; /// Widget for displaying a footnote class VisibleFootnote extends StatelessWidget { diff --git a/packages/stream_chat_flutter/test/src/theme/gallery_footer_theme_test.dart b/packages/stream_chat_flutter/test/src/theme/gallery_footer_theme_test.dart index fb8d7035..6a73e34e 100644 --- a/packages/stream_chat_flutter/test/src/theme/gallery_footer_theme_test.dart +++ b/packages/stream_chat_flutter/test/src/theme/gallery_footer_theme_test.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart' hide TextTheme; import 'package:flutter_test/flutter_test.dart'; -import 'package:stream_chat_flutter/stream_chat_flutter.dart'; import 'package:mocktail/mocktail.dart'; +import 'package:stream_chat_flutter/stream_chat_flutter.dart'; class MockStreamChatClient extends Mock implements StreamChatClient {} diff --git a/packages/stream_chat_flutter/test/src/theme/gallery_header_theme_test.dart b/packages/stream_chat_flutter/test/src/theme/gallery_header_theme_test.dart index aefbb319..534008ba 100644 --- a/packages/stream_chat_flutter/test/src/theme/gallery_header_theme_test.dart +++ b/packages/stream_chat_flutter/test/src/theme/gallery_header_theme_test.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; -import 'package:stream_chat_flutter/stream_chat_flutter.dart'; import 'package:mocktail/mocktail.dart'; +import 'package:stream_chat_flutter/stream_chat_flutter.dart'; class MockStreamChatClient extends Mock implements StreamChatClient {} diff --git a/packages/stream_chat_flutter_core/test/channel_list_core_test.dart b/packages/stream_chat_flutter_core/test/channel_list_core_test.dart index dd87d2cf..8407be67 100644 --- a/packages/stream_chat_flutter_core/test/channel_list_core_test.dart +++ b/packages/stream_chat_flutter_core/test/channel_list_core_test.dart @@ -129,7 +129,7 @@ void main() { emptyBuilder: (BuildContext context) => const Offstage(), errorBuilder: (BuildContext context, Object error) => Container(key: errorWidgetKey), - pagination: pagination, + limit: pagination.limit, ); final mockClient = MockClient(); @@ -186,7 +186,7 @@ void main() { loadingBuilder: (BuildContext context) => const Offstage(), emptyBuilder: (BuildContext context) => Container(key: emptyWidgetKey), errorBuilder: (BuildContext context, Object error) => const Offstage(), - pagination: pagination, + limit: pagination.limit, ); final mockClient = MockClient(); @@ -243,7 +243,7 @@ void main() { loadingBuilder: (BuildContext context) => const Offstage(), emptyBuilder: (BuildContext context) => const Offstage(), errorBuilder: (BuildContext context, Object error) => const Offstage(), - pagination: pagination, + limit: pagination.limit, ); final mockClient = MockClient(); @@ -306,7 +306,7 @@ void main() { loadingBuilder: (BuildContext context) => const Offstage(), emptyBuilder: (BuildContext context) => const Offstage(), errorBuilder: (BuildContext context, Object error) => const Offstage(), - pagination: pagination, + limit: pagination.limit, ); final mockClient = MockClient(); @@ -420,7 +420,7 @@ void main() { emptyBuilder: (BuildContext context) => const Offstage(), errorBuilder: (BuildContext context, Object error) => const Offstage(), - pagination: pagination.copyWith(limit: limit), + limit: limit, ); final mockClient = MockClient(); @@ -516,7 +516,7 @@ void main() { loadingBuilder: (BuildContext context) => const Offstage(), emptyBuilder: (BuildContext context) => const Offstage(), errorBuilder: (BuildContext context, Object error) => const Offstage(), - pagination: pagination, + limit: pagination.limit, ); expect(channelListCore.limit, pagination.limit); diff --git a/packages/stream_chat_flutter_core/test/message_search_list_core_test.dart b/packages/stream_chat_flutter_core/test/message_search_list_core_test.dart index ca416595..475c6c23 100644 --- a/packages/stream_chat_flutter_core/test/message_search_list_core_test.dart +++ b/packages/stream_chat_flutter_core/test/message_search_list_core_test.dart @@ -334,7 +334,7 @@ void main() { loadingBuilder: (BuildContext context) => const Offstage(), emptyBuilder: (BuildContext context) => const Offstage(), errorBuilder: (BuildContext context, Object error) => const Offstage(), - paginationParams: pagination, + limit: pagination.limit, filters: testFilter, messageFilters: testMessageFilter, ); @@ -457,7 +457,7 @@ void main() { emptyBuilder: (BuildContext context) => const Offstage(), errorBuilder: (BuildContext context, Object error) => const Offstage(), - paginationParams: pagination.copyWith(limit: limit), + limit: limit, filters: testFilter, messageFilters: testMessageFilter, ); @@ -561,7 +561,7 @@ void main() { errorBuilder: (BuildContext context, Object? error) => const Offstage(), filters: testFilter, messageFilters: testMessageFilter, - paginationParams: pagination, + limit: pagination.limit, ); expect(messageSearchListCore.limit, pagination.limit); diff --git a/packages/stream_chat_flutter_core/test/user_list_core_test.dart b/packages/stream_chat_flutter_core/test/user_list_core_test.dart index ade58e0f..b077868b 100644 --- a/packages/stream_chat_flutter_core/test/user_list_core_test.dart +++ b/packages/stream_chat_flutter_core/test/user_list_core_test.dart @@ -341,7 +341,7 @@ void main() { loadingBuilder: (BuildContext context) => const Offstage(), emptyBuilder: (BuildContext context) => const Offstage(), errorBuilder: (BuildContext context, Object error) => const Offstage(), - pagination: pagination, + limit: pagination.limit, groupAlphabetically: true, ); @@ -447,7 +447,7 @@ void main() { emptyBuilder: (BuildContext context) => const Offstage(), errorBuilder: (BuildContext context, Object error) => const Offstage(), - pagination: pagination.copyWith(limit: limit), + limit: limit, groupAlphabetically: true, ); @@ -522,15 +522,15 @@ void main() { ); test('`widget.limit` should match `widget.pagination.limit`', () { - const pagination = PaginationParams(limit: 30); + const limit = 30; final userListCore = UserListCore( listBuilder: (_, __) => const Offstage(), loadingBuilder: (BuildContext context) => const Offstage(), emptyBuilder: (BuildContext context) => const Offstage(), errorBuilder: (BuildContext context, Object error) => const Offstage(), - pagination: pagination, + limit: limit, ); - expect(userListCore.limit, pagination.limit); + expect(userListCore.limit, limit); }); } diff --git a/packages/stream_chat_localizations/test/override_test.dart b/packages/stream_chat_localizations/test/override_test.dart index 3e134b6f..513dd336 100644 --- a/packages/stream_chat_localizations/test/override_test.dart +++ b/packages/stream_chat_localizations/test/override_test.dart @@ -2,9 +2,9 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; import 'package:stream_chat_localizations/src/stream_chat_localizations.dart'; -import 'package:flutter_test/flutter_test.dart'; class FooStreamChatLocalizations extends StreamChatLocalizationsEn { FooStreamChatLocalizations( diff --git a/packages/stream_chat_persistence/lib/src/db/shared/native_db.dart b/packages/stream_chat_persistence/lib/src/db/shared/native_db.dart index 27aa23b5..d70426e1 100644 --- a/packages/stream_chat_persistence/lib/src/db/shared/native_db.dart +++ b/packages/stream_chat_persistence/lib/src/db/shared/native_db.dart @@ -7,11 +7,10 @@ import 'package:moor/isolate.dart'; import 'package:moor/moor.dart'; import 'package:path/path.dart'; import 'package:path_provider/path_provider.dart'; +import 'package:stream_chat_persistence/src/db/moor_chat_database.dart'; import 'package:stream_chat_persistence/src/stream_chat_persistence_client.dart'; import 'package:stream_chat_persistence/stream_chat_persistence.dart'; -import 'package:stream_chat_persistence/src/db/moor_chat_database.dart'; - /// A Helper class to construct new instances of [MoorChatDatabase] specifically /// for native platform applications class SharedDB { diff --git a/packages/stream_chat_persistence/lib/src/db/shared/web_db.dart b/packages/stream_chat_persistence/lib/src/db/shared/web_db.dart index bb664443..5b1d8311 100644 --- a/packages/stream_chat_persistence/lib/src/db/shared/web_db.dart +++ b/packages/stream_chat_persistence/lib/src/db/shared/web_db.dart @@ -1,8 +1,7 @@ // coverage:ignore-file import 'package:moor/moor_web.dart'; -import 'package:stream_chat_persistence/src/stream_chat_persistence_client.dart'; - import 'package:stream_chat_persistence/src/db/moor_chat_database.dart'; +import 'package:stream_chat_persistence/src/stream_chat_persistence_client.dart'; /// A Helper class to construct new instances of [MoorChatDatabase] specifically /// for Web applications diff --git a/packages/stream_chat_persistence/test/src/converter/list_coverter_test.dart b/packages/stream_chat_persistence/test/src/converter/list_coverter_test.dart index 73ce2452..a5fe7a49 100644 --- a/packages/stream_chat_persistence/test/src/converter/list_coverter_test.dart +++ b/packages/stream_chat_persistence/test/src/converter/list_coverter_test.dart @@ -1,7 +1,7 @@ import 'dart:convert'; -import 'package:test/test.dart'; import 'package:stream_chat_persistence/src/converter/list_converter.dart'; +import 'package:test/test.dart'; void main() { group('mapToDart', () { diff --git a/packages/stream_chat_persistence/test/src/converter/map_converter_test.dart b/packages/stream_chat_persistence/test/src/converter/map_converter_test.dart index 4727cb0d..6e14140b 100644 --- a/packages/stream_chat_persistence/test/src/converter/map_converter_test.dart +++ b/packages/stream_chat_persistence/test/src/converter/map_converter_test.dart @@ -1,7 +1,7 @@ import 'dart:convert'; -import 'package:test/test.dart'; import 'package:stream_chat_persistence/src/converter/map_converter.dart'; +import 'package:test/test.dart'; void main() { group('mapToDart', () { diff --git a/packages/stream_chat_persistence/test/src/converter/message_sending_status_converter_test.dart b/packages/stream_chat_persistence/test/src/converter/message_sending_status_converter_test.dart index 1035a759..c53d8fb4 100644 --- a/packages/stream_chat_persistence/test/src/converter/message_sending_status_converter_test.dart +++ b/packages/stream_chat_persistence/test/src/converter/message_sending_status_converter_test.dart @@ -1,6 +1,6 @@ -import 'package:test/test.dart'; import 'package:stream_chat/stream_chat.dart'; import 'package:stream_chat_persistence/src/converter/message_sending_status_converter.dart'; +import 'package:test/test.dart'; void main() { group('mapToDart', () { diff --git a/packages/stream_chat_persistence/test/src/dao/user_dao_test.dart b/packages/stream_chat_persistence/test/src/dao/user_dao_test.dart index 5b5d04d4..1fd4eafd 100644 --- a/packages/stream_chat_persistence/test/src/dao/user_dao_test.dart +++ b/packages/stream_chat_persistence/test/src/dao/user_dao_test.dart @@ -1,9 +1,9 @@ import 'dart:math' as math; +import 'package:stream_chat/stream_chat.dart'; import 'package:stream_chat_persistence/src/dao/dao.dart'; import 'package:stream_chat_persistence/src/db/moor_chat_database.dart'; import 'package:test/test.dart'; -import 'package:stream_chat/stream_chat.dart'; import '../../stream_chat_persistence_client_test.dart'; diff --git a/packages/stream_chat_persistence/test/src/db/moor_chat_database_test.dart b/packages/stream_chat_persistence/test/src/db/moor_chat_database_test.dart index 2ec4c76b..101b44ed 100644 --- a/packages/stream_chat_persistence/test/src/db/moor_chat_database_test.dart +++ b/packages/stream_chat_persistence/test/src/db/moor_chat_database_test.dart @@ -1,8 +1,8 @@ -import 'package:test/test.dart'; import 'package:moor/ffi.dart'; import 'package:moor/isolate.dart'; import 'package:moor/moor.dart' hide isNotNull; import 'package:stream_chat_persistence/src/db/moor_chat_database.dart'; +import 'package:test/test.dart'; DatabaseConnection _backgroundConnection() => DatabaseConnection.fromExecutor(VmDatabase.memory()); diff --git a/packages/stream_chat_persistence/test/src/mapper/channel_mapper_test.dart b/packages/stream_chat_persistence/test/src/mapper/channel_mapper_test.dart index 93789cc7..b45f46b4 100644 --- a/packages/stream_chat_persistence/test/src/mapper/channel_mapper_test.dart +++ b/packages/stream_chat_persistence/test/src/mapper/channel_mapper_test.dart @@ -1,9 +1,10 @@ import 'dart:math' as math; -import 'package:test/test.dart'; import 'package:stream_chat/stream_chat.dart'; import 'package:stream_chat_persistence/src/db/moor_chat_database.dart'; import 'package:stream_chat_persistence/src/mapper/channel_mapper.dart'; +import 'package:test/test.dart'; + import '../utils/date_matcher.dart'; void main() { diff --git a/packages/stream_chat_persistence/test/src/mapper/member_mapper_test.dart b/packages/stream_chat_persistence/test/src/mapper/member_mapper_test.dart index feca753b..b3f834ef 100644 --- a/packages/stream_chat_persistence/test/src/mapper/member_mapper_test.dart +++ b/packages/stream_chat_persistence/test/src/mapper/member_mapper_test.dart @@ -1,9 +1,9 @@ import 'dart:math' as math; -import 'package:test/test.dart'; import 'package:stream_chat/stream_chat.dart'; import 'package:stream_chat_persistence/src/db/moor_chat_database.dart'; import 'package:stream_chat_persistence/src/mapper/member_mapper.dart'; +import 'package:test/test.dart'; import '../utils/date_matcher.dart'; diff --git a/packages/stream_chat_persistence/test/src/mapper/message_mapper_test.dart b/packages/stream_chat_persistence/test/src/mapper/message_mapper_test.dart index 0f9e8cf6..52a58a1a 100644 --- a/packages/stream_chat_persistence/test/src/mapper/message_mapper_test.dart +++ b/packages/stream_chat_persistence/test/src/mapper/message_mapper_test.dart @@ -1,10 +1,10 @@ import 'dart:convert'; import 'dart:math' as math; -import 'package:test/test.dart'; import 'package:stream_chat/stream_chat.dart'; import 'package:stream_chat_persistence/src/db/moor_chat_database.dart'; import 'package:stream_chat_persistence/src/mapper/message_mapper.dart'; +import 'package:test/test.dart'; import '../utils/date_matcher.dart'; diff --git a/packages/stream_chat_persistence/test/src/mapper/pinned_message_mapper_test.dart b/packages/stream_chat_persistence/test/src/mapper/pinned_message_mapper_test.dart index 5150df62..40c4ff6c 100644 --- a/packages/stream_chat_persistence/test/src/mapper/pinned_message_mapper_test.dart +++ b/packages/stream_chat_persistence/test/src/mapper/pinned_message_mapper_test.dart @@ -1,10 +1,10 @@ import 'dart:convert'; import 'dart:math' as math; -import 'package:test/test.dart'; import 'package:stream_chat/stream_chat.dart'; import 'package:stream_chat_persistence/src/db/moor_chat_database.dart'; import 'package:stream_chat_persistence/src/mapper/pinned_message_mapper.dart'; +import 'package:test/test.dart'; import '../utils/date_matcher.dart'; diff --git a/packages/stream_chat_persistence/test/src/mapper/pinned_message_reaction_mapper_test.dart b/packages/stream_chat_persistence/test/src/mapper/pinned_message_reaction_mapper_test.dart index bd8d0ba8..b814b7dc 100644 --- a/packages/stream_chat_persistence/test/src/mapper/pinned_message_reaction_mapper_test.dart +++ b/packages/stream_chat_persistence/test/src/mapper/pinned_message_reaction_mapper_test.dart @@ -1,7 +1,7 @@ -import 'package:test/test.dart'; import 'package:stream_chat/stream_chat.dart'; import 'package:stream_chat_persistence/src/db/moor_chat_database.dart'; import 'package:stream_chat_persistence/src/mapper/pinned_message_reaction_mapper.dart'; +import 'package:test/test.dart'; import '../utils/date_matcher.dart'; diff --git a/packages/stream_chat_persistence/test/src/mapper/reaction_mapper_test.dart b/packages/stream_chat_persistence/test/src/mapper/reaction_mapper_test.dart index cb5d231e..e211f685 100644 --- a/packages/stream_chat_persistence/test/src/mapper/reaction_mapper_test.dart +++ b/packages/stream_chat_persistence/test/src/mapper/reaction_mapper_test.dart @@ -1,7 +1,7 @@ -import 'package:test/test.dart'; import 'package:stream_chat/stream_chat.dart'; import 'package:stream_chat_persistence/src/db/moor_chat_database.dart'; import 'package:stream_chat_persistence/src/mapper/reaction_mapper.dart'; +import 'package:test/test.dart'; import '../utils/date_matcher.dart'; diff --git a/packages/stream_chat_persistence/test/src/mapper/read_mapper_test.dart b/packages/stream_chat_persistence/test/src/mapper/read_mapper_test.dart index 73c87da3..eb296a75 100644 --- a/packages/stream_chat_persistence/test/src/mapper/read_mapper_test.dart +++ b/packages/stream_chat_persistence/test/src/mapper/read_mapper_test.dart @@ -1,7 +1,7 @@ -import 'package:test/test.dart'; import 'package:stream_chat/stream_chat.dart'; import 'package:stream_chat_persistence/src/db/moor_chat_database.dart'; import 'package:stream_chat_persistence/src/mapper/read_mapper.dart'; +import 'package:test/test.dart'; import '../utils/date_matcher.dart'; diff --git a/packages/stream_chat_persistence/test/src/mapper/user_mapper_test.dart b/packages/stream_chat_persistence/test/src/mapper/user_mapper_test.dart index eca2a32a..55dbbff4 100644 --- a/packages/stream_chat_persistence/test/src/mapper/user_mapper_test.dart +++ b/packages/stream_chat_persistence/test/src/mapper/user_mapper_test.dart @@ -1,8 +1,9 @@ import 'dart:math' as math; -import 'package:test/test.dart'; + import 'package:stream_chat/stream_chat.dart'; import 'package:stream_chat_persistence/src/db/moor_chat_database.dart'; import 'package:stream_chat_persistence/src/mapper/user_mapper.dart'; +import 'package:test/test.dart'; import '../utils/date_matcher.dart'; From 5e9bd3fb789af266513d8f95d5dd609ebdbcda0f Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Wed, 22 Sep 2021 11:01:16 +0200 Subject: [PATCH 34/70] chore(ui,core,llc,persistance,localization): change AppBar props --- .../stream_chat_flutter/lib/src/channel_header.dart | 11 +++++++---- .../lib/src/channel_list_header.dart | 10 ++++++---- .../stream_chat_flutter/lib/src/gallery_header.dart | 10 ++++++---- .../stream_chat_flutter/lib/src/thread_header.dart | 10 ++++++---- 4 files changed, 25 insertions(+), 16 deletions(-) diff --git a/packages/stream_chat_flutter/lib/src/channel_header.dart b/packages/stream_chat_flutter/lib/src/channel_header.dart index 576c0da3..62f994dc 100644 --- a/packages/stream_chat_flutter/lib/src/channel_header.dart +++ b/packages/stream_chat_flutter/lib/src/channel_header.dart @@ -138,14 +138,17 @@ class ChannelHeader extends StatelessWidget implements PreferredSizeWidget { break; } + final theme = Theme.of(context); + return InfoTile( showMessage: showConnectionStateTile && showStatus, message: statusString, child: AppBar( - titleTextStyle: Theme.of(context).appBarTheme.titleTextStyle, - systemOverlayStyle: Theme.of(context).brightness == Brightness.dark - ? SystemUiOverlayStyle.dark - : SystemUiOverlayStyle.light, + toolbarTextStyle: theme.textTheme.bodyText2, + titleTextStyle: theme.textTheme.headline6, + systemOverlayStyle: theme.brightness == Brightness.dark + ? SystemUiOverlayStyle.light + : SystemUiOverlayStyle.dark, elevation: 1, leading: leadingWidget, backgroundColor: backgroundColor ?? channelHeaderTheme.color, diff --git a/packages/stream_chat_flutter/lib/src/channel_list_header.dart b/packages/stream_chat_flutter/lib/src/channel_list_header.dart index d95e439d..2dfc1ffb 100644 --- a/packages/stream_chat_flutter/lib/src/channel_list_header.dart +++ b/packages/stream_chat_flutter/lib/src/channel_list_header.dart @@ -122,14 +122,16 @@ class ChannelListHeader extends StatelessWidget implements PreferredSizeWidget { final chatThemeData = StreamChatTheme.of(context); final channelListHeaderThemeData = ChannelListHeaderTheme.of(context); + final theme = Theme.of(context); return InfoTile( showMessage: showConnectionStateTile && showStatus, message: statusString, child: AppBar( - titleTextStyle: Theme.of(context).appBarTheme.titleTextStyle, - systemOverlayStyle: Theme.of(context).brightness == Brightness.dark - ? SystemUiOverlayStyle.dark - : SystemUiOverlayStyle.light, + toolbarTextStyle: theme.textTheme.bodyText2, + titleTextStyle: theme.textTheme.headline6, + systemOverlayStyle: theme.brightness == Brightness.dark + ? SystemUiOverlayStyle.light + : SystemUiOverlayStyle.dark, elevation: 1, backgroundColor: backgroundColor ?? channelListHeaderThemeData.color, diff --git a/packages/stream_chat_flutter/lib/src/gallery_header.dart b/packages/stream_chat_flutter/lib/src/gallery_header.dart index 09f79016..fc5f67b4 100644 --- a/packages/stream_chat_flutter/lib/src/gallery_header.dart +++ b/packages/stream_chat_flutter/lib/src/gallery_header.dart @@ -58,11 +58,13 @@ class GalleryHeader extends StatelessWidget implements PreferredSizeWidget { @override Widget build(BuildContext context) { final galleryHeaderThemeData = GalleryHeaderTheme.of(context); + final theme = Theme.of(context); return AppBar( - titleTextStyle: Theme.of(context).appBarTheme.titleTextStyle, - systemOverlayStyle: Theme.of(context).brightness == Brightness.dark - ? SystemUiOverlayStyle.dark - : SystemUiOverlayStyle.light, + toolbarTextStyle: theme.textTheme.bodyText2, + titleTextStyle: theme.textTheme.headline6, + systemOverlayStyle: theme.brightness == Brightness.dark + ? SystemUiOverlayStyle.light + : SystemUiOverlayStyle.dark, elevation: 1, leading: showBackButton ? IconButton( diff --git a/packages/stream_chat_flutter/lib/src/thread_header.dart b/packages/stream_chat_flutter/lib/src/thread_header.dart index 60bfed89..dfe13b41 100644 --- a/packages/stream_chat_flutter/lib/src/thread_header.dart +++ b/packages/stream_chat_flutter/lib/src/thread_header.dart @@ -128,12 +128,14 @@ class ThreadHeader extends StatelessWidget implements PreferredSizeWidget { ], ); + final theme = Theme.of(context); return AppBar( automaticallyImplyLeading: false, - titleTextStyle: Theme.of(context).appBarTheme.titleTextStyle, - systemOverlayStyle: Theme.of(context).brightness == Brightness.dark - ? SystemUiOverlayStyle.dark - : SystemUiOverlayStyle.light, + toolbarTextStyle: theme.textTheme.bodyText2, + titleTextStyle: theme.textTheme.headline6, + systemOverlayStyle: theme.brightness == Brightness.dark + ? SystemUiOverlayStyle.light + : SystemUiOverlayStyle.dark, elevation: 1, leading: leading ?? (showBackButton From b7fe7287a582820a7dd3c1d953a654d0648db3f5 Mon Sep 17 00:00:00 2001 From: Gordon Hayes Date: Wed, 22 Sep 2021 13:00:11 +0200 Subject: [PATCH 35/70] docs: change paginationParams to limit --- .../message_search_list_view.mdx | 2 +- .../message_search_list_core.mdx | 6 +++--- .../lib/src/message_search_list_view.dart | 14 +++++++------- .../lib/src/message_search_list_core.dart | 6 +++--- 4 files changed, 14 insertions(+), 14 deletions(-) diff --git a/docusaurus/docs/Flutter/stream_chat_flutter/message_search_list_view.mdx b/docusaurus/docs/Flutter/stream_chat_flutter/message_search_list_view.mdx index 864b5e49..39bf2d9e 100644 --- a/docusaurus/docs/Flutter/stream_chat_flutter/message_search_list_view.mdx +++ b/docusaurus/docs/Flutter/stream_chat_flutter/message_search_list_view.mdx @@ -29,7 +29,7 @@ class MessageSearchPage extends StatelessWidget { child: MessageSearchListView( filters: Filter.in_('members', [StreamChat.of(context).user!.id],), messageQuery: 'your query here', - paginationParams: PaginationParams(limit: 20), + limit: 20, ), ), ); diff --git a/docusaurus/docs/Flutter/stream_chat_flutter_core/message_search_list_core.mdx b/docusaurus/docs/Flutter/stream_chat_flutter_core/message_search_list_core.mdx index 70cc026f..f3be5e85 100644 --- a/docusaurus/docs/Flutter/stream_chat_flutter_core/message_search_list_core.mdx +++ b/docusaurus/docs/Flutter/stream_chat_flutter_core/message_search_list_core.mdx @@ -28,9 +28,9 @@ class MessageSearchPage extends StatelessWidget { Widget build(BuildContext context) { return Scaffold( body: MessageSearchListCore( - messageQuery: _messageFilter, - filters: _channelsFilter, - paginationParams: PaginationParams(limit: 20), + messageQuery: _messageFilter, + filters: _channelsFilter, + limit: 20, ), ); } diff --git a/packages/stream_chat_flutter/lib/src/message_search_list_view.dart b/packages/stream_chat_flutter/lib/src/message_search_list_view.dart index 5c8c2bc6..a1611412 100644 --- a/packages/stream_chat_flutter/lib/src/message_search_list_view.dart +++ b/packages/stream_chat_flutter/lib/src/message_search_list_view.dart @@ -30,13 +30,13 @@ typedef EmptyMessageSearchBuilder = Widget Function( /// Widget build(BuildContext context) { /// return Scaffold( /// body: MessageSearchListView( -/// messageQuery: _channelQuery, -/// filters: { -/// 'members': { -/// r'$in': [user.id] -/// } -/// }, -/// paginationParams: PaginationParams(limit: 20), +/// messageQuery: _channelQuery, +/// filters: { +/// 'members': { +/// r'$in': [user.id] +/// } +/// }, +/// limit: 20, /// ), /// ); /// } diff --git a/packages/stream_chat_flutter_core/lib/src/message_search_list_core.dart b/packages/stream_chat_flutter_core/lib/src/message_search_list_core.dart index f4d18a0b..565743f2 100644 --- a/packages/stream_chat_flutter_core/lib/src/message_search_list_core.dart +++ b/packages/stream_chat_flutter_core/lib/src/message_search_list_core.dart @@ -18,9 +18,9 @@ import 'package:stream_chat_flutter_core/src/typedef.dart'; /// Widget build(BuildContext context) { /// return Scaffold( /// body: MessageSearchListCore( -/// messageQuery: _messageFilter, -/// filters: _channelsFilter, -/// paginationParams: PaginationParams(limit: 20), +/// messageQuery: _messageFilter, +/// filters: _channelsFilter, +/// limit: 20, /// ), /// ); /// } From 669abf59c942e9db0663269bd64c29dd15582674 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Wed, 22 Sep 2021 15:17:57 +0200 Subject: [PATCH 36/70] chore(llc): bump version --- packages/stream_chat/CHANGELOG.md | 2 +- packages/stream_chat/lib/version.dart | 2 +- packages/stream_chat/pubspec.yaml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/stream_chat/CHANGELOG.md b/packages/stream_chat/CHANGELOG.md index e0c0f693..3c7dd84f 100644 --- a/packages/stream_chat/CHANGELOG.md +++ b/packages/stream_chat/CHANGELOG.md @@ -1,4 +1,4 @@ -## Upcoming +## 3.0.0 🛑️ Breaking Changes from `2.2.1` diff --git a/packages/stream_chat/lib/version.dart b/packages/stream_chat/lib/version.dart index b208e15c..4eda59c0 100644 --- a/packages/stream_chat/lib/version.dart +++ b/packages/stream_chat/lib/version.dart @@ -3,4 +3,4 @@ import 'package:stream_chat/src/client/client.dart'; /// Current package version /// Used in [StreamChatClient] to build the `x-stream-client` header // ignore: constant_identifier_names -const PACKAGE_VERSION = '2.2.1'; +const PACKAGE_VERSION = '3.0.0'; diff --git a/packages/stream_chat/pubspec.yaml b/packages/stream_chat/pubspec.yaml index a07606ba..dd394c1e 100644 --- a/packages/stream_chat/pubspec.yaml +++ b/packages/stream_chat/pubspec.yaml @@ -1,7 +1,7 @@ name: stream_chat homepage: https://getstream.io/ description: The official Dart client for Stream Chat, a service for building chat applications. -version: 2.2.1 +version: 3.0.0 repository: https://github.com/GetStream/stream-chat-flutter issue_tracker: https://github.com/GetStream/stream-chat-flutter/issues From 0e02ccf9e205f4b66f8fc207df40540ca78c7004 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Wed, 22 Sep 2021 15:19:28 +0200 Subject: [PATCH 37/70] chore(core): bump version --- packages/stream_chat_flutter_core/CHANGELOG.md | 2 +- packages/stream_chat_flutter_core/pubspec.yaml | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/stream_chat_flutter_core/CHANGELOG.md b/packages/stream_chat_flutter_core/CHANGELOG.md index c4287a09..e1ec1fc5 100644 --- a/packages/stream_chat_flutter_core/CHANGELOG.md +++ b/packages/stream_chat_flutter_core/CHANGELOG.md @@ -1,4 +1,4 @@ -## Upcoming +## 3.0.0 ⚠️ Deprecated diff --git a/packages/stream_chat_flutter_core/pubspec.yaml b/packages/stream_chat_flutter_core/pubspec.yaml index 85b57124..27010d48 100644 --- a/packages/stream_chat_flutter_core/pubspec.yaml +++ b/packages/stream_chat_flutter_core/pubspec.yaml @@ -1,7 +1,7 @@ name: stream_chat_flutter_core homepage: https://github.com/GetStream/stream-chat-flutter description: Stream Chat official Flutter SDK Core. Build your own chat experience using Dart and Flutter. -version: 2.2.1 +version: 3.0.0 repository: https://github.com/GetStream/stream-chat-flutter issue_tracker: https://github.com/GetStream/stream-chat-flutter/issues @@ -16,7 +16,7 @@ dependencies: sdk: flutter meta: ^1.3.0 rxdart: ^0.27.0 - stream_chat: ^2.2.1 + stream_chat: 3.0.0 dev_dependencies: fake_async: ^1.2.0 From ad3c3572ddf588b5a5fb9457588c1cb423b5d4af Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Wed, 22 Sep 2021 15:25:45 +0200 Subject: [PATCH 38/70] feat!(core): commit breaking changes --- .../stream_chat_flutter_core/CHANGELOG.md | 8 +++--- .../lib/src/channel_list_core.dart | 22 +++------------- .../lib/src/user_list_core.dart | 25 +++---------------- 3 files changed, 12 insertions(+), 43 deletions(-) diff --git a/packages/stream_chat_flutter_core/CHANGELOG.md b/packages/stream_chat_flutter_core/CHANGELOG.md index e1ec1fc5..b14c7cbc 100644 --- a/packages/stream_chat_flutter_core/CHANGELOG.md +++ b/packages/stream_chat_flutter_core/CHANGELOG.md @@ -1,6 +1,6 @@ ## 3.0.0 -⚠️ Deprecated +🛑️ Breaking Changes from `2.2.1` - `MessageSearchListViewCore` `paginationParams` property is now deprecated in favor of `limit`. ```dart @@ -10,7 +10,7 @@ // new limit = 30 ``` -- `UserListViewCore` `pagination` property is now deprecated in favor of `limit`. +- `UserListCore` `pagination` property is now deprecated in favor of `limit`. ```dart // previous pagination = const PaginationParams(limit: 30) @@ -18,7 +18,7 @@ // new limit = 30 ``` -- `ChannelListViewCore` `pagination` property is now deprecated in favor of `limit`. +- `ChannelListCore` `pagination` property is now deprecated in favor of `limit`. ```dart // previous pagination = const PaginationParams(limit: 30) @@ -27,6 +27,8 @@ limit = 30 ``` +- `UserListViewCore` `filter` property now is non-nullable. + 🔄 Changed - `UserListViewCore` filter property now has a default value. diff --git a/packages/stream_chat_flutter_core/lib/src/channel_list_core.dart b/packages/stream_chat_flutter_core/lib/src/channel_list_core.dart index d3d6c312..8ac28e76 100644 --- a/packages/stream_chat_flutter_core/lib/src/channel_list_core.dart +++ b/packages/stream_chat_flutter_core/lib/src/channel_list_core.dart @@ -56,7 +56,7 @@ import 'package:stream_chat_flutter_core/src/typedef.dart'; /// information about the channels. class ChannelListCore extends StatefulWidget { /// Instantiate a new ChannelListView - ChannelListCore({ + const ChannelListCore({ Key? key, required this.errorBuilder, required this.emptyBuilder, @@ -69,15 +69,9 @@ class ChannelListCore extends StatefulWidget { this.memberLimit, this.messageLimit, this.sort, - @Deprecated( - "'pagination' is deprecated and shouldn't be used. " - "This property is no longer used, Please use 'limit' instead", - ) - this.pagination, this.channelListController, - int? limit, - }) : limit = limit ?? pagination?.limit ?? 25, - super(key: key); + this.limit = 25, + }) : super(key: key); /// A [ChannelListController] allows reloading and pagination. /// Use [ChannelListController.loadData] and @@ -124,16 +118,6 @@ class ChannelListCore extends StatefulWidget { /// Number of messages to fetch in each channel final int? messageLimit; - /// Pagination parameters - /// limit: the number of channels to return (max is 30) - /// offset: the offset (max is 1000) - /// message_limit: how many messages should be included to each channel - @Deprecated( - "'pagination' is deprecated and shouldn't be used. " - "This property is no longer used, Please use 'limit' instead", - ) - final PaginationParams? pagination; - /// The amount of channels requested per API call. final int limit; diff --git a/packages/stream_chat_flutter_core/lib/src/user_list_core.dart b/packages/stream_chat_flutter_core/lib/src/user_list_core.dart index 839067bf..05e9730b 100644 --- a/packages/stream_chat_flutter_core/lib/src/user_list_core.dart +++ b/packages/stream_chat_flutter_core/lib/src/user_list_core.dart @@ -57,7 +57,7 @@ import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; /// [errorBuilder] must all be supplied and not null. class UserListCore extends StatefulWidget { /// Instantiate a new [UserListCore] - UserListCore({ + const UserListCore({ required this.errorBuilder, required this.emptyBuilder, required this.loadingBuilder, @@ -66,16 +66,10 @@ class UserListCore extends StatefulWidget { this.filter = const Filter.empty(), this.sort, this.presence, - @Deprecated( - "'pagination' is deprecated and shouldn't be used. " - "This property is no longer used, Please use 'limit' instead", - ) - this.pagination, this.groupAlphabetically = false, this.userListController, - int? limit, - }) : limit = limit ?? pagination?.limit ?? 30, - super(key: key); + this.limit = 30, + }) : super(key: key); /// A [UserListController] allows reloading and pagination. /// Use [UserListController.loadData] and [UserListController.paginateData] @@ -97,8 +91,7 @@ class UserListCore extends StatefulWidget { /// The query filters to use. /// You can query on any of the custom fields you've defined on the [Channel]. /// You can also filter other built-in channel fields. - // TODO: Make it non-nullable in a future breaking release - final Filter? filter; + final Filter filter; /// The sorting used for the channels matching the filters. /// Sorting is based on field and direction, multiple sorting options can be @@ -109,16 +102,6 @@ class UserListCore extends StatefulWidget { /// If true you’ll receive user presence updates via the websocket events final bool? presence; - /// Pagination parameters - /// limit: the number of users to return (max is 30) - /// offset: the offset (max is 1000) - /// message_limit: how many messages should be included to each channel - @Deprecated( - "'pagination' is deprecated and shouldn't be used. " - "This property is no longer used, Please use 'limit' instead", - ) - final PaginationParams? pagination; - /// The amount of users requested per API call. final int limit; From 0810065f10a4562d77644aadc24d6ba775eb46a6 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Wed, 22 Sep 2021 15:28:25 +0200 Subject: [PATCH 39/70] chore(ui): bump version --- packages/stream_chat_flutter/CHANGELOG.md | 5 ++++- .../lib/src/message_search_list_view.dart | 22 +++---------------- .../lib/src/user_list_view.dart | 3 +-- packages/stream_chat_flutter/pubspec.yaml | 4 ++-- 4 files changed, 10 insertions(+), 24 deletions(-) diff --git a/packages/stream_chat_flutter/CHANGELOG.md b/packages/stream_chat_flutter/CHANGELOG.md index c6081ec6..50b0376c 100644 --- a/packages/stream_chat_flutter/CHANGELOG.md +++ b/packages/stream_chat_flutter/CHANGELOG.md @@ -1,4 +1,7 @@ -## Upcoming +## 3.0.0 +🛑️ Breaking Changes from `2.2.1` + +- `UserListView` `filter` property now is non-nullable. 🐞 Fixed diff --git a/packages/stream_chat_flutter/lib/src/message_search_list_view.dart b/packages/stream_chat_flutter/lib/src/message_search_list_view.dart index a1611412..daf34ff4 100644 --- a/packages/stream_chat_flutter/lib/src/message_search_list_view.dart +++ b/packages/stream_chat_flutter/lib/src/message_search_list_view.dart @@ -53,17 +53,12 @@ typedef EmptyMessageSearchBuilder = Widget Function( /// Modify it to change the widget appearance. class MessageSearchListView extends StatefulWidget { /// Instantiate a new MessageSearchListView - MessageSearchListView({ + const MessageSearchListView({ Key? key, required this.filters, this.messageQuery, this.sortOptions, - @Deprecated( - "'paginationParams' is deprecated and shouldn't be used. " - "This property is no longer used, Please use 'limit' instead", - ) - this.paginationParams, - int? limit, + this.limit = 30, this.messageFilters, this.separatorBuilder, this.itemBuilder, @@ -76,8 +71,7 @@ class MessageSearchListView extends StatefulWidget { this.loadingBuilder, this.childBuilder, this.messageSearchListController, - }) : limit = limit ?? paginationParams?.limit ?? 30, - super(key: key); + }) : super(key: key); /// Message String to search on final String? messageQuery; @@ -95,16 +89,6 @@ class MessageSearchListView extends StatefulWidget { /// Direction can be ascending or descending. final List? sortOptions; - /// Pagination parameters - /// limit: the number of users to return (max is 30) - /// offset: the offset (max is 1000) - /// message_limit: how many messages should be included to each channel - @Deprecated( - "'paginationParams' is deprecated and shouldn't be used. " - "This property is no longer used, Please use 'limit' instead", - ) - final PaginationParams? paginationParams; - /// The amount of messages requested per API call. final int limit; diff --git a/packages/stream_chat_flutter/lib/src/user_list_view.dart b/packages/stream_chat_flutter/lib/src/user_list_view.dart index 98cf6621..f7c9fa72 100644 --- a/packages/stream_chat_flutter/lib/src/user_list_view.dart +++ b/packages/stream_chat_flutter/lib/src/user_list_view.dart @@ -82,8 +82,7 @@ class UserListView extends StatefulWidget { /// The query filters to use. /// You can query on any of the custom fields you've defined on the [Channel]. /// You can also filter other built-in channel fields. - // TODO: Make it non-nullable in a future breaking release - final Filter? filter; + final Filter filter; /// The sorting used for the channels matching the filters. /// Sorting is based on field and direction, multiple sorting options can diff --git a/packages/stream_chat_flutter/pubspec.yaml b/packages/stream_chat_flutter/pubspec.yaml index 907e9a7f..cf2c332a 100644 --- a/packages/stream_chat_flutter/pubspec.yaml +++ b/packages/stream_chat_flutter/pubspec.yaml @@ -1,7 +1,7 @@ name: stream_chat_flutter homepage: https://github.com/GetStream/stream-chat-flutter description: Stream Chat official Flutter SDK. Build your own chat experience using Dart and Flutter. -version: 2.2.1 +version: 3.0.0 repository: https://github.com/GetStream/stream-chat-flutter issue_tracker: https://github.com/GetStream/stream-chat-flutter/issues @@ -37,7 +37,7 @@ dependencies: scrollable_positioned_list: ^0.2.0-nullsafety.0 share_plus: ^2.0.3 shimmer: ^2.0.0 - stream_chat_flutter_core: ^2.2.1 + stream_chat_flutter_core: 3.0.0 substring_highlight: ^1.0.26 synchronized: ^3.0.0 url_launcher: ^6.0.3 From bc27b5e70c6aadfb54d47b264b1a779b558f40ce Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Wed, 22 Sep 2021 15:32:56 +0200 Subject: [PATCH 40/70] chore(persistence): bump version --- packages/stream_chat_persistence/CHANGELOG.md | 3 +-- packages/stream_chat_persistence/pubspec.yaml | 4 ++-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/packages/stream_chat_persistence/CHANGELOG.md b/packages/stream_chat_persistence/CHANGELOG.md index 139056bd..de24856e 100644 --- a/packages/stream_chat_persistence/CHANGELOG.md +++ b/packages/stream_chat_persistence/CHANGELOG.md @@ -1,9 +1,8 @@ -## Upcoming +## 3.0.0 - [[#604]](https://github.com/GetStream/stream-chat-flutter/issues/604) Fix cascade deletion by enabling `pragma foreign_keys`. - Added a new table `PinnedMessageReactions` and dao `PinnedMessageReactionDao` specifically for pinned messages. -- Updated `stream_chat`: `^2.2.0` -> `TODO` ## 2.2.0 diff --git a/packages/stream_chat_persistence/pubspec.yaml b/packages/stream_chat_persistence/pubspec.yaml index a8e5f991..b510ac9d 100644 --- a/packages/stream_chat_persistence/pubspec.yaml +++ b/packages/stream_chat_persistence/pubspec.yaml @@ -1,7 +1,7 @@ name: stream_chat_persistence homepage: https://github.com/GetStream/stream-chat-flutter description: Official Stream Chat Persistence library. Build your own chat experience using Dart and Flutter. -version: 2.2.0 +version: 3.0.0 repository: https://github.com/GetStream/stream-chat-flutter issue_tracker: https://github.com/GetStream/stream-chat-flutter/issues @@ -18,7 +18,7 @@ dependencies: path: ^1.8.0 path_provider: ^2.0.1 sqlite3_flutter_libs: ^0.5.0 - stream_chat: ^2.2.0 + stream_chat: 3.0.0 dev_dependencies: build_runner: ^2.0.1 From f09246ec1397619d52b76e230c6f636f49f12b40 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Wed, 22 Sep 2021 15:33:36 +0200 Subject: [PATCH 41/70] chore(localization): bump version --- packages/stream_chat_localizations/CHANGELOG.md | 2 +- packages/stream_chat_localizations/pubspec.yaml | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/stream_chat_localizations/CHANGELOG.md b/packages/stream_chat_localizations/CHANGELOG.md index d8328475..deb564a9 100644 --- a/packages/stream_chat_localizations/CHANGELOG.md +++ b/packages/stream_chat_localizations/CHANGELOG.md @@ -1,4 +1,4 @@ -## Upcoming +## 2.0.0 🐞 Fixed diff --git a/packages/stream_chat_localizations/pubspec.yaml b/packages/stream_chat_localizations/pubspec.yaml index 0ed72acd..c89013c6 100644 --- a/packages/stream_chat_localizations/pubspec.yaml +++ b/packages/stream_chat_localizations/pubspec.yaml @@ -1,6 +1,6 @@ name: stream_chat_localizations description: The Official localizations for Stream Chat Flutter, a service for building chat applications -version: 1.1.0 +version: 2.0.0 homepage: https://github.com/GetStream/stream-chat-flutter repository: https://github.com/GetStream/stream-chat-flutter issue_tracker: https://github.com/GetStream/stream-chat-flutter/issues @@ -14,7 +14,7 @@ dependencies: sdk: flutter flutter_localizations: sdk: flutter - stream_chat_flutter: ^2.2.0 + stream_chat_flutter: 3.0.0 dev_dependencies: flutter_test: From 1fe446c6e65df3e753e8aad55efa37916ec2a70d Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Wed, 22 Sep 2021 15:47:52 +0200 Subject: [PATCH 42/70] fix(core): fix analysis --- packages/stream_chat_flutter_core/test/user_list_core_test.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/stream_chat_flutter_core/test/user_list_core_test.dart b/packages/stream_chat_flutter_core/test/user_list_core_test.dart index b077868b..c916ce17 100644 --- a/packages/stream_chat_flutter_core/test/user_list_core_test.dart +++ b/packages/stream_chat_flutter_core/test/user_list_core_test.dart @@ -522,7 +522,7 @@ void main() { ); test('`widget.limit` should match `widget.pagination.limit`', () { - const limit = 30; + const limit = 20; final userListCore = UserListCore( listBuilder: (_, __) => const Offstage(), loadingBuilder: (BuildContext context) => const Offstage(), From 853a3fe2af9ee6c60861ae4f13e9ccfd5d21dcc4 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Mon, 27 Sep 2021 09:20:33 +0200 Subject: [PATCH 43/70] Apply suggestions from code review --- packages/stream_chat_flutter/CHANGELOG.md | 1 + packages/stream_chat_flutter_core/CHANGELOG.md | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/stream_chat_flutter/CHANGELOG.md b/packages/stream_chat_flutter/CHANGELOG.md index 50b0376c..d1c04d24 100644 --- a/packages/stream_chat_flutter/CHANGELOG.md +++ b/packages/stream_chat_flutter/CHANGELOG.md @@ -1,4 +1,5 @@ ## 3.0.0 + 🛑️ Breaking Changes from `2.2.1` - `UserListView` `filter` property now is non-nullable. diff --git a/packages/stream_chat_flutter_core/CHANGELOG.md b/packages/stream_chat_flutter_core/CHANGELOG.md index b14c7cbc..9cc64f18 100644 --- a/packages/stream_chat_flutter_core/CHANGELOG.md +++ b/packages/stream_chat_flutter_core/CHANGELOG.md @@ -27,11 +27,11 @@ limit = 30 ``` -- `UserListViewCore` `filter` property now is non-nullable. +- `UserListCore` `filter` property now is non-nullable. 🔄 Changed -- `UserListViewCore` filter property now has a default value. +- `UserListCore` filter property now has a default value. ```dart filter = const Filter.empty() ``` From 175d4d38609ccfb9bfc31f434dc0c02b75e22134 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Mon, 27 Sep 2021 09:30:59 +0200 Subject: [PATCH 44/70] chore(ui,core,persistance,localization): added dependency changelogs --- packages/stream_chat_flutter/CHANGELOG.md | 2 ++ packages/stream_chat_flutter_core/CHANGELOG.md | 2 ++ packages/stream_chat_localizations/CHANGELOG.md | 2 ++ packages/stream_chat_persistence/CHANGELOG.md | 1 + 4 files changed, 7 insertions(+) diff --git a/packages/stream_chat_flutter/CHANGELOG.md b/packages/stream_chat_flutter/CHANGELOG.md index d1c04d24..0117a7ce 100644 --- a/packages/stream_chat_flutter/CHANGELOG.md +++ b/packages/stream_chat_flutter/CHANGELOG.md @@ -1,5 +1,7 @@ ## 3.0.0 +- Updated `stream_chat_flutter_core` dependency to [`3.0.0`](https://pub.dev/packages/stream_chat_flutter_core/changelog). + 🛑️ Breaking Changes from `2.2.1` - `UserListView` `filter` property now is non-nullable. diff --git a/packages/stream_chat_flutter_core/CHANGELOG.md b/packages/stream_chat_flutter_core/CHANGELOG.md index 9cc64f18..cc1394a1 100644 --- a/packages/stream_chat_flutter_core/CHANGELOG.md +++ b/packages/stream_chat_flutter_core/CHANGELOG.md @@ -1,5 +1,7 @@ ## 3.0.0 +- Updated `stream_chat` dependency to [`3.0.0`](https://pub.dev/packages/stream_chat/changelog). + 🛑️ Breaking Changes from `2.2.1` - `MessageSearchListViewCore` `paginationParams` property is now deprecated in favor of `limit`. diff --git a/packages/stream_chat_localizations/CHANGELOG.md b/packages/stream_chat_localizations/CHANGELOG.md index deb564a9..c1c469bf 100644 --- a/packages/stream_chat_localizations/CHANGELOG.md +++ b/packages/stream_chat_localizations/CHANGELOG.md @@ -1,5 +1,7 @@ ## 2.0.0 +- Updated `stream_chat_flutter` dependency to [`3.0.0`](https://pub.dev/packages/stream_chat_flutter/changelog). + 🐞 Fixed * Fixed typos in `Italian` translations. diff --git a/packages/stream_chat_persistence/CHANGELOG.md b/packages/stream_chat_persistence/CHANGELOG.md index de24856e..f8a2434f 100644 --- a/packages/stream_chat_persistence/CHANGELOG.md +++ b/packages/stream_chat_persistence/CHANGELOG.md @@ -1,5 +1,6 @@ ## 3.0.0 +- Updated `stream_chat` dependency to [`3.0.0`](https://pub.dev/packages/stream_chat/changelog). - [[#604]](https://github.com/GetStream/stream-chat-flutter/issues/604) Fix cascade deletion by enabling `pragma foreign_keys`. - Added a new table `PinnedMessageReactions` and dao `PinnedMessageReactionDao` specifically for pinned messages. From f345fa1d36181724a62ba3c4404b194350cc22b9 Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Mon, 27 Sep 2021 14:05:56 +0530 Subject: [PATCH 45/70] ref(ui): minor improvements Signed-off-by: xsahil03x --- .../lib/src/message_input.dart | 92 +++++++------------ 1 file changed, 32 insertions(+), 60 deletions(-) diff --git a/packages/stream_chat_flutter/lib/src/message_input.dart b/packages/stream_chat_flutter/lib/src/message_input.dart index 9cd84aa2..95322c10 100644 --- a/packages/stream_chat_flutter/lib/src/message_input.dart +++ b/packages/stream_chat_flutter/lib/src/message_input.dart @@ -171,7 +171,7 @@ class MessageInput extends StatefulWidget { this.disableAttachments = false, this.initialMessage, this.textEditingController, - this.actions, + this.actions = const [], this.actionsLocation = ActionsLocation.left, this.attachmentThumbnailBuilders, this.focusNode, @@ -248,7 +248,7 @@ class MessageInput extends StatefulWidget { final TextEditingController? textEditingController; /// List of action widgets - final List? actions; + final List actions; /// The location of the custom actions final ActionsLocation actionsLocation; @@ -324,7 +324,7 @@ class MessageInputState extends State { final List _mentionedUsers = []; final _imagePicker = ImagePicker(); - late final FocusNode _focusNode; + late final _focusNode = widget.focusNode ?? FocusNode(); bool _inputEnabled = true; bool _commandEnabled = false; bool _showCommandsOverlay = false; @@ -337,7 +337,8 @@ class MessageInputState extends State { int _filePickerIndex = 0; /// The editing controller passed to the input TextField - late final TextEditingController textEditingController; + late final TextEditingController textEditingController = + widget.textEditingController ?? TextEditingController(); late StreamChatThemeData _streamChatTheme; late MessageInputThemeData _messageInputTheme; @@ -349,18 +350,10 @@ class MessageInputState extends State { @override void initState() { super.initState(); - _focusNode = widget.focusNode ?? FocusNode(); - - textEditingController = - widget.textEditingController ?? TextEditingController(); if (widget.editMessage != null || widget.initialMessage != null) { _parseExistingMessage(widget.editMessage ?? widget.initialMessage!); } - - textEditingController.addListener(() { - _onChanged(context, textEditingController.text); - }); - + textEditingController.addListener(_onChangedDebounced); _focusNode.addListener(() { if (_focusNode.hasFocus) { _openFilePickerSection = false; @@ -625,7 +618,7 @@ class MessageInputState extends State { ), secondChild: widget.disableAttachments && !widget.showCommandsButton && - widget.actions?.isNotEmpty != true + widget.actions.isNotEmpty != true ? const Offstage() : Wrap( children: [ @@ -636,7 +629,7 @@ class MessageInputState extends State { channel.state != null && channel.config?.commands.isNotEmpty == true) _buildCommandButton(context), - ...widget.actions ?? [], + ...widget.actions, ].insertBetween(const SizedBox(width: 8)), ), duration: const Duration(milliseconds: 300), @@ -804,50 +797,32 @@ class MessageInputState extends State { ).merge(passedDecoration); } - Timer? _debounce; + late final _onChangedDebounced = debounce( + () { + var value = textEditingController.text; + if (!mounted) return; + value = value.trim(); - String? _previousValue; + final channel = StreamChannel.of(context).channel; + if (value.isNotEmpty) { + channel.keyStroke(widget.parentMessage?.id).catchError((e) {}); + } - void _onChanged( - BuildContext context, - String s, - ) { - if (s == _previousValue) { - return; - } - _previousValue = s; + var actionsLength = widget.actions.length; + if (widget.showCommandsButton) actionsLength += 1; + if (!widget.disableAttachments) actionsLength += 1; - if (_debounce?.isActive == true) _debounce!.cancel(); - _debounce = Timer( - const Duration(milliseconds: 350), - () { - if (!mounted) { - return; - } + setState(() { + _actionsShrunk = value.isNotEmpty && actionsLength > 1; + }); - if (s.isNotEmpty) { - StreamChannel.of(context) - .channel - .keyStroke(widget.parentMessage?.id) - .catchError((e) {}); - } - - _checkCommands(s.trim(), context); - - _checkMentions(s, context); - - _checkEmoji(s, context); - - setState(() { - _actionsShrunk = s.trim().isNotEmpty && - ((widget.actions?.length ?? 0) + - (widget.showCommandsButton ? 1 : 0) + - (widget.disableAttachments ? 0 : 1) > - 1); - }); - }, - ); - } + _checkCommands(value, context); + _checkMentions(value, context); + _checkEmoji(value, context); + }, + const Duration(milliseconds: 350), + leading: true, + ); String _getHint(BuildContext context) { if (_commandEnabled && _chosenCommand!.name == 'giphy') { @@ -867,10 +842,7 @@ class MessageInputState extends State { if (s.isNotEmpty && textEditingController.selection.baseOffset > 0 && textEditingController.text - .substring( - 0, - textEditingController.selection.baseOffset, - ) + .substring(0, textEditingController.selection.baseOffset) .contains(':')) { final textToSelection = textEditingController.text .substring(0, textEditingController.value.selection.start); @@ -1207,7 +1179,7 @@ class MessageInputState extends State { offset: rejoin.length, ), ); - _debounce!.cancel(); + _onChangedDebounced.cancel(); setState(() { _showMentionsOverlay = false; }); From 2b5e827e13527fd56ef26606d14eeada2aff9dcb Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Mon, 27 Sep 2021 10:48:46 +0200 Subject: [PATCH 46/70] chore(ui,core,persistance,localization): loose dependency constraints --- packages/stream_chat_flutter/pubspec.yaml | 2 +- packages/stream_chat_flutter_core/pubspec.yaml | 2 +- packages/stream_chat_localizations/pubspec.yaml | 2 +- packages/stream_chat_persistence/pubspec.yaml | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/stream_chat_flutter/pubspec.yaml b/packages/stream_chat_flutter/pubspec.yaml index cf2c332a..fb7d3af7 100644 --- a/packages/stream_chat_flutter/pubspec.yaml +++ b/packages/stream_chat_flutter/pubspec.yaml @@ -37,7 +37,7 @@ dependencies: scrollable_positioned_list: ^0.2.0-nullsafety.0 share_plus: ^2.0.3 shimmer: ^2.0.0 - stream_chat_flutter_core: 3.0.0 + stream_chat_flutter_core: ^3.0.0 substring_highlight: ^1.0.26 synchronized: ^3.0.0 url_launcher: ^6.0.3 diff --git a/packages/stream_chat_flutter_core/pubspec.yaml b/packages/stream_chat_flutter_core/pubspec.yaml index 27010d48..e5520921 100644 --- a/packages/stream_chat_flutter_core/pubspec.yaml +++ b/packages/stream_chat_flutter_core/pubspec.yaml @@ -16,7 +16,7 @@ dependencies: sdk: flutter meta: ^1.3.0 rxdart: ^0.27.0 - stream_chat: 3.0.0 + stream_chat: ^3.0.0 dev_dependencies: fake_async: ^1.2.0 diff --git a/packages/stream_chat_localizations/pubspec.yaml b/packages/stream_chat_localizations/pubspec.yaml index c89013c6..552ee5ec 100644 --- a/packages/stream_chat_localizations/pubspec.yaml +++ b/packages/stream_chat_localizations/pubspec.yaml @@ -14,7 +14,7 @@ dependencies: sdk: flutter flutter_localizations: sdk: flutter - stream_chat_flutter: 3.0.0 + stream_chat_flutter: ^3.0.0 dev_dependencies: flutter_test: diff --git a/packages/stream_chat_persistence/pubspec.yaml b/packages/stream_chat_persistence/pubspec.yaml index b510ac9d..4f3ab8e9 100644 --- a/packages/stream_chat_persistence/pubspec.yaml +++ b/packages/stream_chat_persistence/pubspec.yaml @@ -18,7 +18,7 @@ dependencies: path: ^1.8.0 path_provider: ^2.0.1 sqlite3_flutter_libs: ^0.5.0 - stream_chat: 3.0.0 + stream_chat: ^3.0.0 dev_dependencies: build_runner: ^2.0.1 From 1e5a042d3b2beb1df8d495e048cfc7294d4cc193 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Mon, 27 Sep 2021 11:08:49 +0200 Subject: [PATCH 47/70] chore(ui): fix dependencies --- packages/stream_chat_flutter/test/src/emoji_test.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/stream_chat_flutter/test/src/emoji_test.dart b/packages/stream_chat_flutter/test/src/emoji_test.dart index 61a0ba58..ce63faa9 100644 --- a/packages/stream_chat_flutter/test/src/emoji_test.dart +++ b/packages/stream_chat_flutter/test/src/emoji_test.dart @@ -1,5 +1,5 @@ +import 'package:flutter_test/flutter_test.dart'; import 'package:stream_chat_flutter/src/emoji/emoji.dart'; -import 'package:test/test.dart'; void main() { group('src/emoji', () { From 6686cc1af63c308e9e56c99b3473b82fc497aa5b Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Mon, 27 Sep 2021 16:27:17 +0530 Subject: [PATCH 48/70] feat(ui): improve add support for `mentionAllAppUsers` in `UserMentionsOverlay`. Signed-off-by: xsahil03x --- .../lib/src/extension.dart | 53 +++++ .../lib/src/mention_tile.dart | 4 + .../lib/src/mentions_overlay.dart | 100 --------- .../lib/src/message_input.dart | 27 +-- .../lib/src/user_mention_tile.dart | 91 +++++++++ .../lib/src/user_mentions_overlay.dart | 191 ++++++++++++++++++ .../stream_chat_flutter/lib/src/utils.dart | 39 +++- .../lib/stream_chat_flutter.dart | 1 + packages/stream_chat_flutter/pubspec.yaml | 1 + .../test/src/extension_test.dart | 71 +++++++ .../lib/src/stream_channel.dart | 22 +- 11 files changed, 481 insertions(+), 119 deletions(-) delete mode 100644 packages/stream_chat_flutter/lib/src/mentions_overlay.dart create mode 100644 packages/stream_chat_flutter/lib/src/user_mention_tile.dart create mode 100644 packages/stream_chat_flutter/lib/src/user_mentions_overlay.dart create mode 100644 packages/stream_chat_flutter/test/src/extension_test.dart diff --git a/packages/stream_chat_flutter/lib/src/extension.dart b/packages/stream_chat_flutter/lib/src/extension.dart index 19bf4523..9551a40d 100644 --- a/packages/stream_chat_flutter/lib/src/extension.dart +++ b/packages/stream_chat_flutter/lib/src/extension.dart @@ -1,4 +1,5 @@ import 'package:characters/characters.dart'; +import 'package:diacritic/diacritic.dart'; import 'package:file_picker/file_picker.dart'; import 'package:flutter/material.dart'; import 'package:stream_chat_flutter/src/emoji/emoji.dart'; @@ -24,6 +25,12 @@ extension StringExtension on String { final characters = trim().characters; return characters.every(_emojiChars.contains); } + + /// Removes accents and diacritics from the given String. + String get diacriticsInsensitive => removeDiacritics(this); + + /// Levenshtein distance between this and [t]. + int levenshteinDistance(String t) => levenshtein(this, t); } /// List extension @@ -170,3 +177,49 @@ extension IconButtonX on IconButton { icon: icon ?? this.icon, ); } + +/// Extensions on List +extension UserListX on List { + /// It does an search on a list of [User] and returns users with + /// `id` or `name` containing the [query]. + /// + /// Results are returned sorted by their edit distance from the + /// searched string, distance is calculated using the [levenshtein] algorithm. + List search(String query) { + String normalize(String input) => input.toLowerCase().diacriticsInsensitive; + + final normalizedQuery = normalize(query); + + final matchingUsers = {}; // User:lDistance + + for (final user in this) { + final normalizedId = normalize(user.id); + final normalizedUserName = normalize(user.name); + final lDistance = normalizedUserName.levenshteinDistance(normalizedQuery); + final containsId = normalizedId.contains(normalizedQuery); + final containsName = normalizedUserName.contains(normalizedQuery); + if (lDistance < 3 || containsId || containsName) { + matchingUsers[user] = lDistance; + } + } + + final entries = matchingUsers.entries.toList(growable: false) + ..sort((prev, curr) { + bool containsQuery(User user) => + normalize(user.id).contains(normalizedQuery) || + normalize(user.name).contains(normalizedQuery); + + final containsInPrev = containsQuery(prev.key); + final containsInCurr = containsQuery(curr.key); + + if (containsInPrev && !containsInCurr) { + return -1; + } else if (!containsInPrev && containsInCurr) { + return 1; + } + return prev.value.compareTo(curr.value); + }); + + return entries.map((e) => e.key).toList(growable: false); + } +} diff --git a/packages/stream_chat_flutter/lib/src/mention_tile.dart b/packages/stream_chat_flutter/lib/src/mention_tile.dart index d9469215..b900be65 100644 --- a/packages/stream_chat_flutter/lib/src/mention_tile.dart +++ b/packages/stream_chat_flutter/lib/src/mention_tile.dart @@ -4,6 +4,10 @@ import 'package:stream_chat_flutter/stream_chat_flutter.dart'; /// This widget is used for showing user tiles for mentions /// Use [title], [subtitle], [leading], [trailing] for /// substituting widgets in respective positions +@Deprecated( + "'MentionTile' widget is deprecated and shouldn't be used. " + "Use 'UserMentionTile' instead", +) class MentionTile extends StatelessWidget { /// Constructor for creating a [MentionTile] widget const MentionTile( diff --git a/packages/stream_chat_flutter/lib/src/mentions_overlay.dart b/packages/stream_chat_flutter/lib/src/mentions_overlay.dart deleted file mode 100644 index 63c43585..00000000 --- a/packages/stream_chat_flutter/lib/src/mentions_overlay.dart +++ /dev/null @@ -1,100 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:stream_chat_flutter/stream_chat_flutter.dart'; - -/// Overlay for displaying users that can be mentioned -class MentionsOverlay extends StatelessWidget { - /// Constructor for creating a [MentionsOverlay] - const MentionsOverlay({ - required this.query, - required this.onMentionResult, - required this.size, - required this.channel, - this.mentionsTileBuilder, - Key? key, - }) : super(key: key); - - /// The size of the overlay - final Size size; - - /// Query for searching users - final String query; - - /// The channel to search for users - final Channel channel; - - /// Callback called when a user is selected - final ValueChanged onMentionResult; - - /// Customize the tile for the mentions overlay - final MentionTileBuilder? mentionsTileBuilder; - - @override - Widget build(BuildContext context) { - final _streamChatTheme = StreamChatTheme.of(context); - - Future>? queryMembers; - - if (query.isNotEmpty) { - queryMembers = channel - .queryMembers(filter: Filter.autoComplete('name', query)) - .then((res) => res.members); - } - - final members = channel.state?.members - .where((m) => m.user?.name.toLowerCase().contains(query) == true) - .toList() ?? - []; - - if (members.isEmpty) { - return const SizedBox(); - } - - return Card( - margin: const EdgeInsets.all(8), - elevation: 2, - color: _streamChatTheme.colorTheme.barsBg, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(8), - ), - clipBehavior: Clip.hardEdge, - child: Container( - constraints: BoxConstraints.loose(size), - decoration: BoxDecoration( - color: _streamChatTheme.colorTheme.barsBg, - ), - child: FutureBuilder>( - future: queryMembers ?? Future.value(members), - initialData: members, - builder: (context, snapshot) => ListView( - padding: const EdgeInsets.all(0), - shrinkWrap: true, - children: [ - const SizedBox( - height: 8, - ), - ...snapshot.data! - .where((it) => it.user != null) - .map( - (m) => Material( - color: _streamChatTheme.colorTheme.barsBg, - child: InkWell( - onTap: () { - onMentionResult(m.user); - }, - child: mentionsTileBuilder != null - ? mentionsTileBuilder!(context, m) - : MentionTile(m, key: ValueKey(m.user?.id)), - ), - ), - ) - .toList(), - const SizedBox( - height: 8, - ), - ], - ), - ), - ), - ); - } -} diff --git a/packages/stream_chat_flutter/lib/src/message_input.dart b/packages/stream_chat_flutter/lib/src/message_input.dart index 95322c10..c8197e59 100644 --- a/packages/stream_chat_flutter/lib/src/message_input.dart +++ b/packages/stream_chat_flutter/lib/src/message_input.dart @@ -15,7 +15,7 @@ import 'package:stream_chat_flutter/src/emoji/emoji.dart'; import 'package:stream_chat_flutter/src/emoji_overlay.dart'; import 'package:stream_chat_flutter/src/extension.dart'; import 'package:stream_chat_flutter/src/media_list_view.dart'; -import 'package:stream_chat_flutter/src/mentions_overlay.dart'; +import 'package:stream_chat_flutter/src/user_mentions_overlay.dart'; import 'package:stream_chat_flutter/src/message_list_view.dart'; import 'package:stream_chat_flutter/src/overlays.dart'; import 'package:stream_chat_flutter/src/quoted_message_widget.dart'; @@ -193,6 +193,7 @@ class MessageInput extends StatefulWidget { this.attachmentButtonBuilder, this.commandButtonBuilder, this.customOverlays = const [], + this.mentionAllAppUsers = false, }) : assert( initialMessage == null || editMessage == null, "Can't provide both `initialMessage` and `editMessage`", @@ -303,6 +304,11 @@ class MessageInput extends StatefulWidget { /// calling `.copyWith`. final ActionButtonBuilder? commandButtonBuilder; + /// When enabled mentions search users across the entire app. + /// + /// Defaults to false. + final bool mentionAllAppUsers; + @override MessageInputState createState() => MessageInputState(); @@ -1159,16 +1165,15 @@ class MessageInputState extends State { // ignore: cast_nullable_to_non_nullable final renderObject = context.findRenderObject() as RenderBox; - return MentionsOverlay( + return UserMentionsOverlay( + query: query, + mentionAllAppUsers: widget.mentionAllAppUsers, + client: StreamChat.of(context).client, channel: StreamChannel.of(context).channel, size: Size(renderObject.size.width - 16, 400), - query: query, - onMentionResult: (user) { - if (user != null) { - _mentionedUsers.add(user); - } - - splits[splits.length - 1] = user!.name; + onMentionUserTap: (user) { + _mentionedUsers.add(user); + splits[splits.length - 1] = user.name; final rejoin = splits.join('@'); textEditingController.value = TextEditingValue( @@ -1180,9 +1185,7 @@ class MessageInputState extends State { ), ); _onChangedDebounced.cancel(); - setState(() { - _showMentionsOverlay = false; - }); + setState(() => _showMentionsOverlay = false); }, ); } diff --git a/packages/stream_chat_flutter/lib/src/user_mention_tile.dart b/packages/stream_chat_flutter/lib/src/user_mention_tile.dart new file mode 100644 index 00000000..ff9e145b --- /dev/null +++ b/packages/stream_chat_flutter/lib/src/user_mention_tile.dart @@ -0,0 +1,91 @@ +import 'package:flutter/material.dart'; +import 'package:stream_chat_flutter/stream_chat_flutter.dart'; + +/// This widget is used for showing user tiles for mentions +/// Use [title], [subtitle], [leading], [trailing] for +/// substituting widgets in respective positions +class UserMentionTile extends StatelessWidget { + /// Constructor for creating a [UserMentionTile] widget + const UserMentionTile( + this.user, { + Key? key, + this.title, + this.subtitle, + this.leading, + this.trailing, + }) : super(key: key); + + /// User to display in the tile + final User user; + + /// Widget to display as title + final Widget? title; + + /// Widget to display below [title] + final Widget? subtitle; + + /// Widget at the start of the tile + final Widget? leading; + + /// Widget at the end of tile + final Widget? trailing; + + @override + Widget build(BuildContext context) { + final chatThemeData = StreamChatTheme.of(context); + return SizedBox( + height: 56, + child: Row( + children: [ + const SizedBox( + width: 16, + ), + leading ?? + UserAvatar( + user: user, + constraints: BoxConstraints.tight(const Size(40, 40)), + ), + const SizedBox(width: 8), + Expanded( + child: Align( + alignment: Alignment.centerLeft, + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + title ?? + Text( + user.name, + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: chatThemeData.textTheme.bodyBold, + ), + const SizedBox(height: 2), + subtitle ?? + Text( + '@${user.id}', + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: chatThemeData.textTheme.footnoteBold.copyWith( + color: chatThemeData.colorTheme.textLowEmphasis, + ), + ), + ], + ), + ), + ), + trailing ?? + Padding( + padding: const EdgeInsets.only( + right: 18, + left: 8, + ), + child: StreamSvgIcon.mentions( + color: chatThemeData.colorTheme.accentPrimary, + ), + ), + ], + ), + ); + } +} diff --git a/packages/stream_chat_flutter/lib/src/user_mentions_overlay.dart b/packages/stream_chat_flutter/lib/src/user_mentions_overlay.dart new file mode 100644 index 00000000..d155ce4c --- /dev/null +++ b/packages/stream_chat_flutter/lib/src/user_mentions_overlay.dart @@ -0,0 +1,191 @@ +import 'package:flutter/material.dart'; +import 'package:stream_chat_flutter/src/extension.dart'; +import 'package:stream_chat_flutter/src/stream_chat_theme.dart'; +import 'package:stream_chat_flutter/src/user_mention_tile.dart'; +import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; + +/// Builder function for building a mention tile. +/// +/// Use [UserMentionTile] for the default implementation. +typedef MentionTileBuilder = Widget Function( + BuildContext context, + User user, +); + +/// Overlay for displaying users that can be mentioned. +class UserMentionsOverlay extends StatefulWidget { + /// Constructor for creating a [UserMentionsOverlay]. + UserMentionsOverlay({ + Key? key, + required this.query, + required this.channel, + required this.size, + this.client, + this.limit = 10, + this.mentionAllAppUsers = false, + this.mentionsTileBuilder, + this.onMentionUserTap, + }) : assert( + channel.state != null, + 'Channel ${channel.cid} is not yet initialized', + ), + assert( + mentionAllAppUsers && client != null, + 'StreamChatClient is required in order to use mentionAllAppUsers', + ), + super(key: key); + + /// Query for searching users. + final String query; + + /// Limit applied on user search results. + final int limit; + + /// The size of the overlay. + final Size size; + + /// The channel to search for users. + final Channel channel; + + /// The client to search for users in case [mentionAllAppUsers] is True. + final StreamChatClient? client; + + /// When enabled mentions search users across the entire app. + /// + /// Defaults to false. + final bool mentionAllAppUsers; + + /// Customize the tile for the mentions overlay. + final MentionTileBuilder? mentionsTileBuilder; + + /// Callback called when a user is selected. + final void Function(User user)? onMentionUserTap; + + @override + _UserMentionsOverlayState createState() => _UserMentionsOverlayState(); +} + +class _UserMentionsOverlayState extends State { + late Future> userMentionsFuture; + late List initialMentions = membersAndWatchers; + + @override + void initState() { + super.initState(); + userMentionsFuture = queryMentions(widget.query); + } + + @override + void didUpdateWidget(covariant UserMentionsOverlay oldWidget) { + super.didUpdateWidget(oldWidget); + if (widget.channel != oldWidget.channel || + widget.query != oldWidget.query || + widget.mentionAllAppUsers != oldWidget.mentionAllAppUsers || + widget.limit != oldWidget.limit) { + userMentionsFuture = queryMentions(widget.query); + } + } + + @override + Widget build(BuildContext context) { + final theme = StreamChatTheme.of(context); + return Card( + margin: const EdgeInsets.all(8), + elevation: 2, + color: theme.colorTheme.barsBg, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(8), + ), + clipBehavior: Clip.hardEdge, + child: Container( + constraints: BoxConstraints.loose(widget.size), + decoration: BoxDecoration(color: theme.colorTheme.barsBg), + child: FutureBuilder>( + future: userMentionsFuture, + initialData: initialMentions, + builder: (context, snapshot) { + final users = snapshot.data!; + return ListView.builder( + padding: const EdgeInsets.all(0), + shrinkWrap: true, + itemCount: users.length, + itemBuilder: (context, index) { + final user = users[index]; + return Material( + color: theme.colorTheme.barsBg, + child: InkWell( + onTap: () => widget.onMentionUserTap?.call(user), + child: widget.mentionsTileBuilder?.call(context, user) ?? + UserMentionTile(user), + ), + ); + }, + ); + }, + ), + ), + ); + } + + List get membersAndWatchers { + final state = widget.channel.state!; + return { + ...state.watchers, + ...state.members.map((it) => it.user), + }.whereType().toList(growable: false); + } + + Future> queryMentions(String query) async { + if (widget.mentionAllAppUsers) { + return _queryUsers(query); + } + + var channelState = widget.channel.state; + + channelState = channelState!; + final members = channelState.members; + + // By default, we return maximum 100 members via queryChannels api call. + // Thus it is safe to assume, that if number of members in channel.state + // is < 100, then all the members are already available on client side + // and we don't need to make any api call to queryMembers endpoint. + if (members.length < 100) { + final matchingUsers = membersAndWatchers.search(query); + return matchingUsers.toList(growable: false); + } + + final result = await _queryMembers(query); + return result + .map((it) => it.user) + .whereType() + .toList(growable: false); + } + + Future> _queryMembers(String query) async { + final response = await widget.channel.queryMembers( + pagination: PaginationParams(limit: widget.limit), + filter: query.isEmpty + ? const Filter.empty() + : Filter.autoComplete('name', query), + ); + return response.members; + } + + Future> _queryUsers(String query) async { + assert( + widget.client != null, + 'StreamChatClient is required in order to query all app users', + ); + final response = await widget.client!.queryUsers( + pagination: PaginationParams(limit: widget.limit), + filter: query.isEmpty + ? const Filter.empty() + : Filter.or([ + Filter.autoComplete('id', query), + Filter.autoComplete('name', query), + ]), + sort: [const SortOption('id', direction: SortOption.ASC)], + ); + return response.users; + } +} diff --git a/packages/stream_chat_flutter/lib/src/utils.dart b/packages/stream_chat_flutter/lib/src/utils.dart index 4dc9fee7..9b85e8c2 100644 --- a/packages/stream_chat_flutter/lib/src/utils.dart +++ b/packages/stream_chat_flutter/lib/src/utils.dart @@ -1,10 +1,11 @@ import 'dart:async'; +import 'dart:math' as math; import 'package:flutter/material.dart'; +import 'package:stream_chat_flutter/src/extension.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; import 'package:url_launcher/url_launcher.dart'; -import 'package:stream_chat_flutter/src/extension.dart'; /// Launch URL Future launchURL(BuildContext context, String url) async { @@ -388,3 +389,39 @@ class Tuple2 { @override int get hashCode => item1.hashCode ^ item2.hashCode; } + +/// Levenshtein algorithm implementation based on: +/// http://en.wikipedia.org/wiki/Levenshtein_distance#Iterative_with_two_matrix_rows +int levenshtein(String s, String t, {bool caseSensitive = true}) { + if (!caseSensitive) { + // ignore: parameter_assignments + s = s.toLowerCase(); + // ignore: parameter_assignments + t = t.toLowerCase(); + } + if (s == t) return 0; + if (s.isEmpty) return t.length; + if (t.isEmpty) return s.length; + + final v0 = List.filled(t.length + 1, 0); + final v1 = List.filled(t.length + 1, 0); + + for (var i = 0; i < t.length + 1; i < i++) { + v0[i] = i; + } + + for (var i = 0; i < s.length; i++) { + v1[0] = i + 1; + + for (var j = 0; j < t.length; j++) { + final cost = (s[i] == t[j]) ? 0 : 1; + v1[j + 1] = math.min(v1[j] + 1, math.min(v0[j + 1] + 1, v0[j] + cost)); + } + + for (var j = 0; j < t.length + 1; j++) { + v0[j] = v1[j]; + } + } + + return v1[t.length]; +} diff --git a/packages/stream_chat_flutter/lib/stream_chat_flutter.dart b/packages/stream_chat_flutter/lib/stream_chat_flutter.dart index 42188fa0..0447fc68 100644 --- a/packages/stream_chat_flutter/lib/stream_chat_flutter.dart +++ b/packages/stream_chat_flutter/lib/stream_chat_flutter.dart @@ -44,5 +44,6 @@ export 'src/unread_indicator.dart'; export 'src/user_avatar.dart'; export 'src/user_item.dart'; export 'src/user_list_view.dart'; +export 'src/user_mention_tile.dart'; export 'src/utils.dart'; export 'src/visible_footnote.dart'; diff --git a/packages/stream_chat_flutter/pubspec.yaml b/packages/stream_chat_flutter/pubspec.yaml index 54888873..a792d67b 100644 --- a/packages/stream_chat_flutter/pubspec.yaml +++ b/packages/stream_chat_flutter/pubspec.yaml @@ -14,6 +14,7 @@ dependencies: characters: ^1.1.0 chewie: ^1.2.0 collection: ^1.15.0 + diacritic: ^0.1.3 dio: ^4.0.0 ezanimation: ^0.5.0 file_picker: ^3.0.1 diff --git a/packages/stream_chat_flutter/test/src/extension_test.dart b/packages/stream_chat_flutter/test/src/extension_test.dart new file mode 100644 index 00000000..9d8d92e4 --- /dev/null +++ b/packages/stream_chat_flutter/test/src/extension_test.dart @@ -0,0 +1,71 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:stream_chat_flutter/src/extension.dart'; +import 'package:stream_chat_flutter/stream_chat_flutter.dart'; + +void main() { + group('List.search', () { + test('should work fine', () { + final tommaso = User(id: 'tommaso', name: 'Tommaso'); + final thierry = User(id: 'thierry', name: 'Thierry'); + final users = [tommaso, thierry]; + + final a = users.search('Tom'); + expect(users.search('Tom'), [tommaso]); + expect(users.search('Thier'), [thierry]); + }); + + test('should search using UpperCased', () { + final tommaso = User(id: 'tommaso', name: 'Tommaso'); + final thierry = User(id: 'thierry', name: 'Thierry'); + final users = [tommaso, thierry]; + + expect(users.search('tom'), [tommaso]); + expect(users.search('thier'), [thierry]); + }); + + test('should search by .id or .name', () { + final user1 = User(id: 'searchingThis'); + final user2 = User(id: 'x', name: 'searchingThis'); + + expect([user1].search('sear'), [user1]); + expect([user2].search('sear'), [user2]); + }); + + test('should search transliterated', () { + final tommaso = User(id: 'tommaso', name: 'Tommaso'); + final thierry = User(id: 'thierry', name: 'Thierry'); + final users = [tommaso, thierry]; + + expect(users.search('tóm'), [tommaso]); + expect(users.search('thíer'), [thierry]); + }); + + test('search and sorted by distance', () { + final tommaso = User(id: 'tommaso', name: 'Tommaso'); + final tomas = User(id: 'tomas', name: 'Tomas'); + final users = [tommaso, tomas]; + + expect(users.search('tom'), [tomas, tommaso]); + }); + + test('should work fine with cyrillic diacritics', () { + final petyo = User(id: '42', name: 'Петьо'); + final anastasia = User(id: '13', name: 'Анастасiя'); + final dmitriy = User(id: '99', name: 'Дмитрий'); + final users = [petyo, anastasia, dmitriy]; + + expect(users.search('petyo'), []); + expect(users.search('Пе'), [petyo]); + expect(users.search('Ана'), [anastasia]); + expect(users.search('Дмитри'), [dmitriy]); + expect(users.search('Дмитрии'), [dmitriy]); + }); + + test('should work fine with french diacritics', () { + final user = User(id: 'fra', name: 'françois'); + + expect([user].search('françois'), [user]); + expect([user].search('franc'), [user]); + }); + }); +} diff --git a/packages/stream_chat_flutter_core/lib/src/stream_channel.dart b/packages/stream_chat_flutter_core/lib/src/stream_channel.dart index fb7b79f2..443529ac 100644 --- a/packages/stream_chat_flutter_core/lib/src/stream_channel.dart +++ b/packages/stream_chat_flutter_core/lib/src/stream_channel.dart @@ -152,13 +152,9 @@ class StreamChannelState extends State { int limit = 20, }) { if (direction == QueryDirection.top) { - return _queryTopMessages( - limit: limit, - ); + return _queryTopMessages(limit: limit); } - return _queryBottomMessages( - limit: limit, - ); + return _queryBottomMessages(limit: limit); } /// Calls [channel.getReplies] updating [queryMessage] stream @@ -311,6 +307,20 @@ class StreamChannelState extends State { return message; } + /// Query channel members. + Future> queryMembers({ + Filter? filter, + List? sort, + PaginationParams? pagination, + }) async { + final response = await channel.queryMembers( + filter: filter, + sort: sort, + pagination: pagination, + ); + return response.members; + } + /// Reloads the channel with latest message Future reloadChannel() => _queryAtMessage(before: 30); From a931682b4c3551580eb5d08b356afb235dbad37d Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Mon, 27 Sep 2021 17:46:57 +0530 Subject: [PATCH 49/70] added not exists filter --- .../stream_chat/lib/src/core/models/filter.dart | 15 +++++++++++---- .../test/src/core/models/filter_test.dart | 2 +- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/packages/stream_chat/lib/src/core/models/filter.dart b/packages/stream_chat/lib/src/core/models/filter.dart index ca494bdb..ba7d044e 100644 --- a/packages/stream_chat/lib/src/core/models/filter.dart +++ b/packages/stream_chat/lib/src/core/models/filter.dart @@ -40,9 +40,12 @@ enum FilterOperator { /// Matches values with the specified prefix. autoComplete, - /// Matches values that exist/don't exist based on the specified boolean value. + /// Matches values that exist. exists, + /// Matches values that don't exist. + notExists, + /// Matches all the values specified in an array. and, @@ -163,9 +166,13 @@ class Filter extends Equatable { factory Filter.autoComplete(String key, String text) => Filter._(operator: FilterOperator.autoComplete, key: key, value: text); - /// Matches values that exist/don't exist based on the specified boolean value. - factory Filter.exists(String key, {bool exists = true}) => - Filter._(operator: FilterOperator.exists, key: key, value: exists); + /// Matches values that exist. + factory Filter.exists(String key) => + Filter._(operator: FilterOperator.exists, key: key, value: true); + + /// Matches values that don't exist. + factory Filter.notExists(String key) => + Filter._(operator: FilterOperator.exists, key: key, value: false); /// Matches any list that contains the specified values factory Filter.contains(String key, Object value) => diff --git a/packages/stream_chat/test/src/core/models/filter_test.dart b/packages/stream_chat/test/src/core/models/filter_test.dart index c0a8ce8f..f836d845 100644 --- a/packages/stream_chat/test/src/core/models/filter_test.dart +++ b/packages/stream_chat/test/src/core/models/filter_test.dart @@ -114,7 +114,7 @@ void main() { test('notExists', () { const key = 'testKey'; - final filter = Filter.exists(key, exists: false); + final filter = Filter.notExists(key); expect(filter.key, key); expect(filter.value, isFalse); expect(filter.operator, FilterOperator.exists.rawValue); From d996eddb568e0b421a25e688a79325253cbb4fde Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Mon, 27 Sep 2021 17:59:59 +0530 Subject: [PATCH 50/70] added changelog --- packages/stream_chat/CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/packages/stream_chat/CHANGELOG.md b/packages/stream_chat/CHANGELOG.md index 3c7dd84f..06370e64 100644 --- a/packages/stream_chat/CHANGELOG.md +++ b/packages/stream_chat/CHANGELOG.md @@ -1,3 +1,9 @@ +## Upcoming + +🛑️ Breaking Changes from `3.0.0` + +- `Filter.notExists` now replaces `Filter.exists(key, exists: false)`. + ## 3.0.0 🛑️ Breaking Changes from `2.2.1` From 98493b77d8bc4c3a30aea80cefb5ee8e2fa345b0 Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Mon, 27 Sep 2021 18:37:26 +0530 Subject: [PATCH 51/70] removed breaking change --- packages/stream_chat/CHANGELOG.md | 2 +- packages/stream_chat/lib/src/core/models/filter.dart | 7 +++---- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/packages/stream_chat/CHANGELOG.md b/packages/stream_chat/CHANGELOG.md index 06370e64..c12377b1 100644 --- a/packages/stream_chat/CHANGELOG.md +++ b/packages/stream_chat/CHANGELOG.md @@ -1,6 +1,6 @@ ## Upcoming -🛑️ Breaking Changes from `3.0.0` +✅ Added - `Filter.notExists` now replaces `Filter.exists(key, exists: false)`. diff --git a/packages/stream_chat/lib/src/core/models/filter.dart b/packages/stream_chat/lib/src/core/models/filter.dart index ba7d044e..fb4cfb42 100644 --- a/packages/stream_chat/lib/src/core/models/filter.dart +++ b/packages/stream_chat/lib/src/core/models/filter.dart @@ -167,12 +167,11 @@ class Filter extends Equatable { Filter._(operator: FilterOperator.autoComplete, key: key, value: text); /// Matches values that exist. - factory Filter.exists(String key) => - Filter._(operator: FilterOperator.exists, key: key, value: true); + factory Filter.exists(String key, {bool exists = true}) => + Filter._(operator: FilterOperator.exists, key: key, value: exists); /// Matches values that don't exist. - factory Filter.notExists(String key) => - Filter._(operator: FilterOperator.exists, key: key, value: false); + factory Filter.notExists(String key) => Filter.exists(key, exists: false); /// Matches any list that contains the specified values factory Filter.contains(String key, Object value) => From 4592491b153ff5d8ea6c8a63bf9a6ddfc52676d9 Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Tue, 28 Sep 2021 14:09:47 +0530 Subject: [PATCH 52/70] Update packages/stream_chat/CHANGELOG.md Co-authored-by: Salvatore Giordano --- packages/stream_chat/CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/stream_chat/CHANGELOG.md b/packages/stream_chat/CHANGELOG.md index c12377b1..cd732218 100644 --- a/packages/stream_chat/CHANGELOG.md +++ b/packages/stream_chat/CHANGELOG.md @@ -2,7 +2,7 @@ ✅ Added -- `Filter.notExists` now replaces `Filter.exists(key, exists: false)`. +- Added `Filter.notExists`. ## 3.0.0 From 8fd25028d3811e7be1aad5a3f18f2e15695be458 Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Tue, 28 Sep 2021 14:10:14 +0530 Subject: [PATCH 53/70] Update packages/stream_chat/lib/src/core/models/filter.dart Co-authored-by: Salvatore Giordano --- packages/stream_chat/lib/src/core/models/filter.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/stream_chat/lib/src/core/models/filter.dart b/packages/stream_chat/lib/src/core/models/filter.dart index fb4cfb42..cd53c9e8 100644 --- a/packages/stream_chat/lib/src/core/models/filter.dart +++ b/packages/stream_chat/lib/src/core/models/filter.dart @@ -166,7 +166,7 @@ class Filter extends Equatable { factory Filter.autoComplete(String key, String text) => Filter._(operator: FilterOperator.autoComplete, key: key, value: text); - /// Matches values that exist. + /// Matches values that exist/don't exist based on the specified boolean value. factory Filter.exists(String key, {bool exists = true}) => Filter._(operator: FilterOperator.exists, key: key, value: exists); From 0f9a6c21ac16f88d4e0314974e6436932db986c2 Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Tue, 28 Sep 2021 14:10:34 +0530 Subject: [PATCH 54/70] Update packages/stream_chat/lib/src/core/models/filter.dart Co-authored-by: Salvatore Giordano --- packages/stream_chat/lib/src/core/models/filter.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/stream_chat/lib/src/core/models/filter.dart b/packages/stream_chat/lib/src/core/models/filter.dart index cd53c9e8..e668c417 100644 --- a/packages/stream_chat/lib/src/core/models/filter.dart +++ b/packages/stream_chat/lib/src/core/models/filter.dart @@ -40,7 +40,7 @@ enum FilterOperator { /// Matches values with the specified prefix. autoComplete, - /// Matches values that exist. + /// Matches values that exist/don't exist based on the specified boolean value. exists, /// Matches values that don't exist. From 517c2cf00b0957409eee160e6d9e3efeaa3ea35e Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Tue, 28 Sep 2021 17:21:52 +0530 Subject: [PATCH 55/70] added docs for Filter.notExists --- .../docs/Flutter/guides/understanding_filters.mdx | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/docusaurus/docs/Flutter/guides/understanding_filters.mdx b/docusaurus/docs/Flutter/guides/understanding_filters.mdx index e9d72ecf..292afd07 100644 --- a/docusaurus/docs/Flutter/guides/understanding_filters.mdx +++ b/docusaurus/docs/Flutter/guides/understanding_filters.mdx @@ -104,7 +104,16 @@ Filter.autoComplete('name', 'demo') The 'exists' filter matches values that exist, or don't exist, based on the specified boolean value. ```dart -Filter.exists('name', true) +Filter.exists('name') +``` + +#### Filter.notExists + +The 'notExists' filter checks if the specified key doesn't exist. This is a simplified call to `Filter.exists` +with the value set to false. + +```dart +Filter.notExists('name') ``` #### Filter.contains From b5060b26f60eb403e6885676e6a68546063b304c Mon Sep 17 00:00:00 2001 From: Omkar Todkar Date: Wed, 29 Sep 2021 02:50:38 +0530 Subject: [PATCH 56/70] support for int userId in tokens --- packages/stream_chat/lib/src/core/http/token.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/stream_chat/lib/src/core/http/token.dart b/packages/stream_chat/lib/src/core/http/token.dart index 7d746286..0830aa68 100644 --- a/packages/stream_chat/lib/src/core/http/token.dart +++ b/packages/stream_chat/lib/src/core/http/token.dart @@ -46,12 +46,12 @@ class Token extends Equatable { /// Creates a [Token] instance from the provided [rawValue] if it's valid. factory Token.fromRawValue(String rawValue) { final jwtBody = JsonWebToken.unverified(rawValue); - final userId = jwtBody.claims.getTyped('user_id'); + final userId = jwtBody.claims.getTyped('user_id'); assert( userId != null, 'Invalid `token`, It should contain `user_id`', ); - return Token._(rawValue: rawValue, userId: userId!, authType: AuthType.jwt); + return Token._(rawValue: rawValue, userId: userId!.toString(), authType: AuthType.jwt); } /// The token which can be used during the development. From 2ce209bed0d68cb66545682ca2333d3c744df8e2 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Wed, 29 Sep 2021 09:55:56 +0200 Subject: [PATCH 57/70] fix(llc): format and analyze --- packages/stream_chat/CHANGELOG.md | 6 ++++++ packages/stream_chat/lib/src/core/http/token.dart | 6 +++++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/packages/stream_chat/CHANGELOG.md b/packages/stream_chat/CHANGELOG.md index 3c7dd84f..fbac9f87 100644 --- a/packages/stream_chat/CHANGELOG.md +++ b/packages/stream_chat/CHANGELOG.md @@ -1,3 +1,9 @@ +## Upcoming + +🐞 Fixed + +- [[#710]](https://github.com/GetStream/stream-chat-flutter/issues/710) Fixed JWT requiring using `String` as id. + ## 3.0.0 🛑️ Breaking Changes from `2.2.1` diff --git a/packages/stream_chat/lib/src/core/http/token.dart b/packages/stream_chat/lib/src/core/http/token.dart index 0830aa68..eba49b82 100644 --- a/packages/stream_chat/lib/src/core/http/token.dart +++ b/packages/stream_chat/lib/src/core/http/token.dart @@ -51,7 +51,11 @@ class Token extends Equatable { userId != null, 'Invalid `token`, It should contain `user_id`', ); - return Token._(rawValue: rawValue, userId: userId!.toString(), authType: AuthType.jwt); + return Token._( + rawValue: rawValue, + userId: userId!.toString(), + authType: AuthType.jwt, + ); } /// The token which can be used during the development. From 35673f140c64921f98e14357b042ff277345d3e6 Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Wed, 29 Sep 2021 16:21:40 +0530 Subject: [PATCH 58/70] ref(llc): fixes and improvements Signed-off-by: xsahil03x --- .../stream_chat/lib/src/client/channel.dart | 98 ++++++++++--------- 1 file changed, 50 insertions(+), 48 deletions(-) diff --git a/packages/stream_chat/lib/src/client/channel.dart b/packages/stream_chat/lib/src/client/channel.dart index 96bd1838..0c6b0ed1 100644 --- a/packages/stream_chat/lib/src/client/channel.dart +++ b/packages/stream_chat/lib/src/client/channel.dart @@ -351,7 +351,7 @@ class Channel { Future get initialized => _initializedCompleter.future; final _cancelableAttachmentUploadRequest = {}; - final _messageAttachmentsUploadCompleter = {}; + final _messageAttachmentsUploadCompleter = >{}; /// Cancels [attachmentId] upload request. Throws exception if the request /// hasn't even started yet, Already completed or Already cancelled. @@ -382,12 +382,10 @@ class Channel { String messageId, Iterable attachmentIds, ) { - final message = [ + var message = [ ...state!.messages, ...state!.threads.values.expand((messages) => messages), - ].firstWhereOrNull( - (it) => it.id == messageId, - ); + ].firstWhereOrNull((it) => it.id == messageId); if (message == null) { throw const StreamChatError('Error, Message not found'); @@ -409,11 +407,15 @@ class Channel { client.logger.info('Found ${attachments.length} attachments'); void updateAttachment(Attachment attachment) { - final index = - message.attachments.indexWhere((it) => it.id == attachment.id); + final index = message!.attachments.indexWhere( + (it) => it.id == attachment.id, + ); if (index != -1) { - message.attachments[index] = attachment; - state?.addMessage(message); + final newAttachments = [...message!.attachments]..[index] = attachment; + final updatedMessage = message!.copyWith(attachments: newAttachments); + state?.addMessage(updatedMessage); + // updating original message for next iteration + message = message!.merge(updatedMessage); } } @@ -476,7 +478,7 @@ class Channel { _cancelableAttachmentUploadRequest.remove(it.id); }); })).whenComplete(() { - if (message.attachments.every((it) => it.uploadState.isSuccess)) { + if (message!.attachments.every((it) => it.uploadState.isSuccess)) { _messageAttachmentsUploadCompleter.remove(messageId)?.complete(message); } }); @@ -1076,33 +1078,7 @@ class Channel { } else { // remove the passed message if response does // not contain message - final oldIndex = state!.messages.indexWhere((m) => m.id == messageId); - - // remove regular message if present - if (oldIndex != -1) { - final oldMessage = state!.messages[oldIndex]; - state!._channelState = state!._channelState.copyWith( - messages: state!.messages..remove(oldMessage), - channel: state?._channelState.channel, - ); - } else { - // remove thread message if present - // also reduces total reply count - final oldMessage = state!.threads.values - .expand((messages) => messages) - .firstWhereOrNull((m) => m.id == messageId); - if (oldMessage?.parentId != null) { - final parentMessage = state!.messages.firstWhereOrNull( - (element) => element.id == oldMessage!.parentId, - ); - if (parentMessage != null) { - state!.addMessage(parentMessage.copyWith( - replyCount: parentMessage.replyCount! - 1)); - } - state!.updateThreadInfo(oldMessage!.parentId!, - state!.threads[oldMessage.parentId!]!..remove(oldMessage)); - } - } + state!.removeMessage(message); await _client.chatPersistenceClient?.deleteMessageById(messageId); } return res; @@ -1704,7 +1680,7 @@ class ChannelClientState { })); } - /// Add a message to this channel. + /// Add a [message] to this [channelState]. void addMessage(Message message) { if (message.parentId == null || message.showInChannel == true) { final newMessages = List.from(_channelState.messages); @@ -1735,6 +1711,35 @@ class ChannelClientState { } } + /// Remove a [message] from this [channelState]. + void removeMessage(Message message) { + final parentId = message.parentId; + // i.e. it's a thread message + // 1. Remove the thread message + // 2. Reduce total reply count of parent message + if (parentId != null) { + final allMessages = [...messages]; + final parentMessage = allMessages.firstWhereOrNull( + (it) => it.id == parentId, + ); + + // return if message not available in the memory + if (parentMessage == null) return; + final replyCount = parentMessage.replyCount; + // return if reply count is null or zero + if (replyCount == null || replyCount == 0) return; + + addMessage(parentMessage.copyWith(replyCount: replyCount - 1)); + updateThreadInfo(parentId, threads[parentId]!..remove(message)); + } else { + // Remove regular message + final allMessages = [...messages]; + if (allMessages.remove(message)) { + _channelState = _channelState.copyWith(messages: allMessages); + } + } + } + void _listenReadEvents() { if (_channelState.channel?.config.readEvents == false) { return; @@ -1773,8 +1778,9 @@ class ChannelClientState { List get messages => _channelState.messages; /// Channel message list as a stream. - Stream> get messagesStream => - channelStateStream.map((cs) => cs.messages); + Stream> get messagesStream => channelStateStream + .map((cs) => cs.messages) + .distinct(const ListEquality().equals); /// Channel pinned message list. List get pinnedMessages => _channelState.pinnedMessages.toList(); @@ -1861,15 +1867,11 @@ class ChannelClientState { if (newThreads.containsKey(parentId)) { newThreads[parentId] = [ - ...newThreads[parentId] - ?.where( - (newMessage) => !messages.any((m) => m.id == newMessage.id)) - .toList() ?? - [], ...messages, - ]; - - newThreads[parentId]!.sort(_sortByCreatedAt); + ...newThreads[parentId]!.where( + (newMessage) => !messages.any((m) => m.id == newMessage.id), + ), + ]..sort(_sortByCreatedAt); } else { newThreads[parentId] = messages; } From 599c824fc08deb6a5f80258adf2bd3af84c30d0d Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Wed, 29 Sep 2021 17:06:11 +0530 Subject: [PATCH 59/70] fix(ui): fix early command check Signed-off-by: xsahil03x --- .../lib/src/message_input.dart | 24 +++++-------------- 1 file changed, 6 insertions(+), 18 deletions(-) diff --git a/packages/stream_chat_flutter/lib/src/message_input.dart b/packages/stream_chat_flutter/lib/src/message_input.dart index 95322c10..73a211ff 100644 --- a/packages/stream_chat_flutter/lib/src/message_input.dart +++ b/packages/stream_chat_flutter/lib/src/message_input.dart @@ -2,6 +2,7 @@ import 'dart:async'; import 'dart:math'; import 'package:cached_network_image/cached_network_image.dart'; +import 'package:collection/collection.dart'; import 'package:file_picker/file_picker.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/foundation.dart'; @@ -878,24 +879,11 @@ class MessageInputState extends State { void _checkCommands(String s, BuildContext context) { if (s.startsWith('/')) { - final matchedCommandsList = StreamChannel.of(context) - .channel - .config - ?.commands - .where((element) => element.name == s.substring(1)) - .toList() ?? - []; - - if (matchedCommandsList.length == 1) { - _chosenCommand = matchedCommandsList[0]; - textEditingController.clear(); - - if (_showCommandsOverlay) { - setState(() { - _commandEnabled = true; - _showCommandsOverlay = false; - }); - } + final allCommands = StreamChannel.of(context).channel.config?.commands; + final command = + allCommands?.firstWhereOrNull((it) => it.name == s.substring(1)); + if (command != null) { + return _setCommand(command); } else if (!_showCommandsOverlay) { setState(() { _showCommandsOverlay = true; From c19b72c04229066fb2c53034bdbfa8b7bb79be59 Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Wed, 29 Sep 2021 17:13:56 +0530 Subject: [PATCH 60/70] Update packages/stream_chat_flutter/lib/src/user_mentions_overlay.dart Co-authored-by: Salvatore Giordano --- packages/stream_chat_flutter/lib/src/user_mentions_overlay.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/stream_chat_flutter/lib/src/user_mentions_overlay.dart b/packages/stream_chat_flutter/lib/src/user_mentions_overlay.dart index d155ce4c..6068369a 100644 --- a/packages/stream_chat_flutter/lib/src/user_mentions_overlay.dart +++ b/packages/stream_chat_flutter/lib/src/user_mentions_overlay.dart @@ -30,7 +30,7 @@ class UserMentionsOverlay extends StatefulWidget { 'Channel ${channel.cid} is not yet initialized', ), assert( - mentionAllAppUsers && client != null, + !mentionAllAppUsers || (mentionAllAppUsers && client != null), 'StreamChatClient is required in order to use mentionAllAppUsers', ), super(key: key); From a0c98aa851ac621388d75f15c626bcd0fc698797 Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Wed, 29 Sep 2021 17:16:56 +0530 Subject: [PATCH 61/70] ref(ui): remove initial data to fix flickering in mentions overlay. Signed-off-by: xsahil03x --- .../stream_chat_flutter/lib/src/user_mentions_overlay.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/stream_chat_flutter/lib/src/user_mentions_overlay.dart b/packages/stream_chat_flutter/lib/src/user_mentions_overlay.dart index 6068369a..e3830231 100644 --- a/packages/stream_chat_flutter/lib/src/user_mentions_overlay.dart +++ b/packages/stream_chat_flutter/lib/src/user_mentions_overlay.dart @@ -67,7 +67,6 @@ class UserMentionsOverlay extends StatefulWidget { class _UserMentionsOverlayState extends State { late Future> userMentionsFuture; - late List initialMentions = membersAndWatchers; @override void initState() { @@ -102,8 +101,9 @@ class _UserMentionsOverlayState extends State { decoration: BoxDecoration(color: theme.colorTheme.barsBg), child: FutureBuilder>( future: userMentionsFuture, - initialData: initialMentions, builder: (context, snapshot) { + if (snapshot.hasError) return const Offstage(); + if (!snapshot.hasData) return const Offstage(); final users = snapshot.data!; return ListView.builder( padding: const EdgeInsets.all(0), From 7ad621f93db07d177ed2b3f7471082d7b031156f Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Wed, 29 Sep 2021 17:21:15 +0530 Subject: [PATCH 62/70] chore(ui): improve `MentionTile` deprecation message. Signed-off-by: xsahil03x --- packages/stream_chat_flutter/lib/src/mention_tile.dart | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/packages/stream_chat_flutter/lib/src/mention_tile.dart b/packages/stream_chat_flutter/lib/src/mention_tile.dart index b900be65..c302ccb8 100644 --- a/packages/stream_chat_flutter/lib/src/mention_tile.dart +++ b/packages/stream_chat_flutter/lib/src/mention_tile.dart @@ -4,10 +4,7 @@ import 'package:stream_chat_flutter/stream_chat_flutter.dart'; /// This widget is used for showing user tiles for mentions /// Use [title], [subtitle], [leading], [trailing] for /// substituting widgets in respective positions -@Deprecated( - "'MentionTile' widget is deprecated and shouldn't be used. " - "Use 'UserMentionTile' instead", -) +@Deprecated('Use `UserMentionTile` instead. Will be removed in future release') class MentionTile extends StatelessWidget { /// Constructor for creating a [MentionTile] widget const MentionTile( From bcf181bf213479b71fd24f43cc1b2e22c2b80ed3 Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Wed, 29 Sep 2021 18:54:44 +0530 Subject: [PATCH 63/70] fix(llc): fix tests Signed-off-by: xsahil03x --- .../test/src/client/channel_test.dart | 182 +++++++++++++++--- packages/stream_chat/test/src/matchers.dart | 59 ++++++ 2 files changed, 217 insertions(+), 24 deletions(-) diff --git a/packages/stream_chat/test/src/client/channel_test.dart b/packages/stream_chat/test/src/client/channel_test.dart index 08ebd091..6ec26d27 100644 --- a/packages/stream_chat/test/src/client/channel_test.dart +++ b/packages/stream_chat/test/src/client/channel_test.dart @@ -334,20 +334,87 @@ void main() { expectLater( // skipping first seed message list -> [] messages channel.state?.messagesStream.skip(1), - emitsInOrder([ + emitsInOrder( [ - isSameMessageAs( - message.copyWith(status: MessageSendingStatus.sending), - matchSendingStatus: true, - ), + // preparing attachments to upload + [ + isSameMessageAs( + message.copyWith( + status: MessageSendingStatus.sending, + attachments: [ + ...attachments.map((it) => it.copyWith( + uploadState: const UploadState.preparing())) + ], + ), + matchSendingStatus: true, + matchAttachments: true, + matchAttachmentsUploadState: true, + ), + ], + // 0th attachment is successfully uploaded + [ + isSameMessageAs( + message.copyWith( + status: MessageSendingStatus.sending, + attachments: [...attachments]..[0] = + attachments[0].copyWith( + uploadState: const UploadState.success(), + ), + ), + matchSendingStatus: true, + matchAttachments: true, + matchAttachmentsUploadState: true, + ), + ], + // 0th and 1st attachment is successfully uploaded + [ + isSameMessageAs( + message.copyWith( + status: MessageSendingStatus.sending, + attachments: [...attachments] + ..[0] = attachments[0].copyWith( + uploadState: const UploadState.success(), + ) + ..[1] = attachments[1].copyWith( + uploadState: const UploadState.success(), + ), + ), + matchSendingStatus: true, + matchAttachments: true, + matchAttachmentsUploadState: true, + ), + ], + // all the attachments are successfully uploaded + [ + isSameMessageAs( + message.copyWith( + status: MessageSendingStatus.sending, + attachments: [ + ...attachments.map((it) => + it.copyWith(uploadState: const UploadState.success())) + ], + ), + matchSendingStatus: true, + matchAttachments: true, + matchAttachmentsUploadState: true, + ), + ], + [ + isSameMessageAs( + message.copyWith( + status: MessageSendingStatus.sent, + attachments: [ + ...attachments.map((it) => + it.copyWith(uploadState: const UploadState.success())) + ], + ), + matchSendingStatus: true, + matchAttachments: true, + matchAttachmentsUploadState: true, + ), + ], ], - [ - isSameMessageAs( - message.copyWith(status: MessageSendingStatus.sent), - matchSendingStatus: true, - ), - ], - ]), + ), ); final res = await channel.sendMessage(message); @@ -472,20 +539,87 @@ void main() { expectLater( // skipping first seed message list -> [] messages channel.state?.messagesStream.skip(1), - emitsInOrder([ + emitsInOrder( [ - isSameMessageAs( - message.copyWith(status: MessageSendingStatus.updating), - matchSendingStatus: true, - ), + // preparing attachments to upload + [ + isSameMessageAs( + message.copyWith( + status: MessageSendingStatus.updating, + attachments: [ + ...attachments.map((it) => it.copyWith( + uploadState: const UploadState.preparing())) + ], + ), + matchSendingStatus: true, + matchAttachments: true, + matchAttachmentsUploadState: true, + ), + ], + // 0th attachment is successfully uploaded + [ + isSameMessageAs( + message.copyWith( + status: MessageSendingStatus.updating, + attachments: [...attachments]..[0] = + attachments[0].copyWith( + uploadState: const UploadState.success(), + ), + ), + matchSendingStatus: true, + matchAttachments: true, + matchAttachmentsUploadState: true, + ), + ], + // 0th and 1st attachment is successfully uploaded + [ + isSameMessageAs( + message.copyWith( + status: MessageSendingStatus.updating, + attachments: [...attachments] + ..[0] = attachments[0].copyWith( + uploadState: const UploadState.success(), + ) + ..[1] = attachments[1].copyWith( + uploadState: const UploadState.success(), + ), + ), + matchSendingStatus: true, + matchAttachments: true, + matchAttachmentsUploadState: true, + ), + ], + // all the attachments are successfully uploaded + [ + isSameMessageAs( + message.copyWith( + status: MessageSendingStatus.updating, + attachments: [ + ...attachments.map((it) => + it.copyWith(uploadState: const UploadState.success())) + ], + ), + matchSendingStatus: true, + matchAttachments: true, + matchAttachmentsUploadState: true, + ), + ], + [ + isSameMessageAs( + message.copyWith( + status: MessageSendingStatus.sent, + attachments: [ + ...attachments.map((it) => + it.copyWith(uploadState: const UploadState.success())) + ], + ), + matchSendingStatus: true, + matchAttachments: true, + matchAttachmentsUploadState: true, + ), + ], ], - [ - isSameMessageAs( - message.copyWith(status: MessageSendingStatus.sent), - matchSendingStatus: true, - ), - ], - ]), + ), ); final res = await channel.updateMessage(message); diff --git a/packages/stream_chat/test/src/matchers.dart b/packages/stream_chat/test/src/matchers.dart index 27cfa43d..e54b113d 100644 --- a/packages/stream_chat/test/src/matchers.dart +++ b/packages/stream_chat/test/src/matchers.dart @@ -1,6 +1,7 @@ import 'package:collection/collection.dart'; import 'package:dio/dio.dart' show MultipartFile; import 'package:stream_chat/src/client/channel.dart'; +import 'package:stream_chat/src/core/models/attachment.dart'; import 'package:stream_chat/src/core/models/channel_state.dart'; import 'package:stream_chat/src/core/models/event.dart'; import 'package:stream_chat/src/core/models/message.dart'; @@ -46,12 +47,16 @@ Matcher isSameMessageAs( bool matchText = false, bool matchReactions = false, bool matchSendingStatus = false, + bool matchAttachments = false, + bool matchAttachmentsUploadState = false, }) => _IsSameMessageAs( targetMessage: targetMessage, matchText: matchText, matchReactions: matchReactions, matchSendingStatus: matchSendingStatus, + matchAttachments: matchAttachments, + matchAttachmentsUploadState: matchAttachmentsUploadState, ); class _IsSameMessageAs extends Matcher { @@ -60,12 +65,16 @@ class _IsSameMessageAs extends Matcher { this.matchText = false, this.matchReactions = false, this.matchSendingStatus = false, + this.matchAttachments = false, + this.matchAttachmentsUploadState = false, }); final Message targetMessage; final bool matchText; final bool matchReactions; final bool matchSendingStatus; + final bool matchAttachments; + final bool matchAttachmentsUploadState; @override Description describe(Description description) => @@ -96,6 +105,56 @@ class _IsSameMessageAs extends Matcher { ?.map((it) => '${it.type}-${it.messageId}') .toList()); } + if (matchAttachments) { + bool matchAttachments() { + final attachments = message.attachments; + final targetAttachments = targetMessage.attachments; + if (identical(attachments, targetAttachments)) return true; + final length = attachments.length; + if (length != targetAttachments.length) return false; + for (var i = 0; i < length; i++) { + if (!isSameAttachmentAs( + attachments[i], + matchUploadState: matchAttachmentsUploadState, + ).matches(targetAttachments[i], matchState)) return false; + } + return true; + } + + matches &= matchAttachments(); + } + return matches; + } +} + +Matcher isSameAttachmentAs( + Attachment targetAttachment, { + bool matchUploadState = false, +}) => + _IsSameAttachmentAs( + targetAttachment: targetAttachment, + matchUploadState: matchUploadState, + ); + +class _IsSameAttachmentAs extends Matcher { + const _IsSameAttachmentAs({ + required this.targetAttachment, + this.matchUploadState = false, + }); + + final Attachment targetAttachment; + final bool matchUploadState; + + @override + Description describe(Description description) => + description.add('is same attachment as $targetAttachment'); + + @override + bool matches(covariant Attachment attachment, Map matchState) { + var matches = attachment.id == targetAttachment.id; + if (matchUploadState) { + matches &= attachment.uploadState == targetAttachment.uploadState; + } return matches; } } From 2bf88bfe0db452b93342c41938ff7059245e849f Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Wed, 29 Sep 2021 19:59:46 +0530 Subject: [PATCH 64/70] fix(ui): fix tests Signed-off-by: xsahil03x --- packages/stream_chat_flutter/lib/src/message_input.dart | 3 ++- .../test/src/message_action_modal_test.dart | 2 ++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/stream_chat_flutter/lib/src/message_input.dart b/packages/stream_chat_flutter/lib/src/message_input.dart index 97d8b2ac..c49158cd 100644 --- a/packages/stream_chat_flutter/lib/src/message_input.dart +++ b/packages/stream_chat_flutter/lib/src/message_input.dart @@ -16,12 +16,12 @@ import 'package:stream_chat_flutter/src/emoji/emoji.dart'; import 'package:stream_chat_flutter/src/emoji_overlay.dart'; import 'package:stream_chat_flutter/src/extension.dart'; import 'package:stream_chat_flutter/src/media_list_view.dart'; -import 'package:stream_chat_flutter/src/user_mentions_overlay.dart'; import 'package:stream_chat_flutter/src/message_list_view.dart'; import 'package:stream_chat_flutter/src/overlays.dart'; import 'package:stream_chat_flutter/src/quoted_message_widget.dart'; import 'package:stream_chat_flutter/src/stream_chat_theme.dart'; import 'package:stream_chat_flutter/src/stream_svg_icon.dart'; +import 'package:stream_chat_flutter/src/user_mentions_overlay.dart'; import 'package:stream_chat_flutter/src/video_service.dart'; import 'package:stream_chat_flutter/src/video_thumbnail_image.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; @@ -1880,6 +1880,7 @@ class MessageInputState extends State { void dispose() { textEditingController.dispose(); _stopSlowMode(); + _onChangedDebounced.cancel(); super.dispose(); } diff --git a/packages/stream_chat_flutter/test/src/message_action_modal_test.dart b/packages/stream_chat_flutter/test/src/message_action_modal_test.dart index 38518b19..9210ac49 100644 --- a/packages/stream_chat_flutter/test/src/message_action_modal_test.dart +++ b/packages/stream_chat_flutter/test/src/message_action_modal_test.dart @@ -263,9 +263,11 @@ void main() { final client = MockClient(); final clientState = MockClientState(); final channel = MockChannel(); + final channelState = MockChannelState(); when(() => client.state).thenReturn(clientState); when(() => clientState.currentUser).thenReturn(OwnUser(id: 'user-id')); + when(() => channel.state).thenReturn(channelState); final themeData = ThemeData(); final streamTheme = StreamChatThemeData.fromTheme(themeData); From aa6fc06402e24821981320d90b2bdd942d64bf26 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Wed, 29 Sep 2021 16:53:29 +0200 Subject: [PATCH 65/70] fix analysis --- packages/stream_chat_flutter/lib/src/message_input.dart | 2 +- packages/stream_chat_flutter/test/src/extension_test.dart | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/stream_chat_flutter/lib/src/message_input.dart b/packages/stream_chat_flutter/lib/src/message_input.dart index c49158cd..d8b22193 100644 --- a/packages/stream_chat_flutter/lib/src/message_input.dart +++ b/packages/stream_chat_flutter/lib/src/message_input.dart @@ -57,7 +57,7 @@ typedef AttachmentThumbnailBuilder = Widget Function( ); /// Builder function for building a mention tile -/// Use [MentionTile] for the default implementation +/// Use [UserMentionTile] for the default implementation typedef MentionTileBuilder = Widget Function( BuildContext context, Member member, diff --git a/packages/stream_chat_flutter/test/src/extension_test.dart b/packages/stream_chat_flutter/test/src/extension_test.dart index 9d8d92e4..696a19fc 100644 --- a/packages/stream_chat_flutter/test/src/extension_test.dart +++ b/packages/stream_chat_flutter/test/src/extension_test.dart @@ -9,7 +9,6 @@ void main() { final thierry = User(id: 'thierry', name: 'Thierry'); final users = [tommaso, thierry]; - final a = users.search('Tom'); expect(users.search('Tom'), [tommaso]); expect(users.search('Thier'), [thierry]); }); From b831ba497333662a088bb8750e6beacfc91a06f1 Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Wed, 29 Sep 2021 21:15:55 +0530 Subject: [PATCH 66/70] refactor(ui): deprecate `MentionTileBuilder`. Signed-off-by: xsahil03x --- .../lib/src/message_input.dart | 39 +++++++++++++++++-- 1 file changed, 35 insertions(+), 4 deletions(-) diff --git a/packages/stream_chat_flutter/lib/src/message_input.dart b/packages/stream_chat_flutter/lib/src/message_input.dart index c49158cd..1aaa6d34 100644 --- a/packages/stream_chat_flutter/lib/src/message_input.dart +++ b/packages/stream_chat_flutter/lib/src/message_input.dart @@ -56,13 +56,25 @@ typedef AttachmentThumbnailBuilder = Widget Function( Attachment, ); -/// Builder function for building a mention tile -/// Use [MentionTile] for the default implementation +/// Builder function for building a mention tile. +/// +/// Use [MentionTile] for the default implementation. +@Deprecated( + 'Use `UserMentionTileBuilder` instead. Will be removed in future release', +) typedef MentionTileBuilder = Widget Function( BuildContext context, Member member, ); +/// Builder function for building a user mention tile. +/// +/// Use [UserMentionTile] for the default implementation. +typedef UserMentionTileBuilder = Widget Function( + BuildContext context, + User user, +); + /// Widget builder for action button. /// /// [defaultActionButton] is the default [IconButton] configuration, @@ -184,7 +196,9 @@ class MessageInput extends StatefulWidget { this.idleSendButton, this.activeSendButton, this.showCommandsButton = true, - this.mentionsTileBuilder, + @Deprecated('''Use `userMentionsTileBuilder` instead. Will be removed in future release''') + this.mentionsTileBuilder, + this.userMentionsTileBuilder, this.maxAttachmentSize = _kDefaultMaxAttachmentSize, this.compressedVideoQuality = VideoQuality.DefaultQuality, this.compressedVideoFrameRate = 30, @@ -279,9 +293,12 @@ class MessageInput extends StatefulWidget { /// Send button widget in an active state final Widget? activeSendButton; - /// Customize the tile for the mentions overlay + /// Customize the tile for the mentions overlay. final MentionTileBuilder? mentionsTileBuilder; + /// Customize the tile for the mentions overlay. + final UserMentionTileBuilder? userMentionsTileBuilder; + /// A callback for error reporting final ErrorListener? onError; @@ -1153,12 +1170,26 @@ class MessageInputState extends State { // ignore: cast_nullable_to_non_nullable final renderObject = context.findRenderObject() as RenderBox; + var tileBuilder = widget.userMentionsTileBuilder; + if (tileBuilder == null && widget.mentionsTileBuilder != null) { + tileBuilder = (context, user) { + final member = Member( + user: user, + userId: user.id, + createdAt: user.createdAt, + updatedAt: user.updatedAt, + ); + return widget.mentionsTileBuilder!.call(context, member); + }; + } + return UserMentionsOverlay( query: query, mentionAllAppUsers: widget.mentionAllAppUsers, client: StreamChat.of(context).client, channel: StreamChannel.of(context).channel, size: Size(renderObject.size.width - 16, 400), + mentionsTileBuilder: tileBuilder, onMentionUserTap: (user) { _mentionedUsers.add(user); splits[splits.length - 1] = user.name; From ade9bc381060a1bf798289f2b365815f6bca5673 Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Wed, 29 Sep 2021 21:25:59 +0530 Subject: [PATCH 67/70] chore(ui): fix analyzer warning. Signed-off-by: xsahil03x --- packages/stream_chat_flutter/lib/src/message_input.dart | 5 ----- 1 file changed, 5 deletions(-) diff --git a/packages/stream_chat_flutter/lib/src/message_input.dart b/packages/stream_chat_flutter/lib/src/message_input.dart index 4397738f..e95d3778 100644 --- a/packages/stream_chat_flutter/lib/src/message_input.dart +++ b/packages/stream_chat_flutter/lib/src/message_input.dart @@ -57,11 +57,6 @@ typedef AttachmentThumbnailBuilder = Widget Function( ); /// Builder function for building a mention tile. -/// -/// Use [UserMentionTile] for the default implementation. -@Deprecated( - 'Use `UserMentionTileBuilder` instead. Will be removed in future release', -) typedef MentionTileBuilder = Widget Function( BuildContext context, Member member, From 09f6a9cd42c12b9aa79f796eedfb938ab18ec0d0 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Thu, 30 Sep 2021 10:14:16 +0200 Subject: [PATCH 68/70] chore(ui): update changelog --- packages/stream_chat_flutter/CHANGELOG.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/packages/stream_chat_flutter/CHANGELOG.md b/packages/stream_chat_flutter/CHANGELOG.md index 32f1b059..35ce4094 100644 --- a/packages/stream_chat_flutter/CHANGELOG.md +++ b/packages/stream_chat_flutter/CHANGELOG.md @@ -1,3 +1,16 @@ +## Upcoming + +✅ Added + +- Added `MessageInput.customOverlays` property to add custom overlays to the message input. +- Added `MessageInput.mentionAllAppUsers` property to mention all app users in the message input. +- The `MessageInput` now supports local search for channels with less than 100 members. + +⚠️ Deprecated + +- `MessageInput.mentionsTileBuilder` is now deprecated in favor of `MessageInput.userMentionsTileBuilder`. +- `MentionTile` is now deprecated in favor of `UserMentionsTile`. + ## 3.0.0 - Updated `stream_chat_flutter_core` dependency to [`3.0.0`](https://pub.dev/packages/stream_chat_flutter_core/changelog). From a7175b0b2c23f9e64f1ab461a77dd2c3a37bfbf8 Mon Sep 17 00:00:00 2001 From: Sahil Kumar Date: Thu, 30 Sep 2021 16:12:06 +0530 Subject: [PATCH 69/70] Update packages/stream_chat_flutter/lib/src/message_input.dart Co-authored-by: Deven Joshi --- packages/stream_chat_flutter/lib/src/message_input.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/stream_chat_flutter/lib/src/message_input.dart b/packages/stream_chat_flutter/lib/src/message_input.dart index e95d3778..82087fab 100644 --- a/packages/stream_chat_flutter/lib/src/message_input.dart +++ b/packages/stream_chat_flutter/lib/src/message_input.dart @@ -1174,7 +1174,7 @@ class MessageInputState extends State { createdAt: user.createdAt, updatedAt: user.updatedAt, ); - return widget.mentionsTileBuilder!.call(context, member); + return widget.mentionsTileBuilder!(context, member); }; } From 4199bc7b6db9345e2512b6689ac150414add1752 Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Thu, 30 Sep 2021 16:38:56 +0530 Subject: [PATCH 70/70] removed filteroperator --- packages/stream_chat/lib/src/core/models/filter.dart | 3 --- 1 file changed, 3 deletions(-) diff --git a/packages/stream_chat/lib/src/core/models/filter.dart b/packages/stream_chat/lib/src/core/models/filter.dart index e668c417..108efcb2 100644 --- a/packages/stream_chat/lib/src/core/models/filter.dart +++ b/packages/stream_chat/lib/src/core/models/filter.dart @@ -43,9 +43,6 @@ enum FilterOperator { /// Matches values that exist/don't exist based on the specified boolean value. exists, - /// Matches values that don't exist. - notExists, - /// Matches all the values specified in an array. and,