align with v4

This commit is contained in:
Salvatore Giordano
2022-04-29 15:24:35 +02:00
parent ccfbe367d0
commit 3e048711f5
9 changed files with 502 additions and 729 deletions
@@ -1,52 +1,45 @@
import 'package:example/localizations.dart'; import 'package:example/localizations.dart';
import 'package:example/routes/routes.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.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 'channel_page.dart';
class ChannelFileDisplayScreen extends StatefulWidget { class ChannelFileDisplayScreen extends StatefulWidget {
/// The sorting used for the channels matching the filters. final StreamMessageThemeData messageTheme;
/// Sorting is based on field and direction, multiple sorting options can be provided.
/// You can sort based on last_updated, last_message_at, updated_at, created_at or member_count.
/// Direction can be ascending or descending.
final List<SortOption>? sortOptions;
/// Pagination parameters
/// limit: the number of users to return (max is 30)
/// offset: the offset (max is 1000)
/// message_limit: how many messages should be included to each channel
final PaginationParams paginationParams;
/// The builder used when the file list is empty.
final WidgetBuilder? emptyBuilder;
const ChannelFileDisplayScreen({ const ChannelFileDisplayScreen({
this.sortOptions, Key? key,
this.paginationParams = const PaginationParams(limit: 20), required this.messageTheme,
this.emptyBuilder, }) : super(key: key);
});
@override @override
_ChannelFileDisplayScreenState createState() => State<ChannelFileDisplayScreen> createState() =>
_ChannelFileDisplayScreenState(); _ChannelFileDisplayScreenState();
} }
class _ChannelFileDisplayScreenState extends State<ChannelFileDisplayScreen> { class _ChannelFileDisplayScreenState extends State<ChannelFileDisplayScreen> {
@override final Map<String?, VideoPlayerController?> controllerCache = {};
void initState() {
super.initState(); late final controller = StreamMessageSearchListController(
final messageSearchBloc = MessageSearchBloc.of(context); client: StreamChat.of(context).client,
messageSearchBloc.search( filter: Filter.in_(
filter: Filter.in_( 'cid',
'cid', [StreamChannel.of(context).channel.cid!],
[StreamChannel.of(context).channel.cid!], ),
messageFilter: Filter.in_(
'attachments.type',
['file'],
),
sort: [
SortOption(
'created_at',
direction: SortOption.ASC,
), ),
messageFilter: Filter.in_( ],
'attachments.type', limit: 20,
['file'], );
),
sort: widget.sortOptions,
pagination: widget.paginationParams,
);
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@@ -58,120 +51,109 @@ class _ChannelFileDisplayScreenState extends State<ChannelFileDisplayScreen> {
title: Text( title: Text(
AppLocalizations.of(context).files, AppLocalizations.of(context).files,
style: TextStyle( style: TextStyle(
color: StreamChatTheme.of(context).colorTheme.textHighEmphasis, color: StreamChatTheme.of(context).colorTheme.textHighEmphasis,
fontSize: 16.0), fontSize: 16.0,
),
leading: Center(
child: InkWell(
onTap: () {
Navigator.of(context).pop();
},
child: Container(
width: 24.0,
height: 24.0,
child: StreamSvgIcon.left(
color: StreamChatTheme.of(context).colorTheme.textHighEmphasis,
size: 24.0,
),
),
), ),
), ),
leading: StreamBackButton(),
backgroundColor: StreamChatTheme.of(context).colorTheme.barsBg, backgroundColor: StreamChatTheme.of(context).colorTheme.barsBg,
), ),
body: _buildMediaGrid(), body: ValueListenableBuilder(
); valueListenable: controller,
} builder: (
BuildContext context,
Widget _buildMediaGrid() { PagedValue<String, GetMessageResponse> value,
final messageSearchBloc = MessageSearchBloc.of(context); Widget? child,
) {
return StreamBuilder<List<GetMessageResponse>>( return value.when(
builder: (context, snapshot) { (items, nextPageKey, error) {
if (snapshot.data == null) { if (items.isEmpty) {
return Center( return Center(
child: const CircularProgressIndicator(), child: Column(
); mainAxisAlignment: MainAxisAlignment.center,
} children: [
StreamSvgIcon.files(
if (snapshot.data!.isEmpty) { size: 136.0,
if (widget.emptyBuilder != null) { color: StreamChatTheme.of(context).colorTheme.disabled,
return widget.emptyBuilder!(context); ),
} SizedBox(height: 16.0),
return Center( Text(
child: Column( AppLocalizations.of(context).noFiles,
mainAxisAlignment: MainAxisAlignment.center, style: TextStyle(
children: [ fontSize: 14.0,
StreamSvgIcon.files( color: StreamChatTheme.of(context)
size: 136.0, .colorTheme
color: StreamChatTheme.of(context).colorTheme.disabled, .textHighEmphasis,
), ),
SizedBox(height: 16.0), ),
Text( SizedBox(height: 8.0),
AppLocalizations.of(context).noFiles, Text(
style: TextStyle( AppLocalizations.of(context).filesAppearHere,
fontSize: 14.0, textAlign: TextAlign.center,
color: style: TextStyle(
StreamChatTheme.of(context).colorTheme.textHighEmphasis, fontSize: 14.0,
color: StreamChatTheme.of(context)
.colorTheme
.textHighEmphasis
.withOpacity(0.5),
),
),
],
), ),
), );
SizedBox(height: 8.0), }
Text( final media = <Attachment, Message>{};
AppLocalizations.of(context).filesAppearHere,
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 14.0,
color: StreamChatTheme.of(context)
.colorTheme
.textHighEmphasis
.withOpacity(0.5),
),
),
],
),
);
}
final media = <Attachment, Message>{}; for (var item in items) {
item.message.attachments
.where((e) => e.type == 'file')
.forEach((e) {
media[e] = item.message;
});
}
for (var item in snapshot.data!) { return LazyLoadScrollView(
item.message.attachments.where((e) => e.type == 'file').forEach((e) { onEndOfPage: () async {
media[e] = item.message; if (nextPageKey != null) {
}); controller.loadMore(nextPageKey);
} }
},
return LazyLoadScrollView( child: ListView.builder(
onEndOfPage: () => messageSearchBloc.search( itemBuilder: (context, position) {
filter: Filter.in_( return Padding(
'cid', padding: const EdgeInsets.all(1.0),
[StreamChannel.of(context).channel.cid!], child: Padding(
), padding: const EdgeInsets.all(8.0),
messageFilter: Filter.in_( child: StreamFileAttachment(
'attachments.type', message: media.values.toList()[position],
['file'], attachment: media.keys.toList()[position],
), ),
sort: widget.sortOptions, ),
pagination: widget.paginationParams.copyWith( );
offset: messageSearchBloc.messageResponses?.length ?? 0, },
), itemCount: media.length,
),
child: ListView.builder(
itemBuilder: (context, position) {
return Padding(
padding: const EdgeInsets.all(1.0),
child: Padding(
padding: const EdgeInsets.all(8.0),
child: StreamFileAttachment(
message: media.values.toList()[position],
attachment: media.keys.toList()[position],
),
), ),
); );
}, },
itemCount: media.length, loading: () => Center(
), child: const CircularProgressIndicator(),
); ),
}, error: (_) => Offstage(),
stream: messageSearchBloc.messagesStream, );
},
),
); );
} }
@override
void dispose() {
controller.dispose();
super.dispose();
}
@override
void initState() {
controller.doInitialLoad();
super.initState();
}
} }
+26 -24
View File
@@ -278,31 +278,33 @@ class _ChannelList extends State<ChannelList> {
emptyBuilder: (_) { emptyBuilder: (_) {
return Center( return Center(
child: Padding( child: Padding(
padding: EdgeInsets.all(8), padding: const EdgeInsets.all(8),
child: Column( child: StreamScrollViewEmptyWidget(
mainAxisAlignment: MainAxisAlignment.center, emptyIcon: StreamSvgIcon.message(
children: [ size: 148,
Expanded(child: StreamChannelListEmptyWidget()), color: StreamChatTheme.of(context)
TextButton( .colorTheme
onPressed: () { .disabled,
Navigator.pushNamed( ),
context, emptyTitle: TextButton(
Routes.NEW_CHAT, onPressed: () {
); Navigator.pushNamed(
}, context,
child: Text( Routes.NEW_CHAT,
'Start a chat', );
style: StreamChatTheme.of(context) },
.textTheme child: Text(
.bodyBold 'Start a chat',
.copyWith( style: StreamChatTheme.of(context)
color: StreamChatTheme.of(context) .textTheme
.colorTheme .bodyBold
.accentPrimary, .copyWith(
), color: StreamChatTheme.of(context)
), .colorTheme
.accentPrimary,
),
), ),
], ),
), ),
), ),
); );
@@ -100,9 +100,7 @@ class _ChannelListPageState extends State<ChannelListPage> {
body: IndexedStack( body: IndexedStack(
index: _currentIndex, index: _currentIndex,
children: [ children: [
MessageSearchBloc( ChannelList(),
child: ChannelList(),
),
UserMentionsPage(), UserMentionsPage(),
], ],
), ),
@@ -1,61 +1,45 @@
import 'package:example/localizations.dart'; import 'package:example/localizations.dart';
import 'package:example/routes/routes.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.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';
import 'channel_page.dart';
class ChannelMediaDisplayScreen extends StatefulWidget { class ChannelMediaDisplayScreen extends StatefulWidget {
/// The sorting used for the channels matching the filters.
/// Sorting is based on field and direction, multiple sorting options can be provided.
/// You can sort based on last_updated, last_message_at, updated_at, created_at or member_count.
/// Direction can be ascending or descending.
final List<SortOption>? sortOptions;
/// Pagination parameters
/// limit: the number of users to return (max is 30)
/// offset: the offset (max is 1000)
/// message_limit: how many messages should be included to each channel
final PaginationParams paginationParams;
/// The builder used when the file list is empty.
final WidgetBuilder? emptyBuilder;
final ShowMessageCallback? onShowMessage;
final StreamMessageThemeData messageTheme; final StreamMessageThemeData messageTheme;
const ChannelMediaDisplayScreen({ const ChannelMediaDisplayScreen({
Key? key,
required this.messageTheme, required this.messageTheme,
this.sortOptions, }) : super(key: key);
this.paginationParams = const PaginationParams(limit: 20),
this.emptyBuilder,
this.onShowMessage,
});
@override @override
_ChannelMediaDisplayScreenState createState() => State<ChannelMediaDisplayScreen> createState() =>
_ChannelMediaDisplayScreenState(); _ChannelMediaDisplayScreenState();
} }
class _ChannelMediaDisplayScreenState extends State<ChannelMediaDisplayScreen> { class _ChannelMediaDisplayScreenState extends State<ChannelMediaDisplayScreen> {
Map<String?, VideoPlayerController?> controllerCache = {}; final Map<String?, VideoPlayerController?> controllerCache = {};
@override late final controller = StreamMessageSearchListController(
void initState() { client: StreamChat.of(context).client,
super.initState(); filter: Filter.in_(
final messageSearchBloc = MessageSearchBloc.of(context); 'cid',
messageSearchBloc.search( [StreamChannel.of(context).channel.cid!],
filter: Filter.in_( ),
'cid', messageFilter: Filter.in_(
[StreamChannel.of(context).channel.cid!], 'attachments.type',
['image', 'video'],
),
sort: [
SortOption(
'created_at',
direction: SortOption.ASC,
), ),
messageFilter: Filter.in_( ],
'attachments.type', limit: 20,
['image', 'video'], );
),
sort: widget.sortOptions,
pagination: widget.paginationParams,
);
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@@ -74,160 +58,169 @@ class _ChannelMediaDisplayScreenState extends State<ChannelMediaDisplayScreen> {
leading: StreamBackButton(), leading: StreamBackButton(),
backgroundColor: StreamChatTheme.of(context).colorTheme.barsBg, backgroundColor: StreamChatTheme.of(context).colorTheme.barsBg,
), ),
body: _buildMediaGrid(), body: ValueListenableBuilder(
); valueListenable: controller,
} builder: (BuildContext context,
PagedValue<String, GetMessageResponse> value, Widget? child) {
Widget _buildMediaGrid() { return value.when(
final messageSearchBloc = MessageSearchBloc.of(context); (items, nextPageKey, error) {
if (items.isEmpty) {
return StreamBuilder<List<GetMessageResponse>>( return Center(
builder: (context, snapshot) { child: Column(
if (snapshot.data == null) { mainAxisAlignment: MainAxisAlignment.center,
return Center( children: [
child: const CircularProgressIndicator(), StreamSvgIcon.pictures(
); size: 136.0,
} color: StreamChatTheme.of(context).colorTheme.disabled,
),
if (snapshot.data!.isEmpty) { SizedBox(height: 16.0),
if (widget.emptyBuilder != null) { Text(
return widget.emptyBuilder!(context); AppLocalizations.of(context).noMedia,
} style: TextStyle(
return Center( fontSize: 14.0,
child: Column( color: StreamChatTheme.of(context)
mainAxisAlignment: MainAxisAlignment.center, .colorTheme
children: [ .textHighEmphasis,
StreamSvgIcon.pictures(
size: 136.0,
color: StreamChatTheme.of(context).colorTheme.disabled,
),
SizedBox(height: 16.0),
Text(
AppLocalizations.of(context).noMedia,
style: TextStyle(
fontSize: 14.0,
color:
StreamChatTheme.of(context).colorTheme.textHighEmphasis,
),
),
SizedBox(height: 8.0),
Text(
AppLocalizations.of(context).photosOrVideosWillAppearHere,
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 14.0,
color: StreamChatTheme.of(context)
.colorTheme
.textHighEmphasis
.withOpacity(0.5),
),
),
],
),
);
}
final media = <_AssetPackage>[];
for (var item in snapshot.data!) {
item.message.attachments
.where((e) =>
(e.type == 'image' || e.type == 'video') &&
e.ogScrapeUrl == null)
.forEach((e) {
VideoPlayerController? controller;
if (e.type == 'video') {
var cachedController = controllerCache[e.assetUrl];
if (cachedController == null) {
controller = VideoPlayerController.network(e.assetUrl!);
controller.initialize();
controllerCache[e.assetUrl] = controller;
} else {
controller = cachedController;
}
}
media.add(_AssetPackage(e, item.message, controller));
});
}
return LazyLoadScrollView(
onEndOfPage: () => messageSearchBloc.search(
filter: Filter.in_(
'cid',
[StreamChannel.of(context).channel.cid!],
),
messageFilter: Filter.in_(
'attachments.type',
['image', 'video'],
),
sort: widget.sortOptions,
pagination: widget.paginationParams.copyWith(
offset: messageSearchBloc.messageResponses?.length ?? 0,
),
),
child: GridView.builder(
gridDelegate:
SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 3),
itemBuilder: (context, position) {
var channel = StreamChannel.of(context).channel;
return Padding(
padding: const EdgeInsets.all(1.0),
child: InkWell(
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => StreamChannel(
channel: channel,
child: StreamFullScreenMedia(
mediaAttachmentPackages: media
.map(
(e) => StreamAttachmentPackage(
attachment: e.attachment,
message: e.message,
),
)
.toList(),
startIndex: position,
userName: media[position].message.user!.name,
onShowMessage: widget.onShowMessage,
),
), ),
), ),
SizedBox(height: 8.0),
Text(
AppLocalizations.of(context)
.photosOrVideosWillAppearHere,
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 14.0,
color: StreamChatTheme.of(context)
.colorTheme
.textHighEmphasis
.withOpacity(0.5),
),
),
],
),
);
}
final media = <_AssetPackage>[];
for (var item in value.asSuccess.items) {
item.message.attachments
.where((e) =>
(e.type == 'image' || e.type == 'video') &&
e.ogScrapeUrl == null)
.forEach((e) {
VideoPlayerController? controller;
if (e.type == 'video') {
var cachedController = controllerCache[e.assetUrl];
if (cachedController == null) {
controller = VideoPlayerController.network(e.assetUrl!);
controller.initialize();
controllerCache[e.assetUrl] = controller;
} else {
controller = cachedController;
}
}
media.add(_AssetPackage(e, item.message, controller));
});
}
return LazyLoadScrollView(
onEndOfPage: () async {
if (nextPageKey != null) {
controller.loadMore(nextPageKey);
}
},
child: GridView.builder(
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 3),
itemBuilder: (context, position) {
var channel = StreamChannel.of(context).channel;
return Padding(
padding: const EdgeInsets.all(1.0),
child: InkWell(
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => StreamChannel(
channel: channel,
child: StreamFullScreenMedia(
mediaAttachmentPackages: media
.map(
(e) => StreamAttachmentPackage(
attachment: e.attachment,
message: e.message,
),
)
.toList(),
startIndex: position,
userName: media[position].message.user!.name,
onShowMessage: (m, c) async {
final client =
StreamChat.of(context).client;
final message = m;
final channel = client.channel(
c.type,
id: c.id,
);
if (channel.state == null) {
await channel.watch();
}
Navigator.pushNamed(
context,
Routes.CHANNEL_PAGE,
arguments: ChannelPageArgs(
channel: channel,
initialMessage: message,
),
);
},
),
),
),
);
},
child: media[position].attachment.type == 'image'
? IgnorePointer(
child: StreamImageAttachment(
attachment: media[position].attachment,
message: media[position].message,
showTitle: false,
size: Size(
MediaQuery.of(context).size.width * 0.8,
MediaQuery.of(context).size.height * 0.3,
),
messageTheme: widget.messageTheme,
),
)
: VideoPlayer(media[position].videoPlayer!),
),
); );
}, },
child: media[position].attachment.type == 'image' itemCount: media.length,
? IgnorePointer(
child: StreamImageAttachment(
attachment: media[position].attachment,
message: media[position].message,
showTitle: false,
size: Size(
MediaQuery.of(context).size.width * 0.8,
MediaQuery.of(context).size.height * 0.3,
),
messageTheme: widget.messageTheme,
),
)
: VideoPlayer(media[position].videoPlayer!),
), ),
); );
}, },
itemCount: media.length, loading: () => Center(
), child: const CircularProgressIndicator(),
); ),
}, error: (_) => Offstage(),
stream: messageSearchBloc.messagesStream, );
},
),
); );
} }
@override @override
void dispose() { void dispose() {
controller.dispose();
super.dispose(); super.dispose();
for (var c in controllerCache.values) { }
c!.dispose();
} @override
void initState() {
controller.doInitialLoad();
super.initState();
} }
} }
@@ -35,7 +35,7 @@ class ChannelPage extends StatefulWidget {
class _ChannelPageState extends State<ChannelPage> { class _ChannelPageState extends State<ChannelPage> {
FocusNode? _focusNode; FocusNode? _focusNode;
MessageInputController _messageInputController = MessageInputController(); StreamMessageInputController _messageInputController = StreamMessageInputController();
@override @override
void initState() { void initState() {
@@ -218,36 +218,7 @@ class _ChatInfoScreenState extends State<ChatInfoScreen> {
MaterialPageRoute( MaterialPageRoute(
builder: (context) => StreamChannel( builder: (context) => StreamChannel(
channel: channel, channel: channel,
child: MessageSearchBloc( child: PinnedMessagesScreen(),
child: PinnedMessagesScreen(
messageTheme: widget.messageTheme,
sortOptions: [
SortOption(
'created_at',
direction: SortOption.ASC,
),
],
onShowMessage: (m, c) async {
final client = StreamChat.of(context).client;
final message = m;
final channel = client.channel(
c.type,
id: c.id,
);
if (channel.state == null) {
await channel.watch();
}
Navigator.pushNamed(
context,
Routes.CHANNEL_PAGE,
arguments: ChannelPageArgs(
channel: channel,
initialMessage: message,
),
);
},
),
),
), ),
), ),
); );
@@ -278,35 +249,8 @@ class _ChatInfoScreenState extends State<ChatInfoScreen> {
MaterialPageRoute( MaterialPageRoute(
builder: (context) => StreamChannel( builder: (context) => StreamChannel(
channel: channel, channel: channel,
child: MessageSearchBloc( child: ChannelMediaDisplayScreen(
child: ChannelMediaDisplayScreen( messageTheme: widget.messageTheme,
messageTheme: widget.messageTheme,
sortOptions: [
SortOption(
'created_at',
direction: SortOption.ASC,
),
],
onShowMessage: (m, c) async {
final client = StreamChat.of(context).client;
final message = m;
final channel = client.channel(
c.type,
id: c.id,
);
if (channel.state == null) {
await channel.watch();
}
Navigator.pushNamed(
context,
Routes.CHANNEL_PAGE,
arguments: ChannelPageArgs(
channel: channel,
initialMessage: message,
),
);
},
),
), ),
), ),
), ),
@@ -338,15 +282,8 @@ class _ChatInfoScreenState extends State<ChatInfoScreen> {
MaterialPageRoute( MaterialPageRoute(
builder: (context) => StreamChannel( builder: (context) => StreamChannel(
channel: channel, channel: channel,
child: MessageSearchBloc( child: ChannelFileDisplayScreen(
child: ChannelFileDisplayScreen( messageTheme: widget.messageTheme,
sortOptions: [
SortOption(
'created_at',
direction: SortOption.ASC,
),
],
),
), ),
), ),
), ),
+126 -183
View File
@@ -40,16 +40,52 @@ class _GroupInfoScreenState extends State<GroupInfoScreen> {
ValueNotifier<bool?> mutedBool = ValueNotifier(false); ValueNotifier<bool?> mutedBool = ValueNotifier(false);
late final channel = StreamChannel.of(context).channel;
late final userListController = StreamUserListController(
client: StreamChat.of(context).client,
limit: 25,
filter: Filter.and(
[
if (_searchController!.text.isNotEmpty)
Filter.autoComplete('name', _userNameQuery),
Filter.notIn('id', [
StreamChat.of(context).currentUser!.id,
...channel.state!.members
.map<String?>(((e) => e.userId))
.whereType<String>(),
]),
],
),
sort: [
SortOption(
'name',
direction: 1,
),
],
);
void _userNameListener() { void _userNameListener() {
if (_searchController!.text == _userNameQuery) { if (_searchController!.text == _userNameQuery) {
return; return;
} }
if (_debounce?.isActive ?? false) _debounce!.cancel(); if (_debounce?.isActive ?? false) _debounce!.cancel();
_debounce = Timer(const Duration(milliseconds: 350), () { _debounce = Timer(const Duration(milliseconds: 350), () {
if (mounted && modalSetStateCallback != null) { if (mounted) {
modalSetStateCallback!(() { _userNameQuery = _searchController!.text;
_userNameQuery = _searchController!.text; userListController.filter = Filter.and(
}); [
if (_searchController!.text.isNotEmpty)
Filter.autoComplete('name', _userNameQuery),
Filter.notIn('id', [
StreamChat.of(context).currentUser!.id,
...channel.state!.members
.map<String?>(((e) => e.userId))
.whereType<String>(),
]),
],
);
userListController.doInitialLoad();
} }
}); });
} }
@@ -57,25 +93,28 @@ class _GroupInfoScreenState extends State<GroupInfoScreen> {
@override @override
void initState() { void initState() {
super.initState(); super.initState();
var channel = StreamChannel.of(context);
_nameController = TextEditingController.fromValue( _nameController = TextEditingController.fromValue(
TextEditingValue( TextEditingValue(text: (channel.extraData['name'] as String?) ?? ''),
text: (channel.channel.extraData['name'] as String?) ?? ''),
); );
_searchController = TextEditingController()..addListener(_userNameListener); _searchController = TextEditingController()..addListener(_userNameListener);
_nameController!.addListener(() { _nameController!.addListener(() {
setState(() {}); setState(() {});
}); });
mutedBool = ValueNotifier(StreamChannel.of(context).channel.isMuted); mutedBool = ValueNotifier(channel.isMuted);
}
@override
void dispose() {
userListController.dispose();
super.dispose();
} }
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
var channel = StreamChannel.of(context);
return StreamBuilder<List<Member>>( return StreamBuilder<List<Member>>(
stream: channel.channel.state!.membersStream, stream: channel.state!.membersStream,
builder: (context, snapshot) { builder: (context, snapshot) {
if (!snapshot.hasData) { if (!snapshot.hasData) {
return Container( return Container(
@@ -94,7 +133,7 @@ class _GroupInfoScreenState extends State<GroupInfoScreen> {
title: Column( title: Column(
children: [ children: [
StreamBuilder<ChannelState>( StreamBuilder<ChannelState>(
stream: channel.channelStateStream, stream: channel.state?.channelStateStream,
builder: (context, state) { builder: (context, state) {
if (!state.hasData) { if (!state.hasData) {
return Text( return Text(
@@ -131,7 +170,7 @@ class _GroupInfoScreenState extends State<GroupInfoScreen> {
height: 3.0, height: 3.0,
), ),
Text( Text(
'${channel.channel.memberCount} ${AppLocalizations.of(context).members}, ${snapshot.data?.where((e) => e.user!.online).length ?? 0} ${AppLocalizations.of(context).online}', '${channel.memberCount} ${AppLocalizations.of(context).members}, ${snapshot.data?.where((e) => e.user!.online).length ?? 0} ${AppLocalizations.of(context).online}',
style: TextStyle( style: TextStyle(
color: StreamChatTheme.of(context) color: StreamChatTheme.of(context)
.colorTheme .colorTheme
@@ -144,7 +183,7 @@ class _GroupInfoScreenState extends State<GroupInfoScreen> {
), ),
centerTitle: true, centerTitle: true,
actions: [ actions: [
if (channel.channel.ownCapabilities if (channel.ownCapabilities
.contains(PermissionType.updateChannelMembers)) .contains(PermissionType.updateChannelMembers))
StreamNeumorphicButton( StreamNeumorphicButton(
child: InkWell( child: InkWell(
@@ -169,7 +208,7 @@ class _GroupInfoScreenState extends State<GroupInfoScreen> {
height: 8.0, height: 8.0,
color: StreamChatTheme.of(context).colorTheme.disabled, color: StreamChatTheme.of(context).colorTheme.disabled,
), ),
if (channel.channel.ownCapabilities if (channel.ownCapabilities
.contains(PermissionType.updateChannel)) .contains(PermissionType.updateChannel))
_buildNameTile(), _buildNameTile(),
_buildOptionListTiles(), _buildOptionListTiles(),
@@ -336,7 +375,6 @@ class _GroupInfoScreenState extends State<GroupInfoScreen> {
} }
Widget _buildNameTile() { Widget _buildNameTile() {
var channel = StreamChannel.of(context).channel;
var channelName = (channel.extraData['name'] as String?) ?? ''; var channelName = (channel.extraData['name'] as String?) ?? '';
return Material( return Material(
@@ -412,7 +450,7 @@ class _GroupInfoScreenState extends State<GroupInfoScreen> {
size: 24.0, size: 24.0,
), ),
onTap: () { onTap: () {
StreamChannel.of(context).channel.update({ channel.update({
'name': _nameController!.text.trim(), 'name': _nameController!.text.trim(),
}).catchError((err) { }).catchError((err) {
setState(() { setState(() {
@@ -432,8 +470,6 @@ class _GroupInfoScreenState extends State<GroupInfoScreen> {
} }
Widget _buildOptionListTiles() { Widget _buildOptionListTiles() {
var channel = StreamChannel.of(context);
return Column( return Column(
children: [ children: [
// OptionListTile( // OptionListTile(
@@ -448,10 +484,9 @@ class _GroupInfoScreenState extends State<GroupInfoScreen> {
// ), // ),
// onTap: () {}, // onTap: () {},
// ), // ),
if (channel.channel.ownCapabilities if (channel.ownCapabilities.contains(PermissionType.muteChannel))
.contains(PermissionType.muteChannel))
StreamBuilder<bool>( StreamBuilder<bool>(
stream: StreamChannel.of(context).channel.isMutedStream, stream: channel.isMutedStream,
builder: (context, snapshot) { builder: (context, snapshot) {
mutedBool.value = snapshot.data; mutedBool.value = snapshot.data;
@@ -482,9 +517,9 @@ class _GroupInfoScreenState extends State<GroupInfoScreen> {
mutedBool.value = val; mutedBool.value = val;
if (snapshot.data!) { if (snapshot.data!) {
channel.channel.unmute(); channel.unmute();
} else { } else {
channel.channel.mute(); channel.mute();
} }
}, },
); );
@@ -517,36 +552,7 @@ class _GroupInfoScreenState extends State<GroupInfoScreen> {
MaterialPageRoute( MaterialPageRoute(
builder: (context) => StreamChannel( builder: (context) => StreamChannel(
channel: channel, channel: channel,
child: MessageSearchBloc( child: PinnedMessagesScreen(),
child: PinnedMessagesScreen(
messageTheme: widget.messageTheme,
sortOptions: [
SortOption(
'created_at',
direction: SortOption.ASC,
),
],
onShowMessage: (m, c) async {
final client = StreamChat.of(context).client;
final message = m;
final channel = client.channel(
c.type,
id: c.id,
);
if (channel.state == null) {
await channel.watch();
}
Navigator.pushNamed(
context,
Routes.CHANNEL_PAGE,
arguments: ChannelPageArgs(
channel: channel,
initialMessage: message,
),
);
},
),
),
), ),
), ),
); );
@@ -578,36 +584,8 @@ class _GroupInfoScreenState extends State<GroupInfoScreen> {
MaterialPageRoute( MaterialPageRoute(
builder: (context) => StreamChannel( builder: (context) => StreamChannel(
channel: channel, channel: channel,
child: MessageSearchBloc( child: ChannelMediaDisplayScreen(
child: ChannelMediaDisplayScreen( messageTheme: widget.messageTheme,
messageTheme: widget.messageTheme,
sortOptions: [
SortOption(
'created_at',
direction: SortOption.ASC,
),
],
paginationParams: PaginationParams(limit: 20),
onShowMessage: (m, c) async {
final client = StreamChat.of(context).client;
final message = m;
final channel = client.channel(
c.type,
id: c.id,
);
if (channel.state == null) {
await channel.watch();
}
await Navigator.pushNamed(
context,
Routes.CHANNEL_PAGE,
arguments: ChannelPageArgs(
channel: channel,
initialMessage: message,
),
);
},
),
), ),
), ),
), ),
@@ -640,25 +618,16 @@ class _GroupInfoScreenState extends State<GroupInfoScreen> {
MaterialPageRoute( MaterialPageRoute(
builder: (context) => StreamChannel( builder: (context) => StreamChannel(
channel: channel, channel: channel,
child: MessageSearchBloc( child: ChannelFileDisplayScreen(
child: ChannelFileDisplayScreen( messageTheme: widget.messageTheme,
sortOptions: [
SortOption(
'created_at',
direction: SortOption.ASC,
),
],
paginationParams: PaginationParams(limit: 20),
),
), ),
), ),
), ),
); );
}, },
), ),
if (!channel.channel.isDistinct && if (!channel.isDistinct &&
channel.channel.ownCapabilities channel.ownCapabilities.contains(PermissionType.leaveChannel))
.contains(PermissionType.leaveChannel))
StreamOptionListTile( StreamOptionListTile(
tileColor: StreamChatTheme.of(context).colorTheme.appBg, tileColor: StreamChatTheme.of(context).colorTheme.appBg,
separatorColor: StreamChatTheme.of(context).colorTheme.disabled, separatorColor: StreamChatTheme.of(context).colorTheme.disabled,
@@ -703,106 +672,81 @@ class _GroupInfoScreenState extends State<GroupInfoScreen> {
} }
void _buildAddUserModal(context) { void _buildAddUserModal(context) {
var channel = StreamChannel.of(context).channel;
final userListController = StreamUserListController(
client: channel.client,
limit: 25,
filter: Filter.and(
[
if (_searchController!.text.isNotEmpty)
Filter.autoComplete('name', _userNameQuery),
Filter.notIn('id', [
StreamChat.of(context).currentUser!.id,
...channel.state!.members
.map<String?>(((e) => e.userId))
.whereType<String>(),
]),
],
),
sort: [
SortOption(
'name',
direction: 1,
),
],
);
showDialog( showDialog(
useRootNavigator: false, useRootNavigator: false,
context: context, context: context,
barrierColor: StreamChatTheme.of(context).colorTheme.overlay, barrierColor: StreamChatTheme.of(context).colorTheme.overlay,
builder: (context) { builder: (context) {
return StatefulBuilder(builder: (context, modalSetState) { return Padding(
modalSetStateCallback = modalSetState; padding: EdgeInsets.only(top: 16.0, left: 8.0, right: 8.0),
return Padding( child: Material(
padding: EdgeInsets.only(top: 16.0, left: 8.0, right: 8.0), borderRadius: BorderRadius.only(
child: Material( topLeft: Radius.circular(16.0),
borderRadius: BorderRadius.only( topRight: Radius.circular(16.0),
topLeft: Radius.circular(16.0), ),
topRight: Radius.circular(16.0), clipBehavior: Clip.antiAlias,
), child: Scaffold(
clipBehavior: Clip.antiAlias, body: Column(
child: Scaffold( children: [
body: Column( Padding(
children: [ padding: const EdgeInsets.all(16),
Padding( child: _buildTextInputSection(),
padding: const EdgeInsets.all(16), ),
child: _buildTextInputSection(modalSetState), Expanded(
), child: StreamUserGridView(
Expanded( controller: userListController,
child: StreamUserListView( onUserTap: (user) async {
controller: userListController, _searchController!.clear();
onUserTap: (user) async {
_searchController!.clear();
await channel.addMembers([user.id]); await channel.addMembers([user.id]);
Navigator.pop(context); Navigator.pop(context);
setState(() {}); setState(() {});
}, },
emptyBuilder: (_) { emptyBuilder: (_) {
return LayoutBuilder( return LayoutBuilder(
builder: (context, viewportConstraints) { builder: (context, viewportConstraints) {
return SingleChildScrollView( return SingleChildScrollView(
physics: AlwaysScrollableScrollPhysics(), physics: AlwaysScrollableScrollPhysics(),
child: ConstrainedBox( child: ConstrainedBox(
constraints: BoxConstraints( constraints: BoxConstraints(
minHeight: viewportConstraints.maxHeight, minHeight: viewportConstraints.maxHeight,
), ),
child: Center( child: Center(
child: Column( child: Column(
children: [ children: [
Padding( Padding(
padding: const EdgeInsets.all(24), padding: const EdgeInsets.all(24),
child: StreamSvgIcon.search( child: StreamSvgIcon.search(
size: 96, size: 96,
color: StreamChatTheme.of(context) color: StreamChatTheme.of(context)
.colorTheme .colorTheme
.textLowEmphasis, .textLowEmphasis,
),
), ),
Text(AppLocalizations.of(context) ),
.noUserMatchesTheseKeywords), Text(AppLocalizations.of(context)
], .noUserMatchesTheseKeywords),
), ],
), ),
), ),
); ),
}, );
); },
}, );
), },
), ),
], ),
), ],
), ),
), ),
); ),
}); );
}, },
).then((_) => userListController.dispose()); ).whenComplete(() {
_searchController?.clear();
});
} }
Widget _buildTextInputSection(modalSetState) { Widget _buildTextInputSection() {
final theme = StreamChatTheme.of(context); final theme = StreamChatTheme.of(context);
return Column( return Column(
children: [ children: [
@@ -862,7 +806,6 @@ class _GroupInfoScreenState extends State<GroupInfoScreen> {
} }
void _showUserInfoModal(User? user, bool isUserAdmin) { void _showUserInfoModal(User? user, bool isUserAdmin) {
var channel = StreamChannel.of(context).channel;
final color = StreamChatTheme.of(context).colorTheme.barsBg; final color = StreamChatTheme.of(context).colorTheme.barsBg;
showModalBottomSheet( showModalBottomSheet(
@@ -1,60 +1,34 @@
import 'package:example/localizations.dart'; import 'package:example/localizations.dart';
import 'package:example/routes/routes.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.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 'channel_page.dart';
class PinnedMessagesScreen extends StatefulWidget { class PinnedMessagesScreen extends StatefulWidget {
/// The sorting used for the channels matching the filters.
/// Sorting is based on field and direction, multiple sorting options can be provided.
/// You can sort based on last_updated, last_message_at, updated_at, created_at or member_count.
/// Direction can be ascending or descending.
final List<SortOption>? sortOptions;
/// Pagination parameters
/// limit: the number of users to return (max is 30)
/// offset: the offset (max is 1000)
/// message_limit: how many messages should be included to each channel
final PaginationParams paginationParams;
/// The builder used when the file list is empty.
final WidgetBuilder? emptyBuilder;
final ShowMessageCallback? onShowMessage;
final StreamMessageThemeData messageTheme;
const PinnedMessagesScreen({
required this.messageTheme,
this.sortOptions,
this.paginationParams = const PaginationParams(limit: 20),
this.emptyBuilder,
this.onShowMessage,
});
@override @override
_PinnedMessagesScreenState createState() => _PinnedMessagesScreenState(); State<PinnedMessagesScreen> createState() => _PinnedMessagesScreenState();
} }
class _PinnedMessagesScreenState extends State<PinnedMessagesScreen> { class _PinnedMessagesScreenState extends State<PinnedMessagesScreen> {
Map<String?, VideoPlayerController?> controllerCache = {}; late final controller = StreamMessageSearchListController(
client: StreamChat.of(context).client,
@override filter: Filter.in_(
void initState() { 'cid',
super.initState(); [StreamChannel.of(context).channel.cid!],
final messageSearchBloc = MessageSearchBloc.of(context); ),
messageSearchBloc.search( messageFilter: Filter.equal(
filter: Filter.in_( 'pinned',
'cid', true,
[StreamChannel.of(context).channel.cid!], ),
sort: [
SortOption(
'created_at',
direction: SortOption.ASC,
), ),
messageFilter: Filter.equal( ],
'pinned', limit: 20,
true, );
),
sort: widget.sortOptions,
pagination: widget.paginationParams,
);
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@@ -73,25 +47,9 @@ class _PinnedMessagesScreenState extends State<PinnedMessagesScreen> {
leading: StreamBackButton(), leading: StreamBackButton(),
backgroundColor: StreamChatTheme.of(context).colorTheme.barsBg, backgroundColor: StreamChatTheme.of(context).colorTheme.barsBg,
), ),
body: _buildMediaGrid(), body: StreamMessageSearchListView(
); controller: controller,
} emptyBuilder: (_) {
Widget _buildMediaGrid() {
final messageSearchBloc = MessageSearchBloc.of(context);
return StreamBuilder<List<GetMessageResponse>>(
builder: (context, snapshot) {
if (snapshot.data == null) {
return Center(
child: const CircularProgressIndicator(),
);
}
if (snapshot.data!.isEmpty) {
if (widget.emptyBuilder != null) {
return widget.emptyBuilder!(context);
}
return Center( return Center(
child: Column( child: Column(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
@@ -140,74 +98,33 @@ class _PinnedMessagesScreenState extends State<PinnedMessagesScreen> {
], ],
), ),
); );
} },
onMessageTap: (messageResponse) async {
var data = snapshot.data ?? []; final client = StreamChat.of(context).client;
final message = messageResponse.message;
return LazyLoadScrollView( final channel = client.channel(
onEndOfPage: () => messageSearchBloc.search( messageResponse.channel!.type,
filter: Filter.in_( id: messageResponse.channel!.id,
'cid', );
[StreamChannel.of(context).channel.cid!], if (channel.state == null) {
await channel.watch();
}
Navigator.pushNamed(
context,
Routes.CHANNEL_PAGE,
arguments: ChannelPageArgs(
channel: channel,
initialMessage: message,
), ),
messageFilter: Filter.equal( );
'pinned', },
true, ),
),
sort: widget.sortOptions,
pagination: widget.paginationParams.copyWith(
offset: messageSearchBloc.messageResponses?.length ?? 0,
),
),
child: ListView.builder(
itemBuilder: (context, position) {
var user = data[position].message.user!;
var attachments = data[position].message.attachments;
var text = data[position].message.text ?? '';
return ListTile(
leading: StreamUserAvatar(
user: user,
constraints: BoxConstraints.tightFor(
width: 40.0,
height: 40.0,
),
borderRadius: BorderRadius.circular(28),
),
title: Text(
user.name,
style: TextStyle(
color: StreamChatTheme.of(context)
.colorTheme
.textHighEmphasis,
fontWeight: FontWeight.bold),
),
subtitle: Text(
text != ''
? text
: (attachments.isNotEmpty
? '${attachments.length} ${attachments.length > 1 ? AppLocalizations.of(context).attachments : AppLocalizations.of(context).attachment}'
: ''),
),
onTap: () {
widget.onShowMessage?.call(data[position].message,
StreamChannel.of(context).channel);
},
);
},
itemCount: snapshot.data!.length,
),
);
},
stream: messageSearchBloc.messagesStream,
); );
} }
@override @override
void dispose() { void dispose() {
controller.dispose();
super.dispose(); super.dispose();
for (var c in controllerCache.values) {
c!.dispose();
}
} }
} }
+3 -2
View File
@@ -19,12 +19,13 @@ class ThreadPage extends StatefulWidget {
class _ThreadPageState extends State<ThreadPage> { class _ThreadPageState extends State<ThreadPage> {
FocusNode _focusNode = FocusNode(); FocusNode _focusNode = FocusNode();
late MessageInputController _messageInputController; late StreamMessageInputController _messageInputController;
@override @override
void initState() { void initState() {
super.initState(); super.initState();
_messageInputController = MessageInputController(message: widget.parent); _messageInputController =
StreamMessageInputController(message: widget.parent);
} }
@override @override