Files
flutter-samples/packages/stream_chat_v1/lib/channel_list.dart
T
Salvatore Giordano 3e048711f5 align with v4
2022-04-29 15:24:35 +02:00

320 lines
13 KiB
Dart

import 'dart:async';
import 'package:example/localizations.dart';
import 'package:example/routes/routes.dart';
import 'package:example/search_text_field.dart';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
import 'package:flutter_slidable/flutter_slidable.dart';
import 'channel_page.dart';
import 'chat_info_screen.dart';
import 'group_info_screen.dart';
class ChannelList extends StatefulWidget {
@override
_ChannelList createState() => _ChannelList();
}
class _ChannelList extends State<ChannelList> {
ScrollController _scrollController = ScrollController();
late StreamMessageSearchListController _messageSearchListController =
StreamMessageSearchListController(
client: StreamChat.of(context).client,
filter: Filter.in_('members', [StreamChat.of(context).currentUser!.id]),
limit: 5,
searchQuery: '',
sort: [
SortOption(
'created_at',
direction: SortOption.ASC,
),
],
);
TextEditingController? _controller;
bool _isSearchActive = false;
Timer? _debounce;
void _channelQueryListener() {
if (_debounce?.isActive ?? false) _debounce!.cancel();
_debounce = Timer(const Duration(milliseconds: 350), () {
if (mounted) {
_messageSearchListController.searchQuery = _controller!.text;
setState(() {
_isSearchActive = _controller!.text.isNotEmpty;
});
if (_isSearchActive) _messageSearchListController.doInitialLoad();
}
});
}
late final _channelListController = StreamChannelListController(
client: StreamChat.of(context).client,
filter: Filter.in_(
'members',
[StreamChat.of(context).currentUser!.id],
),
presence: true,
limit: 30,
);
@override
void initState() {
super.initState();
_controller = TextEditingController()..addListener(_channelQueryListener);
}
@override
void dispose() {
_controller?.removeListener(_channelQueryListener);
_controller?.dispose();
_scrollController.dispose();
_channelListController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return WillPopScope(
onWillPop: () async {
if (_isSearchActive) {
_controller!.clear();
setState(() => _isSearchActive = false);
return false;
}
return true;
},
child: NotificationListener<ScrollUpdateNotification>(
onNotification: (ScrollNotification scrollInfo) {
if (_scrollController.position.userScrollDirection ==
ScrollDirection.reverse) {
FocusScope.of(context).unfocus();
}
return true;
},
child: NestedScrollView(
controller: _scrollController,
floatHeaderSlivers: false,
headerSliverBuilder: (_, __) => [
SliverToBoxAdapter(
child: SearchTextField(
controller: _controller,
showCloseButton: _isSearchActive,
hintText: AppLocalizations.of(context).search,
),
),
],
body: _isSearchActive
? StreamMessageSearchListView(
controller: _messageSearchListController,
emptyBuilder: (_) {
return LayoutBuilder(
builder: (context, viewportConstraints) {
return SingleChildScrollView(
physics: AlwaysScrollableScrollPhysics(),
child: ConstrainedBox(
constraints: BoxConstraints(
minHeight: viewportConstraints.maxHeight,
),
child: Center(
child: Column(
children: [
Padding(
padding: const EdgeInsets.all(24),
child: StreamSvgIcon.search(
size: 96,
color: Colors.grey,
),
),
Text(
AppLocalizations.of(context).noResults,
),
],
),
),
),
);
},
);
},
itemBuilder: (
context,
messageResponses,
index,
defaultWidget,
) {
return defaultWidget.copyWith(
onTap: () async {
final messageResponse = messageResponses[index];
FocusScope.of(context).requestFocus(FocusNode());
final client = StreamChat.of(context).client;
final message = messageResponse.message;
final channel = client.channel(
messageResponse.channel!.type,
id: messageResponse.channel!.id,
);
if (channel.state == null) {
await channel.watch();
}
Navigator.pushNamed(
context,
Routes.CHANNEL_PAGE,
arguments: ChannelPageArgs(
channel: channel,
initialMessage: message,
),
);
},
);
},
)
: SlidableAutoCloseBehavior(
closeWhenOpened: true,
child: RefreshIndicator(
onRefresh: _channelListController.refresh,
child: StreamChannelListView(
controller: _channelListController,
itemBuilder: (context, channels, index, defaultWidget) {
final chatTheme = StreamChatTheme.of(context);
final backgroundColor = chatTheme.colorTheme.inputBg;
final channel = channels[index];
final canDeleteChannel = channel.ownCapabilities
.contains(PermissionType.deleteChannel);
return Slidable(
groupTag: 'channels-actions',
endActionPane: ActionPane(
extentRatio: canDeleteChannel ? 0.40 : 0.20,
motion: const BehindMotion(),
children: [
CustomSlidableAction(
child: Icon(Icons.more_horiz),
backgroundColor: backgroundColor,
onPressed: (_) {
showChannelInfoModalBottomSheet(
context: context,
channel: channel,
onViewInfoTap: () {
Navigator.pop(context);
Navigator.push(
context,
MaterialPageRoute(
builder: (context) {
final isOneToOne =
channel.memberCount == 2 &&
channel.isDistinct;
return StreamChannel(
channel: channel,
child: isOneToOne
? ChatInfoScreen(
messageTheme: chatTheme
.ownMessageTheme,
user: channel
.state!.members
.where((m) =>
m.userId !=
channel
.client
.state
.currentUser!
.id)
.first
.user,
)
: GroupInfoScreen(
messageTheme: chatTheme
.ownMessageTheme,
),
);
},
),
);
},
);
},
),
if (canDeleteChannel)
CustomSlidableAction(
backgroundColor: backgroundColor,
child: StreamSvgIcon.delete(
color: chatTheme.colorTheme.accentError,
),
onPressed: (_) async {
final res = await showConfirmationDialog(
context,
title: 'Delete Conversation',
question:
'Are you sure you want to delete this conversation?',
okText: 'Delete',
cancelText: 'Cancel',
icon: StreamSvgIcon.delete(
color: chatTheme.colorTheme.accentError,
),
);
if (res == true) {
await _channelListController
.deleteChannel(channel);
}
},
),
],
),
child: defaultWidget,
);
},
onChannelTap: (channel) {
Navigator.pushNamed(
context,
Routes.CHANNEL_PAGE,
arguments: ChannelPageArgs(
channel: channel,
),
);
},
emptyBuilder: (_) {
return Center(
child: Padding(
padding: const EdgeInsets.all(8),
child: StreamScrollViewEmptyWidget(
emptyIcon: StreamSvgIcon.message(
size: 148,
color: StreamChatTheme.of(context)
.colorTheme
.disabled,
),
emptyTitle: TextButton(
onPressed: () {
Navigator.pushNamed(
context,
Routes.NEW_CHAT,
);
},
child: Text(
'Start a chat',
style: StreamChatTheme.of(context)
.textTheme
.bodyBold
.copyWith(
color: StreamChatTheme.of(context)
.colorTheme
.accentPrimary,
),
),
),
),
),
);
},
),
),
),
),
),
);
}
}