Merge branch 'develop' into perf
This commit is contained in:
@@ -4,7 +4,6 @@ import 'package:stream_chat_persistence/stream_chat_persistence.dart';
|
||||
|
||||
final chatPersistentClient = StreamChatPersistenceClient(
|
||||
logLevel: Level.INFO,
|
||||
connectionMode: ConnectionMode.background,
|
||||
);
|
||||
|
||||
void main() async {
|
||||
|
||||
@@ -137,6 +137,7 @@ class ChannelHeader extends StatelessWidget implements PreferredSizeWidget {
|
||||
showMessage: showConnectionStateTile ? showStatus : false,
|
||||
message: statusString,
|
||||
child: AppBar(
|
||||
textTheme: Theme.of(context).textTheme,
|
||||
brightness: Theme.of(context).brightness,
|
||||
elevation: 1,
|
||||
leading: leadingWidget,
|
||||
|
||||
@@ -120,6 +120,7 @@ class ChannelListHeader extends StatelessWidget implements PreferredSizeWidget {
|
||||
showMessage: showConnectionStateTile ? showStatus : false,
|
||||
message: statusString,
|
||||
child: AppBar(
|
||||
textTheme: Theme.of(context).textTheme,
|
||||
brightness: Theme.of(context).brightness,
|
||||
elevation: 1,
|
||||
backgroundColor: chatThemeData.channelListHeaderTheme.color,
|
||||
|
||||
@@ -12,6 +12,9 @@ import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart';
|
||||
/// Callback called when tapping on a channel
|
||||
typedef ChannelTapCallback = void Function(Channel, Widget?);
|
||||
|
||||
/// Callback called when tapping on a channel
|
||||
typedef ChannelInfoCallback = void Function(Channel);
|
||||
|
||||
/// Builder used to create a custom [ChannelPreview] from a [Channel]
|
||||
typedef ChannelPreviewBuilder = Widget Function(BuildContext, Channel);
|
||||
|
||||
@@ -78,6 +81,9 @@ class ChannelListView extends StatefulWidget {
|
||||
this.emptyBuilder,
|
||||
this.loadingBuilder,
|
||||
this.listBuilder,
|
||||
this.onMoreDetailsPressed,
|
||||
this.onDeletePressed,
|
||||
this.swipeActions,
|
||||
}) : super(key: key);
|
||||
|
||||
/// If true a default swipe to action behaviour will be added to this widget
|
||||
@@ -158,6 +164,15 @@ class ChannelListView extends StatefulWidget {
|
||||
/// The builder used when the channel list is empty.
|
||||
final WidgetBuilder? emptyBuilder;
|
||||
|
||||
/// Callback used when the more details slidable option is pressed
|
||||
final ChannelInfoCallback? onMoreDetailsPressed;
|
||||
|
||||
/// Callback used when the delete slidable option is pressed
|
||||
final ChannelInfoCallback? onDeletePressed;
|
||||
|
||||
/// List of actions for slidable
|
||||
final List<SwipeAction>? swipeActions;
|
||||
|
||||
@override
|
||||
_ChannelListViewState createState() => _ChannelListViewState();
|
||||
}
|
||||
@@ -466,61 +481,79 @@ class _ChannelListViewState extends State<ChannelListView> {
|
||||
enabled: widget.swipeToAction,
|
||||
actionPane: const SlidableBehindActionPane(),
|
||||
actionExtentRatio: 0.12,
|
||||
secondaryActions: <Widget>[
|
||||
IconSlideAction(
|
||||
color: backgroundColor,
|
||||
icon: Icons.more_horiz,
|
||||
onTap: () {
|
||||
showModalBottomSheet(
|
||||
clipBehavior: Clip.hardEdge,
|
||||
shape: const RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.only(
|
||||
topLeft: Radius.circular(32),
|
||||
topRight: Radius.circular(32),
|
||||
),
|
||||
),
|
||||
context: context,
|
||||
builder: (context) => StreamChannel(
|
||||
channel: channel,
|
||||
child: ChannelBottomSheet(
|
||||
onViewInfoTap: () {
|
||||
widget.onViewInfoTap?.call(channel);
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
if ([
|
||||
'admin',
|
||||
'owner',
|
||||
].contains(channel.state!.members
|
||||
.firstWhereOrNull(
|
||||
(m) => m.userId == channel.client.state.user?.id)
|
||||
?.role))
|
||||
IconSlideAction(
|
||||
color: backgroundColor,
|
||||
iconWidget: StreamSvgIcon.delete(
|
||||
color: chatThemeData.colorTheme.accentRed,
|
||||
secondaryActions: widget.swipeActions
|
||||
?.map((e) => IconSlideAction(
|
||||
color: e.color,
|
||||
iconWidget: e.iconWidget,
|
||||
onTap: () {
|
||||
e.onTap?.call(channel);
|
||||
},
|
||||
))
|
||||
.toList() ??
|
||||
<Widget>[
|
||||
IconSlideAction(
|
||||
color: backgroundColor,
|
||||
icon: Icons.more_horiz,
|
||||
onTap: widget.onMoreDetailsPressed != null
|
||||
? () {
|
||||
widget.onMoreDetailsPressed!(channel);
|
||||
}
|
||||
: () {
|
||||
showModalBottomSheet(
|
||||
clipBehavior: Clip.hardEdge,
|
||||
shape: const RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.only(
|
||||
topLeft: Radius.circular(32),
|
||||
topRight: Radius.circular(32),
|
||||
),
|
||||
),
|
||||
context: context,
|
||||
builder: (context) => StreamChannel(
|
||||
channel: channel,
|
||||
child: ChannelBottomSheet(
|
||||
onViewInfoTap: () {
|
||||
widget.onViewInfoTap?.call(channel);
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
onTap: () async {
|
||||
final res = await showConfirmationDialog(
|
||||
context,
|
||||
title: 'Delete Conversation',
|
||||
okText: 'DELETE',
|
||||
question:
|
||||
'Are you sure you want to delete this conversation?',
|
||||
cancelText: 'CANCEL',
|
||||
icon: StreamSvgIcon.delete(
|
||||
if ([
|
||||
'admin',
|
||||
'owner',
|
||||
].contains(channel.state!.members
|
||||
.firstWhereOrNull(
|
||||
(m) => m.userId == channel.client.state.user?.id)
|
||||
?.role))
|
||||
IconSlideAction(
|
||||
color: backgroundColor,
|
||||
iconWidget: StreamSvgIcon.delete(
|
||||
color: chatThemeData.colorTheme.accentRed,
|
||||
),
|
||||
);
|
||||
if (res == true) {
|
||||
await channel.delete();
|
||||
}
|
||||
},
|
||||
),
|
||||
],
|
||||
onTap: widget.onDeletePressed != null
|
||||
? () {
|
||||
widget.onDeletePressed!(channel);
|
||||
}
|
||||
: () async {
|
||||
final res = await showConfirmationDialog(
|
||||
context,
|
||||
title: 'Delete Conversation',
|
||||
okText: 'DELETE',
|
||||
question:
|
||||
// ignore: lines_longer_than_80_chars
|
||||
'Are you sure you want to delete this conversation?',
|
||||
cancelText: 'CANCEL',
|
||||
icon: StreamSvgIcon.delete(
|
||||
color: chatThemeData.colorTheme.accentRed,
|
||||
),
|
||||
);
|
||||
if (res == true) {
|
||||
await channel.delete();
|
||||
}
|
||||
},
|
||||
),
|
||||
],
|
||||
child: Container(
|
||||
color: chatThemeData.colorTheme.whiteSnow,
|
||||
child: widget.channelPreviewBuilder?.call(context, channel) ??
|
||||
@@ -641,3 +674,22 @@ class _ChannelListViewState extends State<ChannelListView> {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Class for slidable action
|
||||
class SwipeAction {
|
||||
/// Constructor for creating [SwipeAction]
|
||||
SwipeAction({
|
||||
this.color,
|
||||
required this.iconWidget,
|
||||
this.onTap,
|
||||
});
|
||||
|
||||
/// Background color of action
|
||||
Color? color;
|
||||
|
||||
/// Widget to display as icon
|
||||
Widget iconWidget;
|
||||
|
||||
/// Callback when icon is tapped
|
||||
ChannelInfoCallback? onTap;
|
||||
}
|
||||
|
||||
@@ -53,6 +53,7 @@ class ImageHeader extends StatelessWidget implements PreferredSizeWidget {
|
||||
Widget build(BuildContext context) {
|
||||
final chatThemeData = StreamChatTheme.of(context);
|
||||
return AppBar(
|
||||
textTheme: Theme.of(context).textTheme,
|
||||
brightness: Theme.of(context).brightness,
|
||||
elevation: 1,
|
||||
leading: showBackButton
|
||||
|
||||
@@ -571,9 +571,9 @@ class _MessageListViewState extends State<MessageListView> {
|
||||
return const SizedBox();
|
||||
}
|
||||
|
||||
var index = _getTopElement(values).index;
|
||||
var index = _getTopElement(values)?.index;
|
||||
|
||||
if (index > messages.length) {
|
||||
if (index == null || index > messages.length) {
|
||||
return const SizedBox();
|
||||
}
|
||||
|
||||
@@ -599,10 +599,17 @@ class _MessageListViewState extends State<MessageListView> {
|
||||
StreamChannelState? channel, QueryDirection direction) =>
|
||||
_messageListController.paginateData!(direction: direction);
|
||||
|
||||
ItemPosition _getTopElement(Iterable<ItemPosition> values) => values
|
||||
.where((ItemPosition position) => position.itemLeadingEdge < 0.9)
|
||||
.reduce((ItemPosition max, ItemPosition position) =>
|
||||
position.itemLeadingEdge > max.itemLeadingEdge ? position : max);
|
||||
ItemPosition? _getTopElement(Iterable<ItemPosition> values) {
|
||||
final inView =
|
||||
values.where((ItemPosition position) => position.itemLeadingEdge < 0.9);
|
||||
|
||||
if (inView.isEmpty) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return inView.reduce((ItemPosition max, ItemPosition position) =>
|
||||
position.itemLeadingEdge > max.itemLeadingEdge ? position : max);
|
||||
}
|
||||
|
||||
Widget _buildScrollToBottom() => StreamBuilder<Tuple2<bool, int>>(
|
||||
stream: Rx.combineLatest2(
|
||||
|
||||
@@ -39,6 +39,7 @@ class StreamChat extends StatefulWidget {
|
||||
this.streamChatThemeData,
|
||||
this.onBackgroundEventReceived,
|
||||
this.backgroundKeepAlive = const Duration(minutes: 1),
|
||||
this.connectivityStream,
|
||||
}) : super(key: key);
|
||||
|
||||
/// Client to do chat ops with
|
||||
@@ -59,6 +60,11 @@ class StreamChat extends StatefulWidget {
|
||||
/// upon the [Event.type]
|
||||
final EventHandler? onBackgroundEventReceived;
|
||||
|
||||
/// Stream of connectivity result
|
||||
/// Visible for testing
|
||||
@visibleForTesting
|
||||
final Stream<ConnectivityResult>? connectivityStream;
|
||||
|
||||
@override
|
||||
StreamChatState createState() => StreamChatState();
|
||||
|
||||
@@ -102,6 +108,7 @@ class StreamChatState extends State<StreamChat> {
|
||||
client: client,
|
||||
onBackgroundEventReceived: widget.onBackgroundEventReceived,
|
||||
backgroundKeepAlive: widget.backgroundKeepAlive,
|
||||
connectivityStream: widget.connectivityStream,
|
||||
child: widget.child ?? const Offstage(),
|
||||
),
|
||||
);
|
||||
|
||||
@@ -230,22 +230,23 @@ class StreamChatThemeData {
|
||||
),
|
||||
),
|
||||
channelPreviewTheme: ChannelPreviewTheme(
|
||||
unreadCounterColor: colorTheme.accentRed,
|
||||
avatarTheme: AvatarTheme(
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
constraints: const BoxConstraints.tightFor(
|
||||
height: 40,
|
||||
width: 40,
|
||||
),
|
||||
unreadCounterColor: colorTheme.accentRed,
|
||||
avatarTheme: AvatarTheme(
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
constraints: const BoxConstraints.tightFor(
|
||||
height: 40,
|
||||
width: 40,
|
||||
),
|
||||
title: textTheme.bodyBold,
|
||||
subtitle: textTheme.footnote.copyWith(
|
||||
color: const Color(0xff7A7A7A),
|
||||
),
|
||||
lastMessageAt: textTheme.footnote.copyWith(
|
||||
color: colorTheme.black.withOpacity(.5),
|
||||
),
|
||||
indicatorIconSize: 16),
|
||||
),
|
||||
title: textTheme.bodyBold,
|
||||
subtitle: textTheme.footnote.copyWith(
|
||||
color: const Color(0xff7A7A7A),
|
||||
),
|
||||
lastMessageAt: textTheme.footnote.copyWith(
|
||||
color: colorTheme.black.withOpacity(.5),
|
||||
),
|
||||
indicatorIconSize: 16,
|
||||
),
|
||||
channelListHeaderTheme: ChannelListHeaderTheme(
|
||||
avatarTheme: AvatarTheme(
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
@@ -1186,7 +1187,8 @@ class MessageInputTheme {
|
||||
actionButtonIdleColor: other.actionButtonIdleColor,
|
||||
sendButtonColor: other.sendButtonColor,
|
||||
sendButtonIdleColor: other.sendButtonIdleColor,
|
||||
inputTextStyle: other.inputTextStyle,
|
||||
inputTextStyle:
|
||||
inputTextStyle?.merge(other.inputTextStyle) ?? other.inputTextStyle,
|
||||
inputDecoration: inputDecoration?.merge(other.inputDecoration) ??
|
||||
other.inputDecoration,
|
||||
activeBorderGradient: other.activeBorderGradient,
|
||||
|
||||
@@ -101,6 +101,7 @@ class ThreadHeader extends StatelessWidget implements PreferredSizeWidget {
|
||||
final chatThemeData = StreamChatTheme.of(context);
|
||||
return AppBar(
|
||||
automaticallyImplyLeading: false,
|
||||
textTheme: Theme.of(context).textTheme,
|
||||
brightness: Theme.of(context).brightness,
|
||||
elevation: 1,
|
||||
leading: leading ??
|
||||
|
||||
@@ -79,6 +79,7 @@ void main() {
|
||||
),
|
||||
),
|
||||
),
|
||||
connectivityStream: Stream.value(ConnectivityResult.mobile),
|
||||
),
|
||||
),
|
||||
surfaceSize: const Size.square(200),
|
||||
@@ -131,6 +132,7 @@ void main() {
|
||||
),
|
||||
),
|
||||
),
|
||||
connectivityStream: Stream.value(ConnectivityResult.mobile),
|
||||
),
|
||||
),
|
||||
surfaceSize: const Size.square(200),
|
||||
@@ -187,6 +189,7 @@ void main() {
|
||||
),
|
||||
),
|
||||
),
|
||||
connectivityStream: Stream.value(ConnectivityResult.mobile),
|
||||
),
|
||||
),
|
||||
surfaceSize: const Size.square(200),
|
||||
|
||||
@@ -60,6 +60,7 @@ void main() {
|
||||
maskColor: theme.ownMessageTheme.reactionsMaskColor!,
|
||||
),
|
||||
),
|
||||
connectivityStream: Stream.value(ConnectivityResult.mobile),
|
||||
),
|
||||
surfaceSize: const Size(100, 100),
|
||||
);
|
||||
@@ -96,6 +97,7 @@ void main() {
|
||||
maskColor: theme.ownMessageTheme.reactionsMaskColor!,
|
||||
),
|
||||
),
|
||||
connectivityStream: Stream.value(ConnectivityResult.mobile),
|
||||
),
|
||||
surfaceSize: const Size(100, 100),
|
||||
);
|
||||
@@ -118,6 +120,7 @@ void main() {
|
||||
StreamChat(
|
||||
client: client,
|
||||
streamChatThemeData: StreamChatThemeData.fromTheme(themeData),
|
||||
connectivityStream: Stream.value(ConnectivityResult.mobile),
|
||||
child: Container(
|
||||
color: Colors.black,
|
||||
child: ReactionBubble(
|
||||
@@ -162,6 +165,7 @@ void main() {
|
||||
StreamChat(
|
||||
client: client,
|
||||
streamChatThemeData: StreamChatThemeData.fromTheme(themeData),
|
||||
connectivityStream: Stream.value(ConnectivityResult.mobile),
|
||||
child: Container(
|
||||
color: Colors.black,
|
||||
child: ReactionBubble(
|
||||
@@ -204,6 +208,7 @@ void main() {
|
||||
await tester.pumpWidgetBuilder(
|
||||
StreamChat(
|
||||
client: client,
|
||||
connectivityStream: Stream.value(ConnectivityResult.mobile),
|
||||
streamChatThemeData: StreamChatThemeData.fromTheme(themeData),
|
||||
child: SizedBox(
|
||||
child: ReactionBubble(
|
||||
|
||||
@@ -100,6 +100,7 @@ void main() {
|
||||
),
|
||||
),
|
||||
),
|
||||
connectivityStream: Stream.value(ConnectivityResult.mobile),
|
||||
),
|
||||
),
|
||||
surfaceSize: const Size.square(200),
|
||||
@@ -151,6 +152,7 @@ void main() {
|
||||
),
|
||||
),
|
||||
),
|
||||
connectivityStream: Stream.value(ConnectivityResult.mobile),
|
||||
),
|
||||
),
|
||||
surfaceSize: const Size.square(200),
|
||||
|
||||
Reference in New Issue
Block a user