feat: Added channel_list new implementation, fixed upper repo
This commit is contained in:
@@ -1,5 +1,4 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:stream_chat_flutter/src/lazy_load_scroll_view.dart';
|
|
||||||
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
|
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
|
||||||
|
|
||||||
class ChannelFileDisplayScreen extends StatefulWidget {
|
class ChannelFileDisplayScreen extends StatefulWidget {
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ import 'package:stream_chat_flutter/src/stream_chat_theme.dart';
|
|||||||
import '../stream_chat_flutter.dart';
|
import '../stream_chat_flutter.dart';
|
||||||
import './channel_name.dart';
|
import './channel_name.dart';
|
||||||
import 'channel_image.dart';
|
import 'channel_image.dart';
|
||||||
import 'stream_channel.dart';
|
|
||||||
|
|
||||||
/// 
|
/// 
|
||||||
/// 
|
/// 
|
||||||
|
|||||||
@@ -5,16 +5,13 @@ import 'package:flutter/foundation.dart';
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_slidable/flutter_slidable.dart';
|
import 'package:flutter_slidable/flutter_slidable.dart';
|
||||||
import 'package:shimmer/shimmer.dart';
|
import 'package:shimmer/shimmer.dart';
|
||||||
import 'package:stream_chat/stream_chat.dart';
|
|
||||||
import 'package:stream_chat_flutter/src/channels_bloc.dart';
|
|
||||||
import 'package:stream_chat_flutter/src/stream_svg_icon.dart';
|
import 'package:stream_chat_flutter/src/stream_svg_icon.dart';
|
||||||
import 'package:stream_chat_flutter/src/utils.dart';
|
import 'package:stream_chat_flutter/src/utils.dart';
|
||||||
|
import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart';
|
||||||
|
|
||||||
import '../stream_chat_flutter.dart';
|
import '../stream_chat_flutter.dart';
|
||||||
import 'channel_bottom_sheet.dart';
|
import 'channel_bottom_sheet.dart';
|
||||||
import 'channel_preview.dart';
|
import 'channel_preview.dart';
|
||||||
import 'stream_channel.dart';
|
|
||||||
import 'stream_chat.dart';
|
|
||||||
|
|
||||||
/// Callback called when tapping on a channel
|
/// Callback called when tapping on a channel
|
||||||
typedef ChannelTapCallback = void Function(Channel, Widget);
|
typedef ChannelTapCallback = void Function(Channel, Widget);
|
||||||
@@ -158,168 +155,161 @@ class _ChannelListViewState extends State<ChannelListView>
|
|||||||
with WidgetsBindingObserver {
|
with WidgetsBindingObserver {
|
||||||
final ScrollController _scrollController = ScrollController();
|
final ScrollController _scrollController = ScrollController();
|
||||||
final SlidableController _slideController = SlidableController();
|
final SlidableController _slideController = SlidableController();
|
||||||
|
final ChannelListController _channelListController = ChannelListController();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final channelsBloc = ChannelsBloc.of(context);
|
|
||||||
|
|
||||||
if (!widget.pullToRefresh) {
|
if (!widget.pullToRefresh) {
|
||||||
return _buildListView(channelsBloc);
|
_channelListController.loadData();
|
||||||
}
|
}
|
||||||
|
|
||||||
return RefreshIndicator(
|
return RefreshIndicator(
|
||||||
onRefresh: () async {
|
onRefresh: () async {
|
||||||
return channelsBloc.queryChannels(
|
_channelListController.loadData();
|
||||||
filter: widget.filter,
|
|
||||||
sortOptions: widget.sort,
|
|
||||||
paginationParams: widget.pagination,
|
|
||||||
options: widget.options,
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
child: _buildListView(channelsBloc),
|
child: ChannelListCore(
|
||||||
|
channelListController: _channelListController,
|
||||||
|
listBuilder: (context, list) {
|
||||||
|
return _buildListView(list);
|
||||||
|
},
|
||||||
|
emptyBuilder: (BuildContext context) {
|
||||||
|
return _buildEmptyWidget();
|
||||||
|
},
|
||||||
|
errorBuilder: (Error error) {
|
||||||
|
return _buildErrorWidget(context);
|
||||||
|
},
|
||||||
|
loadingBuilder: (BuildContext context) {
|
||||||
|
return _buildLoadingWidget();
|
||||||
|
},
|
||||||
|
pagination: widget.pagination,
|
||||||
|
options: widget.options,
|
||||||
|
sort: widget.sort,
|
||||||
|
filter: widget.filter,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
StreamBuilder<List<Channel>> _buildListView(
|
Widget _buildListView(
|
||||||
ChannelsBlocState channelsBlocState,
|
List<Channel> channels,
|
||||||
) {
|
) {
|
||||||
return StreamBuilder<List<Channel>>(
|
var child;
|
||||||
stream: channelsBlocState.channelsStream,
|
|
||||||
builder: (context, snapshot) {
|
|
||||||
var child;
|
|
||||||
if (snapshot.hasError) {
|
|
||||||
child = _buildErrorWidget(
|
|
||||||
snapshot,
|
|
||||||
context,
|
|
||||||
channelsBlocState,
|
|
||||||
);
|
|
||||||
} else if (!snapshot.hasData) {
|
|
||||||
child = _buildLoadingWidget();
|
|
||||||
} else {
|
|
||||||
final channels = snapshot.data;
|
|
||||||
|
|
||||||
if (channels.isEmpty && widget.emptyBuilder != null) {
|
if (channels.isNotEmpty) {
|
||||||
child = widget.emptyBuilder(context);
|
if (widget.crossAxisCount > 1) {
|
||||||
}
|
child = GridView.builder(
|
||||||
|
padding: widget.padding,
|
||||||
if (channels.isEmpty && widget.emptyBuilder == null) {
|
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
|
||||||
child = LayoutBuilder(
|
crossAxisCount: widget.crossAxisCount),
|
||||||
builder: (context, viewportConstraints) {
|
itemCount: channels.length,
|
||||||
return SingleChildScrollView(
|
physics: AlwaysScrollableScrollPhysics(),
|
||||||
physics: AlwaysScrollableScrollPhysics(),
|
controller: _scrollController,
|
||||||
child: Stack(
|
itemBuilder: (context, index) {
|
||||||
children: [
|
return _gridItemBuilder(context, index, channels);
|
||||||
ConstrainedBox(
|
},
|
||||||
constraints: BoxConstraints(
|
);
|
||||||
minHeight: viewportConstraints.maxHeight,
|
} else {
|
||||||
),
|
child = ListView.separated(
|
||||||
child: Column(
|
padding: widget.padding,
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
physics: AlwaysScrollableScrollPhysics(),
|
||||||
children: [
|
itemCount:
|
||||||
Padding(
|
channels.isNotEmpty ? channels.length + 1 : channels.length,
|
||||||
padding: const EdgeInsets.all(8.0),
|
separatorBuilder: (_, index) {
|
||||||
child: StreamSvgIcon.message(
|
if (widget.separatorBuilder != null) {
|
||||||
size: 136,
|
return widget.separatorBuilder(context, index);
|
||||||
color: StreamChatTheme.of(context)
|
|
||||||
.colorTheme
|
|
||||||
.greyGainsboro,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.all(8.0),
|
|
||||||
child: Text(
|
|
||||||
'Let’s start chatting!',
|
|
||||||
style: StreamChatTheme.of(context)
|
|
||||||
.textTheme
|
|
||||||
.headline,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.symmetric(
|
|
||||||
vertical: 8.0,
|
|
||||||
horizontal: 52,
|
|
||||||
),
|
|
||||||
child: Text(
|
|
||||||
'How about sending your first message to a friend?',
|
|
||||||
textAlign: TextAlign.center,
|
|
||||||
style: StreamChatTheme.of(context)
|
|
||||||
.textTheme
|
|
||||||
.body
|
|
||||||
.copyWith(
|
|
||||||
color: StreamChatTheme.of(context)
|
|
||||||
.colorTheme
|
|
||||||
.grey,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
if (widget.onStartChatPressed != null)
|
|
||||||
Positioned(
|
|
||||||
right: 0,
|
|
||||||
left: 0,
|
|
||||||
bottom: 32,
|
|
||||||
child: Center(
|
|
||||||
child: FlatButton(
|
|
||||||
onPressed: widget.onStartChatPressed,
|
|
||||||
child: Text(
|
|
||||||
'Start a chat',
|
|
||||||
style: StreamChatTheme.of(context)
|
|
||||||
.textTheme
|
|
||||||
.bodyBold
|
|
||||||
.copyWith(
|
|
||||||
color: StreamChatTheme.of(context)
|
|
||||||
.colorTheme
|
|
||||||
.accentBlue,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (channels.isNotEmpty) {
|
|
||||||
if (widget.crossAxisCount > 1) {
|
|
||||||
child = GridView.builder(
|
|
||||||
padding: widget.padding,
|
|
||||||
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
|
|
||||||
crossAxisCount: widget.crossAxisCount),
|
|
||||||
itemCount: channels.length,
|
|
||||||
physics: AlwaysScrollableScrollPhysics(),
|
|
||||||
controller: _scrollController,
|
|
||||||
itemBuilder: (context, index) {
|
|
||||||
return _gridItemBuilder(context, index, channels);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
child = ListView.separated(
|
|
||||||
padding: widget.padding,
|
|
||||||
physics: AlwaysScrollableScrollPhysics(),
|
|
||||||
itemCount:
|
|
||||||
channels.isNotEmpty ? channels.length + 1 : channels.length,
|
|
||||||
separatorBuilder: (_, index) {
|
|
||||||
if (widget.separatorBuilder != null) {
|
|
||||||
return widget.separatorBuilder(context, index);
|
|
||||||
}
|
|
||||||
return _separatorBuilder(context, index);
|
|
||||||
},
|
|
||||||
itemBuilder: (context, index) {
|
|
||||||
return _listItemBuilder(context, index, channels);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
return _separatorBuilder(context, index);
|
||||||
}
|
},
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
return _listItemBuilder(context, index, channels);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return AnimatedSwitcher(
|
return AnimatedSwitcher(
|
||||||
child: child,
|
child: child,
|
||||||
duration: Duration(milliseconds: 500),
|
duration: Duration(milliseconds: 500),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildEmptyWidget() {
|
||||||
|
if (widget.emptyBuilder != null) {
|
||||||
|
return widget.emptyBuilder(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
return LayoutBuilder(
|
||||||
|
builder: (context, viewportConstraints) {
|
||||||
|
return SingleChildScrollView(
|
||||||
|
physics: AlwaysScrollableScrollPhysics(),
|
||||||
|
child: Stack(
|
||||||
|
children: [
|
||||||
|
ConstrainedBox(
|
||||||
|
constraints: BoxConstraints(
|
||||||
|
minHeight: viewportConstraints.maxHeight,
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.all(8.0),
|
||||||
|
child: StreamSvgIcon.message(
|
||||||
|
size: 136,
|
||||||
|
color: StreamChatTheme.of(context)
|
||||||
|
.colorTheme
|
||||||
|
.greyGainsboro,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.all(8.0),
|
||||||
|
child: Text(
|
||||||
|
'Let’s start chatting!',
|
||||||
|
style: StreamChatTheme.of(context).textTheme.headline,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(
|
||||||
|
vertical: 8.0,
|
||||||
|
horizontal: 52,
|
||||||
|
),
|
||||||
|
child: Text(
|
||||||
|
'How about sending your first message to a friend?',
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
style: StreamChatTheme.of(context)
|
||||||
|
.textTheme
|
||||||
|
.body
|
||||||
|
.copyWith(
|
||||||
|
color:
|
||||||
|
StreamChatTheme.of(context).colorTheme.grey,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
if (widget.onStartChatPressed != null)
|
||||||
|
Positioned(
|
||||||
|
right: 0,
|
||||||
|
left: 0,
|
||||||
|
bottom: 32,
|
||||||
|
child: Center(
|
||||||
|
child: FlatButton(
|
||||||
|
onPressed: widget.onStartChatPressed,
|
||||||
|
child: Text(
|
||||||
|
'Start a chat',
|
||||||
|
style: StreamChatTheme.of(context)
|
||||||
|
.textTheme
|
||||||
|
.bodyBold
|
||||||
|
.copyWith(
|
||||||
|
color: StreamChatTheme.of(context)
|
||||||
|
.colorTheme
|
||||||
|
.accentBlue,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@@ -443,27 +433,8 @@ class _ChannelListViewState extends State<ChannelListView>
|
|||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildErrorWidget(
|
Widget _buildErrorWidget(
|
||||||
AsyncSnapshot<List<Channel>> snapshot,
|
|
||||||
BuildContext context,
|
BuildContext context,
|
||||||
ChannelsBlocState channelsBlocState,
|
|
||||||
) {
|
) {
|
||||||
if (snapshot.error is Error) {
|
|
||||||
print((snapshot.error as Error).stackTrace);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (widget.errorBuilder != null) {
|
|
||||||
return widget.errorBuilder(snapshot.error);
|
|
||||||
}
|
|
||||||
|
|
||||||
var message = snapshot.error.toString();
|
|
||||||
if (snapshot.error is DioError) {
|
|
||||||
final dioError = snapshot.error as DioError;
|
|
||||||
if (dioError.type == DioErrorType.RESPONSE) {
|
|
||||||
message = dioError.message;
|
|
||||||
} else {
|
|
||||||
message = 'Check your connection and retry';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return Center(
|
return Center(
|
||||||
child: Column(
|
child: Column(
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
@@ -484,20 +455,9 @@ class _ChannelListViewState extends State<ChannelListView>
|
|||||||
),
|
),
|
||||||
style: Theme.of(context).textTheme.headline6,
|
style: Theme.of(context).textTheme.headline6,
|
||||||
),
|
),
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.only(
|
|
||||||
top: 16.0,
|
|
||||||
),
|
|
||||||
child: Text(message),
|
|
||||||
),
|
|
||||||
FlatButton(
|
FlatButton(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
channelsBlocState.queryChannels(
|
_channelListController.loadData();
|
||||||
filter: widget.filter,
|
|
||||||
sortOptions: widget.sort,
|
|
||||||
paginationParams: widget.pagination,
|
|
||||||
options: widget.options,
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
child: Text('Retry'),
|
child: Text('Retry'),
|
||||||
),
|
),
|
||||||
@@ -714,14 +674,7 @@ class _ChannelListViewState extends State<ChannelListView>
|
|||||||
if (_scrollController.position.maxScrollExtent ==
|
if (_scrollController.position.maxScrollExtent ==
|
||||||
_scrollController.offset &&
|
_scrollController.offset &&
|
||||||
_scrollController.offset != 0) {
|
_scrollController.offset != 0) {
|
||||||
channelsProvider.queryChannels(
|
_channelListController.paginateData();
|
||||||
filter: widget.filter,
|
|
||||||
sortOptions: widget.sort,
|
|
||||||
paginationParams: widget.pagination.copyWith(
|
|
||||||
offset: channelsProvider.channels?.length ?? 0,
|
|
||||||
),
|
|
||||||
options: widget.options,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -759,12 +712,7 @@ class _ChannelListViewState extends State<ChannelListView>
|
|||||||
EventType.channelVisible,
|
EventType.channelVisible,
|
||||||
)
|
)
|
||||||
.listen((event) {
|
.listen((event) {
|
||||||
channelsBloc.queryChannels(
|
_channelListController.loadData();
|
||||||
filter: widget.filter,
|
|
||||||
sortOptions: widget.sort,
|
|
||||||
paginationParams: widget.pagination,
|
|
||||||
options: widget.options,
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -777,13 +725,7 @@ class _ChannelListViewState extends State<ChannelListView>
|
|||||||
widget.pagination?.toJson()?.toString() !=
|
widget.pagination?.toJson()?.toString() !=
|
||||||
oldWidget.pagination?.toJson()?.toString() ||
|
oldWidget.pagination?.toJson()?.toString() ||
|
||||||
widget.options?.toString() != oldWidget.options?.toString()) {
|
widget.options?.toString() != oldWidget.options?.toString()) {
|
||||||
final channelsBloc = ChannelsBloc.of(context);
|
_channelListController.loadData();
|
||||||
channelsBloc.queryChannels(
|
|
||||||
filter: widget.filter,
|
|
||||||
sortOptions: widget.sort,
|
|
||||||
paginationParams: widget.pagination,
|
|
||||||
options: widget.options,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:stream_chat_flutter/src/lazy_load_scroll_view.dart';
|
|
||||||
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
|
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
|
||||||
import 'package:video_player/video_player.dart';
|
import 'package:video_player/video_player.dart';
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ import 'package:flutter/rendering.dart';
|
|||||||
import 'package:stream_chat/stream_chat.dart';
|
import 'package:stream_chat/stream_chat.dart';
|
||||||
|
|
||||||
import '../stream_chat_flutter.dart';
|
import '../stream_chat_flutter.dart';
|
||||||
import 'stream_channel.dart';
|
|
||||||
|
|
||||||
/// It shows the current [Channel] name using a [Text] widget.
|
/// It shows the current [Channel] name using a [Text] widget.
|
||||||
///
|
///
|
||||||
|
|||||||
@@ -1,185 +0,0 @@
|
|||||||
import 'dart:async';
|
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:rxdart/rxdart.dart';
|
|
||||||
import 'package:stream_chat/stream_chat.dart';
|
|
||||||
import 'package:stream_chat_flutter/src/stream_chat.dart';
|
|
||||||
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
|
|
||||||
|
|
||||||
/// Widget dedicated to the management of a channel list with pagination
|
|
||||||
class ChannelsBloc extends StatefulWidget {
|
|
||||||
/// The widget child
|
|
||||||
final Widget child;
|
|
||||||
|
|
||||||
/// Set this to true to prevent channels to be brought to the top of the list when a new message arrives
|
|
||||||
final bool lockChannelsOrder;
|
|
||||||
|
|
||||||
/// Comparator used to sort the channels when a message.new event is received
|
|
||||||
final Comparator<Channel> channelsComparator;
|
|
||||||
|
|
||||||
/// Function used to evaluate if a channel should be added to the list when a message.new event is received
|
|
||||||
final bool Function(Event) shouldAddChannel;
|
|
||||||
|
|
||||||
/// Instantiate a new ChannelsBloc
|
|
||||||
const ChannelsBloc({
|
|
||||||
Key key,
|
|
||||||
this.child,
|
|
||||||
this.lockChannelsOrder = false,
|
|
||||||
this.channelsComparator,
|
|
||||||
this.shouldAddChannel,
|
|
||||||
}) : super(key: key);
|
|
||||||
|
|
||||||
@override
|
|
||||||
ChannelsBlocState createState() => ChannelsBlocState();
|
|
||||||
|
|
||||||
/// Use this method to get the current [ChannelsBlocState] instance
|
|
||||||
static ChannelsBlocState of(BuildContext context) {
|
|
||||||
ChannelsBlocState streamChatState;
|
|
||||||
|
|
||||||
streamChatState = context.findAncestorStateOfType<ChannelsBlocState>();
|
|
||||||
|
|
||||||
if (streamChatState == null) {
|
|
||||||
throw Exception('You must have a ChannelsBloc widget as ancestor');
|
|
||||||
}
|
|
||||||
|
|
||||||
return streamChatState;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The current state of the [ChannelsBloc]
|
|
||||||
class ChannelsBlocState extends State<ChannelsBloc>
|
|
||||||
with AutomaticKeepAliveClientMixin {
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
super.build(context);
|
|
||||||
return widget.child;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The current channel list
|
|
||||||
List<Channel> get channels => _channelsController.value;
|
|
||||||
|
|
||||||
/// The current channel list as a stream
|
|
||||||
Stream<List<Channel>> get channelsStream => _channelsController.stream;
|
|
||||||
|
|
||||||
final BehaviorSubject<bool> _queryChannelsLoadingController =
|
|
||||||
BehaviorSubject.seeded(false);
|
|
||||||
|
|
||||||
final BehaviorSubject<List<Channel>> _channelsController = BehaviorSubject();
|
|
||||||
|
|
||||||
/// The stream notifying the state of queryChannel call
|
|
||||||
Stream<bool> get queryChannelsLoading =>
|
|
||||||
_queryChannelsLoadingController.stream;
|
|
||||||
|
|
||||||
final List<Channel> _hiddenChannels = [];
|
|
||||||
|
|
||||||
/// Calls [client.queryChannels] updating [queryChannelsLoading] stream
|
|
||||||
Future<void> queryChannels({
|
|
||||||
Map<String, dynamic> filter,
|
|
||||||
List<SortOption> sortOptions,
|
|
||||||
PaginationParams paginationParams,
|
|
||||||
Map<String, dynamic> options,
|
|
||||||
bool onlyOffline = false,
|
|
||||||
}) async {
|
|
||||||
final client = StreamChat.of(context).client;
|
|
||||||
|
|
||||||
if (client.state?.user == null ||
|
|
||||||
_queryChannelsLoadingController.value == true) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
_queryChannelsLoadingController.sink.add(true);
|
|
||||||
|
|
||||||
try {
|
|
||||||
final clear = paginationParams == null ||
|
|
||||||
paginationParams.offset == null ||
|
|
||||||
paginationParams.offset == 0;
|
|
||||||
final oldChannels = List<Channel>.from(channels ?? []);
|
|
||||||
final _channels = await client.queryChannels(
|
|
||||||
filter: filter,
|
|
||||||
sort: sortOptions,
|
|
||||||
options: options,
|
|
||||||
paginationParams: paginationParams,
|
|
||||||
onlyOffline: onlyOffline,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (clear) {
|
|
||||||
_channelsController.add(_channels);
|
|
||||||
} else {
|
|
||||||
final l = oldChannels + _channels;
|
|
||||||
_channelsController.add(l);
|
|
||||||
}
|
|
||||||
_queryChannelsLoadingController.sink.add(false);
|
|
||||||
} catch (err, stackTrace) {
|
|
||||||
print(err);
|
|
||||||
print(stackTrace);
|
|
||||||
_queryChannelsLoadingController.addError(err, stackTrace);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
final List<StreamSubscription> _subscriptions = [];
|
|
||||||
|
|
||||||
@override
|
|
||||||
void initState() {
|
|
||||||
super.initState();
|
|
||||||
|
|
||||||
final client = StreamChat.of(context).client;
|
|
||||||
|
|
||||||
if (!widget.lockChannelsOrder) {
|
|
||||||
_subscriptions.add(client.on(EventType.messageNew).listen((e) {
|
|
||||||
final newChannels = List<Channel>.from(channels ?? []);
|
|
||||||
final index = newChannels.indexWhere((c) => c.cid == e.cid);
|
|
||||||
if (index > -1) {
|
|
||||||
if (index > 0) {
|
|
||||||
final channel = newChannels.removeAt(index);
|
|
||||||
newChannels.insert(0, channel);
|
|
||||||
}
|
|
||||||
} else if (widget.shouldAddChannel != null &&
|
|
||||||
widget.shouldAddChannel(e)) {
|
|
||||||
final hiddenIndex = _hiddenChannels.indexWhere((c) => c.cid == e.cid);
|
|
||||||
if (hiddenIndex > -1) {
|
|
||||||
newChannels.insert(0, _hiddenChannels[hiddenIndex]);
|
|
||||||
_hiddenChannels.removeAt(hiddenIndex);
|
|
||||||
} else {
|
|
||||||
if (client.state?.channels != null &&
|
|
||||||
client.state?.channels[e.cid] != null) {
|
|
||||||
newChannels.insert(0, client.state.channels[e.cid]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (widget.channelsComparator != null) {
|
|
||||||
newChannels.sort(widget.channelsComparator);
|
|
||||||
}
|
|
||||||
_channelsController.add(newChannels);
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
_subscriptions.add(client.on(EventType.channelHidden).listen((event) async {
|
|
||||||
final newChannels = List<Channel>.from(channels ?? []);
|
|
||||||
final channelIndex = newChannels.indexWhere((c) => c.cid == event.cid);
|
|
||||||
if (channelIndex > -1) {
|
|
||||||
final channel = newChannels.removeAt(channelIndex);
|
|
||||||
_hiddenChannels.add(channel);
|
|
||||||
_channelsController.add(newChannels);
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
|
|
||||||
_subscriptions.add(client
|
|
||||||
.on(EventType.channelDeleted, EventType.notificationRemovedFromChannel)
|
|
||||||
.listen((e) {
|
|
||||||
final channel = e.channel;
|
|
||||||
_channelsController
|
|
||||||
.add(List.from(channels..removeWhere((c) => c.cid == channel.cid)));
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void dispose() {
|
|
||||||
_channelsController.close();
|
|
||||||
_queryChannelsLoadingController.close();
|
|
||||||
_subscriptions.forEach((s) => s.cancel());
|
|
||||||
super.dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
bool get wantKeepAlive => true;
|
|
||||||
}
|
|
||||||
@@ -6,7 +6,6 @@ import 'package:photo_view/photo_view.dart';
|
|||||||
import 'package:stream_chat_flutter/src/image_footer.dart';
|
import 'package:stream_chat_flutter/src/image_footer.dart';
|
||||||
import 'package:stream_chat_flutter/src/image_header.dart';
|
import 'package:stream_chat_flutter/src/image_header.dart';
|
||||||
import 'package:video_player/video_player.dart';
|
import 'package:video_player/video_player.dart';
|
||||||
import 'stream_channel.dart';
|
|
||||||
|
|
||||||
import '../stream_chat_flutter.dart';
|
import '../stream_chat_flutter.dart';
|
||||||
|
|
||||||
|
|||||||
@@ -2,8 +2,8 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:stream_chat/stream_chat.dart';
|
import 'package:stream_chat/stream_chat.dart';
|
||||||
import 'package:stream_chat_flutter/src/stream_chat_theme.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/stream_svg_icon.dart';
|
||||||
|
import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart';
|
||||||
import 'image_actions_modal.dart';
|
import 'image_actions_modal.dart';
|
||||||
import 'stream_channel.dart';
|
|
||||||
|
|
||||||
class ImageHeader extends StatelessWidget implements PreferredSizeWidget {
|
class ImageHeader extends StatelessWidget implements PreferredSizeWidget {
|
||||||
/// True if this header shows the leading back button
|
/// True if this header shows the leading back button
|
||||||
|
|||||||
@@ -1,148 +0,0 @@
|
|||||||
import 'package:flutter/foundation.dart';
|
|
||||||
import 'package:flutter/widgets.dart';
|
|
||||||
|
|
||||||
enum _LoadingStatus { LOADING, STABLE }
|
|
||||||
|
|
||||||
/// A widget that wraps a [Widget] and will trigger [onEndOfPage]/[onStartOfPage] when it
|
|
||||||
/// reaches the bottom/start of the list
|
|
||||||
class LazyLoadScrollView extends StatefulWidget {
|
|
||||||
/// The [Widget] that this widget watches for changes on
|
|
||||||
final Widget child;
|
|
||||||
|
|
||||||
/// Called when the [child] reaches the start of the list
|
|
||||||
final AsyncCallback onStartOfPage;
|
|
||||||
|
|
||||||
/// Called when the [child] reaches the end of the list
|
|
||||||
final AsyncCallback onEndOfPage;
|
|
||||||
|
|
||||||
/// Called when the list scrolling starts
|
|
||||||
final VoidCallback onPageScrollStart;
|
|
||||||
|
|
||||||
/// Called when the list scrolling ends
|
|
||||||
final VoidCallback onPageScrollEnd;
|
|
||||||
|
|
||||||
/// Called every time the [child] is in-between the list
|
|
||||||
final VoidCallback onInBetweenOfPage;
|
|
||||||
|
|
||||||
/// The offset to take into account when triggering [onEndOfPage]/[onStartOfPage] in pixels
|
|
||||||
final double scrollOffset;
|
|
||||||
|
|
||||||
/// Used to determine if loading of new data has finished. You should use set this if you aren't using a FutureBuilder or StreamBuilder
|
|
||||||
final bool isLoading;
|
|
||||||
|
|
||||||
/// Initiates a LazyLoadScrollView widget
|
|
||||||
const LazyLoadScrollView({
|
|
||||||
Key key,
|
|
||||||
@required this.child,
|
|
||||||
this.onStartOfPage,
|
|
||||||
this.onEndOfPage,
|
|
||||||
this.onPageScrollStart,
|
|
||||||
this.onPageScrollEnd,
|
|
||||||
this.onInBetweenOfPage,
|
|
||||||
this.isLoading = false,
|
|
||||||
this.scrollOffset = 100,
|
|
||||||
}) : assert(child != null),
|
|
||||||
super(key: key);
|
|
||||||
|
|
||||||
@override
|
|
||||||
State<StatefulWidget> createState() => _LazyLoadScrollViewState();
|
|
||||||
}
|
|
||||||
|
|
||||||
class _LazyLoadScrollViewState extends State<LazyLoadScrollView> {
|
|
||||||
_LoadingStatus _loadMoreStatus = _LoadingStatus.STABLE;
|
|
||||||
double _scrollPosition = 0.0;
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return NotificationListener(
|
|
||||||
child: widget.child,
|
|
||||||
onNotification: _onNotification,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool _onNotification(Notification notification) {
|
|
||||||
if (notification is ScrollStartNotification) {
|
|
||||||
if (widget.onPageScrollStart != null) {
|
|
||||||
widget.onPageScrollStart();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (notification is ScrollEndNotification) {
|
|
||||||
if (widget.onPageScrollEnd != null) {
|
|
||||||
widget.onPageScrollEnd();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (notification is ScrollUpdateNotification) {
|
|
||||||
final pixels = notification.metrics.pixels;
|
|
||||||
final maxScrollExtent = notification.metrics.maxScrollExtent;
|
|
||||||
final minScrollExtent = notification.metrics.minScrollExtent;
|
|
||||||
final scrollOffset = widget.scrollOffset;
|
|
||||||
|
|
||||||
if (pixels > (minScrollExtent + scrollOffset) &&
|
|
||||||
pixels < (maxScrollExtent - scrollOffset)) {
|
|
||||||
if (widget.onInBetweenOfPage != null) {
|
|
||||||
widget.onInBetweenOfPage();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
final extentBefore = notification.metrics.extentBefore;
|
|
||||||
final extentAfter = notification.metrics.extentAfter;
|
|
||||||
final scrollingDown = _scrollPosition < pixels;
|
|
||||||
|
|
||||||
if (scrollOffset == null || scrollOffset == 0) {
|
|
||||||
if (extentAfter == 0) {
|
|
||||||
_onEndOfPage();
|
|
||||||
}
|
|
||||||
if (extentBefore == 0) {
|
|
||||||
_onStartOfPage();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (scrollingDown) {
|
|
||||||
if (extentAfter <= scrollOffset) {
|
|
||||||
_onEndOfPage();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (extentBefore <= scrollOffset) {
|
|
||||||
_onStartOfPage();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_scrollPosition = pixels;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (notification is OverscrollNotification) {
|
|
||||||
if (notification.overscroll > 0) {
|
|
||||||
_onEndOfPage();
|
|
||||||
}
|
|
||||||
if (notification.overscroll < 0) {
|
|
||||||
_onStartOfPage();
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
void _onEndOfPage() {
|
|
||||||
if (_loadMoreStatus != null && _loadMoreStatus == _LoadingStatus.STABLE) {
|
|
||||||
_loadMoreStatus = _LoadingStatus.LOADING;
|
|
||||||
if (widget.onEndOfPage != null) {
|
|
||||||
widget.onEndOfPage().whenComplete(() {
|
|
||||||
_loadMoreStatus = _LoadingStatus.STABLE;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void _onStartOfPage() {
|
|
||||||
if (_loadMoreStatus != null && _loadMoreStatus == _LoadingStatus.STABLE) {
|
|
||||||
_loadMoreStatus = _LoadingStatus.LOADING;
|
|
||||||
if (widget.onStartOfPage != null) {
|
|
||||||
widget.onStartOfPage().whenComplete(() {
|
|
||||||
_loadMoreStatus = _LoadingStatus.STABLE;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -4,7 +4,6 @@ import 'package:flutter/foundation.dart';
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_svg/flutter_svg.dart';
|
import 'package:flutter_svg/flutter_svg.dart';
|
||||||
import 'package:photo_manager/photo_manager.dart';
|
import 'package:photo_manager/photo_manager.dart';
|
||||||
import 'package:stream_chat_flutter/src/lazy_load_scroll_view.dart';
|
|
||||||
import 'package:stream_chat_flutter/src/stream_svg_icon.dart';
|
import 'package:stream_chat_flutter/src/stream_svg_icon.dart';
|
||||||
|
|
||||||
import '../stream_chat_flutter.dart';
|
import '../stream_chat_flutter.dart';
|
||||||
|
|||||||
@@ -6,9 +6,9 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:stream_chat/stream_chat.dart';
|
import 'package:stream_chat/stream_chat.dart';
|
||||||
import 'package:stream_chat_flutter/src/reaction_picker.dart';
|
import 'package:stream_chat_flutter/src/reaction_picker.dart';
|
||||||
import 'package:stream_chat_flutter/src/stream_channel.dart';
|
|
||||||
import 'package:stream_chat_flutter/src/stream_svg_icon.dart';
|
import 'package:stream_chat_flutter/src/stream_svg_icon.dart';
|
||||||
import 'package:stream_chat_flutter/src/utils.dart';
|
import 'package:stream_chat_flutter/src/utils.dart';
|
||||||
|
import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart';
|
||||||
|
|
||||||
import 'message_input.dart';
|
import 'message_input.dart';
|
||||||
import 'message_widget.dart';
|
import 'message_widget.dart';
|
||||||
|
|||||||
@@ -26,7 +26,6 @@ import 'package:video_compress/video_compress.dart';
|
|||||||
import '../stream_chat_flutter.dart';
|
import '../stream_chat_flutter.dart';
|
||||||
import 'extension.dart';
|
import 'extension.dart';
|
||||||
import 'quoted_message_widget.dart';
|
import 'quoted_message_widget.dart';
|
||||||
import 'stream_channel.dart';
|
|
||||||
|
|
||||||
typedef FileUploader = Future<String> Function(PlatformFile, Channel);
|
typedef FileUploader = Future<String> Function(PlatformFile, Channel);
|
||||||
typedef AttachmentThumbnailBuilder = Widget Function(
|
typedef AttachmentThumbnailBuilder = Widget Function(
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ import 'package:rxdart/rxdart.dart';
|
|||||||
import 'package:scrollable_positioned_list/scrollable_positioned_list.dart';
|
import 'package:scrollable_positioned_list/scrollable_positioned_list.dart';
|
||||||
import 'package:stream_chat/stream_chat.dart';
|
import 'package:stream_chat/stream_chat.dart';
|
||||||
import 'package:stream_chat_flutter/src/info_tile.dart';
|
import 'package:stream_chat_flutter/src/info_tile.dart';
|
||||||
import 'package:stream_chat_flutter/src/lazy_load_scroll_view.dart';
|
|
||||||
import 'package:stream_chat_flutter/src/message_widget.dart';
|
import 'package:stream_chat_flutter/src/message_widget.dart';
|
||||||
import 'package:stream_chat_flutter/src/stream_svg_icon.dart';
|
import 'package:stream_chat_flutter/src/stream_svg_icon.dart';
|
||||||
import 'package:stream_chat_flutter/src/system_message.dart';
|
import 'package:stream_chat_flutter/src/system_message.dart';
|
||||||
@@ -16,7 +15,6 @@ import 'package:visibility_detector/visibility_detector.dart';
|
|||||||
|
|
||||||
import '../stream_chat_flutter.dart';
|
import '../stream_chat_flutter.dart';
|
||||||
import 'date_divider.dart';
|
import 'date_divider.dart';
|
||||||
import 'stream_channel.dart';
|
|
||||||
import 'swipeable.dart';
|
import 'swipeable.dart';
|
||||||
import 'extension.dart';
|
import 'extension.dart';
|
||||||
|
|
||||||
|
|||||||
@@ -1,150 +0,0 @@
|
|||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:rxdart/rxdart.dart';
|
|
||||||
import 'package:stream_chat/stream_chat.dart';
|
|
||||||
|
|
||||||
import 'stream_chat.dart';
|
|
||||||
|
|
||||||
/// Widget dedicated to the management of a message list with pagination
|
|
||||||
class MessageSearchBloc extends StatefulWidget {
|
|
||||||
/// The widget child
|
|
||||||
final Widget child;
|
|
||||||
|
|
||||||
/// Instantiate a new MessageSearchBloc
|
|
||||||
const MessageSearchBloc({
|
|
||||||
Key key,
|
|
||||||
@required this.child,
|
|
||||||
}) : super(key: key);
|
|
||||||
|
|
||||||
@override
|
|
||||||
MessageSearchBlocState createState() => MessageSearchBlocState();
|
|
||||||
|
|
||||||
/// Use this method to get the current [MessageSearchBlocState] instance
|
|
||||||
static MessageSearchBlocState of(BuildContext context) {
|
|
||||||
MessageSearchBlocState state;
|
|
||||||
|
|
||||||
state = context.findAncestorStateOfType<MessageSearchBlocState>();
|
|
||||||
|
|
||||||
if (state == null) {
|
|
||||||
throw Exception('You must have a MessageSearchBloc widget as ancestor');
|
|
||||||
}
|
|
||||||
|
|
||||||
return state;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The current state of the [MessageSearchBloc]
|
|
||||||
class MessageSearchBlocState extends State<MessageSearchBloc>
|
|
||||||
with AutomaticKeepAliveClientMixin {
|
|
||||||
/// The current messages list
|
|
||||||
List<GetMessageResponse> get messageResponses => _messageResponses.value;
|
|
||||||
|
|
||||||
/// The current messages list as a stream
|
|
||||||
Stream<List<GetMessageResponse>> get messagesStream =>
|
|
||||||
_messageResponses.stream;
|
|
||||||
|
|
||||||
final BehaviorSubject<List<GetMessageResponse>> _messageResponses =
|
|
||||||
BehaviorSubject();
|
|
||||||
|
|
||||||
final BehaviorSubject<bool> _queryMessagesLoadingController =
|
|
||||||
BehaviorSubject.seeded(false);
|
|
||||||
|
|
||||||
/// The stream notifying the state of queryUsers call
|
|
||||||
Stream<bool> get queryMessagesLoading =>
|
|
||||||
_queryMessagesLoadingController.stream;
|
|
||||||
|
|
||||||
/// Calls [Client.search] updating [messageResponses] stream
|
|
||||||
Future<void> search({
|
|
||||||
Map<String, dynamic> filter,
|
|
||||||
Map<String, dynamic> messageFilter,
|
|
||||||
List<SortOption> sort,
|
|
||||||
String query,
|
|
||||||
PaginationParams pagination,
|
|
||||||
}) async {
|
|
||||||
_messageResponses.add(null);
|
|
||||||
try {
|
|
||||||
final messages = await _search(
|
|
||||||
filter: filter,
|
|
||||||
messageFilter: messageFilter,
|
|
||||||
sort: sort,
|
|
||||||
query: query,
|
|
||||||
pagination: pagination,
|
|
||||||
);
|
|
||||||
_messageResponses.add(messages.results);
|
|
||||||
} catch (err, stk) {
|
|
||||||
_messageResponses.addError(err, stk);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Calls [Client.search] updating [queryMessagesLoading] stream
|
|
||||||
Future<void> loadMore({
|
|
||||||
Map<String, dynamic> filter,
|
|
||||||
Map<String, dynamic> messageFilter,
|
|
||||||
List<SortOption> sort,
|
|
||||||
String query,
|
|
||||||
PaginationParams pagination,
|
|
||||||
}) async {
|
|
||||||
if (_queryMessagesLoadingController.value == true) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
_queryMessagesLoadingController.add(true);
|
|
||||||
try {
|
|
||||||
final clear = pagination == null ||
|
|
||||||
pagination.offset == null ||
|
|
||||||
pagination.offset == 0;
|
|
||||||
|
|
||||||
final oldMessages = List<GetMessageResponse>.from(messageResponses ?? []);
|
|
||||||
|
|
||||||
final messages = await _search(
|
|
||||||
filter: filter,
|
|
||||||
messageFilter: messageFilter,
|
|
||||||
sort: sort,
|
|
||||||
query: query,
|
|
||||||
pagination: pagination,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (clear) {
|
|
||||||
_messageResponses.add(messages.results);
|
|
||||||
} else {
|
|
||||||
final temp = oldMessages + messages.results;
|
|
||||||
_messageResponses.add(temp);
|
|
||||||
}
|
|
||||||
|
|
||||||
_queryMessagesLoadingController.add(false);
|
|
||||||
} catch (err, stackTrace) {
|
|
||||||
_queryMessagesLoadingController.addError(err, stackTrace);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<SearchMessagesResponse> _search({
|
|
||||||
Map<String, dynamic> filter,
|
|
||||||
Map<String, dynamic> messageFilter,
|
|
||||||
List<SortOption> sort,
|
|
||||||
String query,
|
|
||||||
PaginationParams pagination,
|
|
||||||
}) {
|
|
||||||
final client = StreamChat.of(context).client;
|
|
||||||
return client.search(
|
|
||||||
filter,
|
|
||||||
sort,
|
|
||||||
query,
|
|
||||||
pagination,
|
|
||||||
messageFilters: messageFilter,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
super.build(context);
|
|
||||||
return widget.child;
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void dispose() {
|
|
||||||
_messageResponses.close();
|
|
||||||
_queryMessagesLoadingController.close();
|
|
||||||
super.dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
bool get wantKeepAlive => true;
|
|
||||||
}
|
|
||||||
@@ -4,10 +4,9 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:stream_chat/stream_chat.dart';
|
import 'package:stream_chat/stream_chat.dart';
|
||||||
import 'package:stream_chat_flutter/src/info_tile.dart';
|
import 'package:stream_chat_flutter/src/info_tile.dart';
|
||||||
import 'package:stream_chat_flutter/src/message_search_item.dart';
|
import 'package:stream_chat_flutter/src/message_search_item.dart';
|
||||||
|
import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart';
|
||||||
|
|
||||||
import '../stream_chat_flutter.dart';
|
import '../stream_chat_flutter.dart';
|
||||||
import 'lazy_load_scroll_view.dart';
|
|
||||||
import 'message_search_bloc.dart';
|
|
||||||
|
|
||||||
/// Callback called when tapping on a user
|
/// Callback called when tapping on a user
|
||||||
typedef MessageSearchItemTapCallback = void Function(GetMessageResponse);
|
typedef MessageSearchItemTapCallback = void Function(GetMessageResponse);
|
||||||
|
|||||||
@@ -1,356 +0,0 @@
|
|||||||
import 'dart:async';
|
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:rxdart/rxdart.dart';
|
|
||||||
import 'package:stream_chat/stream_chat.dart';
|
|
||||||
|
|
||||||
enum QueryDirection { top, bottom }
|
|
||||||
|
|
||||||
/// Widget used to provide information about the channel to the widget tree
|
|
||||||
///
|
|
||||||
/// Use [StreamChannel.of] to get the current [StreamChannelState] instance.
|
|
||||||
class StreamChannel extends StatefulWidget {
|
|
||||||
const StreamChannel({
|
|
||||||
Key key,
|
|
||||||
@required this.child,
|
|
||||||
@required this.channel,
|
|
||||||
this.showLoading = true,
|
|
||||||
this.initialMessageId,
|
|
||||||
}) : assert(child != null),
|
|
||||||
assert(channel != null),
|
|
||||||
super(key: key);
|
|
||||||
|
|
||||||
final Widget child;
|
|
||||||
final Channel channel;
|
|
||||||
final bool showLoading;
|
|
||||||
|
|
||||||
/// If passed the channel will load from this particular message.
|
|
||||||
final String initialMessageId;
|
|
||||||
|
|
||||||
/// Use this method to get the current [StreamChannelState] instance
|
|
||||||
static StreamChannelState of(BuildContext context) {
|
|
||||||
StreamChannelState streamChannelState;
|
|
||||||
|
|
||||||
streamChannelState = context.findAncestorStateOfType<StreamChannelState>();
|
|
||||||
|
|
||||||
if (streamChannelState == null) {
|
|
||||||
throw Exception(
|
|
||||||
'You must have a StreamChannel widget at the top of your widget tree');
|
|
||||||
}
|
|
||||||
|
|
||||||
return streamChannelState;
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
StreamChannelState createState() => StreamChannelState();
|
|
||||||
}
|
|
||||||
|
|
||||||
class StreamChannelState extends State<StreamChannel> {
|
|
||||||
/// Current channel
|
|
||||||
Channel get channel => widget.channel;
|
|
||||||
|
|
||||||
/// InitialMessageId
|
|
||||||
String get initialMessageId => widget.initialMessageId;
|
|
||||||
|
|
||||||
/// Current channel state stream
|
|
||||||
Stream<ChannelState> get channelStateStream =>
|
|
||||||
widget.channel.state.channelStateStream;
|
|
||||||
|
|
||||||
final _queryTopMessagesController = BehaviorSubject.seeded(false);
|
|
||||||
final _queryBottomMessagesController = BehaviorSubject.seeded(false);
|
|
||||||
|
|
||||||
/// The stream notifying the state of [_queryTopMessages] call
|
|
||||||
Stream<bool> get queryTopMessages => _queryTopMessagesController.stream;
|
|
||||||
|
|
||||||
/// The stream notifying the state of [_queryBottomMessages] call
|
|
||||||
Stream<bool> get queryBottomMessages => _queryBottomMessagesController.stream;
|
|
||||||
|
|
||||||
bool _topPaginationEnded = false;
|
|
||||||
bool _bottomPaginationEnded = false;
|
|
||||||
|
|
||||||
Future<void> _queryTopMessages({
|
|
||||||
int limit = 20,
|
|
||||||
bool preferOffline = false,
|
|
||||||
}) async {
|
|
||||||
if (_topPaginationEnded || _queryTopMessagesController?.value == true) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
_queryTopMessagesController.add(true);
|
|
||||||
|
|
||||||
if (channel.state.messages.isEmpty) {
|
|
||||||
return _queryTopMessagesController.add(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
final oldestMessage = channel.state.messages.first;
|
|
||||||
|
|
||||||
try {
|
|
||||||
final state = await queryBeforeMessage(
|
|
||||||
oldestMessage.id,
|
|
||||||
limit: limit,
|
|
||||||
preferOffline: preferOffline,
|
|
||||||
);
|
|
||||||
if (state.messages.isEmpty || state.messages.length < limit) {
|
|
||||||
_topPaginationEnded = true;
|
|
||||||
}
|
|
||||||
_queryTopMessagesController.add(false);
|
|
||||||
} catch (e, stk) {
|
|
||||||
_queryTopMessagesController.addError(e, stk);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> _queryBottomMessages({
|
|
||||||
int limit = 20,
|
|
||||||
bool preferOffline = false,
|
|
||||||
}) async {
|
|
||||||
if (_bottomPaginationEnded ||
|
|
||||||
_queryBottomMessagesController?.value == true ||
|
|
||||||
channel?.state?.isUpToDate == true) return;
|
|
||||||
_queryBottomMessagesController.add(true);
|
|
||||||
|
|
||||||
if (channel.state.messages.isEmpty) {
|
|
||||||
return _queryBottomMessagesController.add(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
final recentMessage = channel.state.messages.last;
|
|
||||||
|
|
||||||
try {
|
|
||||||
final state = await queryAfterMessage(
|
|
||||||
recentMessage.id,
|
|
||||||
limit: limit,
|
|
||||||
preferOffline: preferOffline,
|
|
||||||
);
|
|
||||||
if (state.messages.isEmpty || state.messages.length < limit) {
|
|
||||||
_bottomPaginationEnded = true;
|
|
||||||
}
|
|
||||||
_queryBottomMessagesController.add(false);
|
|
||||||
} catch (e, stk) {
|
|
||||||
_queryBottomMessagesController.addError(e, stk);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Calls [channel.query] updating [queryMessage] stream
|
|
||||||
Future<void> queryMessages({QueryDirection direction = QueryDirection.top}) {
|
|
||||||
if (direction == QueryDirection.top) return _queryTopMessages();
|
|
||||||
return _queryBottomMessages();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Calls [channel.getReplies] updating [queryMessage] stream
|
|
||||||
Future<void> getReplies(
|
|
||||||
String parentId, {
|
|
||||||
int limit = 50,
|
|
||||||
bool preferOffline = false,
|
|
||||||
}) async {
|
|
||||||
if (_topPaginationEnded || _queryTopMessagesController.value) return;
|
|
||||||
_queryTopMessagesController.add(true);
|
|
||||||
|
|
||||||
Message message;
|
|
||||||
if (channel.state.threads.containsKey(parentId)) {
|
|
||||||
final thread = channel.state.threads[parentId];
|
|
||||||
if (thread.isNotEmpty) {
|
|
||||||
message = thread.first;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
final response = await channel.getReplies(
|
|
||||||
parentId,
|
|
||||||
PaginationParams(
|
|
||||||
lessThan: message?.id,
|
|
||||||
limit: limit,
|
|
||||||
),
|
|
||||||
preferOffline: preferOffline,
|
|
||||||
);
|
|
||||||
if (response.messages.isEmpty || response.messages.length < limit) {
|
|
||||||
_topPaginationEnded = true;
|
|
||||||
}
|
|
||||||
_queryTopMessagesController.add(false);
|
|
||||||
} catch (e, stk) {
|
|
||||||
_queryTopMessagesController.addError(e, stk);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Query the channel members and watchers
|
|
||||||
Future<void> queryMembersAndWatchers() async {
|
|
||||||
await widget.channel.query(
|
|
||||||
membersPagination: PaginationParams(
|
|
||||||
offset: channel.state.members?.length,
|
|
||||||
limit: 100,
|
|
||||||
),
|
|
||||||
watchersPagination: PaginationParams(
|
|
||||||
offset: channel.state.watchers?.length,
|
|
||||||
limit: 100,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Loads channel at specific message
|
|
||||||
Future<void> loadChannelAtMessage(
|
|
||||||
String messageId, {
|
|
||||||
int before = 20,
|
|
||||||
int after = 20,
|
|
||||||
bool preferOffline = false,
|
|
||||||
}) {
|
|
||||||
return queryAtMessage(
|
|
||||||
messageId: messageId,
|
|
||||||
before: before,
|
|
||||||
after: after,
|
|
||||||
preferOffline: preferOffline,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
///
|
|
||||||
Future<void> queryAtMessage({
|
|
||||||
String messageId,
|
|
||||||
int before = 20,
|
|
||||||
int after = 20,
|
|
||||||
bool preferOffline = false,
|
|
||||||
}) async {
|
|
||||||
if (channel.state == null) return;
|
|
||||||
channel.state.isUpToDate = false;
|
|
||||||
channel.state.truncate();
|
|
||||||
|
|
||||||
if (messageId == null) {
|
|
||||||
await channel.query(
|
|
||||||
messagesPagination: PaginationParams(
|
|
||||||
limit: before,
|
|
||||||
),
|
|
||||||
preferOffline: preferOffline,
|
|
||||||
);
|
|
||||||
channel.state.isUpToDate = true;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
return Future.wait([
|
|
||||||
queryBeforeMessage(
|
|
||||||
messageId,
|
|
||||||
limit: before,
|
|
||||||
preferOffline: preferOffline,
|
|
||||||
),
|
|
||||||
queryAfterMessage(
|
|
||||||
messageId,
|
|
||||||
limit: after,
|
|
||||||
preferOffline: preferOffline,
|
|
||||||
),
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
///
|
|
||||||
Future<ChannelState> queryBeforeMessage(
|
|
||||||
String messageId, {
|
|
||||||
int limit = 20,
|
|
||||||
bool preferOffline = false,
|
|
||||||
}) {
|
|
||||||
return channel.query(
|
|
||||||
messagesPagination: PaginationParams(
|
|
||||||
lessThan: messageId,
|
|
||||||
limit: limit,
|
|
||||||
),
|
|
||||||
preferOffline: preferOffline,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
///
|
|
||||||
Future<ChannelState> queryAfterMessage(
|
|
||||||
String messageId, {
|
|
||||||
int limit = 20,
|
|
||||||
bool preferOffline = false,
|
|
||||||
}) async {
|
|
||||||
final state = await channel.query(
|
|
||||||
messagesPagination: PaginationParams(
|
|
||||||
greaterThanOrEqual: messageId,
|
|
||||||
limit: limit,
|
|
||||||
),
|
|
||||||
preferOffline: preferOffline,
|
|
||||||
);
|
|
||||||
if (state.messages.isEmpty || state.messages.length < limit) {
|
|
||||||
channel.state.isUpToDate = true;
|
|
||||||
}
|
|
||||||
return state;
|
|
||||||
}
|
|
||||||
|
|
||||||
///
|
|
||||||
Future<Message> getMessage(String messageId) async {
|
|
||||||
var message = channel.state.messages.firstWhere(
|
|
||||||
(it) => it.id == messageId,
|
|
||||||
orElse: () => null,
|
|
||||||
);
|
|
||||||
if (message == null) {
|
|
||||||
final response = await channel.getMessagesById([messageId]);
|
|
||||||
message = response.messages.first;
|
|
||||||
}
|
|
||||||
return message;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Reloads the channel with latest message
|
|
||||||
Future<void> reloadChannel() => queryAtMessage(before: 30);
|
|
||||||
|
|
||||||
List<Future<bool>> _futures;
|
|
||||||
|
|
||||||
Future<bool> get _loadChannelAtMessage async {
|
|
||||||
try {
|
|
||||||
await loadChannelAtMessage(initialMessageId);
|
|
||||||
return true;
|
|
||||||
} catch (e, stk) {
|
|
||||||
print('Error: $e\nStack: $stk');
|
|
||||||
rethrow;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void initState() {
|
|
||||||
super.initState();
|
|
||||||
_futures = [widget.channel.initialized];
|
|
||||||
if (initialMessageId != null) {
|
|
||||||
_futures.add(_loadChannelAtMessage);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void dispose() {
|
|
||||||
_queryTopMessagesController.close();
|
|
||||||
_queryBottomMessagesController.close();
|
|
||||||
super.dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
Widget child = FutureBuilder<List<bool>>(
|
|
||||||
future: Future.wait(_futures),
|
|
||||||
initialData: [
|
|
||||||
channel.state != null,
|
|
||||||
if (initialMessageId != null) false,
|
|
||||||
],
|
|
||||||
builder: (context, snapshot) {
|
|
||||||
if (snapshot.hasError) {
|
|
||||||
if (snapshot.error is Error) {
|
|
||||||
print((snapshot.error as Error).stackTrace);
|
|
||||||
}
|
|
||||||
var message = snapshot.error.toString();
|
|
||||||
if (snapshot.error is DioError) {
|
|
||||||
final dioError = snapshot.error as DioError;
|
|
||||||
if (dioError.type == DioErrorType.RESPONSE) {
|
|
||||||
message = dioError.message;
|
|
||||||
} else {
|
|
||||||
message = 'Check your connection and retry';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return Center(
|
|
||||||
child: Text(message),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
final initialized = snapshot.data[0];
|
|
||||||
final dataLoaded = initialMessageId == null ? true : snapshot.data[1];
|
|
||||||
if (widget.showLoading && (!initialized || !dataLoaded)) {
|
|
||||||
return Center(
|
|
||||||
child: CircularProgressIndicator(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return widget.child;
|
|
||||||
},
|
|
||||||
);
|
|
||||||
if (initialMessageId != null) {
|
|
||||||
child = Material(child: child);
|
|
||||||
}
|
|
||||||
return child;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -6,6 +6,7 @@ import 'package:flutter_app_badger/flutter_app_badger.dart';
|
|||||||
import 'package:flutter_portal/flutter_portal.dart';
|
import 'package:flutter_portal/flutter_portal.dart';
|
||||||
import 'package:stream_chat/stream_chat.dart';
|
import 'package:stream_chat/stream_chat.dart';
|
||||||
import 'package:stream_chat_flutter/src/stream_chat_theme.dart';
|
import 'package:stream_chat_flutter/src/stream_chat_theme.dart';
|
||||||
|
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
|
||||||
|
|
||||||
/// Widget used to provide information about the chat to the widget tree
|
/// Widget used to provide information about the chat to the widget tree
|
||||||
///
|
///
|
||||||
@@ -61,9 +62,8 @@ class StreamChat extends StatefulWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// The current state of the StreamChat widget
|
/// The current state of the StreamChat widget
|
||||||
class StreamChatState extends State<StreamChat> with WidgetsBindingObserver {
|
class StreamChatState extends State<StreamChat> {
|
||||||
Client get client => widget.client;
|
Client get client => widget.client;
|
||||||
Timer _disconnectTimer;
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
@@ -81,7 +81,10 @@ class StreamChatState extends State<StreamChat> with WidgetsBindingObserver {
|
|||||||
accentColor: streamTheme.colorTheme.accentBlue,
|
accentColor: streamTheme.colorTheme.accentBlue,
|
||||||
scaffoldBackgroundColor: streamTheme.colorTheme.white,
|
scaffoldBackgroundColor: streamTheme.colorTheme.white,
|
||||||
),
|
),
|
||||||
child: widget.child,
|
child: StreamChatCore(
|
||||||
|
child: widget.child,
|
||||||
|
client: client,
|
||||||
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
@@ -106,7 +109,6 @@ class StreamChatState extends State<StreamChat> with WidgetsBindingObserver {
|
|||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
WidgetsBinding.instance.addObserver(this);
|
|
||||||
client.state?.totalUnreadCountStream?.listen((count) {
|
client.state?.totalUnreadCountStream?.listen((count) {
|
||||||
if (count > 0) {
|
if (count > 0) {
|
||||||
FlutterAppBadger.updateBadgeCount(count);
|
FlutterAppBadger.updateBadgeCount(count);
|
||||||
@@ -115,67 +117,4 @@ class StreamChatState extends State<StreamChat> with WidgetsBindingObserver {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
StreamSubscription _newMessageSubscription;
|
|
||||||
|
|
||||||
@override
|
|
||||||
void didChangeAppLifecycleState(AppLifecycleState state) {
|
|
||||||
if (client.state?.user != null) {
|
|
||||||
if (state == AppLifecycleState.paused) {
|
|
||||||
if (client.showLocalNotification != null) {
|
|
||||||
_newMessageSubscription = client
|
|
||||||
.on(EventType.messageNew)
|
|
||||||
.where((e) => e.user?.id != user.id)
|
|
||||||
.where((e) => e.message.silent != true)
|
|
||||||
.where((e) => e.message.shadowed != true)
|
|
||||||
.listen((event) async {
|
|
||||||
final channel = client.channel(
|
|
||||||
event.channelType,
|
|
||||||
id: event.channelId,
|
|
||||||
);
|
|
||||||
|
|
||||||
client.showLocalNotification(
|
|
||||||
event.message,
|
|
||||||
ChannelModel(
|
|
||||||
id: channel.id,
|
|
||||||
createdAt: channel.createdAt,
|
|
||||||
extraData: channel.extraData,
|
|
||||||
type: channel.type,
|
|
||||||
memberCount: channel.memberCount,
|
|
||||||
frozen: channel.frozen,
|
|
||||||
cid: channel.cid,
|
|
||||||
deletedAt: channel.deletedAt,
|
|
||||||
config: channel.config,
|
|
||||||
createdBy: channel.createdBy,
|
|
||||||
updatedAt: channel.updatedAt,
|
|
||||||
lastMessageAt: channel.lastMessageAt,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
});
|
|
||||||
_disconnectTimer = Timer(client.backgroundKeepAlive, () {
|
|
||||||
client.disconnect();
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
client.disconnect();
|
|
||||||
}
|
|
||||||
} else if (state == AppLifecycleState.resumed) {
|
|
||||||
_newMessageSubscription?.cancel();
|
|
||||||
if (_disconnectTimer?.isActive == true) {
|
|
||||||
_disconnectTimer.cancel();
|
|
||||||
} else {
|
|
||||||
if (client.wsConnectionStatus.value ==
|
|
||||||
ConnectionStatus.disconnected) {
|
|
||||||
NotificationService.handleIosMessageQueue(client);
|
|
||||||
client.connect();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void dispose() {
|
|
||||||
WidgetsBinding.instance.removeObserver(this);
|
|
||||||
super.dispose();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:lottie/lottie.dart';
|
import 'package:lottie/lottie.dart';
|
||||||
import 'package:stream_chat/stream_chat.dart';
|
import 'package:stream_chat/stream_chat.dart';
|
||||||
import 'package:stream_chat_flutter/src/stream_channel.dart';
|
import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart';
|
||||||
|
|
||||||
/// Widget to show the current list of typing users
|
/// Widget to show the current list of typing users
|
||||||
class TypingIndicator extends StatelessWidget {
|
class TypingIndicator extends StatelessWidget {
|
||||||
|
|||||||
@@ -2,8 +2,6 @@ import 'dart:convert';
|
|||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:stream_chat/stream_chat.dart';
|
import 'package:stream_chat/stream_chat.dart';
|
||||||
import 'package:stream_chat_flutter/src/lazy_load_scroll_view.dart';
|
|
||||||
import 'package:stream_chat_flutter/src/users_bloc.dart';
|
|
||||||
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
|
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
|
||||||
|
|
||||||
import 'user_item.dart';
|
import 'user_item.dart';
|
||||||
@@ -513,42 +511,3 @@ class _UserListViewState extends State<UserListView>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract class ListItem {
|
|
||||||
String get key {
|
|
||||||
if (this is ListHeaderItem) {
|
|
||||||
final header = (this as ListHeaderItem).heading;
|
|
||||||
return 'HEADER-$header';
|
|
||||||
}
|
|
||||||
if (this is ListUserItem) {
|
|
||||||
final user = (this as ListUserItem).user;
|
|
||||||
return 'USER-${user.id}';
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget when({
|
|
||||||
@required Widget Function(String heading) headerItem,
|
|
||||||
@required Widget Function(User user) userItem,
|
|
||||||
}) {
|
|
||||||
if (this is ListHeaderItem) {
|
|
||||||
return headerItem((this as ListHeaderItem).heading);
|
|
||||||
}
|
|
||||||
if (this is ListUserItem) {
|
|
||||||
return userItem((this as ListUserItem).user);
|
|
||||||
}
|
|
||||||
return SizedBox();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class ListHeaderItem extends ListItem {
|
|
||||||
final String heading;
|
|
||||||
|
|
||||||
ListHeaderItem(this.heading);
|
|
||||||
}
|
|
||||||
|
|
||||||
class ListUserItem extends ListItem {
|
|
||||||
final User user;
|
|
||||||
|
|
||||||
ListUserItem(this.user);
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,108 +0,0 @@
|
|||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:rxdart/rxdart.dart';
|
|
||||||
import 'package:stream_chat/stream_chat.dart';
|
|
||||||
|
|
||||||
import 'stream_chat.dart';
|
|
||||||
|
|
||||||
/// Widget dedicated to the management of a users list with pagination
|
|
||||||
class UsersBloc extends StatefulWidget {
|
|
||||||
/// The widget child
|
|
||||||
final Widget child;
|
|
||||||
|
|
||||||
/// Instantiate a new UsersBloc
|
|
||||||
const UsersBloc({
|
|
||||||
Key key,
|
|
||||||
@required this.child,
|
|
||||||
}) : super(key: key);
|
|
||||||
|
|
||||||
@override
|
|
||||||
UsersBlocState createState() => UsersBlocState();
|
|
||||||
|
|
||||||
/// Use this method to get the current [UsersBlocState] instance
|
|
||||||
static UsersBlocState of(BuildContext context) {
|
|
||||||
UsersBlocState state;
|
|
||||||
|
|
||||||
state = context.findAncestorStateOfType<UsersBlocState>();
|
|
||||||
|
|
||||||
if (state == null) {
|
|
||||||
throw Exception('You must have a UsersBloc widget as ancestor');
|
|
||||||
}
|
|
||||||
|
|
||||||
return state;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The current state of the [UsersBloc]
|
|
||||||
class UsersBlocState extends State<UsersBloc>
|
|
||||||
with AutomaticKeepAliveClientMixin {
|
|
||||||
/// The current users list
|
|
||||||
List<User> get users => _usersController.value;
|
|
||||||
|
|
||||||
/// The current users list as a stream
|
|
||||||
Stream<List<User>> get usersStream => _usersController.stream;
|
|
||||||
|
|
||||||
final BehaviorSubject<List<User>> _usersController = BehaviorSubject();
|
|
||||||
|
|
||||||
final BehaviorSubject<bool> _queryUsersLoadingController =
|
|
||||||
BehaviorSubject.seeded(false);
|
|
||||||
|
|
||||||
/// The stream notifying the state of queryUsers call
|
|
||||||
Stream<bool> get queryUsersLoading => _queryUsersLoadingController.stream;
|
|
||||||
|
|
||||||
/// Calls [Client.queryUsers] updating [queryUsersLoading] stream
|
|
||||||
Future<void> queryUsers({
|
|
||||||
Map<String, dynamic> filter,
|
|
||||||
List<SortOption> sort,
|
|
||||||
Map<String, dynamic> options,
|
|
||||||
PaginationParams pagination,
|
|
||||||
}) async {
|
|
||||||
final client = StreamChat.of(context).client;
|
|
||||||
|
|
||||||
if (client.state?.user == null ||
|
|
||||||
_queryUsersLoadingController.value == true) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
_queryUsersLoadingController.add(true);
|
|
||||||
try {
|
|
||||||
final clear = pagination == null ||
|
|
||||||
pagination.offset == null ||
|
|
||||||
pagination.offset == 0;
|
|
||||||
|
|
||||||
final oldUsers = List<User>.from(users ?? []);
|
|
||||||
|
|
||||||
final usersResponse = await client.queryUsers(
|
|
||||||
filter: filter,
|
|
||||||
sort: sort,
|
|
||||||
options: options,
|
|
||||||
pagination: pagination,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (clear) {
|
|
||||||
_usersController.add(usersResponse.users);
|
|
||||||
} else {
|
|
||||||
final temp = oldUsers + usersResponse.users;
|
|
||||||
_usersController.add(temp);
|
|
||||||
}
|
|
||||||
|
|
||||||
_queryUsersLoadingController.add(false);
|
|
||||||
} catch (err, stackTrace) {
|
|
||||||
_queryUsersLoadingController.addError(err, stackTrace);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
super.build(context);
|
|
||||||
return widget.child;
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void dispose() {
|
|
||||||
_usersController.close();
|
|
||||||
_queryUsersLoadingController.close();
|
|
||||||
super.dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
bool get wantKeepAlive => true;
|
|
||||||
}
|
|
||||||
@@ -7,7 +7,6 @@ export 'src/channel_list_header.dart';
|
|||||||
export 'src/channel_list_view.dart';
|
export 'src/channel_list_view.dart';
|
||||||
export 'src/channel_name.dart';
|
export 'src/channel_name.dart';
|
||||||
export 'src/channel_preview.dart';
|
export 'src/channel_preview.dart';
|
||||||
export 'src/channels_bloc.dart';
|
|
||||||
export 'src/date_divider.dart';
|
export 'src/date_divider.dart';
|
||||||
export 'src/deleted_message.dart';
|
export 'src/deleted_message.dart';
|
||||||
export 'src/file_attachment.dart';
|
export 'src/file_attachment.dart';
|
||||||
@@ -22,8 +21,6 @@ export 'src/message_text.dart';
|
|||||||
export 'src/message_widget.dart';
|
export 'src/message_widget.dart';
|
||||||
export 'src/reaction_picker.dart';
|
export 'src/reaction_picker.dart';
|
||||||
export 'src/sending_indicator.dart';
|
export 'src/sending_indicator.dart';
|
||||||
export 'src/stream_channel.dart';
|
|
||||||
export 'src/stream_chat.dart';
|
|
||||||
export 'src/stream_chat_theme.dart';
|
export 'src/stream_chat_theme.dart';
|
||||||
export 'src/stream_neumorphic_button.dart';
|
export 'src/stream_neumorphic_button.dart';
|
||||||
export 'src/stream_svg_icon.dart';
|
export 'src/stream_svg_icon.dart';
|
||||||
@@ -35,11 +32,8 @@ export 'src/user_item.dart';
|
|||||||
export 'src/user_item.dart';
|
export 'src/user_item.dart';
|
||||||
export 'src/user_list_view.dart';
|
export 'src/user_list_view.dart';
|
||||||
export 'src/user_list_view.dart';
|
export 'src/user_list_view.dart';
|
||||||
export 'src/users_bloc.dart';
|
|
||||||
export 'src/users_bloc.dart';
|
|
||||||
export 'src/utils.dart';
|
export 'src/utils.dart';
|
||||||
export 'src/video_attachment.dart';
|
export 'src/video_attachment.dart';
|
||||||
export 'src/message_search_bloc.dart';
|
|
||||||
export 'src/message_search_item.dart';
|
export 'src/message_search_item.dart';
|
||||||
export 'src/message_search_list_view.dart';
|
export 'src/message_search_list_view.dart';
|
||||||
export 'src/unread_indicator.dart';
|
export 'src/unread_indicator.dart';
|
||||||
@@ -47,3 +41,5 @@ export 'src/option_list_tile.dart';
|
|||||||
export 'src/channel_file_display_screen.dart';
|
export 'src/channel_file_display_screen.dart';
|
||||||
export 'src/channel_media_display_screen.dart';
|
export 'src/channel_media_display_screen.dart';
|
||||||
export 'src/info_tile.dart';
|
export 'src/info_tile.dart';
|
||||||
|
export 'src/stream_chat.dart';
|
||||||
|
export 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart';
|
||||||
|
|||||||
@@ -11,6 +11,9 @@ environment:
|
|||||||
dependencies:
|
dependencies:
|
||||||
flutter:
|
flutter:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
|
# TODO: Change over to main dependency when deployed
|
||||||
|
stream_chat_flutter_core:
|
||||||
|
path: ../stream_chat_flutter_core
|
||||||
flutter_app_badger: ^1.1.2
|
flutter_app_badger: ^1.1.2
|
||||||
photo_view: ^0.10.3
|
photo_view: ^0.10.3
|
||||||
rxdart: ^0.24.1
|
rxdart: ^0.24.1
|
||||||
@@ -29,7 +32,6 @@ dependencies:
|
|||||||
image_picker: ^0.6.7+17
|
image_picker: ^0.6.7+17
|
||||||
flutter_keyboard_visibility: ^4.0.2
|
flutter_keyboard_visibility: ^4.0.2
|
||||||
mime: ^0.9.7
|
mime: ^0.9.7
|
||||||
stream_chat: ^0.2.24
|
|
||||||
video_compress: ^2.1.1
|
video_compress: ^2.1.1
|
||||||
visibility_detector: ^0.1.5
|
visibility_detector: ^0.1.5
|
||||||
http_parser: ^3.1.4
|
http_parser: ^3.1.4
|
||||||
|
|||||||
@@ -49,7 +49,6 @@ class ChannelListCore extends StatefulWidget {
|
|||||||
this.options,
|
this.options,
|
||||||
this.sort,
|
this.sort,
|
||||||
this.pagination,
|
this.pagination,
|
||||||
this.pullToRefresh = true,
|
|
||||||
@required this.errorBuilder,
|
@required this.errorBuilder,
|
||||||
@required this.emptyBuilder,
|
@required this.emptyBuilder,
|
||||||
@required this.loadingBuilder,
|
@required this.loadingBuilder,
|
||||||
@@ -92,9 +91,6 @@ class ChannelListCore extends StatefulWidget {
|
|||||||
/// message_limit: how many messages should be included to each channel
|
/// message_limit: how many messages should be included to each channel
|
||||||
final PaginationParams pagination;
|
final PaginationParams pagination;
|
||||||
|
|
||||||
/// Set it to false to disable the pull-to-refresh widget
|
|
||||||
final bool pullToRefresh;
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
_ChannelListCoreState createState() => _ChannelListCoreState();
|
_ChannelListCoreState createState() => _ChannelListCoreState();
|
||||||
}
|
}
|
||||||
@@ -105,21 +101,7 @@ class _ChannelListCoreState extends State<ChannelListCore>
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final channelsBloc = ChannelsBloc.of(context);
|
final channelsBloc = ChannelsBloc.of(context);
|
||||||
|
|
||||||
if (!widget.pullToRefresh) {
|
return _buildListView(channelsBloc);
|
||||||
return _buildListView(channelsBloc);
|
|
||||||
}
|
|
||||||
|
|
||||||
return RefreshIndicator(
|
|
||||||
onRefresh: () async {
|
|
||||||
return channelsBloc.queryChannels(
|
|
||||||
filter: widget.filter,
|
|
||||||
sortOptions: widget.sort,
|
|
||||||
paginationParams: widget.pagination,
|
|
||||||
options: widget.options,
|
|
||||||
);
|
|
||||||
},
|
|
||||||
child: _buildListView(channelsBloc),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
StreamBuilder<List<Channel>> _buildListView(
|
StreamBuilder<List<Channel>> _buildListView(
|
||||||
|
|||||||
Reference in New Issue
Block a user