Merge branch 'develop' into develop

This commit is contained in:
Salvatore Giordano
2022-04-26 16:07:48 +02:00
committed by GitHub
47 changed files with 870 additions and 263 deletions
+20
View File
@@ -4,6 +4,25 @@
- `centerTitle` and `elevation` properties to `ChannelHeader`, `ThreadHeader` and `ChannelListHeader`.
🐞 Fixed
- [[#1067]](https://github.com/GetStream/stream-chat-flutter/issues/1067): Fix name text overflow in reaction card.
- [[#842]](https://github.com/GetStream/stream-chat-flutter/issues/842): show date divider for first message.
- Loosen up url check for attachment download.
- Use `ogScrapeUrl` for LinkAttachments.
## 3.6.1
- Updated `stream_chat_flutter_core` dependency to [`3.6.1`](https://pub.dev/packages/stream_chat_flutter_core/changelog).
## 3.6.0
🐞 Fixed
- [[#892]](https://github.com/GetStream/stream-chat-flutter/issues/892): Fix default `initialAlignment` in `MessageListView`.
- Fix `MessageInputTheme.inputBackgroundColor` color not being used in some widgets of `MessageInput`
- Removed dependency on `visibility_detector`
## 3.5.1
🐞 Fixed
@@ -27,6 +46,7 @@
- Fix default `Channel` route not opening from `ChannelListView` when `ChannelAvatar` is tapped
## 3.4.0
- Updated `stream_chat_flutter_core` dependency to [`3.4.0`](https://pub.dev/packages/stream_chat_flutter_core/changelog).
🐞 Fixed
@@ -40,7 +40,7 @@ android {
defaultConfig {
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
applicationId "com.example.example"
minSdkVersion 21
minSdkVersion 22
targetSdkVersion 31
versionCode flutterVersionCode.toInteger()
versionName flutterVersionName
@@ -2,7 +2,6 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter/scheduler.dart';
@@ -6,7 +6,6 @@ import 'dart:async';
import 'dart:math';
import 'package:collection/collection.dart' show IterableExtension;
import 'package:flutter/foundation.dart';
import 'package:flutter/scheduler.dart';
import 'package:flutter/widgets.dart';
@@ -18,14 +18,11 @@ class AttachmentTitle extends StatelessWidget {
@override
Widget build(BuildContext context) {
final normalizedTitleLink = attachment.titleLink?.replaceFirst(
RegExp(r'https?://(www\.)?'),
'',
);
final ogScrapeUrl = attachment.ogScrapeUrl;
return GestureDetector(
onTap: () {
final titleLink = attachment.titleLink;
if (titleLink != null) launchURL(context, titleLink);
final ogScrapeUrl = attachment.ogScrapeUrl;
if (ogScrapeUrl != null) launchURL(context, ogScrapeUrl);
},
child: Padding(
padding: const EdgeInsets.all(8),
@@ -42,8 +39,8 @@ class AttachmentTitle extends StatelessWidget {
fontWeight: FontWeight.bold,
),
),
if (normalizedTitleLink != null)
Text(normalizedTitleLink, style: messageTheme.messageTextStyle),
if (ogScrapeUrl != null)
Text(ogScrapeUrl, style: messageTheme.messageTextStyle),
],
),
),
@@ -37,11 +37,11 @@ class UrlAttachment extends StatelessWidget {
final chatThemeData = StreamChatTheme.of(context);
return GestureDetector(
onTap: () {
final titleLink = urlAttachment.titleLink;
if (titleLink != null) {
final ogScrapeUrl = urlAttachment.ogScrapeUrl;
if (ogScrapeUrl != null) {
onLinkTap != null
? onLinkTap!(titleLink)
: launchURL(context, titleLink);
? onLinkTap!(ogScrapeUrl)
: launchURL(context, ogScrapeUrl);
}
},
child: Column(
@@ -225,3 +225,12 @@ extension UserListX on List<User> {
return entries.map((e) => e.key).toList(growable: false);
}
}
/// Extensions on [Uri]
extension UriX on Uri {
/// Return the URI adding the http scheme if it is missing
Uri get withScheme {
if (hasScheme) return this;
return Uri.parse('http://${toString()}');
}
}
@@ -480,6 +480,7 @@ class MessageInputState extends State<MessageInput> {
if (widget.editMessage == null) {
child = Material(
elevation: 8,
color: _messageInputTheme.inputBackgroundColor,
child: child,
);
}
@@ -679,6 +680,7 @@ class MessageInputState extends State<MessageInput> {
gradient: _focusNode.hasFocus
? _messageInputTheme.activeBorderGradient
: _messageInputTheme.idleBorderGradient,
color: _messageInputTheme.inputBackgroundColor,
),
child: Padding(
padding: const EdgeInsets.all(1.5),
@@ -1272,7 +1274,7 @@ class MessageInputState extends State<MessageInput> {
Widget _buildReplyToMessage() {
if (!_hasQuotedMessage) return const Offstage();
final containsUrl = widget.quotedMessage!.attachments
.any((element) => element.titleLink != null);
.any((element) => element.ogScrapeUrl != null);
return QuotedMessageWidget(
reverse: true,
showBorder: !containsUrl,
@@ -8,7 +8,6 @@ import 'package:stream_chat_flutter/scrollable_positioned_list/scrollable_positi
import 'package:stream_chat_flutter/src/extension.dart';
import 'package:stream_chat_flutter/src/swipeable.dart';
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
import 'package:visibility_detector/visibility_detector.dart';
/// Widget builder for message
/// [defaultMessageWidget] is the default [MessageWidget] configuration
@@ -369,7 +368,7 @@ class _MessageListViewState extends State<MessageListView> {
double get _initialAlignment {
final initialAlignment = widget.initialAlignment;
if (initialAlignment != null) return initialAlignment;
return 0.1;
return streamChannel!.initialMessageId == null ? 0 : 0.1;
}
bool _isInitialMessage(String id) => streamChannel!.initialMessageId == id;
@@ -561,6 +560,9 @@ class _MessageListViewState extends State<MessageListView> {
if (widget.reverse
? widget.headerBuilder == null
: widget.footerBuilder == null) {
if (messages.isNotEmpty) {
return _buildDateDivider(messages.last);
}
if (_isThreadConversation) return const Offstage();
return const SizedBox(height: 52);
}
@@ -585,21 +587,12 @@ class _MessageListViewState extends State<MessageListView> {
message = messages[i - 2];
nextMessage = messages[i - 1];
}
if (!Jiffy(message.createdAt.toLocal()).isSame(
nextMessage.createdAt.toLocal(),
Units.DAY,
)) {
final divider = widget.dateDividerBuilder != null
? widget.dateDividerBuilder!(
nextMessage.createdAt.toLocal(),
)
: Padding(
padding: const EdgeInsets.symmetric(vertical: 12),
child: DateDivider(
dateTime: nextMessage.createdAt.toLocal(),
),
);
return divider;
return _buildDateDivider(nextMessage);
}
final timeDiff =
Jiffy(nextMessage.createdAt.toLocal()).diff(
@@ -749,6 +742,20 @@ class _MessageListViewState extends State<MessageListView> {
return child;
}
Widget _buildDateDivider(Message message) {
final divider = widget.dateDividerBuilder != null
? widget.dateDividerBuilder!(
message.createdAt.toLocal(),
)
: Padding(
padding: const EdgeInsets.symmetric(vertical: 12),
child: DateDivider(
dateTime: message.createdAt.toLocal(),
),
);
return divider;
}
Widget _buildThreadSeparator() {
if (widget.threadSeparatorBuilder != null) {
return widget.threadSeparatorBuilder!.call(context);
@@ -805,7 +812,11 @@ class _MessageListViewState extends State<MessageListView> {
index = _getBottomElementIndex(values);
}
if (index == null) return const Offstage();
if ((index == null) ||
(!_isThreadConversation && index == itemCount - 2) ||
(_isThreadConversation && index == itemCount - 1)) {
return const Offstage();
}
if (index <= 2 || index >= itemCount - 3) {
if (widget.reverse) {
@@ -884,7 +895,6 @@ class _MessageListViewState extends State<MessageListView> {
_scrollController!.jumpTo(index: 0);
});
} else {
_showScrollToBottom.value = false;
_scrollController!.scrollTo(
index: 0,
duration: const Duration(seconds: 1),
@@ -946,26 +956,7 @@ class _MessageListViewState extends State<MessageListView> {
int index,
) {
final messageWidget = buildMessage(message, messages, index);
return VisibilityDetector(
key: ValueKey('visibility: ${message.id}'),
onVisibilityChanged: (visibility) {
final isVisible = visibility.visibleBounds != Rect.zero;
if (isVisible) {
final channel = streamChannel.channel;
if (_upToDate &&
channel.config?.readEvents == true &&
channel.state!.unreadCount > 0) {
streamChannel.channel.markRead();
}
}
if (mounted) {
if (_showScrollToBottom.value == isVisible) {
_showScrollToBottom.value = !isVisible;
}
}
},
child: messageWidget,
);
return messageWidget;
}
Widget buildParentMessage(
@@ -1101,7 +1092,7 @@ class _MessageListViewState extends State<MessageListView> {
final isOnlyEmoji = message.text?.isOnlyEmoji ?? false;
final hasUrlAttachment =
message.attachments.any((it) => it.titleLink != null);
message.attachments.any((it) => it.ogScrapeUrl != null);
final borderSide =
isOnlyEmoji || hasUrlAttachment || (isMyMessage && !hasFileAttachment)
@@ -1284,6 +1275,8 @@ class _MessageListViewState extends State<MessageListView> {
_scrollController = widget.scrollController ?? ItemScrollController();
_itemPositionListener =
widget.itemPositionListener ?? ItemPositionsListener.create();
_itemPositionListener.itemPositions
.addListener(_handleItemPositionsChanged);
_getOnThreadTap();
super.initState();
@@ -1332,6 +1325,34 @@ class _MessageListViewState extends State<MessageListView> {
super.didChangeDependencies();
}
void _handleItemPositionsChanged() {
final _itemPositions = _itemPositionListener.itemPositions.value.toList();
final _firstItemIndex =
_itemPositions.indexWhere((element) => element.index == 1);
var _isFirstItemVisible = false;
if (_firstItemIndex != -1) {
final _firstItem = _itemPositions[_firstItemIndex];
_isFirstItemVisible =
_firstItem.itemLeadingEdge > 0 && _firstItem.itemTrailingEdge < 1;
}
if (_isFirstItemVisible) {
// most recent message is visible
final channel = streamChannel?.channel;
if (channel != null) {
if (_upToDate &&
channel.config?.readEvents == true &&
channel.state!.unreadCount > 0) {
streamChannel!.channel.markRead();
}
}
}
if (mounted) {
if (_showScrollToBottom.value == _isFirstItemVisible) {
_showScrollToBottom.value = !_isFirstItemVisible;
}
}
}
void _getOnThreadTap() {
if (widget.onThreadTap != null) {
_onThreadTap = (Message message) {
@@ -1369,6 +1390,8 @@ class _MessageListViewState extends State<MessageListView> {
streamChannel!.reloadChannel();
}
_messageNewListener?.cancel();
_itemPositionListener.itemPositions
.removeListener(_handleItemPositionsChanged);
super.dispose();
}
}
@@ -236,6 +236,8 @@ class MessageReactionsModal extends StatelessWidget {
reaction.user!.name.split(' ')[0],
style: chatThemeData.textTheme.footnoteBold,
textAlign: TextAlign.center,
overflow: TextOverflow.ellipsis,
maxLines: 1,
),
],
),
@@ -574,11 +574,11 @@ class _MessageWidgetState extends State<MessageWidget>
bool get isOnlyEmoji => widget.message.text?.isOnlyEmoji == true;
bool get hasNonUrlAttachments => widget.message.attachments
.where((it) => it.titleLink == null || it.type == 'giphy')
.where((it) => it.ogScrapeUrl == null || it.type == 'giphy')
.isNotEmpty;
bool get hasUrlAttachments => widget.message.attachments
.any((it) => it.titleLink != null && it.type != 'giphy');
.any((it) => it.ogScrapeUrl != null && it.type != 'giphy');
bool get showBottomRow =>
showThreadReplyIndicator ||
@@ -999,9 +999,9 @@ class _MessageWidgetState extends State<MessageWidget>
Widget _buildUrlAttachment() {
final urlAttachment = widget.message.attachments
.firstWhere((element) => element.titleLink != null);
.firstWhere((element) => element.ogScrapeUrl != null);
final host = Uri.parse(urlAttachment.titleLink!).host;
final host = Uri.parse(urlAttachment.ogScrapeUrl!).withScheme.host;
final splitList = host.split('.');
final hostName = splitList.length == 3 ? splitList[1] : splitList[0];
final hostDisplayName = urlAttachment.authorName?.capitalize() ??
@@ -1173,7 +1173,7 @@ class _MessageWidgetState extends State<MessageWidget>
widget.message.attachments
.where((element) =>
(element.titleLink == null && element.type != null) ||
(element.ogScrapeUrl == null && element.type != null) ||
element.type == 'giphy')
.forEach((e) {
if (attachmentGroups[e.type] == null) {
@@ -97,7 +97,7 @@ class QuotedMessageWidget extends StatelessWidget {
bool get _hasAttachments => message.attachments.isNotEmpty;
bool get _containsLinkAttachment =>
message.attachments.any((element) => element.titleLink != null);
message.attachments.any((element) => element.ogScrapeUrl != null);
bool get _containsText => message.text?.isNotEmpty == true;
@@ -201,7 +201,7 @@ class QuotedMessageWidget extends StatelessWidget {
Attachment attachment;
if (_containsLinkAttachment) {
attachment = message.attachments.firstWhere(
(element) => element.titleLink != null,
(element) => element.ogScrapeUrl != null,
);
child = _buildUrlAttachment(attachment);
} else {
@@ -8,9 +8,9 @@ import 'package:url_launcher/url_launcher.dart';
/// Launch URL
Future<void> launchURL(BuildContext context, String url) async {
if (await canLaunch(url)) {
await launch(url);
} else {
try {
await launch(Uri.parse(url).withScheme.toString());
} catch (e) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(context.translations.launchUrlError)),
);
+3 -4
View File
@@ -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: 3.5.1
version: 3.6.1
repository: https://github.com/GetStream/stream-chat-flutter
issue_tracker: https://github.com/GetStream/stream-chat-flutter/issues
@@ -36,14 +36,13 @@ dependencies:
rxdart: ^0.27.0
share_plus: ^4.0.1
shimmer: ^2.0.0
stream_chat_flutter_core: ^3.5.1
stream_chat_flutter_core: ^3.6.1
substring_highlight: ^1.0.26
synchronized: ^3.0.0
url_launcher: ^6.0.3
video_compress: ^3.0.0
video_player: ^2.1.0
video_thumbnail: ^0.4.3
visibility_detector: ^0.2.0
video_thumbnail: ^0.5.0
flutter:
assets: