Merge pull request #31 from GetStream/nnbd-migration
stream chat v1 nnbd migration
This commit is contained in:
@@ -6,7 +6,8 @@ class ImagePickerImpl extends ImagePickerRepository {
|
|||||||
@override
|
@override
|
||||||
Future<File> pickImage() async {
|
Future<File> pickImage() async {
|
||||||
final picker = ImagePicker();
|
final picker = ImagePicker();
|
||||||
final pickedFile = await picker.getImage(source: ImageSource.gallery, maxWidth: 400);
|
final pickedFile =
|
||||||
|
await picker.getImage(source: ImageSource.gallery, maxWidth: 400);
|
||||||
return File(pickedFile.path);
|
return File(pickedFile.path);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -46,7 +46,9 @@ class StreamApiLocalImpl extends StreamApiRepository {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<Channel> createGroupChat(String channelId, String name, List<String> members, {String image}) async {
|
Future<Channel> createGroupChat(
|
||||||
|
String channelId, String name, List<String> members,
|
||||||
|
{String image}) async {
|
||||||
final channel = _client.channel('messaging', id: channelId, extraData: {
|
final channel = _client.channel('messaging', id: channelId, extraData: {
|
||||||
'name': name,
|
'name': name,
|
||||||
'image': image,
|
'image': image,
|
||||||
@@ -58,13 +60,14 @@ class StreamApiLocalImpl extends StreamApiRepository {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Future<Channel> createSimpleChat(String friendId) async {
|
Future<Channel> createSimpleChat(String friendId) async {
|
||||||
final channel =
|
final channel = _client.channel('messaging',
|
||||||
_client.channel('messaging', id: '${_client.state.user.id.hashCode}${friendId.hashCode}', extraData: {
|
id: '${_client.state.user.id.hashCode}${friendId.hashCode}',
|
||||||
'members': [
|
extraData: {
|
||||||
friendId,
|
'members': [
|
||||||
_client.state.user.id,
|
friendId,
|
||||||
],
|
_client.state.user.id,
|
||||||
});
|
],
|
||||||
|
});
|
||||||
await channel.watch();
|
await channel.watch();
|
||||||
return channel;
|
return channel;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,8 +20,10 @@ class AuthImpl extends AuthRepository {
|
|||||||
try {
|
try {
|
||||||
UserCredential userCredential;
|
UserCredential userCredential;
|
||||||
final GoogleSignInAccount googleUser = await GoogleSignIn().signIn();
|
final GoogleSignInAccount googleUser = await GoogleSignIn().signIn();
|
||||||
final GoogleSignInAuthentication googleAuth = await googleUser.authentication;
|
final GoogleSignInAuthentication googleAuth =
|
||||||
final GoogleAuthCredential googleAuthCredential = GoogleAuthProvider.credential(
|
await googleUser.authentication;
|
||||||
|
final GoogleAuthCredential googleAuthCredential =
|
||||||
|
GoogleAuthProvider.credential(
|
||||||
accessToken: googleAuth.accessToken,
|
accessToken: googleAuth.accessToken,
|
||||||
idToken: googleAuth.idToken,
|
idToken: googleAuth.idToken,
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -62,7 +62,8 @@ class StreamApiImpl extends StreamApiRepository {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<Channel> createGroupChat(String id, String name, List<String> members, {String image}) async {
|
Future<Channel> createGroupChat(String id, String name, List<String> members,
|
||||||
|
{String image}) async {
|
||||||
final channel = _client.channel('messaging', id: id, extraData: {
|
final channel = _client.channel('messaging', id: id, extraData: {
|
||||||
'name': name,
|
'name': name,
|
||||||
'image': image,
|
'image': image,
|
||||||
@@ -74,13 +75,14 @@ class StreamApiImpl extends StreamApiRepository {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Future<Channel> createSimpleChat(String friendId) async {
|
Future<Channel> createSimpleChat(String friendId) async {
|
||||||
final channel =
|
final channel = _client.channel('messaging',
|
||||||
_client.channel('messaging', id: '${_client.state.user.id.hashCode}${friendId.hashCode}', extraData: {
|
id: '${_client.state.user.id.hashCode}${friendId.hashCode}',
|
||||||
'members': [
|
extraData: {
|
||||||
friendId,
|
'members': [
|
||||||
_client.state.user.id,
|
friendId,
|
||||||
],
|
_client.state.user.id,
|
||||||
});
|
],
|
||||||
|
});
|
||||||
await channel.watch();
|
await channel.watch();
|
||||||
return channel;
|
return channel;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,9 @@ abstract class StreamApiRepository {
|
|||||||
Future<String> getToken(String userId);
|
Future<String> getToken(String userId);
|
||||||
Future<bool> connectIfExist(String userId);
|
Future<bool> connectIfExist(String userId);
|
||||||
Future<ChatUser> connectUser(ChatUser user, String token);
|
Future<ChatUser> connectUser(ChatUser user, String token);
|
||||||
Future<Channel> createGroupChat(String channelId, String name, List<String> members, {String image});
|
Future<Channel> createGroupChat(
|
||||||
|
String channelId, String name, List<String> members,
|
||||||
|
{String image});
|
||||||
Future<Channel> createSimpleChat(String friendId);
|
Future<Channel> createSimpleChat(String friendId);
|
||||||
Future<void> logout();
|
Future<void> logout();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,10 +18,13 @@ import 'package:stream_chat_flutter/stream_chat_flutter.dart';
|
|||||||
List<RepositoryProvider> buildRepositories(StreamChatClient client) {
|
List<RepositoryProvider> buildRepositories(StreamChatClient client) {
|
||||||
//TODO: Here you can use your local implementations of your repositories
|
//TODO: Here you can use your local implementations of your repositories
|
||||||
return [
|
return [
|
||||||
RepositoryProvider<StreamApiRepository>(create: (_) => StreamApiImpl(client)),
|
RepositoryProvider<StreamApiRepository>(
|
||||||
RepositoryProvider<PersistentStorageRepository>(create: (_) => PersistentStorageImpl()),
|
create: (_) => StreamApiImpl(client)),
|
||||||
|
RepositoryProvider<PersistentStorageRepository>(
|
||||||
|
create: (_) => PersistentStorageImpl()),
|
||||||
RepositoryProvider<AuthRepository>(create: (_) => AuthImpl()),
|
RepositoryProvider<AuthRepository>(create: (_) => AuthImpl()),
|
||||||
RepositoryProvider<UploadStorageRepository>(create: (_) => UploadStorageImpl()),
|
RepositoryProvider<UploadStorageRepository>(
|
||||||
|
create: (_) => UploadStorageImpl()),
|
||||||
RepositoryProvider<ImagePickerRepository>(create: (_) => ImagePickerImpl()),
|
RepositoryProvider<ImagePickerRepository>(create: (_) => ImagePickerImpl()),
|
||||||
RepositoryProvider<ProfileSignInUseCase>(
|
RepositoryProvider<ProfileSignInUseCase>(
|
||||||
create: (context) => ProfileSignInUseCase(
|
create: (context) => ProfileSignInUseCase(
|
||||||
|
|||||||
@@ -25,7 +25,8 @@ class CreateGroupUseCase {
|
|||||||
final channelId = Uuid().v4();
|
final channelId = Uuid().v4();
|
||||||
String image;
|
String image;
|
||||||
if (input.imageFile != null) {
|
if (input.imageFile != null) {
|
||||||
image = await _uploadStorageRepository.uploadPhoto(input.imageFile, 'channels/$channelId');
|
image = await _uploadStorageRepository.uploadPhoto(
|
||||||
|
input.imageFile, 'channels/$channelId');
|
||||||
}
|
}
|
||||||
final channel = await _streamApiRepository.createGroupChat(
|
final channel = await _streamApiRepository.createGroupChat(
|
||||||
channelId,
|
channelId,
|
||||||
|
|||||||
@@ -27,8 +27,10 @@ class ProfileSignInUseCase {
|
|||||||
final token = await _streamApiRepository.getToken(auth.id);
|
final token = await _streamApiRepository.getToken(auth.id);
|
||||||
String image;
|
String image;
|
||||||
if (input.imageFile != null) {
|
if (input.imageFile != null) {
|
||||||
image = await _uploadStorageRepository.uploadPhoto(input.imageFile, 'users/${auth.id}');
|
image = await _uploadStorageRepository.uploadPhoto(
|
||||||
|
input.imageFile, 'users/${auth.id}');
|
||||||
}
|
}
|
||||||
await _streamApiRepository.connectUser(ChatUser(name: input.name, id: auth.id, image: image), token);
|
await _streamApiRepository.connectUser(
|
||||||
|
ChatUser(name: input.name, id: auth.id, image: image), token);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,7 +34,8 @@ class MyApp extends StatelessWidget {
|
|||||||
return StreamChat(
|
return StreamChat(
|
||||||
child: child,
|
child: child,
|
||||||
client: _streamChatClient,
|
client: _streamChatClient,
|
||||||
streamChatThemeData: StreamChatThemeData.fromTheme(Theme.of(context)).copyWith(
|
streamChatThemeData:
|
||||||
|
StreamChatThemeData.fromTheme(Theme.of(context)).copyWith(
|
||||||
ownMessageTheme: MessageTheme(
|
ownMessageTheme: MessageTheme(
|
||||||
messageBackgroundColor: Theme.of(context).accentColor,
|
messageBackgroundColor: Theme.of(context).accentColor,
|
||||||
messageText: TextStyle(color: Colors.white),
|
messageText: TextStyle(color: Colors.white),
|
||||||
|
|||||||
@@ -18,5 +18,7 @@ Future pushAndReplaceToPage(BuildContext context, Widget widget) async {
|
|||||||
|
|
||||||
Future popAllAndPush(BuildContext context, Widget widget) async {
|
Future popAllAndPush(BuildContext context, Widget widget) async {
|
||||||
await Navigator.pushAndRemoveUntil(
|
await Navigator.pushAndRemoveUntil(
|
||||||
context, MaterialPageRoute(builder: (BuildContext context) => widget), ModalRoute.withName('/'));
|
context,
|
||||||
|
MaterialPageRoute(builder: (BuildContext context) => widget),
|
||||||
|
ModalRoute.withName('/'));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -90,7 +90,8 @@ class MyChannelPreview extends StatelessWidget {
|
|||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
Flexible(
|
Flexible(
|
||||||
child: ChannelName(
|
child: ChannelName(
|
||||||
textStyle: StreamChatTheme.of(context).channelPreviewTheme.title,
|
textStyle:
|
||||||
|
StreamChatTheme.of(context).channelPreviewTheme.title,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
StreamBuilder<List<Member>>(
|
StreamBuilder<List<Member>>(
|
||||||
@@ -99,7 +100,8 @@ class MyChannelPreview extends StatelessWidget {
|
|||||||
builder: (context, snapshot) {
|
builder: (context, snapshot) {
|
||||||
if (!snapshot.hasData ||
|
if (!snapshot.hasData ||
|
||||||
snapshot.data.isEmpty ||
|
snapshot.data.isEmpty ||
|
||||||
!snapshot.data.any((Member e) => e.user.id == channel.client.state.user.id)) {
|
!snapshot.data.any((Member e) =>
|
||||||
|
e.user.id == channel.client.state.user.id)) {
|
||||||
return SizedBox();
|
return SizedBox();
|
||||||
}
|
}
|
||||||
return ChannelUnreadIndicator(
|
return ChannelUnreadIndicator(
|
||||||
@@ -118,15 +120,21 @@ class MyChannelPreview extends StatelessWidget {
|
|||||||
(m) => !m.isDeleted && m.shadowed != true,
|
(m) => !m.isDeleted && m.shadowed != true,
|
||||||
orElse: () => null,
|
orElse: () => null,
|
||||||
);
|
);
|
||||||
if (lastMessage?.user?.id == StreamChat.of(context).user.id) {
|
if (lastMessage?.user?.id ==
|
||||||
|
StreamChat.of(context).user.id) {
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: const EdgeInsets.only(right: 4.0),
|
padding: const EdgeInsets.only(right: 4.0),
|
||||||
child: SendingIndicator(
|
child: SendingIndicator(
|
||||||
message: lastMessage,
|
message: lastMessage,
|
||||||
size: StreamChatTheme.of(context).channelPreviewTheme.indicatorIconSize,
|
size: StreamChatTheme.of(context)
|
||||||
|
.channelPreviewTheme
|
||||||
|
.indicatorIconSize,
|
||||||
isMessageRead: channel.state.read
|
isMessageRead: channel.state.read
|
||||||
?.where((element) => element.user.id != channel.client.state.user.id)
|
?.where((element) =>
|
||||||
?.where((element) => element.lastRead.isAfter(lastMessage.createdAt))
|
element.user.id !=
|
||||||
|
channel.client.state.user.id)
|
||||||
|
?.where((element) => element.lastRead
|
||||||
|
.isAfter(lastMessage.createdAt))
|
||||||
?.isNotEmpty ==
|
?.isNotEmpty ==
|
||||||
true,
|
true,
|
||||||
),
|
),
|
||||||
@@ -158,7 +166,8 @@ class MyChannelPreview extends StatelessWidget {
|
|||||||
|
|
||||||
var startOfDay = DateTime(now.year, now.month, now.day);
|
var startOfDay = DateTime(now.year, now.month, now.day);
|
||||||
|
|
||||||
if (lastMessageAt.millisecondsSinceEpoch >= startOfDay.millisecondsSinceEpoch) {
|
if (lastMessageAt.millisecondsSinceEpoch >=
|
||||||
|
startOfDay.millisecondsSinceEpoch) {
|
||||||
stringDate = Jiffy(lastMessageAt.toLocal()).format('HH:mm');
|
stringDate = Jiffy(lastMessageAt.toLocal()).format('HH:mm');
|
||||||
} else if (lastMessageAt.millisecondsSinceEpoch >=
|
} else if (lastMessageAt.millisecondsSinceEpoch >=
|
||||||
startOfDay.subtract(Duration(days: 1)).millisecondsSinceEpoch) {
|
startOfDay.subtract(Duration(days: 1)).millisecondsSinceEpoch) {
|
||||||
@@ -187,8 +196,14 @@ class MyChannelPreview extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
Text(
|
Text(
|
||||||
' Channel is muted',
|
' Channel is muted',
|
||||||
style: StreamChatTheme.of(context).channelPreviewTheme.subtitle.copyWith(
|
style: StreamChatTheme.of(context)
|
||||||
color: StreamChatTheme.of(context).channelPreviewTheme.subtitle.color,
|
.channelPreviewTheme
|
||||||
|
.subtitle
|
||||||
|
.copyWith(
|
||||||
|
color: StreamChatTheme.of(context)
|
||||||
|
.channelPreviewTheme
|
||||||
|
.subtitle
|
||||||
|
.color,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@@ -198,7 +213,8 @@ class MyChannelPreview extends StatelessWidget {
|
|||||||
channel: channel,
|
channel: channel,
|
||||||
alternativeWidget: _buildLastMessage(context),
|
alternativeWidget: _buildLastMessage(context),
|
||||||
style: StreamChatTheme.of(context).channelPreviewTheme.subtitle.copyWith(
|
style: StreamChatTheme.of(context).channelPreviewTheme.subtitle.copyWith(
|
||||||
color: StreamChatTheme.of(context).channelPreviewTheme.subtitle.color,
|
color:
|
||||||
|
StreamChatTheme.of(context).channelPreviewTheme.subtitle.color,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -208,7 +224,9 @@ class MyChannelPreview extends StatelessWidget {
|
|||||||
stream: channel.state.messagesStream,
|
stream: channel.state.messagesStream,
|
||||||
initialData: channel.state.messages,
|
initialData: channel.state.messages,
|
||||||
builder: (context, snapshot) {
|
builder: (context, snapshot) {
|
||||||
final lastMessage = snapshot.data?.lastWhere((m) => m.shadowed != true && !m.isDeleted, orElse: () => null);
|
final lastMessage = snapshot.data?.lastWhere(
|
||||||
|
(m) => m.shadowed != true && !m.isDeleted,
|
||||||
|
orElse: () => null);
|
||||||
if (lastMessage == null) {
|
if (lastMessage == null) {
|
||||||
return SizedBox();
|
return SizedBox();
|
||||||
}
|
}
|
||||||
@@ -224,7 +242,9 @@ class MyChannelPreview extends StatelessWidget {
|
|||||||
} else if (e.type == 'giphy') {
|
} else if (e.type == 'giphy') {
|
||||||
return '[GIF]';
|
return '[GIF]';
|
||||||
}
|
}
|
||||||
return e == lastMessage.attachments.last ? (e.title ?? 'File') : '${e.title ?? 'File'} , ';
|
return e == lastMessage.attachments.last
|
||||||
|
? (e.title ?? 'File')
|
||||||
|
: '${e.title ?? 'File'} , ';
|
||||||
}).where((e) => e != null),
|
}).where((e) => e != null),
|
||||||
lastMessage.text ?? '',
|
lastMessage.text ?? '',
|
||||||
];
|
];
|
||||||
@@ -238,11 +258,21 @@ class MyChannelPreview extends StatelessWidget {
|
|||||||
lastMessage.mentionedUsers,
|
lastMessage.mentionedUsers,
|
||||||
lastMessage.attachments,
|
lastMessage.attachments,
|
||||||
StreamChatTheme.of(context).channelPreviewTheme.subtitle.copyWith(
|
StreamChatTheme.of(context).channelPreviewTheme.subtitle.copyWith(
|
||||||
color: StreamChatTheme.of(context).channelPreviewTheme.subtitle.color,
|
color: StreamChatTheme.of(context)
|
||||||
fontStyle: (lastMessage.isSystem || lastMessage.isDeleted) ? FontStyle.italic : FontStyle.normal),
|
.channelPreviewTheme
|
||||||
|
.subtitle
|
||||||
|
.color,
|
||||||
|
fontStyle: (lastMessage.isSystem || lastMessage.isDeleted)
|
||||||
|
? FontStyle.italic
|
||||||
|
: FontStyle.normal),
|
||||||
StreamChatTheme.of(context).channelPreviewTheme.subtitle.copyWith(
|
StreamChatTheme.of(context).channelPreviewTheme.subtitle.copyWith(
|
||||||
color: StreamChatTheme.of(context).channelPreviewTheme.subtitle.color,
|
color: StreamChatTheme.of(context)
|
||||||
fontStyle: (lastMessage.isSystem || lastMessage.isDeleted) ? FontStyle.italic : FontStyle.normal,
|
.channelPreviewTheme
|
||||||
|
.subtitle
|
||||||
|
.color,
|
||||||
|
fontStyle: (lastMessage.isSystem || lastMessage.isDeleted)
|
||||||
|
? FontStyle.italic
|
||||||
|
: FontStyle.normal,
|
||||||
fontWeight: FontWeight.bold),
|
fontWeight: FontWeight.bold),
|
||||||
),
|
),
|
||||||
maxLines: 1,
|
maxLines: 1,
|
||||||
@@ -252,19 +282,27 @@ class MyChannelPreview extends StatelessWidget {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
TextSpan _getDisplayText(String text, List<User> mentions, List<Attachment> attachments, TextStyle normalTextStyle,
|
TextSpan _getDisplayText(
|
||||||
|
String text,
|
||||||
|
List<User> mentions,
|
||||||
|
List<Attachment> attachments,
|
||||||
|
TextStyle normalTextStyle,
|
||||||
TextStyle mentionsTextStyle) {
|
TextStyle mentionsTextStyle) {
|
||||||
var textList = text.split(' ');
|
var textList = text.split(' ');
|
||||||
var resList = <TextSpan>[];
|
var resList = <TextSpan>[];
|
||||||
for (var e in textList) {
|
for (var e in textList) {
|
||||||
if (mentions != null && mentions.isNotEmpty && mentions.any((element) => '@${element.name}' == e)) {
|
if (mentions != null &&
|
||||||
|
mentions.isNotEmpty &&
|
||||||
|
mentions.any((element) => '@${element.name}' == e)) {
|
||||||
resList.add(TextSpan(
|
resList.add(TextSpan(
|
||||||
text: '$e ',
|
text: '$e ',
|
||||||
style: mentionsTextStyle,
|
style: mentionsTextStyle,
|
||||||
));
|
));
|
||||||
} else if (attachments != null &&
|
} else if (attachments != null &&
|
||||||
attachments.isNotEmpty &&
|
attachments.isNotEmpty &&
|
||||||
attachments.where((e) => e.title != null).any((element) => element.title == e)) {
|
attachments
|
||||||
|
.where((e) => e.title != null)
|
||||||
|
.any((element) => element.title == e)) {
|
||||||
resList.add(TextSpan(
|
resList.add(TextSpan(
|
||||||
text: '$e ',
|
text: '$e ',
|
||||||
style: normalTextStyle.copyWith(fontStyle: FontStyle.italic),
|
style: normalTextStyle.copyWith(fontStyle: FontStyle.italic),
|
||||||
@@ -301,7 +339,9 @@ class ChannelUnreadIndicator extends StatelessWidget {
|
|||||||
|
|
||||||
return Material(
|
return Material(
|
||||||
borderRadius: BorderRadius.circular(8),
|
borderRadius: BorderRadius.circular(8),
|
||||||
color: StreamChatTheme.of(context).channelPreviewTheme.unreadCounterColor,
|
color: StreamChatTheme.of(context)
|
||||||
|
.channelPreviewTheme
|
||||||
|
.unreadCounterColor,
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.only(
|
padding: const EdgeInsets.only(
|
||||||
left: 5.0,
|
left: 5.0,
|
||||||
|
|||||||
@@ -43,8 +43,10 @@ class ChatView extends StatelessWidget {
|
|||||||
name = channel.extraData['name'];
|
name = channel.extraData['name'];
|
||||||
image = channel.extraData['image'];
|
image = channel.extraData['image'];
|
||||||
} else {
|
} else {
|
||||||
final friend =
|
final friend = channel.state.members
|
||||||
channel.state.members.where((element) => element.userId != currentUser.id).first.user;
|
.where((element) => element.userId != currentUser.id)
|
||||||
|
.first
|
||||||
|
.user;
|
||||||
name = friend.name;
|
name = friend.name;
|
||||||
image = friend.extraData['image'];
|
image = friend.extraData['image'];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,21 +13,27 @@ class FriendsSelectionCubit extends Cubit<List<ChatUserState>> {
|
|||||||
FriendsSelectionCubit(this._streamApiRepository) : super([]);
|
FriendsSelectionCubit(this._streamApiRepository) : super([]);
|
||||||
final StreamApiRepository _streamApiRepository;
|
final StreamApiRepository _streamApiRepository;
|
||||||
|
|
||||||
List<ChatUserState> get selectedUsers => state.where((element) => element.selected).toList();
|
List<ChatUserState> get selectedUsers =>
|
||||||
|
state.where((element) => element.selected).toList();
|
||||||
|
|
||||||
Future<void> init() async {
|
Future<void> init() async {
|
||||||
final chatUsers = (await _streamApiRepository.getChatUsers()).map((e) => ChatUserState(e)).toList();
|
final chatUsers = (await _streamApiRepository.getChatUsers())
|
||||||
|
.map((e) => ChatUserState(e))
|
||||||
|
.toList();
|
||||||
emit(chatUsers);
|
emit(chatUsers);
|
||||||
}
|
}
|
||||||
|
|
||||||
void selectUser(ChatUserState chatUser) {
|
void selectUser(ChatUserState chatUser) {
|
||||||
final index = state.indexWhere((element) => element.chatUser.id == chatUser.chatUser.id);
|
final index = state
|
||||||
state[index] = ChatUserState(state[index].chatUser, selected: !chatUser.selected);
|
.indexWhere((element) => element.chatUser.id == chatUser.chatUser.id);
|
||||||
|
state[index] =
|
||||||
|
ChatUserState(state[index].chatUser, selected: !chatUser.selected);
|
||||||
emit(List<ChatUserState>.from(state));
|
emit(List<ChatUserState>.from(state));
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<Channel> createFriendChannel(ChatUserState chatUserState) async {
|
Future<Channel> createFriendChannel(ChatUserState chatUserState) async {
|
||||||
return await _streamApiRepository.createSimpleChat(chatUserState.chatUser.id);
|
return await _streamApiRepository
|
||||||
|
.createSimpleChat(chatUserState.chatUser.id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,8 +7,11 @@ import 'package:flutter_bloc/flutter_bloc.dart';
|
|||||||
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
|
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
|
||||||
|
|
||||||
class FriendsSelectionView extends StatelessWidget {
|
class FriendsSelectionView extends StatelessWidget {
|
||||||
void _createFriendChannel(BuildContext context, ChatUserState chatUserState) async {
|
void _createFriendChannel(
|
||||||
final channel = await context.read<FriendsSelectionCubit>().createFriendChannel(chatUserState);
|
BuildContext context, ChatUserState chatUserState) async {
|
||||||
|
final channel = await context
|
||||||
|
.read<FriendsSelectionCubit>()
|
||||||
|
.createFriendChannel(chatUserState);
|
||||||
pushAndReplaceToPage(
|
pushAndReplaceToPage(
|
||||||
context,
|
context,
|
||||||
Scaffold(
|
Scaffold(
|
||||||
@@ -26,19 +29,23 @@ class FriendsSelectionView extends StatelessWidget {
|
|||||||
final accentColor = Theme.of(context).accentColor;
|
final accentColor = Theme.of(context).accentColor;
|
||||||
return MultiBlocProvider(
|
return MultiBlocProvider(
|
||||||
providers: [
|
providers: [
|
||||||
BlocProvider(create: (context) => FriendsSelectionCubit(context.read())..init()),
|
BlocProvider(
|
||||||
|
create: (context) => FriendsSelectionCubit(context.read())..init()),
|
||||||
BlocProvider(create: (_) => FriendsGroupCubit()),
|
BlocProvider(create: (_) => FriendsGroupCubit()),
|
||||||
],
|
],
|
||||||
child: BlocBuilder<FriendsGroupCubit, bool>(builder: (context, isGroup) {
|
child: BlocBuilder<FriendsGroupCubit, bool>(builder: (context, isGroup) {
|
||||||
return BlocBuilder<FriendsSelectionCubit, List<ChatUserState>>(builder: (context, snapshot) {
|
return BlocBuilder<FriendsSelectionCubit, List<ChatUserState>>(
|
||||||
final selectedUsers = context.read<FriendsSelectionCubit>().selectedUsers;
|
builder: (context, snapshot) {
|
||||||
|
final selectedUsers =
|
||||||
|
context.read<FriendsSelectionCubit>().selectedUsers;
|
||||||
|
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
floatingActionButton: isGroup && selectedUsers.isNotEmpty
|
floatingActionButton: isGroup && selectedUsers.isNotEmpty
|
||||||
? FloatingActionButton(
|
? FloatingActionButton(
|
||||||
child: Icon(Icons.arrow_right_alt_rounded),
|
child: Icon(Icons.arrow_right_alt_rounded),
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
pushAndReplaceToPage(context, GroupSelectionView(selectedUsers));
|
pushAndReplaceToPage(
|
||||||
|
context, GroupSelectionView(selectedUsers));
|
||||||
})
|
})
|
||||||
: null,
|
: null,
|
||||||
backgroundColor: Theme.of(context).canvasColor,
|
backgroundColor: Theme.of(context).canvasColor,
|
||||||
@@ -91,12 +98,14 @@ class FriendsSelectionView extends StatelessWidget {
|
|||||||
backgroundColor: accentColor,
|
backgroundColor: accentColor,
|
||||||
child: Icon(Icons.group_outlined),
|
child: Icon(Icons.group_outlined),
|
||||||
),
|
),
|
||||||
title: Text('Create group', style: TextStyle(fontWeight: FontWeight.w700)),
|
title: Text('Create group',
|
||||||
|
style: TextStyle(fontWeight: FontWeight.w700)),
|
||||||
subtitle: Text('Talk with 2 or more contacts'),
|
subtitle: Text('Talk with 2 or more contacts'),
|
||||||
)
|
)
|
||||||
else if (isGroup && selectedUsers.isEmpty)
|
else if (isGroup && selectedUsers.isEmpty)
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.only(top: 15.0, left: 20.0, bottom: 20),
|
padding: const EdgeInsets.only(
|
||||||
|
top: 15.0, left: 20.0, bottom: 20),
|
||||||
child: Column(
|
child: Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
@@ -123,7 +132,8 @@ class FriendsSelectionView extends StatelessWidget {
|
|||||||
itemBuilder: (context, index) {
|
itemBuilder: (context, index) {
|
||||||
final chatUserState = selectedUsers[index];
|
final chatUserState = selectedUsers[index];
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 13.0),
|
padding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: 13.0),
|
||||||
child: Stack(
|
child: Stack(
|
||||||
clipBehavior: Clip.none,
|
clipBehavior: Clip.none,
|
||||||
children: [
|
children: [
|
||||||
@@ -132,7 +142,8 @@ class FriendsSelectionView extends StatelessWidget {
|
|||||||
children: [
|
children: [
|
||||||
CircleAvatar(
|
CircleAvatar(
|
||||||
radius: 30,
|
radius: 30,
|
||||||
backgroundImage: NetworkImage(chatUserState.chatUser.image),
|
backgroundImage: NetworkImage(
|
||||||
|
chatUserState.chatUser.image),
|
||||||
),
|
),
|
||||||
Text(chatUserState.chatUser.name),
|
Text(chatUserState.chatUser.name),
|
||||||
],
|
],
|
||||||
@@ -141,11 +152,14 @@ class FriendsSelectionView extends StatelessWidget {
|
|||||||
bottom: 40,
|
bottom: 40,
|
||||||
right: -4,
|
right: -4,
|
||||||
child: InkWell(
|
child: InkWell(
|
||||||
onTap: () => context.read<FriendsSelectionCubit>().selectUser(chatUserState),
|
onTap: () => context
|
||||||
|
.read<FriendsSelectionCubit>()
|
||||||
|
.selectUser(chatUserState),
|
||||||
child: CircleAvatar(
|
child: CircleAvatar(
|
||||||
radius: 9,
|
radius: 9,
|
||||||
backgroundColor: accentColor,
|
backgroundColor: accentColor,
|
||||||
child: Icon(Icons.close_rounded, size: 12),
|
child: Icon(Icons.close_rounded,
|
||||||
|
size: 12),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -163,7 +177,8 @@ class FriendsSelectionView extends StatelessWidget {
|
|||||||
_createFriendChannel(context, chatUserState);
|
_createFriendChannel(context, chatUserState);
|
||||||
},
|
},
|
||||||
leading: CircleAvatar(
|
leading: CircleAvatar(
|
||||||
backgroundImage: NetworkImage(chatUserState.chatUser.image),
|
backgroundImage:
|
||||||
|
NetworkImage(chatUserState.chatUser.image),
|
||||||
),
|
),
|
||||||
title: Text(chatUserState.chatUser.name),
|
title: Text(chatUserState.chatUser.name),
|
||||||
trailing: isGroup
|
trailing: isGroup
|
||||||
@@ -171,7 +186,9 @@ class FriendsSelectionView extends StatelessWidget {
|
|||||||
value: chatUserState.selected,
|
value: chatUserState.selected,
|
||||||
onChanged: (val) {
|
onChanged: (val) {
|
||||||
print('select user for group');
|
print('select user for group');
|
||||||
context.read<FriendsSelectionCubit>().selectUser(chatUserState);
|
context
|
||||||
|
.read<FriendsSelectionCubit>()
|
||||||
|
.selectUser(chatUserState);
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
: null,
|
: null,
|
||||||
|
|||||||
@@ -21,7 +21,8 @@ class GroupSelectionView extends StatelessWidget {
|
|||||||
context.read(),
|
context.read(),
|
||||||
context.read(),
|
context.read(),
|
||||||
),
|
),
|
||||||
child: BlocConsumer<GroupSelectionCubit, GroupSelectionState>(listener: (context, snapshot) {
|
child: BlocConsumer<GroupSelectionCubit, GroupSelectionState>(
|
||||||
|
listener: (context, snapshot) {
|
||||||
if (snapshot.channel != null) {
|
if (snapshot.channel != null) {
|
||||||
pushAndReplaceToPage(
|
pushAndReplaceToPage(
|
||||||
context,
|
context,
|
||||||
@@ -76,9 +77,12 @@ class GroupSelectionView extends StatelessWidget {
|
|||||||
vertical: 20,
|
vertical: 20,
|
||||||
),
|
),
|
||||||
child: TextField(
|
child: TextField(
|
||||||
controller: context.read<GroupSelectionCubit>().nameTextController,
|
controller:
|
||||||
|
context.read<GroupSelectionCubit>().nameTextController,
|
||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
fillColor: Theme.of(context).bottomNavigationBarTheme.backgroundColor,
|
fillColor: Theme.of(context)
|
||||||
|
.bottomNavigationBarTheme
|
||||||
|
.backgroundColor,
|
||||||
hintText: 'Name of the group',
|
hintText: 'Name of the group',
|
||||||
hintStyle: TextStyle(
|
hintStyle: TextStyle(
|
||||||
fontSize: 13,
|
fontSize: 13,
|
||||||
@@ -99,7 +103,8 @@ class GroupSelectionView extends StatelessWidget {
|
|||||||
children: [
|
children: [
|
||||||
CircleAvatar(
|
CircleAvatar(
|
||||||
radius: 30,
|
radius: 30,
|
||||||
backgroundImage: NetworkImage(chatUserState.chatUser.image),
|
backgroundImage:
|
||||||
|
NetworkImage(chatUserState.chatUser.image),
|
||||||
),
|
),
|
||||||
Text(chatUserState.chatUser.name),
|
Text(chatUserState.chatUser.name),
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -61,7 +61,9 @@ class HomeNavigationBar extends StatelessWidget {
|
|||||||
child: Container(
|
child: Container(
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
borderRadius: BorderRadius.circular(25),
|
borderRadius: BorderRadius.circular(25),
|
||||||
color: Theme.of(context).bottomNavigationBarTheme.backgroundColor,
|
color: Theme.of(context)
|
||||||
|
.bottomNavigationBarTheme
|
||||||
|
.backgroundColor,
|
||||||
),
|
),
|
||||||
child: Row(
|
child: Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
||||||
@@ -122,8 +124,10 @@ class _HomeNavItem extends StatelessWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final selectedColor = Theme.of(context).bottomNavigationBarTheme.selectedItemColor;
|
final selectedColor =
|
||||||
final unselectedColor = Theme.of(context).bottomNavigationBarTheme.unselectedItemColor;
|
Theme.of(context).bottomNavigationBarTheme.selectedItemColor;
|
||||||
|
final unselectedColor =
|
||||||
|
Theme.of(context).bottomNavigationBarTheme.unselectedItemColor;
|
||||||
final color = selected ? selectedColor : unselectedColor;
|
final color = selected ? selectedColor : unselectedColor;
|
||||||
return GestureDetector(
|
return GestureDetector(
|
||||||
onTap: onTap,
|
onTap: onTap,
|
||||||
|
|||||||
@@ -16,7 +16,8 @@ class SettingsView extends StatelessWidget {
|
|||||||
return MultiBlocProvider(
|
return MultiBlocProvider(
|
||||||
providers: [
|
providers: [
|
||||||
BlocProvider(
|
BlocProvider(
|
||||||
create: (_) => SettingsSwitchCubit(context.read<AppThemeCubit>().isDark),
|
create: (_) =>
|
||||||
|
SettingsSwitchCubit(context.read<AppThemeCubit>().isDark),
|
||||||
),
|
),
|
||||||
BlocProvider(
|
BlocProvider(
|
||||||
create: (_) => SettingsLogoutCubit(context.read()),
|
create: (_) => SettingsLogoutCubit(context.read()),
|
||||||
@@ -75,11 +76,14 @@ class SettingsView extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
Spacer(),
|
Spacer(),
|
||||||
BlocBuilder<SettingsSwitchCubit, bool>(builder: (context, snapshot) {
|
BlocBuilder<SettingsSwitchCubit, bool>(
|
||||||
|
builder: (context, snapshot) {
|
||||||
return Switch(
|
return Switch(
|
||||||
value: snapshot,
|
value: snapshot,
|
||||||
onChanged: (val) {
|
onChanged: (val) {
|
||||||
context.read<SettingsSwitchCubit>().onChangeDarkMode(val);
|
context
|
||||||
|
.read<SettingsSwitchCubit>()
|
||||||
|
.onChangeDarkMode(val);
|
||||||
context.read<AppThemeCubit>().updateTheme(val);
|
context.read<AppThemeCubit>().updateTheme(val);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -13,7 +13,8 @@ class ProfileVerifyView extends StatelessWidget {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return BlocProvider(
|
return BlocProvider(
|
||||||
create: (context) => ProfileVerifyCubit(context.read(), context.read()),
|
create: (context) => ProfileVerifyCubit(context.read(), context.read()),
|
||||||
child: BlocConsumer<ProfileVerifyCubit, ProfileState>(listener: (context, snapshot) {
|
child: BlocConsumer<ProfileVerifyCubit, ProfileState>(
|
||||||
|
listener: (context, snapshot) {
|
||||||
if (snapshot.success) {
|
if (snapshot.success) {
|
||||||
pushAndReplaceToPage(context, HomeView());
|
pushAndReplaceToPage(context, HomeView());
|
||||||
}
|
}
|
||||||
@@ -59,9 +60,12 @@ class ProfileVerifyView extends StatelessWidget {
|
|||||||
vertical: 20,
|
vertical: 20,
|
||||||
),
|
),
|
||||||
child: TextField(
|
child: TextField(
|
||||||
controller: context.read<ProfileVerifyCubit>().nameController,
|
controller:
|
||||||
|
context.read<ProfileVerifyCubit>().nameController,
|
||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
fillColor: Theme.of(context).bottomNavigationBarTheme.backgroundColor,
|
fillColor: Theme.of(context)
|
||||||
|
.bottomNavigationBarTheme
|
||||||
|
.backgroundColor,
|
||||||
hintText: 'Or just how people now you',
|
hintText: 'Or just how people now you',
|
||||||
hintStyle: TextStyle(
|
hintStyle: TextStyle(
|
||||||
fontSize: 13,
|
fontSize: 13,
|
||||||
|
|||||||
@@ -11,7 +11,8 @@ class SignInView extends StatelessWidget {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return BlocProvider(
|
return BlocProvider(
|
||||||
create: (context) => SignInCubit(context.read()),
|
create: (context) => SignInCubit(context.read()),
|
||||||
child: BlocConsumer<SignInCubit, SignInState>(listener: (context, snapshot) {
|
child:
|
||||||
|
BlocConsumer<SignInCubit, SignInState>(listener: (context, snapshot) {
|
||||||
if (snapshot == SignInState.none) {
|
if (snapshot == SignInState.none) {
|
||||||
pushAndReplaceToPage(context, ProfileVerifyView());
|
pushAndReplaceToPage(context, ProfileVerifyView());
|
||||||
} else {
|
} else {
|
||||||
@@ -59,7 +60,9 @@ class SignInView extends StatelessWidget {
|
|||||||
shape: RoundedRectangleBorder(
|
shape: RoundedRectangleBorder(
|
||||||
borderRadius: BorderRadius.circular(10),
|
borderRadius: BorderRadius.circular(10),
|
||||||
),
|
),
|
||||||
color: Theme.of(context).bottomNavigationBarTheme.backgroundColor,
|
color: Theme.of(context)
|
||||||
|
.bottomNavigationBarTheme
|
||||||
|
.backgroundColor,
|
||||||
child: InkWell(
|
child: InkWell(
|
||||||
onTap: () {
|
onTap: () {
|
||||||
context.read<SignInCubit>().signIn();
|
context.read<SignInCubit>().signIn();
|
||||||
|
|||||||
@@ -49,7 +49,6 @@ class CupertinoCircleAvatar extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class Divider extends StatelessWidget {
|
class Divider extends StatelessWidget {
|
||||||
const Divider({
|
const Divider({
|
||||||
Key key,
|
Key key,
|
||||||
|
|||||||
@@ -17,13 +17,13 @@ class _AdvancedOptionsPageState extends State<AdvancedOptionsPage> {
|
|||||||
final _formKey = GlobalKey<FormState>();
|
final _formKey = GlobalKey<FormState>();
|
||||||
|
|
||||||
final TextEditingController _apiKeyController = TextEditingController();
|
final TextEditingController _apiKeyController = TextEditingController();
|
||||||
String _apiKeyError;
|
String? _apiKeyError;
|
||||||
|
|
||||||
final TextEditingController _userIdController = TextEditingController();
|
final TextEditingController _userIdController = TextEditingController();
|
||||||
String _userIdError;
|
String? _userIdError;
|
||||||
|
|
||||||
final TextEditingController _userTokenController = TextEditingController();
|
final TextEditingController _userTokenController = TextEditingController();
|
||||||
String _userTokenError;
|
String? _userTokenError;
|
||||||
|
|
||||||
final TextEditingController _usernameController = TextEditingController();
|
final TextEditingController _usernameController = TextEditingController();
|
||||||
|
|
||||||
@@ -73,7 +73,7 @@ class _AdvancedOptionsPageState extends State<AdvancedOptionsPage> {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
validator: (value) {
|
validator: (value) {
|
||||||
if (value.isEmpty) {
|
if (value!.isEmpty) {
|
||||||
setState(() {
|
setState(() {
|
||||||
_apiKeyError =
|
_apiKeyError =
|
||||||
'Please enter the Chat API Key'.toUpperCase();
|
'Please enter the Chat API Key'.toUpperCase();
|
||||||
@@ -119,7 +119,7 @@ class _AdvancedOptionsPageState extends State<AdvancedOptionsPage> {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
validator: (value) {
|
validator: (value) {
|
||||||
if (value.isEmpty) {
|
if (value!.isEmpty) {
|
||||||
setState(() {
|
setState(() {
|
||||||
_userIdError =
|
_userIdError =
|
||||||
'Please enter the User ID'.toUpperCase();
|
'Please enter the User ID'.toUpperCase();
|
||||||
@@ -165,7 +165,7 @@ class _AdvancedOptionsPageState extends State<AdvancedOptionsPage> {
|
|||||||
},
|
},
|
||||||
controller: _userTokenController,
|
controller: _userTokenController,
|
||||||
validator: (value) {
|
validator: (value) {
|
||||||
if (value.isEmpty) {
|
if (value!.isEmpty) {
|
||||||
setState(() {
|
setState(() {
|
||||||
_userTokenError =
|
_userTokenError =
|
||||||
'Please enter the user token'.toUpperCase();
|
'Please enter the user token'.toUpperCase();
|
||||||
@@ -221,14 +221,22 @@ class _AdvancedOptionsPageState extends State<AdvancedOptionsPage> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
Spacer(),
|
Spacer(),
|
||||||
RaisedButton(
|
ElevatedButton(
|
||||||
color: Theme.of(context).brightness == Brightness.light
|
style: ButtonStyle(
|
||||||
? StreamChatTheme.of(context).colorTheme.accentBlue
|
backgroundColor: MaterialStateProperty.all<Color>(
|
||||||
: Colors.white,
|
Theme.of(context).brightness == Brightness.light
|
||||||
elevation: 0,
|
? StreamChatTheme.of(context)
|
||||||
padding: const EdgeInsets.symmetric(vertical: 16),
|
.colorTheme
|
||||||
shape: RoundedRectangleBorder(
|
.accentBlue
|
||||||
borderRadius: BorderRadius.circular(26),
|
: Colors.white),
|
||||||
|
elevation: MaterialStateProperty.all<double>(0),
|
||||||
|
padding: MaterialStateProperty.all<EdgeInsets>(
|
||||||
|
const EdgeInsets.symmetric(vertical: 16)),
|
||||||
|
shape: MaterialStateProperty.all(
|
||||||
|
RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(26),
|
||||||
|
),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
child: Text(
|
child: Text(
|
||||||
'Login',
|
'Login',
|
||||||
@@ -243,7 +251,7 @@ class _AdvancedOptionsPageState extends State<AdvancedOptionsPage> {
|
|||||||
if (loading) {
|
if (loading) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (_formKey.currentState.validate()) {
|
if (_formKey.currentState!.validate()) {
|
||||||
final apiKey = _apiKeyController.text;
|
final apiKey = _apiKeyController.text;
|
||||||
final userId = _userIdController.text;
|
final userId = _userIdController.text;
|
||||||
final userToken = _userTokenController.text;
|
final userToken = _userTokenController.text;
|
||||||
|
|||||||
@@ -6,16 +6,16 @@ class ChannelFileDisplayScreen extends StatefulWidget {
|
|||||||
/// Sorting is based on field and direction, multiple sorting options can be provided.
|
/// 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.
|
/// You can sort based on last_updated, last_message_at, updated_at, created_at or member_count.
|
||||||
/// Direction can be ascending or descending.
|
/// Direction can be ascending or descending.
|
||||||
final List<SortOption> sortOptions;
|
final List<SortOption>? sortOptions;
|
||||||
|
|
||||||
/// Pagination parameters
|
/// Pagination parameters
|
||||||
/// limit: the number of users to return (max is 30)
|
/// limit: the number of users to return (max is 30)
|
||||||
/// offset: the offset (max is 1000)
|
/// offset: the offset (max is 1000)
|
||||||
/// message_limit: how many messages should be included to each channel
|
/// message_limit: how many messages should be included to each channel
|
||||||
final PaginationParams paginationParams;
|
final PaginationParams? paginationParams;
|
||||||
|
|
||||||
/// The builder used when the file list is empty.
|
/// The builder used when the file list is empty.
|
||||||
final WidgetBuilder emptyBuilder;
|
final WidgetBuilder? emptyBuilder;
|
||||||
|
|
||||||
const ChannelFileDisplayScreen({
|
const ChannelFileDisplayScreen({
|
||||||
this.sortOptions,
|
this.sortOptions,
|
||||||
@@ -34,16 +34,14 @@ class _ChannelFileDisplayScreenState extends State<ChannelFileDisplayScreen> {
|
|||||||
super.initState();
|
super.initState();
|
||||||
final messageSearchBloc = MessageSearchBloc.of(context);
|
final messageSearchBloc = MessageSearchBloc.of(context);
|
||||||
messageSearchBloc.search(
|
messageSearchBloc.search(
|
||||||
filter: {
|
filter: Filter.in_(
|
||||||
'cid': {
|
'cid',
|
||||||
r'$in': [StreamChannel.of(context).channel.cid]
|
[StreamChannel.of(context).channel.cid!],
|
||||||
}
|
),
|
||||||
},
|
messageFilter: Filter.in_(
|
||||||
messageFilter: {
|
'attachments.type',
|
||||||
'attachments.type': {
|
['file'],
|
||||||
r'$in': ['file'],
|
),
|
||||||
},
|
|
||||||
},
|
|
||||||
sort: widget.sortOptions,
|
sort: widget.sortOptions,
|
||||||
pagination: widget.paginationParams,
|
pagination: widget.paginationParams,
|
||||||
);
|
);
|
||||||
@@ -95,9 +93,9 @@ class _ChannelFileDisplayScreenState extends State<ChannelFileDisplayScreen> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (snapshot.data.isEmpty) {
|
if (snapshot.data!.isEmpty) {
|
||||||
if (widget.emptyBuilder != null) {
|
if (widget.emptyBuilder != null) {
|
||||||
return widget.emptyBuilder(context);
|
return widget.emptyBuilder!(context);
|
||||||
}
|
}
|
||||||
return Center(
|
return Center(
|
||||||
child: Column(
|
child: Column(
|
||||||
@@ -134,7 +132,7 @@ class _ChannelFileDisplayScreenState extends State<ChannelFileDisplayScreen> {
|
|||||||
|
|
||||||
final media = <Attachment, Message>{};
|
final media = <Attachment, Message>{};
|
||||||
|
|
||||||
for (var item in snapshot.data) {
|
for (var item in snapshot.data!) {
|
||||||
item.message.attachments.where((e) => e.type == 'file').forEach((e) {
|
item.message.attachments.where((e) => e.type == 'file').forEach((e) {
|
||||||
media[e] = item.message;
|
media[e] = item.message;
|
||||||
});
|
});
|
||||||
@@ -142,18 +140,16 @@ class _ChannelFileDisplayScreenState extends State<ChannelFileDisplayScreen> {
|
|||||||
|
|
||||||
return LazyLoadScrollView(
|
return LazyLoadScrollView(
|
||||||
onEndOfPage: () => messageSearchBloc.search(
|
onEndOfPage: () => messageSearchBloc.search(
|
||||||
filter: {
|
filter: Filter.in_(
|
||||||
'cid': {
|
'cid',
|
||||||
r'$in': [StreamChannel.of(context).channel.cid]
|
[StreamChannel.of(context).channel.cid!],
|
||||||
}
|
),
|
||||||
},
|
messageFilter: Filter.in_(
|
||||||
messageFilter: {
|
'attachments.type',
|
||||||
'attachments.type': {
|
['file'],
|
||||||
r'$in': ['file']
|
),
|
||||||
},
|
|
||||||
},
|
|
||||||
sort: widget.sortOptions,
|
sort: widget.sortOptions,
|
||||||
pagination: widget.paginationParams.copyWith(
|
pagination: widget.paginationParams!.copyWith(
|
||||||
offset: messageSearchBloc.messageResponses?.length ?? 0,
|
offset: messageSearchBloc.messageResponses?.length ?? 0,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -7,20 +7,23 @@ class ChannelMediaDisplayScreen extends StatefulWidget {
|
|||||||
/// Sorting is based on field and direction, multiple sorting options can be provided.
|
/// 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.
|
/// You can sort based on last_updated, last_message_at, updated_at, created_at or member_count.
|
||||||
/// Direction can be ascending or descending.
|
/// Direction can be ascending or descending.
|
||||||
final List<SortOption> sortOptions;
|
final List<SortOption>? sortOptions;
|
||||||
|
|
||||||
/// Pagination parameters
|
/// Pagination parameters
|
||||||
/// limit: the number of users to return (max is 30)
|
/// limit: the number of users to return (max is 30)
|
||||||
/// offset: the offset (max is 1000)
|
/// offset: the offset (max is 1000)
|
||||||
/// message_limit: how many messages should be included to each channel
|
/// message_limit: how many messages should be included to each channel
|
||||||
final PaginationParams paginationParams;
|
final PaginationParams? paginationParams;
|
||||||
|
|
||||||
/// The builder used when the file list is empty.
|
/// The builder used when the file list is empty.
|
||||||
final WidgetBuilder emptyBuilder;
|
final WidgetBuilder? emptyBuilder;
|
||||||
|
|
||||||
final ShowMessageCallback onShowMessage;
|
final ShowMessageCallback? onShowMessage;
|
||||||
|
|
||||||
|
final MessageTheme messageTheme;
|
||||||
|
|
||||||
const ChannelMediaDisplayScreen({
|
const ChannelMediaDisplayScreen({
|
||||||
|
required this.messageTheme,
|
||||||
this.sortOptions,
|
this.sortOptions,
|
||||||
this.paginationParams,
|
this.paginationParams,
|
||||||
this.emptyBuilder,
|
this.emptyBuilder,
|
||||||
@@ -33,23 +36,21 @@ class ChannelMediaDisplayScreen extends StatefulWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class _ChannelMediaDisplayScreenState extends State<ChannelMediaDisplayScreen> {
|
class _ChannelMediaDisplayScreenState extends State<ChannelMediaDisplayScreen> {
|
||||||
Map<String, VideoPlayerController> controllerCache = {};
|
Map<String?, VideoPlayerController?> controllerCache = {};
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
final messageSearchBloc = MessageSearchBloc.of(context);
|
final messageSearchBloc = MessageSearchBloc.of(context);
|
||||||
messageSearchBloc.search(
|
messageSearchBloc.search(
|
||||||
filter: {
|
filter: Filter.in_(
|
||||||
'cid': {
|
'cid',
|
||||||
r'$in': [StreamChannel.of(context).channel.cid],
|
[StreamChannel.of(context).channel.cid!],
|
||||||
}
|
),
|
||||||
},
|
messageFilter: Filter.in_(
|
||||||
messageFilter: {
|
'attachments.type',
|
||||||
'attachments.type': {
|
['image', 'video'],
|
||||||
r'$in': ['image', 'video']
|
),
|
||||||
},
|
|
||||||
},
|
|
||||||
sort: widget.sortOptions,
|
sort: widget.sortOptions,
|
||||||
pagination: widget.paginationParams,
|
pagination: widget.paginationParams,
|
||||||
);
|
);
|
||||||
@@ -102,9 +103,9 @@ class _ChannelMediaDisplayScreenState extends State<ChannelMediaDisplayScreen> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (snapshot.data.isEmpty) {
|
if (snapshot.data!.isEmpty) {
|
||||||
if (widget.emptyBuilder != null) {
|
if (widget.emptyBuilder != null) {
|
||||||
return widget.emptyBuilder(context);
|
return widget.emptyBuilder!(context);
|
||||||
}
|
}
|
||||||
return Center(
|
return Center(
|
||||||
child: Column(
|
child: Column(
|
||||||
@@ -141,18 +142,18 @@ class _ChannelMediaDisplayScreenState extends State<ChannelMediaDisplayScreen> {
|
|||||||
|
|
||||||
final media = <_AssetPackage>[];
|
final media = <_AssetPackage>[];
|
||||||
|
|
||||||
for (var item in snapshot.data) {
|
for (var item in snapshot.data!) {
|
||||||
item.message.attachments
|
item.message.attachments
|
||||||
.where((e) =>
|
.where((e) =>
|
||||||
(e.type == 'image' || e.type == 'video') &&
|
(e.type == 'image' || e.type == 'video') &&
|
||||||
e.ogScrapeUrl == null)
|
e.ogScrapeUrl == null)
|
||||||
.forEach((e) {
|
.forEach((e) {
|
||||||
VideoPlayerController controller;
|
VideoPlayerController? controller;
|
||||||
if (e.type == 'video') {
|
if (e.type == 'video') {
|
||||||
var cachedController = controllerCache[e.assetUrl];
|
var cachedController = controllerCache[e.assetUrl];
|
||||||
|
|
||||||
if (cachedController == null) {
|
if (cachedController == null) {
|
||||||
controller = VideoPlayerController.network(e.assetUrl);
|
controller = VideoPlayerController.network(e.assetUrl!);
|
||||||
controller.initialize();
|
controller.initialize();
|
||||||
controllerCache[e.assetUrl] = controller;
|
controllerCache[e.assetUrl] = controller;
|
||||||
} else {
|
} else {
|
||||||
@@ -165,18 +166,16 @@ class _ChannelMediaDisplayScreenState extends State<ChannelMediaDisplayScreen> {
|
|||||||
|
|
||||||
return LazyLoadScrollView(
|
return LazyLoadScrollView(
|
||||||
onEndOfPage: () => messageSearchBloc.search(
|
onEndOfPage: () => messageSearchBloc.search(
|
||||||
filter: {
|
filter: Filter.in_(
|
||||||
'cid': {
|
'cid',
|
||||||
r'$in': [StreamChannel.of(context).channel.cid]
|
[StreamChannel.of(context).channel.cid!],
|
||||||
}
|
),
|
||||||
},
|
messageFilter: Filter.in_(
|
||||||
messageFilter: {
|
'attachments.type',
|
||||||
'attachments.type': {
|
['image', 'video'],
|
||||||
r'$in': ['image', 'video']
|
),
|
||||||
},
|
|
||||||
},
|
|
||||||
sort: widget.sortOptions,
|
sort: widget.sortOptions,
|
||||||
pagination: widget.paginationParams.copyWith(
|
pagination: widget.paginationParams!.copyWith(
|
||||||
offset: messageSearchBloc.messageResponses?.length ?? 0,
|
offset: messageSearchBloc.messageResponses?.length ?? 0,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -199,8 +198,7 @@ class _ChannelMediaDisplayScreenState extends State<ChannelMediaDisplayScreen> {
|
|||||||
media.map((e) => e.attachment).toList(),
|
media.map((e) => e.attachment).toList(),
|
||||||
startIndex: position,
|
startIndex: position,
|
||||||
message: media[position].message,
|
message: media[position].message,
|
||||||
sentAt: media[position].message.createdAt,
|
userName: media[position].message.user!.name,
|
||||||
userName: media[position].message.user.name,
|
|
||||||
onShowMessage: widget.onShowMessage,
|
onShowMessage: widget.onShowMessage,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -217,9 +215,10 @@ class _ChannelMediaDisplayScreenState extends State<ChannelMediaDisplayScreen> {
|
|||||||
MediaQuery.of(context).size.width * 0.8,
|
MediaQuery.of(context).size.width * 0.8,
|
||||||
MediaQuery.of(context).size.height * 0.3,
|
MediaQuery.of(context).size.height * 0.3,
|
||||||
),
|
),
|
||||||
|
messageTheme: widget.messageTheme,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
: VideoPlayer(media[position].videoPlayer),
|
: VideoPlayer(media[position].videoPlayer!),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
@@ -235,7 +234,7 @@ class _ChannelMediaDisplayScreenState extends State<ChannelMediaDisplayScreen> {
|
|||||||
void dispose() {
|
void dispose() {
|
||||||
super.dispose();
|
super.dispose();
|
||||||
for (var c in controllerCache.values) {
|
for (var c in controllerCache.values) {
|
||||||
c.dispose();
|
c!.dispose();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -243,7 +242,7 @@ class _ChannelMediaDisplayScreenState extends State<ChannelMediaDisplayScreen> {
|
|||||||
class _AssetPackage {
|
class _AssetPackage {
|
||||||
Attachment attachment;
|
Attachment attachment;
|
||||||
Message message;
|
Message message;
|
||||||
VideoPlayerController videoPlayer;
|
VideoPlayerController? videoPlayer;
|
||||||
|
|
||||||
_AssetPackage(this.attachment, this.message, this.videoPlayer);
|
_AssetPackage(this.attachment, this.message, this.videoPlayer);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import 'package:collection/collection.dart' show IterableExtension;
|
||||||
import 'package:flutter/cupertino.dart';
|
import 'package:flutter/cupertino.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:jiffy/jiffy.dart';
|
import 'package:jiffy/jiffy.dart';
|
||||||
@@ -11,16 +12,22 @@ import 'routes/routes.dart';
|
|||||||
/// Detail screen for a 1:1 chat correspondence
|
/// Detail screen for a 1:1 chat correspondence
|
||||||
class ChatInfoScreen extends StatefulWidget {
|
class ChatInfoScreen extends StatefulWidget {
|
||||||
/// User in consideration
|
/// User in consideration
|
||||||
final User user;
|
final User? user;
|
||||||
|
|
||||||
const ChatInfoScreen({Key key, this.user}) : super(key: key);
|
final MessageTheme messageTheme;
|
||||||
|
|
||||||
|
const ChatInfoScreen({
|
||||||
|
Key? key,
|
||||||
|
required this.messageTheme,
|
||||||
|
this.user,
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
_ChatInfoScreenState createState() => _ChatInfoScreenState();
|
_ChatInfoScreenState createState() => _ChatInfoScreenState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _ChatInfoScreenState extends State<ChatInfoScreen> {
|
class _ChatInfoScreenState extends State<ChatInfoScreen> {
|
||||||
ValueNotifier<bool> mutedBool = ValueNotifier(false);
|
ValueNotifier<bool?> mutedBool = ValueNotifier(false);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
@@ -48,9 +55,9 @@ class _ChatInfoScreenState extends State<ChatInfoScreen> {
|
|||||||
if ([
|
if ([
|
||||||
'admin',
|
'admin',
|
||||||
'owner',
|
'owner',
|
||||||
].contains(channel.state.members
|
].contains(channel.state!.members
|
||||||
.firstWhere((m) => m.userId == channel.client.state.user.id,
|
.firstWhereOrNull(
|
||||||
orElse: () => null)
|
(m) => m.userId == channel.client.state.user!.id)
|
||||||
?.role))
|
?.role))
|
||||||
_buildDeleteListTile(),
|
_buildDeleteListTile(),
|
||||||
],
|
],
|
||||||
@@ -70,7 +77,7 @@ class _ChatInfoScreenState extends State<ChatInfoScreen> {
|
|||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.all(16.0),
|
padding: const EdgeInsets.all(16.0),
|
||||||
child: UserAvatar(
|
child: UserAvatar(
|
||||||
user: widget.user,
|
user: widget.user!,
|
||||||
constraints: BoxConstraints(
|
constraints: BoxConstraints(
|
||||||
maxWidth: 72.0,
|
maxWidth: 72.0,
|
||||||
maxHeight: 72.0,
|
maxHeight: 72.0,
|
||||||
@@ -80,19 +87,19 @@ class _ChatInfoScreenState extends State<ChatInfoScreen> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
Text(
|
Text(
|
||||||
widget.user.name,
|
widget.user!.name,
|
||||||
style: TextStyle(fontSize: 16.0, fontWeight: FontWeight.bold),
|
style: TextStyle(fontSize: 16.0, fontWeight: FontWeight.bold),
|
||||||
),
|
),
|
||||||
SizedBox(height: 7.0),
|
SizedBox(height: 7.0),
|
||||||
_buildConnectedTitleState(),
|
_buildConnectedTitleState(),
|
||||||
SizedBox(height: 15.0),
|
SizedBox(height: 15.0),
|
||||||
OptionListTile(
|
OptionListTile(
|
||||||
title: '@${widget.user.id}',
|
title: '@${widget.user!.id}',
|
||||||
tileColor: StreamChatTheme.of(context).colorTheme.whiteSnow,
|
tileColor: StreamChatTheme.of(context).colorTheme.whiteSnow,
|
||||||
trailing: Padding(
|
trailing: Padding(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 8.0),
|
padding: const EdgeInsets.symmetric(horizontal: 8.0),
|
||||||
child: Text(
|
child: Text(
|
||||||
widget.user.name,
|
widget.user!.name,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: StreamChatTheme.of(context)
|
color: StreamChatTheme.of(context)
|
||||||
.colorTheme
|
.colorTheme
|
||||||
@@ -155,15 +162,15 @@ class _ChatInfoScreenState extends State<ChatInfoScreen> {
|
|||||||
),
|
),
|
||||||
trailing: snapshot.data == null
|
trailing: snapshot.data == null
|
||||||
? CircularProgressIndicator()
|
? CircularProgressIndicator()
|
||||||
: ValueListenableBuilder<bool>(
|
: ValueListenableBuilder<bool?>(
|
||||||
valueListenable: mutedBool,
|
valueListenable: mutedBool,
|
||||||
builder: (context, value, _) {
|
builder: (context, value, _) {
|
||||||
return CupertinoSwitch(
|
return CupertinoSwitch(
|
||||||
value: value,
|
value: value!,
|
||||||
onChanged: (val) {
|
onChanged: (val) {
|
||||||
mutedBool.value = val;
|
mutedBool.value = val;
|
||||||
|
|
||||||
if (snapshot.data) {
|
if (snapshot.data!) {
|
||||||
channel.channel.unmute();
|
channel.channel.unmute();
|
||||||
} else {
|
} else {
|
||||||
channel.channel.mute();
|
channel.channel.mute();
|
||||||
@@ -217,6 +224,7 @@ class _ChatInfoScreenState extends State<ChatInfoScreen> {
|
|||||||
channel: channel,
|
channel: channel,
|
||||||
child: MessageSearchBloc(
|
child: MessageSearchBloc(
|
||||||
child: ChannelMediaDisplayScreen(
|
child: ChannelMediaDisplayScreen(
|
||||||
|
messageTheme: widget.messageTheme,
|
||||||
sortOptions: [
|
sortOptions: [
|
||||||
SortOption(
|
SortOption(
|
||||||
'created_at',
|
'created_at',
|
||||||
@@ -387,7 +395,7 @@ class _ChatInfoScreenState extends State<ChatInfoScreen> {
|
|||||||
return Row(
|
return Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
if (widget.user.online)
|
if (widget.user!.online)
|
||||||
Material(
|
Material(
|
||||||
type: MaterialType.circle,
|
type: MaterialType.circle,
|
||||||
child: Container(
|
child: Container(
|
||||||
@@ -404,7 +412,7 @@ class _ChatInfoScreenState extends State<ChatInfoScreen> {
|
|||||||
color: StreamChatTheme.of(context).colorTheme.white,
|
color: StreamChatTheme.of(context).colorTheme.white,
|
||||||
),
|
),
|
||||||
alternativeWidget,
|
alternativeWidget,
|
||||||
if (widget.user.online)
|
if (widget.user!.online)
|
||||||
SizedBox(
|
SizedBox(
|
||||||
width: 24.0,
|
width: 24.0,
|
||||||
),
|
),
|
||||||
@@ -414,8 +422,8 @@ class _ChatInfoScreenState extends State<ChatInfoScreen> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class _SharedGroupsScreen extends StatefulWidget {
|
class _SharedGroupsScreen extends StatefulWidget {
|
||||||
final User mainUser;
|
final User? mainUser;
|
||||||
final User otherUser;
|
final User? otherUser;
|
||||||
|
|
||||||
_SharedGroupsScreen(this.mainUser, this.otherUser);
|
_SharedGroupsScreen(this.mainUser, this.otherUser);
|
||||||
|
|
||||||
@@ -445,20 +453,10 @@ class __SharedGroupsScreenState extends State<_SharedGroupsScreen> {
|
|||||||
),
|
),
|
||||||
body: StreamBuilder<List<Channel>>(
|
body: StreamBuilder<List<Channel>>(
|
||||||
stream: chat.client.queryChannels(
|
stream: chat.client.queryChannels(
|
||||||
filter: {
|
filter: Filter.and([
|
||||||
r'$and': [
|
Filter.in_('members', [widget.otherUser!.id]),
|
||||||
{
|
Filter.in_('members', [widget.mainUser!.id]),
|
||||||
'members': {
|
]),
|
||||||
r'$in': [widget.otherUser.id],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
'members': {
|
|
||||||
r'$in': [widget.mainUser.id],
|
|
||||||
},
|
|
||||||
}
|
|
||||||
],
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
builder: (context, snapshot) {
|
builder: (context, snapshot) {
|
||||||
if (!snapshot.hasData) {
|
if (!snapshot.hasData) {
|
||||||
@@ -467,7 +465,7 @@ class __SharedGroupsScreenState extends State<_SharedGroupsScreen> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (snapshot.data.isEmpty) {
|
if (snapshot.data!.isEmpty) {
|
||||||
return Center(
|
return Center(
|
||||||
child: Column(
|
child: Column(
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
@@ -501,11 +499,11 @@ class __SharedGroupsScreenState extends State<_SharedGroupsScreen> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
final channels = snapshot.data
|
final channels = snapshot.data!
|
||||||
.where((c) =>
|
.where((c) =>
|
||||||
c.state.members.any((m) =>
|
c.state!.members.any((m) =>
|
||||||
m.userId != widget.mainUser.id &&
|
m.userId != widget.mainUser!.id &&
|
||||||
m.userId != widget.otherUser.id) ||
|
m.userId != widget.otherUser!.id) ||
|
||||||
!c.isDistinct)
|
!c.isDistinct)
|
||||||
.toList();
|
.toList();
|
||||||
|
|
||||||
@@ -525,24 +523,24 @@ class __SharedGroupsScreenState extends State<_SharedGroupsScreen> {
|
|||||||
|
|
||||||
Widget _buildListTile(Channel channel) {
|
Widget _buildListTile(Channel channel) {
|
||||||
var extraData = channel.extraData;
|
var extraData = channel.extraData;
|
||||||
var members = channel.state.members;
|
var members = channel.state!.members;
|
||||||
|
|
||||||
var textStyle = TextStyle(fontSize: 14.0, fontWeight: FontWeight.bold);
|
var textStyle = TextStyle(fontSize: 14.0, fontWeight: FontWeight.bold);
|
||||||
|
|
||||||
return Container(
|
return Container(
|
||||||
height: 64.0,
|
height: 64.0,
|
||||||
child: LayoutBuilder(builder: (context, constraints) {
|
child: LayoutBuilder(builder: (context, constraints) {
|
||||||
String title;
|
String? title;
|
||||||
if (extraData['name'] == null) {
|
if (extraData['name'] == null) {
|
||||||
final otherMembers = members.where(
|
final otherMembers = members.where(
|
||||||
(member) => member.userId != StreamChat.of(context).user.id);
|
(member) => member.userId != StreamChat.of(context).user!.id);
|
||||||
if (otherMembers.isNotEmpty) {
|
if (otherMembers.isNotEmpty) {
|
||||||
final maxWidth = constraints.maxWidth;
|
final maxWidth = constraints.maxWidth;
|
||||||
final maxChars = maxWidth / textStyle.fontSize;
|
final maxChars = maxWidth / textStyle.fontSize!;
|
||||||
var currentChars = 0;
|
var currentChars = 0;
|
||||||
final currentMembers = <Member>[];
|
final currentMembers = <Member>[];
|
||||||
otherMembers.forEach((element) {
|
otherMembers.forEach((element) {
|
||||||
final newLength = currentChars + element.user.name.length;
|
final newLength = currentChars + element.user!.name.length;
|
||||||
if (newLength < maxChars) {
|
if (newLength < maxChars) {
|
||||||
currentChars = newLength;
|
currentChars = newLength;
|
||||||
currentMembers.add(element);
|
currentMembers.add(element);
|
||||||
@@ -552,7 +550,7 @@ class __SharedGroupsScreenState extends State<_SharedGroupsScreen> {
|
|||||||
final exceedingMembers =
|
final exceedingMembers =
|
||||||
otherMembers.length - currentMembers.length;
|
otherMembers.length - currentMembers.length;
|
||||||
title =
|
title =
|
||||||
'${currentMembers.map((e) => e.user.name).join(', ')} ${exceedingMembers > 0 ? '+ $exceedingMembers' : ''}';
|
'${currentMembers.map((e) => e.user!.name).join(', ')} ${exceedingMembers > 0 ? '+ $exceedingMembers' : ''}';
|
||||||
} else {
|
} else {
|
||||||
title = 'No title';
|
title = 'No title';
|
||||||
}
|
}
|
||||||
@@ -575,7 +573,7 @@ class __SharedGroupsScreenState extends State<_SharedGroupsScreen> {
|
|||||||
),
|
),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: Text(
|
child: Text(
|
||||||
title,
|
title!,
|
||||||
style: textStyle,
|
style: textStyle,
|
||||||
)),
|
)),
|
||||||
Padding(
|
Padding(
|
||||||
|
|||||||
@@ -6,18 +6,18 @@ typedef OnChipAdded<T> = void Function(T chip);
|
|||||||
typedef OnChipRemoved<T> = void Function(T chip);
|
typedef OnChipRemoved<T> = void Function(T chip);
|
||||||
|
|
||||||
class ChipsInputTextField<T> extends StatefulWidget {
|
class ChipsInputTextField<T> extends StatefulWidget {
|
||||||
final TextEditingController controller;
|
final TextEditingController? controller;
|
||||||
final FocusNode focusNode;
|
final FocusNode? focusNode;
|
||||||
final ValueChanged<String> onInputChanged;
|
final ValueChanged<String>? onInputChanged;
|
||||||
final ChipBuilder<T> chipBuilder;
|
final ChipBuilder<T> chipBuilder;
|
||||||
final OnChipAdded<T> onChipAdded;
|
final OnChipAdded<T>? onChipAdded;
|
||||||
final OnChipRemoved<T> onChipRemoved;
|
final OnChipRemoved<T>? onChipRemoved;
|
||||||
final String hint;
|
final String hint;
|
||||||
|
|
||||||
const ChipsInputTextField({
|
const ChipsInputTextField({
|
||||||
Key key,
|
Key? key,
|
||||||
@required this.chipBuilder,
|
required this.chipBuilder,
|
||||||
@required this.controller,
|
required this.controller,
|
||||||
this.onInputChanged,
|
this.onInputChanged,
|
||||||
this.focusNode,
|
this.focusNode,
|
||||||
this.onChipAdded,
|
this.onChipAdded,
|
||||||
@@ -35,7 +35,7 @@ class ChipInputTextFieldState<T> extends State<ChipsInputTextField<T>> {
|
|||||||
|
|
||||||
void addItem(T item) {
|
void addItem(T item) {
|
||||||
setState(() => _chips.add(item));
|
setState(() => _chips.add(item));
|
||||||
if (widget.onChipAdded != null) widget.onChipAdded(item);
|
if (widget.onChipAdded != null) widget.onChipAdded!(item);
|
||||||
}
|
}
|
||||||
|
|
||||||
void removeItem(T item) {
|
void removeItem(T item) {
|
||||||
@@ -43,7 +43,7 @@ class ChipInputTextFieldState<T> extends State<ChipsInputTextField<T>> {
|
|||||||
_chips.remove(item);
|
_chips.remove(item);
|
||||||
if (_chips.isEmpty) resumeItemAddition();
|
if (_chips.isEmpty) resumeItemAddition();
|
||||||
});
|
});
|
||||||
if (widget.onChipRemoved != null) widget.onChipRemoved(item);
|
if (widget.onChipRemoved != null) widget.onChipRemoved!(item);
|
||||||
}
|
}
|
||||||
|
|
||||||
void pauseItemAddition() {
|
void pauseItemAddition() {
|
||||||
|
|||||||
@@ -6,11 +6,11 @@ import 'main.dart';
|
|||||||
import 'routes/routes.dart';
|
import 'routes/routes.dart';
|
||||||
|
|
||||||
class GroupChatDetailsScreen extends StatefulWidget {
|
class GroupChatDetailsScreen extends StatefulWidget {
|
||||||
final List<User> selectedUsers;
|
final List<User>? selectedUsers;
|
||||||
|
|
||||||
const GroupChatDetailsScreen({
|
const GroupChatDetailsScreen({
|
||||||
Key key,
|
Key? key,
|
||||||
@required this.selectedUsers,
|
required this.selectedUsers,
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -20,14 +20,14 @@ class GroupChatDetailsScreen extends StatefulWidget {
|
|||||||
class _GroupChatDetailsScreenState extends State<GroupChatDetailsScreen> {
|
class _GroupChatDetailsScreenState extends State<GroupChatDetailsScreen> {
|
||||||
final _selectedUsers = <User>[];
|
final _selectedUsers = <User>[];
|
||||||
|
|
||||||
TextEditingController _groupNameController;
|
TextEditingController? _groupNameController;
|
||||||
|
|
||||||
bool _isGroupNameEmpty = true;
|
bool _isGroupNameEmpty = true;
|
||||||
|
|
||||||
int get _totalUsers => _selectedUsers.length;
|
int get _totalUsers => _selectedUsers.length;
|
||||||
|
|
||||||
void _groupNameListener() {
|
void _groupNameListener() {
|
||||||
final name = _groupNameController.text;
|
final name = _groupNameController!.text;
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
setState(() {
|
setState(() {
|
||||||
_isGroupNameEmpty = name.isEmpty;
|
_isGroupNameEmpty = name.isEmpty;
|
||||||
@@ -38,7 +38,7 @@ class _GroupChatDetailsScreenState extends State<GroupChatDetailsScreen> {
|
|||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
_selectedUsers.addAll(widget.selectedUsers);
|
_selectedUsers.addAll(widget.selectedUsers!);
|
||||||
_groupNameController = TextEditingController()
|
_groupNameController = TextEditingController()
|
||||||
..addListener(_groupNameListener);
|
..addListener(_groupNameListener);
|
||||||
}
|
}
|
||||||
@@ -124,13 +124,13 @@ class _GroupChatDetailsScreenState extends State<GroupChatDetailsScreen> {
|
|||||||
? null
|
? null
|
||||||
: () async {
|
: () async {
|
||||||
try {
|
try {
|
||||||
final groupName = _groupNameController.text;
|
final groupName = _groupNameController!.text;
|
||||||
final client = StreamChat.of(context).client;
|
final client = StreamChat.of(context).client;
|
||||||
final channel = client.channel('messaging',
|
final channel = client.channel('messaging',
|
||||||
id: Uuid().v4(),
|
id: Uuid().v4(),
|
||||||
extraData: {
|
extraData: {
|
||||||
'members': [
|
'members': [
|
||||||
client.state.user.id,
|
client.state.user!.id,
|
||||||
..._selectedUsers.map((e) => e.id),
|
..._selectedUsers.map((e) => e.id),
|
||||||
],
|
],
|
||||||
'name': groupName,
|
'name': groupName,
|
||||||
@@ -306,7 +306,7 @@ class _GroupChatDetailsScreenState extends State<GroupChatDetailsScreen> {
|
|||||||
Row(
|
Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
FlatButton(
|
TextButton(
|
||||||
child: Text(
|
child: Text(
|
||||||
'OK',
|
'OK',
|
||||||
style: StreamChatTheme.of(context)
|
style: StreamChatTheme.of(context)
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
|
||||||
|
import 'package:collection/collection.dart' show IterableExtension;
|
||||||
import 'package:flutter/cupertino.dart';
|
import 'package:flutter/cupertino.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:jiffy/jiffy.dart';
|
import 'package:jiffy/jiffy.dart';
|
||||||
@@ -14,34 +15,41 @@ import 'main.dart';
|
|||||||
import 'routes/routes.dart';
|
import 'routes/routes.dart';
|
||||||
|
|
||||||
class GroupInfoScreen extends StatefulWidget {
|
class GroupInfoScreen extends StatefulWidget {
|
||||||
|
final MessageTheme messageTheme;
|
||||||
|
|
||||||
|
const GroupInfoScreen({
|
||||||
|
Key? key,
|
||||||
|
required this.messageTheme,
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
_GroupInfoScreenState createState() => _GroupInfoScreenState();
|
_GroupInfoScreenState createState() => _GroupInfoScreenState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _GroupInfoScreenState extends State<GroupInfoScreen> {
|
class _GroupInfoScreenState extends State<GroupInfoScreen> {
|
||||||
TextEditingController _nameController;
|
TextEditingController? _nameController;
|
||||||
|
|
||||||
TextEditingController _searchController;
|
TextEditingController? _searchController;
|
||||||
String _userNameQuery = '';
|
String _userNameQuery = '';
|
||||||
|
|
||||||
Timer _debounce;
|
Timer? _debounce;
|
||||||
Function modalSetStateCallback;
|
Function? modalSetStateCallback;
|
||||||
|
|
||||||
final FocusNode _focusNode = FocusNode();
|
final FocusNode _focusNode = FocusNode();
|
||||||
|
|
||||||
bool listExpanded = false;
|
bool listExpanded = false;
|
||||||
|
|
||||||
ValueNotifier<bool> mutedBool = ValueNotifier(false);
|
ValueNotifier<bool?> mutedBool = ValueNotifier(false);
|
||||||
|
|
||||||
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 != null) {
|
||||||
modalSetStateCallback(() {
|
modalSetStateCallback!(() {
|
||||||
_userNameQuery = _searchController.text;
|
_userNameQuery = _searchController!.text;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -55,7 +63,7 @@ class _GroupInfoScreenState extends State<GroupInfoScreen> {
|
|||||||
TextEditingValue(text: channel.channel.extraData['name'] ?? ''));
|
TextEditingValue(text: channel.channel.extraData['name'] ?? ''));
|
||||||
_searchController = TextEditingController()..addListener(_userNameListener);
|
_searchController = TextEditingController()..addListener(_userNameListener);
|
||||||
|
|
||||||
_nameController.addListener(() {
|
_nameController!.addListener(() {
|
||||||
setState(() {});
|
setState(() {});
|
||||||
});
|
});
|
||||||
mutedBool = ValueNotifier(StreamChannel.of(context).channel.isMuted);
|
mutedBool = ValueNotifier(StreamChannel.of(context).channel.isMuted);
|
||||||
@@ -66,7 +74,7 @@ class _GroupInfoScreenState extends State<GroupInfoScreen> {
|
|||||||
var channel = StreamChannel.of(context);
|
var channel = StreamChannel.of(context);
|
||||||
|
|
||||||
return StreamBuilder<List<Member>>(
|
return StreamBuilder<List<Member>>(
|
||||||
stream: channel.channel.state.membersStream,
|
stream: channel.channel.state!.membersStream,
|
||||||
builder: (context, snapshot) {
|
builder: (context, snapshot) {
|
||||||
if (!snapshot.hasData) {
|
if (!snapshot.hasData) {
|
||||||
return Container(
|
return Container(
|
||||||
@@ -75,9 +83,8 @@ class _GroupInfoScreenState extends State<GroupInfoScreen> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
var userMember = snapshot.data.firstWhere(
|
var userMember = snapshot.data!.firstWhereOrNull(
|
||||||
(e) => e.user.id == StreamChat.of(context).user.id,
|
(e) => e.user!.id == StreamChat.of(context).user!.id,
|
||||||
orElse: () => null,
|
|
||||||
);
|
);
|
||||||
var isOwner = userMember?.role == 'owner';
|
var isOwner = userMember?.role == 'owner';
|
||||||
|
|
||||||
@@ -111,9 +118,9 @@ class _GroupInfoScreenState extends State<GroupInfoScreen> {
|
|||||||
_getChannelName(
|
_getChannelName(
|
||||||
2 * MediaQuery.of(context).size.width / 3,
|
2 * MediaQuery.of(context).size.width / 3,
|
||||||
members: snapshot.data,
|
members: snapshot.data,
|
||||||
extraData: state.data.channel.extraData,
|
extraData: state.data!.channel!.extraData,
|
||||||
maxFontSize: 16.0,
|
maxFontSize: 16.0,
|
||||||
),
|
)!,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: StreamChatTheme.of(context).colorTheme.black,
|
color: StreamChatTheme.of(context).colorTheme.black,
|
||||||
fontSize: 16,
|
fontSize: 16,
|
||||||
@@ -126,7 +133,7 @@ class _GroupInfoScreenState extends State<GroupInfoScreen> {
|
|||||||
height: 3.0,
|
height: 3.0,
|
||||||
),
|
),
|
||||||
Text(
|
Text(
|
||||||
'${channel.channel.memberCount} Members, ${snapshot?.data?.where((e) => e.user.online)?.length ?? 0} Online',
|
'${channel.channel.memberCount} Members, ${snapshot.data?.where((e) => e.user!.online).length ?? 0} Online',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: StreamChatTheme.of(context)
|
color: StreamChatTheme.of(context)
|
||||||
.colorTheme
|
.colorTheme
|
||||||
@@ -158,7 +165,7 @@ class _GroupInfoScreenState extends State<GroupInfoScreen> {
|
|||||||
),
|
),
|
||||||
body: ListView(
|
body: ListView(
|
||||||
children: [
|
children: [
|
||||||
_buildMembers(snapshot.data),
|
_buildMembers(snapshot.data!),
|
||||||
Container(
|
Container(
|
||||||
height: 8.0,
|
height: 8.0,
|
||||||
color: StreamChatTheme.of(context).colorTheme.greyGainsboro,
|
color: StreamChatTheme.of(context).colorTheme.greyGainsboro,
|
||||||
@@ -197,9 +204,8 @@ class _GroupInfoScreenState extends State<GroupInfoScreen> {
|
|||||||
return Material(
|
return Material(
|
||||||
child: InkWell(
|
child: InkWell(
|
||||||
onTap: () {
|
onTap: () {
|
||||||
final userMember = groupMembers.firstWhere(
|
final userMember = groupMembers.firstWhereOrNull(
|
||||||
(e) => e.user.id == StreamChat.of(context).user.id,
|
(e) => e.user!.id == StreamChat.of(context).user!.id,
|
||||||
orElse: () => null,
|
|
||||||
);
|
);
|
||||||
_showUserInfoModal(member.user, userMember?.role == 'owner');
|
_showUserInfoModal(member.user, userMember?.role == 'owner');
|
||||||
},
|
},
|
||||||
@@ -213,7 +219,7 @@ class _GroupInfoScreenState extends State<GroupInfoScreen> {
|
|||||||
padding: const EdgeInsets.symmetric(
|
padding: const EdgeInsets.symmetric(
|
||||||
horizontal: 8.0, vertical: 12.0),
|
horizontal: 8.0, vertical: 12.0),
|
||||||
child: UserAvatar(
|
child: UserAvatar(
|
||||||
user: member.user,
|
user: member.user!,
|
||||||
constraints: BoxConstraints(
|
constraints: BoxConstraints(
|
||||||
maxHeight: 40.0, maxWidth: 40.0),
|
maxHeight: 40.0, maxWidth: 40.0),
|
||||||
),
|
),
|
||||||
@@ -224,14 +230,14 @@ class _GroupInfoScreenState extends State<GroupInfoScreen> {
|
|||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
Text(
|
Text(
|
||||||
member.user.name,
|
member.user!.name,
|
||||||
style: TextStyle(fontWeight: FontWeight.bold),
|
style: TextStyle(fontWeight: FontWeight.bold),
|
||||||
),
|
),
|
||||||
SizedBox(
|
SizedBox(
|
||||||
height: 1.0,
|
height: 1.0,
|
||||||
),
|
),
|
||||||
Text(
|
Text(
|
||||||
_getLastSeen(member.user),
|
_getLastSeen(member.user!),
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: StreamChatTheme.of(context)
|
color: StreamChatTheme.of(context)
|
||||||
.colorTheme
|
.colorTheme
|
||||||
@@ -371,7 +377,7 @@ class _GroupInfoScreenState extends State<GroupInfoScreen> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
if ((channelName == null) ||
|
if ((channelName == null) ||
|
||||||
(channelName != _nameController.text.trim()))
|
(channelName != _nameController!.text.trim()))
|
||||||
Row(
|
Row(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
@@ -379,12 +385,12 @@ class _GroupInfoScreenState extends State<GroupInfoScreen> {
|
|||||||
child: StreamSvgIcon.closeSmall(),
|
child: StreamSvgIcon.closeSmall(),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
setState(() {
|
setState(() {
|
||||||
_nameController.text = _getChannelName(
|
_nameController!.text = _getChannelName(
|
||||||
2 * MediaQuery.of(context).size.width / 3,
|
2 * MediaQuery.of(context).size.width / 3,
|
||||||
members: channel.state.members,
|
members: channel.state!.members,
|
||||||
extraData: channel.extraData,
|
extraData: channel.extraData,
|
||||||
maxFontSize: 16.0,
|
maxFontSize: 16.0,
|
||||||
);
|
)!;
|
||||||
_focusNode.unfocus();
|
_focusNode.unfocus();
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
@@ -399,10 +405,10 @@ class _GroupInfoScreenState extends State<GroupInfoScreen> {
|
|||||||
),
|
),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
StreamChannel.of(context).channel.update({
|
StreamChannel.of(context).channel.update({
|
||||||
'name': _nameController.text.trim(),
|
'name': _nameController!.text.trim(),
|
||||||
}).catchError((err) {
|
}).catchError((err) {
|
||||||
setState(() {
|
setState(() {
|
||||||
_nameController.text = channelName;
|
_nameController!.text = channelName;
|
||||||
_focusNode.unfocus();
|
_focusNode.unfocus();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -457,15 +463,15 @@ class _GroupInfoScreenState extends State<GroupInfoScreen> {
|
|||||||
),
|
),
|
||||||
trailing: snapshot.data == null
|
trailing: snapshot.data == null
|
||||||
? CircularProgressIndicator()
|
? CircularProgressIndicator()
|
||||||
: ValueListenableBuilder<bool>(
|
: ValueListenableBuilder<bool?>(
|
||||||
valueListenable: mutedBool,
|
valueListenable: mutedBool,
|
||||||
builder: (context, value, _) {
|
builder: (context, value, _) {
|
||||||
return CupertinoSwitch(
|
return CupertinoSwitch(
|
||||||
value: value,
|
value: value!,
|
||||||
onChanged: (val) {
|
onChanged: (val) {
|
||||||
mutedBool.value = val;
|
mutedBool.value = val;
|
||||||
|
|
||||||
if (snapshot.data) {
|
if (snapshot.data!) {
|
||||||
channel.channel.unmute();
|
channel.channel.unmute();
|
||||||
} else {
|
} else {
|
||||||
channel.channel.mute();
|
channel.channel.mute();
|
||||||
@@ -502,6 +508,7 @@ class _GroupInfoScreenState extends State<GroupInfoScreen> {
|
|||||||
channel: channel,
|
channel: channel,
|
||||||
child: MessageSearchBloc(
|
child: MessageSearchBloc(
|
||||||
child: ChannelMediaDisplayScreen(
|
child: ChannelMediaDisplayScreen(
|
||||||
|
messageTheme: widget.messageTheme,
|
||||||
sortOptions: [
|
sortOptions: [
|
||||||
SortOption(
|
SortOption(
|
||||||
'created_at',
|
'created_at',
|
||||||
@@ -609,7 +616,7 @@ class _GroupInfoScreenState extends State<GroupInfoScreen> {
|
|||||||
);
|
);
|
||||||
if (res == true) {
|
if (res == true) {
|
||||||
final channel = StreamChannel.of(context).channel;
|
final channel = StreamChannel.of(context).channel;
|
||||||
await channel.removeMembers([StreamChat.of(context).user.id]);
|
await channel.removeMembers([StreamChat.of(context).user!.id]);
|
||||||
Navigator.pop(context);
|
Navigator.pop(context);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -647,7 +654,7 @@ class _GroupInfoScreenState extends State<GroupInfoScreen> {
|
|||||||
child: UserListView(
|
child: UserListView(
|
||||||
selectedUsers: {},
|
selectedUsers: {},
|
||||||
onUserTap: (user, _) async {
|
onUserTap: (user, _) async {
|
||||||
_searchController.clear();
|
_searchController!.clear();
|
||||||
|
|
||||||
await channel.addMembers([user.id]);
|
await channel.addMembers([user.id]);
|
||||||
Navigator.pop(context);
|
Navigator.pop(context);
|
||||||
@@ -657,18 +664,17 @@ class _GroupInfoScreenState extends State<GroupInfoScreen> {
|
|||||||
pagination: PaginationParams(
|
pagination: PaginationParams(
|
||||||
limit: 25,
|
limit: 25,
|
||||||
),
|
),
|
||||||
filter: {
|
filter: Filter.and(
|
||||||
if (_searchController.text.isNotEmpty)
|
[
|
||||||
'name': {
|
if (_searchController!.text.isNotEmpty)
|
||||||
r'$autocomplete': _userNameQuery,
|
Filter.autoComplete('name', _userNameQuery),
|
||||||
},
|
Filter.notIn('id', [
|
||||||
'id': {
|
StreamChat.of(context).user!.id,
|
||||||
r'$nin': [
|
...channel.state!.members.map(((e) => e.userId!)
|
||||||
StreamChat.of(context).user.id,
|
as Object Function(Member)),
|
||||||
...channel.state.members.map((e) => e.userId),
|
]),
|
||||||
],
|
],
|
||||||
},
|
),
|
||||||
},
|
|
||||||
sort: [
|
sort: [
|
||||||
SortOption(
|
SortOption(
|
||||||
'name',
|
'name',
|
||||||
@@ -778,7 +784,7 @@ class _GroupInfoScreenState extends State<GroupInfoScreen> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
void _showUserInfoModal(User user, bool isUserAdmin) {
|
void _showUserInfoModal(User? user, bool isUserAdmin) {
|
||||||
var channel = StreamChannel.of(context).channel;
|
var channel = StreamChannel.of(context).channel;
|
||||||
|
|
||||||
showModalBottomSheet(
|
showModalBottomSheet(
|
||||||
@@ -798,7 +804,7 @@ class _GroupInfoScreenState extends State<GroupInfoScreen> {
|
|||||||
),
|
),
|
||||||
Center(
|
Center(
|
||||||
child: Text(
|
child: Text(
|
||||||
user.name,
|
user!.name,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 16.0,
|
fontSize: 16.0,
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
@@ -808,7 +814,7 @@ class _GroupInfoScreenState extends State<GroupInfoScreen> {
|
|||||||
SizedBox(
|
SizedBox(
|
||||||
height: 5.0,
|
height: 5.0,
|
||||||
),
|
),
|
||||||
_buildConnectedTitleState(user),
|
_buildConnectedTitleState(user)!,
|
||||||
Center(
|
Center(
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.all(16.0),
|
padding: const EdgeInsets.all(16.0),
|
||||||
@@ -822,7 +828,7 @@ class _GroupInfoScreenState extends State<GroupInfoScreen> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
if (StreamChat.of(context).user.id != user.id)
|
if (StreamChat.of(context).user!.id != user.id)
|
||||||
_buildModalListTile(
|
_buildModalListTile(
|
||||||
context,
|
context,
|
||||||
StreamSvgIcon.user(
|
StreamSvgIcon.user(
|
||||||
@@ -836,7 +842,7 @@ class _GroupInfoScreenState extends State<GroupInfoScreen> {
|
|||||||
var c = client.channel('messaging', extraData: {
|
var c = client.channel('messaging', extraData: {
|
||||||
'members': [
|
'members': [
|
||||||
user.id,
|
user.id,
|
||||||
StreamChat.of(context).user.id,
|
StreamChat.of(context).user!.id,
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -848,6 +854,7 @@ class _GroupInfoScreenState extends State<GroupInfoScreen> {
|
|||||||
builder: (context) => StreamChannel(
|
builder: (context) => StreamChannel(
|
||||||
channel: c,
|
channel: c,
|
||||||
child: ChatInfoScreen(
|
child: ChatInfoScreen(
|
||||||
|
messageTheme: widget.messageTheme,
|
||||||
user: user,
|
user: user,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -855,7 +862,7 @@ class _GroupInfoScreenState extends State<GroupInfoScreen> {
|
|||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
if (StreamChat.of(context).user.id != user.id)
|
if (StreamChat.of(context).user!.id != user.id)
|
||||||
_buildModalListTile(
|
_buildModalListTile(
|
||||||
context,
|
context,
|
||||||
StreamSvgIcon.message(
|
StreamSvgIcon.message(
|
||||||
@@ -869,7 +876,7 @@ class _GroupInfoScreenState extends State<GroupInfoScreen> {
|
|||||||
var c = client.channel('messaging', extraData: {
|
var c = client.channel('messaging', extraData: {
|
||||||
'members': [
|
'members': [
|
||||||
user.id,
|
user.id,
|
||||||
StreamChat.of(context).user.id,
|
StreamChat.of(context).user!.id,
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -887,7 +894,7 @@ class _GroupInfoScreenState extends State<GroupInfoScreen> {
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
if (!channel.isDistinct &&
|
if (!channel.isDistinct &&
|
||||||
StreamChat.of(context).user.id != user.id &&
|
StreamChat.of(context).user!.id != user.id &&
|
||||||
isUserAdmin)
|
isUserAdmin)
|
||||||
_buildModalListTile(
|
_buildModalListTile(
|
||||||
context,
|
context,
|
||||||
@@ -899,7 +906,7 @@ class _GroupInfoScreenState extends State<GroupInfoScreen> {
|
|||||||
// TODO: Add make owner implementation (Remaining from backend)
|
// TODO: Add make owner implementation (Remaining from backend)
|
||||||
}),
|
}),
|
||||||
if (!channel.isDistinct &&
|
if (!channel.isDistinct &&
|
||||||
StreamChat.of(context).user.id != user.id &&
|
StreamChat.of(context).user!.id != user.id &&
|
||||||
isUserAdmin)
|
isUserAdmin)
|
||||||
_buildModalListTile(
|
_buildModalListTile(
|
||||||
context,
|
context,
|
||||||
@@ -934,7 +941,7 @@ class _GroupInfoScreenState extends State<GroupInfoScreen> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildConnectedTitleState(User user) {
|
Widget? _buildConnectedTitleState(User? user) {
|
||||||
var alternativeWidget;
|
var alternativeWidget;
|
||||||
|
|
||||||
final otherMember = user;
|
final otherMember = user;
|
||||||
@@ -966,7 +973,7 @@ class _GroupInfoScreenState extends State<GroupInfoScreen> {
|
|||||||
|
|
||||||
Widget _buildModalListTile(
|
Widget _buildModalListTile(
|
||||||
BuildContext context, Widget leading, String title, VoidCallback onTap,
|
BuildContext context, Widget leading, String title, VoidCallback onTap,
|
||||||
{Color color}) {
|
{Color? color}) {
|
||||||
color ??= StreamChatTheme.of(context).colorTheme.black;
|
color ??= StreamChatTheme.of(context).colorTheme.black;
|
||||||
|
|
||||||
return Material(
|
return Material(
|
||||||
@@ -1003,24 +1010,24 @@ class _GroupInfoScreenState extends State<GroupInfoScreen> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
String _getChannelName(
|
String? _getChannelName(
|
||||||
double width, {
|
double width, {
|
||||||
List<Member> members,
|
List<Member>? members,
|
||||||
Map extraData,
|
required Map extraData,
|
||||||
double maxFontSize,
|
double? maxFontSize,
|
||||||
}) {
|
}) {
|
||||||
String title;
|
String? title;
|
||||||
var client = StreamChat.of(context);
|
var client = StreamChat.of(context);
|
||||||
if (extraData['name'] == null) {
|
if (extraData['name'] == null) {
|
||||||
final otherMembers =
|
final otherMembers =
|
||||||
members.where((member) => member.user.id != client.user.id);
|
members!.where((member) => member.user!.id != client.user!.id);
|
||||||
if (otherMembers.isNotEmpty) {
|
if (otherMembers.isNotEmpty) {
|
||||||
final maxWidth = width;
|
final maxWidth = width;
|
||||||
final maxChars = maxWidth / maxFontSize;
|
final maxChars = maxWidth / maxFontSize!;
|
||||||
var currentChars = 0;
|
var currentChars = 0;
|
||||||
final currentMembers = <Member>[];
|
final currentMembers = <Member>[];
|
||||||
otherMembers.forEach((element) {
|
otherMembers.forEach((element) {
|
||||||
final newLength = currentChars + element.user.name.length;
|
final newLength = currentChars + element.user!.name.length;
|
||||||
if (newLength < maxChars) {
|
if (newLength < maxChars) {
|
||||||
currentChars = newLength;
|
currentChars = newLength;
|
||||||
currentMembers.add(element);
|
currentMembers.add(element);
|
||||||
@@ -1029,7 +1036,7 @@ class _GroupInfoScreenState extends State<GroupInfoScreen> {
|
|||||||
|
|
||||||
final exceedingMembers = otherMembers.length - currentMembers.length;
|
final exceedingMembers = otherMembers.length - currentMembers.length;
|
||||||
title =
|
title =
|
||||||
'${currentMembers.map((e) => e.user.name).join(', ')} ${exceedingMembers > 0 ? '+ $exceedingMembers' : ''}';
|
'${currentMembers.map((e) => e.user!.name).join(', ')} ${exceedingMembers > 0 ? '+ $exceedingMembers' : ''}';
|
||||||
} else {
|
} else {
|
||||||
title = 'No title';
|
title = 'No title';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
|
||||||
|
import 'package:collection/collection.dart' show IterableExtension;
|
||||||
import 'package:example/chat_info_screen.dart';
|
import 'package:example/chat_info_screen.dart';
|
||||||
import 'package:example/choose_user_page.dart';
|
import 'package:example/choose_user_page.dart';
|
||||||
import 'package:example/group_info_screen.dart';
|
import 'package:example/group_info_screen.dart';
|
||||||
@@ -38,15 +39,15 @@ class MyApp extends StatefulWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class _MyAppState extends State<MyApp> with TickerProviderStateMixin {
|
class _MyAppState extends State<MyApp> with TickerProviderStateMixin {
|
||||||
InitData _initData;
|
InitData? _initData;
|
||||||
bool _animCompleted = false;
|
bool _animCompleted = false;
|
||||||
Animation<double> _animation, _scaleAnimation;
|
Animation<double>? _animation, _scaleAnimation;
|
||||||
AnimationController _animationController, _scaleAnimationController;
|
AnimationController? _animationController, _scaleAnimationController;
|
||||||
Animation<Color> _colorAnimation;
|
Animation<Color?>? _colorAnimation;
|
||||||
int timeOfStartMs;
|
late int timeOfStartMs;
|
||||||
|
|
||||||
Future<InitData> _initConnection() async {
|
Future<InitData> _initConnection() async {
|
||||||
String apiKey, userId, token;
|
String? apiKey, userId, token;
|
||||||
|
|
||||||
if (!kIsWeb) {
|
if (!kIsWeb) {
|
||||||
final secureStorage = FlutterSecureStorage();
|
final secureStorage = FlutterSecureStorage();
|
||||||
@@ -57,7 +58,7 @@ class _MyAppState extends State<MyApp> with TickerProviderStateMixin {
|
|||||||
|
|
||||||
final client = StreamChatClient(
|
final client = StreamChatClient(
|
||||||
apiKey ?? kDefaultStreamApiKey,
|
apiKey ?? kDefaultStreamApiKey,
|
||||||
logLevel: Level.SEVERE,
|
logLevel: Level.INFO,
|
||||||
)..chatPersistenceClient = chatPersistentClient;
|
)..chatPersistenceClient = chatPersistentClient;
|
||||||
|
|
||||||
if (userId != null) {
|
if (userId != null) {
|
||||||
@@ -84,7 +85,7 @@ class _MyAppState extends State<MyApp> with TickerProviderStateMixin {
|
|||||||
begin: 1.0,
|
begin: 1.0,
|
||||||
end: 1.5,
|
end: 1.5,
|
||||||
).animate(CurvedAnimation(
|
).animate(CurvedAnimation(
|
||||||
parent: _scaleAnimationController,
|
parent: _scaleAnimationController!,
|
||||||
curve: Curves.easeInOutBack,
|
curve: Curves.easeInOutBack,
|
||||||
));
|
));
|
||||||
|
|
||||||
@@ -98,21 +99,21 @@ class _MyAppState extends State<MyApp> with TickerProviderStateMixin {
|
|||||||
begin: 0.0,
|
begin: 0.0,
|
||||||
end: 1000.0,
|
end: 1000.0,
|
||||||
).animate(CurvedAnimation(
|
).animate(CurvedAnimation(
|
||||||
parent: _animationController,
|
parent: _animationController!,
|
||||||
curve: Curves.easeInOut,
|
curve: Curves.easeInOut,
|
||||||
));
|
));
|
||||||
_colorAnimation = ColorTween(
|
_colorAnimation = ColorTween(
|
||||||
begin: Color(0xff005FFF),
|
begin: Color(0xff005FFF),
|
||||||
end: Color(0xff005FFF),
|
end: Color(0xff005FFF),
|
||||||
).animate(CurvedAnimation(
|
).animate(CurvedAnimation(
|
||||||
parent: _animationController,
|
parent: _animationController!,
|
||||||
curve: Curves.easeInOut,
|
curve: Curves.easeInOut,
|
||||||
));
|
));
|
||||||
_colorAnimation = ColorTween(
|
_colorAnimation = ColorTween(
|
||||||
begin: Color(0xff005FFF),
|
begin: Color(0xff005FFF),
|
||||||
end: Colors.transparent,
|
end: Colors.transparent,
|
||||||
).animate(CurvedAnimation(
|
).animate(CurvedAnimation(
|
||||||
parent: _animationController,
|
parent: _animationController!,
|
||||||
curve: Curves.easeInOut,
|
curve: Curves.easeInOut,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
@@ -132,22 +133,22 @@ class _MyAppState extends State<MyApp> with TickerProviderStateMixin {
|
|||||||
var now = DateTime.now().millisecondsSinceEpoch;
|
var now = DateTime.now().millisecondsSinceEpoch;
|
||||||
|
|
||||||
if (now - timeOfStartMs > 1500) {
|
if (now - timeOfStartMs > 1500) {
|
||||||
SchedulerBinding.instance.addPostFrameCallback((timeStamp) {
|
SchedulerBinding.instance!.addPostFrameCallback((timeStamp) {
|
||||||
_scaleAnimationController.forward().whenComplete(() {
|
_scaleAnimationController?.forward().whenComplete(() {
|
||||||
_animationController.forward();
|
_animationController?.forward();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
Future.delayed(Duration(milliseconds: 1500)).then((value) {
|
Future.delayed(Duration(milliseconds: 1500)).then((value) {
|
||||||
_scaleAnimationController.forward().whenComplete(() {
|
_scaleAnimationController?.forward().whenComplete(() {
|
||||||
_animationController.forward();
|
_animationController?.forward();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!kIsWeb) {
|
if (!kIsWeb) {
|
||||||
_initData.client.state?.totalUnreadCountStream?.listen((count) {
|
_initData!.client.state.totalUnreadCountStream.listen((count) {
|
||||||
if (count > 0) {
|
if (count! > 0) {
|
||||||
FlutterAppBadger.updateBadgeCount(count);
|
FlutterAppBadger.updateBadgeCount(count);
|
||||||
} else {
|
} else {
|
||||||
FlutterAppBadger.removeBadge();
|
FlutterAppBadger.removeBadge();
|
||||||
@@ -156,7 +157,7 @@ class _MyAppState extends State<MyApp> with TickerProviderStateMixin {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
_animationController.addStatusListener((status) {
|
_animationController?.addStatusListener((status) {
|
||||||
if (status == AnimationStatus.completed) {
|
if (status == AnimationStatus.completed) {
|
||||||
setState(() {
|
setState(() {
|
||||||
_animCompleted = true;
|
_animCompleted = true;
|
||||||
@@ -174,20 +175,20 @@ class _MyAppState extends State<MyApp> with TickerProviderStateMixin {
|
|||||||
alignment: Alignment.center,
|
alignment: Alignment.center,
|
||||||
children: [
|
children: [
|
||||||
AnimatedBuilder(
|
AnimatedBuilder(
|
||||||
animation: _scaleAnimation,
|
animation: _scaleAnimation!,
|
||||||
builder: (context, _) {
|
builder: (context, _) {
|
||||||
return Transform.scale(
|
return Transform.scale(
|
||||||
scale: _scaleAnimation.value,
|
scale: _scaleAnimation!.value,
|
||||||
child: AnimatedBuilder(
|
child: AnimatedBuilder(
|
||||||
animation: _colorAnimation,
|
animation: _colorAnimation!,
|
||||||
builder: (context, snapshot) {
|
builder: (context, snapshot) {
|
||||||
return Container(
|
return Container(
|
||||||
alignment: Alignment.center,
|
alignment: Alignment.center,
|
||||||
constraints: BoxConstraints.expand(),
|
constraints: BoxConstraints.expand(),
|
||||||
color: _colorAnimation == null
|
color: _colorAnimation == null
|
||||||
? Color(0xff005FFF)
|
? Color(0xff005FFF)
|
||||||
: _colorAnimation.value,
|
: _colorAnimation!.value,
|
||||||
child: !_animationController.isAnimating
|
child: !_animationController!.isAnimating
|
||||||
? Lottie.asset(
|
? Lottie.asset(
|
||||||
'assets/floating_boat.json',
|
'assets/floating_boat.json',
|
||||||
alignment: Alignment.center,
|
alignment: Alignment.center,
|
||||||
@@ -199,16 +200,16 @@ class _MyAppState extends State<MyApp> with TickerProviderStateMixin {
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
AnimatedBuilder(
|
AnimatedBuilder(
|
||||||
animation: _animation,
|
animation: _animation!,
|
||||||
builder: (context, snapshot) {
|
builder: (context, snapshot) {
|
||||||
return Transform.scale(
|
return Transform.scale(
|
||||||
scale: _animation.value,
|
scale: _animation!.value,
|
||||||
child: Container(
|
child: Container(
|
||||||
width: 1.0,
|
width: 1.0,
|
||||||
height: 1.0,
|
height: 1.0,
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: Colors.white
|
color: Colors.white
|
||||||
.withOpacity(1 - _animationController.value),
|
.withOpacity(1 - _animationController!.value),
|
||||||
shape: BoxShape.circle,
|
shape: BoxShape.circle,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -227,19 +228,19 @@ class _MyAppState extends State<MyApp> with TickerProviderStateMixin {
|
|||||||
children: [
|
children: [
|
||||||
if (_initData != null)
|
if (_initData != null)
|
||||||
PreferenceBuilder<int>(
|
PreferenceBuilder<int>(
|
||||||
preference: _initData.preferences.getInt(
|
preference: _initData!.preferences.getInt(
|
||||||
'theme',
|
'theme',
|
||||||
defaultValue: 0,
|
defaultValue: 0,
|
||||||
),
|
),
|
||||||
builder: (context, snapshot) => MaterialApp(
|
builder: (context, snapshot) => MaterialApp(
|
||||||
builder: (context, child) {
|
builder: (context, child) {
|
||||||
return StreamChat(
|
return StreamChat(
|
||||||
client: _initData.client,
|
client: _initData!.client,
|
||||||
onBackgroundEventReceived: (e) =>
|
onBackgroundEventReceived: (e) => showLocalNotification(
|
||||||
showLocalNotification(e, _initData.client.state.user.id),
|
e, _initData!.client.state.user!.id),
|
||||||
child: Builder(
|
child: Builder(
|
||||||
builder: (context) => AnnotatedRegion<SystemUiOverlayStyle>(
|
builder: (context) => AnnotatedRegion<SystemUiOverlayStyle>(
|
||||||
child: child,
|
child: child!,
|
||||||
value: SystemUiOverlayStyle(
|
value: SystemUiOverlayStyle(
|
||||||
systemNavigationBarColor:
|
systemNavigationBarColor:
|
||||||
StreamChatTheme.of(context).colorTheme.white,
|
StreamChatTheme.of(context).colorTheme.white,
|
||||||
@@ -260,7 +261,7 @@ class _MyAppState extends State<MyApp> with TickerProviderStateMixin {
|
|||||||
1: ThemeMode.light,
|
1: ThemeMode.light,
|
||||||
}[snapshot],
|
}[snapshot],
|
||||||
onGenerateRoute: AppRoutes.generateRoute,
|
onGenerateRoute: AppRoutes.generateRoute,
|
||||||
initialRoute: _initData.client.state.user == null
|
initialRoute: _initData!.client.state.user == null
|
||||||
? Routes.CHOOSE_USER
|
? Routes.CHOOSE_USER
|
||||||
: Routes.HOME,
|
: Routes.HOME,
|
||||||
),
|
),
|
||||||
@@ -319,7 +320,7 @@ class _HomePageState extends State<HomePage> {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final user = StreamChat.of(context).user;
|
final user = StreamChat.of(context).user!;
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
backgroundColor: StreamChatTheme.of(context).colorTheme.whiteSnow,
|
backgroundColor: StreamChatTheme.of(context).colorTheme.whiteSnow,
|
||||||
appBar: ChannelListHeader(
|
appBar: ChannelListHeader(
|
||||||
@@ -495,19 +496,15 @@ class _HomePageState extends State<HomePage> {
|
|||||||
class UserMentionPage extends StatelessWidget {
|
class UserMentionPage extends StatelessWidget {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final user = StreamChat.of(context).user;
|
final user = StreamChat.of(context).user!;
|
||||||
return MessageSearchBloc(
|
return MessageSearchBloc(
|
||||||
child: MessageSearchListView(
|
child: MessageSearchListView(
|
||||||
filters: {
|
filters: Filter.in_('members', [user.id]),
|
||||||
'members': {
|
messageFilters: Filter.custom(
|
||||||
r'$in': [user.id],
|
operator: r'$contains',
|
||||||
},
|
key: 'mentioned_users.id',
|
||||||
},
|
value: user.id,
|
||||||
messageFilters: {
|
),
|
||||||
'mentioned_users.id': {
|
|
||||||
r'$contains': user.id,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
sortOptions: [
|
sortOptions: [
|
||||||
SortOption(
|
SortOption(
|
||||||
'created_at',
|
'created_at',
|
||||||
@@ -559,8 +556,8 @@ class UserMentionPage extends StatelessWidget {
|
|||||||
final client = StreamChat.of(context).client;
|
final client = StreamChat.of(context).client;
|
||||||
final message = messageResponse.message;
|
final message = messageResponse.message;
|
||||||
final channel = client.channel(
|
final channel = client.channel(
|
||||||
messageResponse.channel.type,
|
messageResponse.channel!.type,
|
||||||
id: messageResponse.channel.id,
|
id: messageResponse.channel!.id,
|
||||||
);
|
);
|
||||||
if (channel.state == null) {
|
if (channel.state == null) {
|
||||||
await channel.watch();
|
await channel.watch();
|
||||||
@@ -585,20 +582,20 @@ class ChannelListPage extends StatefulWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class _ChannelListPageState extends State<ChannelListPage> {
|
class _ChannelListPageState extends State<ChannelListPage> {
|
||||||
TextEditingController _controller;
|
TextEditingController? _controller;
|
||||||
|
|
||||||
String _channelQuery = '';
|
String _channelQuery = '';
|
||||||
|
|
||||||
bool _isSearchActive = false;
|
bool _isSearchActive = false;
|
||||||
|
|
||||||
Timer _debounce;
|
Timer? _debounce;
|
||||||
|
|
||||||
void _channelQueryListener() {
|
void _channelQueryListener() {
|
||||||
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) {
|
if (mounted) {
|
||||||
setState(() {
|
setState(() {
|
||||||
_channelQuery = _controller.text;
|
_channelQuery = _controller!.text;
|
||||||
_isSearchActive = _channelQuery.isNotEmpty;
|
_isSearchActive = _channelQuery.isNotEmpty;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -624,7 +621,7 @@ class _ChannelListPageState extends State<ChannelListPage> {
|
|||||||
return WillPopScope(
|
return WillPopScope(
|
||||||
onWillPop: () async {
|
onWillPop: () async {
|
||||||
if (_isSearchActive) {
|
if (_isSearchActive) {
|
||||||
_controller.clear();
|
_controller!.clear();
|
||||||
setState(() => _isSearchActive = false);
|
setState(() => _isSearchActive = false);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -651,11 +648,7 @@ class _ChannelListPageState extends State<ChannelListPage> {
|
|||||||
? MessageSearchListView(
|
? MessageSearchListView(
|
||||||
showErrorTile: true,
|
showErrorTile: true,
|
||||||
messageQuery: _channelQuery,
|
messageQuery: _channelQuery,
|
||||||
filters: {
|
filters: Filter.in_('members', [user!.id]),
|
||||||
'members': {
|
|
||||||
r'$in': [user.id]
|
|
||||||
},
|
|
||||||
},
|
|
||||||
sortOptions: [
|
sortOptions: [
|
||||||
SortOption(
|
SortOption(
|
||||||
'created_at',
|
'created_at',
|
||||||
@@ -699,8 +692,8 @@ class _ChannelListPageState extends State<ChannelListPage> {
|
|||||||
final client = StreamChat.of(context).client;
|
final client = StreamChat.of(context).client;
|
||||||
final message = messageResponse.message;
|
final message = messageResponse.message;
|
||||||
final channel = client.channel(
|
final channel = client.channel(
|
||||||
messageResponse.channel.type,
|
messageResponse.channel!.type,
|
||||||
id: messageResponse.channel.id,
|
id: messageResponse.channel!.id,
|
||||||
);
|
);
|
||||||
if (channel.state == null) {
|
if (channel.state == null) {
|
||||||
await channel.watch();
|
await channel.watch();
|
||||||
@@ -720,11 +713,7 @@ class _ChannelListPageState extends State<ChannelListPage> {
|
|||||||
Navigator.pushNamed(context, Routes.NEW_CHAT);
|
Navigator.pushNamed(context, Routes.NEW_CHAT);
|
||||||
},
|
},
|
||||||
swipeToAction: true,
|
swipeToAction: true,
|
||||||
filter: {
|
filter: Filter.in_('members', [user!.id]),
|
||||||
'members': {
|
|
||||||
r'$in': [user.id],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
options: {
|
options: {
|
||||||
'presence': true,
|
'presence': true,
|
||||||
},
|
},
|
||||||
@@ -741,10 +730,12 @@ class _ChannelListPageState extends State<ChannelListPage> {
|
|||||||
builder: (context) => StreamChannel(
|
builder: (context) => StreamChannel(
|
||||||
channel: channel,
|
channel: channel,
|
||||||
child: ChatInfoScreen(
|
child: ChatInfoScreen(
|
||||||
user: channel.state.members
|
messageTheme: StreamChatTheme.of(context)
|
||||||
|
.ownMessageTheme,
|
||||||
|
user: channel.state!.members
|
||||||
.where((m) =>
|
.where((m) =>
|
||||||
m.userId !=
|
m.userId !=
|
||||||
channel.client.state.user.id)
|
channel.client.state.user!.id)
|
||||||
.first
|
.first
|
||||||
.user,
|
.user,
|
||||||
),
|
),
|
||||||
@@ -757,7 +748,10 @@ class _ChannelListPageState extends State<ChannelListPage> {
|
|||||||
MaterialPageRoute(
|
MaterialPageRoute(
|
||||||
builder: (context) => StreamChannel(
|
builder: (context) => StreamChannel(
|
||||||
channel: channel,
|
channel: channel,
|
||||||
child: GroupInfoScreen(),
|
child: GroupInfoScreen(
|
||||||
|
messageTheme: StreamChatTheme.of(context)
|
||||||
|
.ownMessageTheme,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@@ -774,8 +768,8 @@ class _ChannelListPageState extends State<ChannelListPage> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class ChannelPageArgs {
|
class ChannelPageArgs {
|
||||||
final Channel channel;
|
final Channel? channel;
|
||||||
final Message initialMessage;
|
final Message? initialMessage;
|
||||||
|
|
||||||
const ChannelPageArgs({
|
const ChannelPageArgs({
|
||||||
this.channel,
|
this.channel,
|
||||||
@@ -784,12 +778,12 @@ class ChannelPageArgs {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class ChannelPage extends StatefulWidget {
|
class ChannelPage extends StatefulWidget {
|
||||||
final int initialScrollIndex;
|
final int? initialScrollIndex;
|
||||||
final double initialAlignment;
|
final double? initialAlignment;
|
||||||
final bool highlightInitialMessage;
|
final bool highlightInitialMessage;
|
||||||
|
|
||||||
const ChannelPage({
|
const ChannelPage({
|
||||||
Key key,
|
Key? key,
|
||||||
this.initialScrollIndex,
|
this.initialScrollIndex,
|
||||||
this.initialAlignment,
|
this.initialAlignment,
|
||||||
this.highlightInitialMessage = false,
|
this.highlightInitialMessage = false,
|
||||||
@@ -800,8 +794,8 @@ class ChannelPage extends StatefulWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class _ChannelPageState extends State<ChannelPage> {
|
class _ChannelPageState extends State<ChannelPage> {
|
||||||
Message _quotedMessage;
|
Message? _quotedMessage;
|
||||||
FocusNode _focusNode;
|
FocusNode? _focusNode;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
@@ -811,14 +805,14 @@ class _ChannelPageState extends State<ChannelPage> {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
_focusNode.dispose();
|
_focusNode!.dispose();
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
void _reply(Message message) {
|
void _reply(Message message) {
|
||||||
setState(() => _quotedMessage = message);
|
setState(() => _quotedMessage = message);
|
||||||
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
|
WidgetsBinding.instance!.addPostFrameCallback((timeStamp) {
|
||||||
_focusNode.requestFocus();
|
_focusNode!.requestFocus();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -833,9 +827,8 @@ class _ChannelPageState extends State<ChannelPage> {
|
|||||||
|
|
||||||
if (channel.memberCount == 2 && channel.isDistinct) {
|
if (channel.memberCount == 2 && channel.isDistinct) {
|
||||||
final currentUser = StreamChat.of(context).user;
|
final currentUser = StreamChat.of(context).user;
|
||||||
final otherUser = channel.state.members.firstWhere(
|
final otherUser = channel.state!.members.firstWhereOrNull(
|
||||||
(element) => element.user.id != currentUser.id,
|
(element) => element.user!.id != currentUser!.id,
|
||||||
orElse: () => null,
|
|
||||||
);
|
);
|
||||||
if (otherUser != null) {
|
if (otherUser != null) {
|
||||||
final pop = await Navigator.push(
|
final pop = await Navigator.push(
|
||||||
@@ -843,6 +836,7 @@ class _ChannelPageState extends State<ChannelPage> {
|
|||||||
MaterialPageRoute(
|
MaterialPageRoute(
|
||||||
builder: (context) => StreamChannel(
|
builder: (context) => StreamChannel(
|
||||||
child: ChatInfoScreen(
|
child: ChatInfoScreen(
|
||||||
|
messageTheme: StreamChatTheme.of(context).ownMessageTheme,
|
||||||
user: otherUser.user,
|
user: otherUser.user,
|
||||||
),
|
),
|
||||||
channel: channel,
|
channel: channel,
|
||||||
@@ -859,7 +853,9 @@ class _ChannelPageState extends State<ChannelPage> {
|
|||||||
context,
|
context,
|
||||||
MaterialPageRoute(
|
MaterialPageRoute(
|
||||||
builder: (context) => StreamChannel(
|
builder: (context) => StreamChannel(
|
||||||
child: GroupInfoScreen(),
|
child: GroupInfoScreen(
|
||||||
|
messageTheme: StreamChatTheme.of(context).ownMessageTheme,
|
||||||
|
),
|
||||||
channel: channel,
|
channel: channel,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -936,7 +932,7 @@ class _ChannelPageState extends State<ChannelPage> {
|
|||||||
quotedMessage: _quotedMessage,
|
quotedMessage: _quotedMessage,
|
||||||
onQuotedMessageCleared: () {
|
onQuotedMessageCleared: () {
|
||||||
setState(() => _quotedMessage = null);
|
setState(() => _quotedMessage = null);
|
||||||
_focusNode.unfocus();
|
_focusNode!.unfocus();
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@@ -946,12 +942,12 @@ class _ChannelPageState extends State<ChannelPage> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class ThreadPage extends StatefulWidget {
|
class ThreadPage extends StatefulWidget {
|
||||||
final Message parent;
|
final Message? parent;
|
||||||
final int initialScrollIndex;
|
final int? initialScrollIndex;
|
||||||
final double initialAlignment;
|
final double? initialAlignment;
|
||||||
|
|
||||||
ThreadPage({
|
ThreadPage({
|
||||||
Key key,
|
Key? key,
|
||||||
this.parent,
|
this.parent,
|
||||||
this.initialScrollIndex,
|
this.initialScrollIndex,
|
||||||
this.initialAlignment,
|
this.initialAlignment,
|
||||||
@@ -962,7 +958,7 @@ class ThreadPage extends StatefulWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class _ThreadPageState extends State<ThreadPage> {
|
class _ThreadPageState extends State<ThreadPage> {
|
||||||
Message _quotedMessage;
|
Message? _quotedMessage;
|
||||||
FocusNode _focusNode = FocusNode();
|
FocusNode _focusNode = FocusNode();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -973,7 +969,7 @@ class _ThreadPageState extends State<ThreadPage> {
|
|||||||
|
|
||||||
void _reply(Message message) {
|
void _reply(Message message) {
|
||||||
setState(() => _quotedMessage = message);
|
setState(() => _quotedMessage = message);
|
||||||
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
|
WidgetsBinding.instance!.addPostFrameCallback((timeStamp) {
|
||||||
_focusNode.requestFocus();
|
_focusNode.requestFocus();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -983,7 +979,7 @@ class _ThreadPageState extends State<ThreadPage> {
|
|||||||
return Scaffold(
|
return Scaffold(
|
||||||
backgroundColor: StreamChatTheme.of(context).colorTheme.whiteSnow,
|
backgroundColor: StreamChatTheme.of(context).colorTheme.whiteSnow,
|
||||||
appBar: ThreadHeader(
|
appBar: ThreadHeader(
|
||||||
parent: widget.parent,
|
parent: widget.parent!,
|
||||||
),
|
),
|
||||||
body: Column(
|
body: Column(
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
@@ -996,7 +992,7 @@ class _ThreadPageState extends State<ThreadPage> {
|
|||||||
onReplyTap: _reply,
|
onReplyTap: _reply,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
if (widget.parent.type != 'deleted')
|
if (widget.parent!.type != 'deleted')
|
||||||
MessageInput(
|
MessageInput(
|
||||||
parentMessage: widget.parent,
|
parentMessage: widget.parent,
|
||||||
focusNode: _focusNode,
|
focusNode: _focusNode,
|
||||||
@@ -1021,8 +1017,8 @@ class InitData {
|
|||||||
|
|
||||||
class HolePainter extends CustomPainter {
|
class HolePainter extends CustomPainter {
|
||||||
HolePainter({
|
HolePainter({
|
||||||
@required this.color,
|
required this.color,
|
||||||
@required this.holeSize,
|
required this.holeSize,
|
||||||
});
|
});
|
||||||
|
|
||||||
Color color;
|
Color color;
|
||||||
|
|||||||
@@ -16,9 +16,9 @@ class _NewChatScreenState extends State<NewChatScreen> {
|
|||||||
final _chipInputTextFieldStateKey =
|
final _chipInputTextFieldStateKey =
|
||||||
GlobalKey<ChipInputTextFieldState<User>>();
|
GlobalKey<ChipInputTextFieldState<User>>();
|
||||||
|
|
||||||
TextEditingController _controller;
|
late TextEditingController _controller;
|
||||||
|
|
||||||
ChipInputTextFieldState get _chipInputTextFieldState =>
|
ChipInputTextFieldState? get _chipInputTextFieldState =>
|
||||||
_chipInputTextFieldStateKey.currentState;
|
_chipInputTextFieldStateKey.currentState;
|
||||||
|
|
||||||
String _userNameQuery = '';
|
String _userNameQuery = '';
|
||||||
@@ -30,14 +30,14 @@ class _NewChatScreenState extends State<NewChatScreen> {
|
|||||||
|
|
||||||
bool _isSearchActive = false;
|
bool _isSearchActive = false;
|
||||||
|
|
||||||
Channel channel;
|
Channel? channel;
|
||||||
|
|
||||||
Timer _debounce;
|
Timer? _debounce;
|
||||||
|
|
||||||
bool _showUserList = true;
|
bool _showUserList = true;
|
||||||
|
|
||||||
void _userNameListener() {
|
void _userNameListener() {
|
||||||
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)
|
if (mounted)
|
||||||
setState(() {
|
setState(() {
|
||||||
@@ -70,13 +70,13 @@ class _NewChatScreenState extends State<NewChatScreen> {
|
|||||||
'state': false,
|
'state': false,
|
||||||
'watch': false,
|
'watch': false,
|
||||||
},
|
},
|
||||||
filter: {
|
filter: Filter.raw(value: {
|
||||||
'members': [
|
'members': [
|
||||||
..._selectedUsers.map((e) => e.id),
|
..._selectedUsers.map((e) => e.id),
|
||||||
chatState.user.id,
|
chatState.user!.id,
|
||||||
],
|
],
|
||||||
'distinct': true,
|
'distinct': true,
|
||||||
},
|
}),
|
||||||
messageLimit: 0,
|
messageLimit: 0,
|
||||||
paginationParams: PaginationParams(
|
paginationParams: PaginationParams(
|
||||||
limit: 1,
|
limit: 1,
|
||||||
@@ -86,14 +86,14 @@ class _NewChatScreenState extends State<NewChatScreen> {
|
|||||||
final _channelExisted = res.length == 1;
|
final _channelExisted = res.length == 1;
|
||||||
if (_channelExisted) {
|
if (_channelExisted) {
|
||||||
channel = res.first;
|
channel = res.first;
|
||||||
await channel.watch();
|
await channel!.watch();
|
||||||
} else {
|
} else {
|
||||||
channel = chatState.client.channel(
|
channel = chatState.client.channel(
|
||||||
'messaging',
|
'messaging',
|
||||||
extraData: {
|
extraData: {
|
||||||
'members': [
|
'members': [
|
||||||
..._selectedUsers.map((e) => e.id),
|
..._selectedUsers.map((e) => e.id),
|
||||||
chatState.user.id,
|
chatState.user!.id,
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@@ -110,9 +110,9 @@ class _NewChatScreenState extends State<NewChatScreen> {
|
|||||||
void dispose() {
|
void dispose() {
|
||||||
_searchFocusNode.dispose();
|
_searchFocusNode.dispose();
|
||||||
_messageInputFocusNode.dispose();
|
_messageInputFocusNode.dispose();
|
||||||
_controller?.clear();
|
_controller.clear();
|
||||||
_controller?.removeListener(_userNameListener);
|
_controller.removeListener(_userNameListener);
|
||||||
_controller?.dispose();
|
_controller.dispose();
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -158,7 +158,7 @@ class _NewChatScreenState extends State<NewChatScreen> {
|
|||||||
message: statusString,
|
message: statusString,
|
||||||
child: StreamChannel(
|
child: StreamChannel(
|
||||||
showLoading: false,
|
showLoading: false,
|
||||||
channel: channel,
|
channel: channel!,
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
@@ -169,7 +169,7 @@ class _NewChatScreenState extends State<NewChatScreen> {
|
|||||||
chipBuilder: (context, user) {
|
chipBuilder: (context, user) {
|
||||||
return GestureDetector(
|
return GestureDetector(
|
||||||
onTap: () {
|
onTap: () {
|
||||||
_chipInputTextFieldState.removeItem(user);
|
_chipInputTextFieldState?.removeItem(user);
|
||||||
_searchFocusNode.requestFocus();
|
_searchFocusNode.requestFocus();
|
||||||
},
|
},
|
||||||
child: Stack(
|
child: Stack(
|
||||||
@@ -299,24 +299,21 @@ class _NewChatScreenState extends State<NewChatScreen> {
|
|||||||
_controller.clear();
|
_controller.clear();
|
||||||
if (!_selectedUsers.contains(user)) {
|
if (!_selectedUsers.contains(user)) {
|
||||||
_chipInputTextFieldState
|
_chipInputTextFieldState
|
||||||
..addItem(user)
|
?..addItem(user)
|
||||||
..pauseItemAddition();
|
..pauseItemAddition();
|
||||||
} else {
|
} else {
|
||||||
_chipInputTextFieldState.removeItem(user);
|
_chipInputTextFieldState!.removeItem(user);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
pagination: PaginationParams(
|
pagination: PaginationParams(
|
||||||
limit: 25,
|
limit: 25,
|
||||||
),
|
),
|
||||||
filter: {
|
filter: Filter.and([
|
||||||
if (_userNameQuery.isNotEmpty)
|
if (_userNameQuery.isNotEmpty)
|
||||||
'name': {
|
Filter.autoComplete('name', _userNameQuery),
|
||||||
r'$autocomplete': _userNameQuery,
|
Filter.notEqual(
|
||||||
},
|
'id', StreamChat.of(context).user!.id),
|
||||||
'id': {
|
]),
|
||||||
r'$ne': StreamChat.of(context).user.id,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
sort: [
|
sort: [
|
||||||
SortOption(
|
SortOption(
|
||||||
'name',
|
'name',
|
||||||
@@ -370,7 +367,7 @@ class _NewChatScreenState extends State<NewChatScreen> {
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
: FutureBuilder<bool>(
|
: FutureBuilder<bool>(
|
||||||
future: channel.initialized,
|
future: channel!.initialized,
|
||||||
builder: (context, snapshot) {
|
builder: (context, snapshot) {
|
||||||
if (snapshot.data == true) {
|
if (snapshot.data == true) {
|
||||||
return MessageListView();
|
return MessageListView();
|
||||||
@@ -394,7 +391,7 @@ class _NewChatScreenState extends State<NewChatScreen> {
|
|||||||
MessageInput(
|
MessageInput(
|
||||||
focusNode: _messageInputFocusNode,
|
focusNode: _messageInputFocusNode,
|
||||||
preMessageSending: (message) async {
|
preMessageSending: (message) async {
|
||||||
await channel.watch();
|
await channel!.watch();
|
||||||
return message;
|
return message;
|
||||||
},
|
},
|
||||||
onMessageSent: (m) {
|
onMessageSent: (m) {
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ class NewGroupChatScreen extends StatefulWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class _NewGroupChatScreenState extends State<NewGroupChatScreen> {
|
class _NewGroupChatScreenState extends State<NewGroupChatScreen> {
|
||||||
TextEditingController _controller;
|
TextEditingController? _controller;
|
||||||
|
|
||||||
String _userNameQuery = '';
|
String _userNameQuery = '';
|
||||||
|
|
||||||
@@ -20,14 +20,14 @@ class _NewGroupChatScreenState extends State<NewGroupChatScreen> {
|
|||||||
|
|
||||||
bool _isSearchActive = false;
|
bool _isSearchActive = false;
|
||||||
|
|
||||||
Timer _debounce;
|
Timer? _debounce;
|
||||||
|
|
||||||
void _userNameListener() {
|
void _userNameListener() {
|
||||||
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) {
|
if (mounted) {
|
||||||
setState(() {
|
setState(() {
|
||||||
_userNameQuery = _controller.text;
|
_userNameQuery = _controller!.text;
|
||||||
_isSearchActive = _userNameQuery.isNotEmpty;
|
_isSearchActive = _userNameQuery.isNotEmpty;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -80,7 +80,7 @@ class _NewGroupChatScreenState extends State<NewGroupChatScreen> {
|
|||||||
setState(() {
|
setState(() {
|
||||||
_selectedUsers
|
_selectedUsers
|
||||||
..clear()
|
..clear()
|
||||||
..addAll(updatedList);
|
..addAll(updatedList as Iterable<User>);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -244,15 +244,11 @@ class _NewGroupChatScreenState extends State<NewGroupChatScreen> {
|
|||||||
pagination: PaginationParams(
|
pagination: PaginationParams(
|
||||||
limit: 25,
|
limit: 25,
|
||||||
),
|
),
|
||||||
filter: {
|
filter: Filter.and([
|
||||||
if (_userNameQuery.isNotEmpty)
|
if (_userNameQuery.isNotEmpty)
|
||||||
'name': {
|
Filter.autoComplete('name', _userNameQuery),
|
||||||
r'$autocomplete': _userNameQuery,
|
Filter.notEqual('id', StreamChat.of(context).user!.id),
|
||||||
},
|
]),
|
||||||
'id': {
|
|
||||||
r'$ne': StreamChat.of(context).user.id,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
sort: [
|
sort: [
|
||||||
SortOption(
|
SortOption(
|
||||||
'name',
|
'name',
|
||||||
@@ -315,8 +311,8 @@ class _HeaderDelegate extends SliverPersistentHeaderDelegate {
|
|||||||
final double height;
|
final double height;
|
||||||
|
|
||||||
const _HeaderDelegate({
|
const _HeaderDelegate({
|
||||||
@required this.child,
|
required this.child,
|
||||||
@required this.height,
|
required this.height,
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ void showLocalNotification(Event event, String currentUserId) async {
|
|||||||
EventType.messageNew,
|
EventType.messageNew,
|
||||||
EventType.notificationMessageNew,
|
EventType.notificationMessageNew,
|
||||||
].contains(event.type) ||
|
].contains(event.type) ||
|
||||||
event.user.id == currentUserId) {
|
event.user!.id == currentUserId) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (event.message == null) return;
|
if (event.message == null) return;
|
||||||
@@ -21,9 +21,9 @@ void showLocalNotification(Event event, String currentUserId) async {
|
|||||||
);
|
);
|
||||||
await flutterLocalNotificationsPlugin.initialize(initializationSettings);
|
await flutterLocalNotificationsPlugin.initialize(initializationSettings);
|
||||||
await flutterLocalNotificationsPlugin.show(
|
await flutterLocalNotificationsPlugin.show(
|
||||||
event.message.id.hashCode,
|
event.message!.id.hashCode,
|
||||||
event.message.user.name,
|
event.message!.user!.name,
|
||||||
event.message.text,
|
event.message!.text,
|
||||||
NotificationDetails(
|
NotificationDetails(
|
||||||
android: AndroidNotificationDetails(
|
android: AndroidNotificationDetails(
|
||||||
'message channel',
|
'message channel',
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import '../group_info_screen.dart';
|
|||||||
|
|
||||||
class AppRoutes {
|
class AppRoutes {
|
||||||
/// Add entry for new route here
|
/// Add entry for new route here
|
||||||
static Route<dynamic> generateRoute(RouteSettings settings) {
|
static Route<dynamic>? generateRoute(RouteSettings settings) {
|
||||||
final args = settings.arguments;
|
final args = settings.arguments;
|
||||||
switch (settings.name) {
|
switch (settings.name) {
|
||||||
case Routes.APP:
|
case Routes.APP:
|
||||||
@@ -44,7 +44,7 @@ class AppRoutes {
|
|||||||
builder: (_) {
|
builder: (_) {
|
||||||
final arg = args as ChannelPageArgs;
|
final arg = args as ChannelPageArgs;
|
||||||
return StreamChannel(
|
return StreamChannel(
|
||||||
channel: arg.channel,
|
channel: arg.channel!,
|
||||||
initialMessageId: arg.initialMessage?.id,
|
initialMessageId: arg.initialMessage?.id,
|
||||||
child: ChannelPage(
|
child: ChannelPage(
|
||||||
highlightInitialMessage: arg.initialMessage != null,
|
highlightInitialMessage: arg.initialMessage != null,
|
||||||
@@ -68,22 +68,25 @@ class AppRoutes {
|
|||||||
settings: const RouteSettings(name: Routes.NEW_GROUP_CHAT_DETAILS),
|
settings: const RouteSettings(name: Routes.NEW_GROUP_CHAT_DETAILS),
|
||||||
builder: (_) {
|
builder: (_) {
|
||||||
return GroupChatDetailsScreen(
|
return GroupChatDetailsScreen(
|
||||||
selectedUsers: args,
|
selectedUsers: args as List<User>?,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
case Routes.CHAT_INFO_SCREEN:
|
case Routes.CHAT_INFO_SCREEN:
|
||||||
return MaterialPageRoute(
|
return MaterialPageRoute(
|
||||||
settings: const RouteSettings(name: Routes.CHAT_INFO_SCREEN),
|
settings: const RouteSettings(name: Routes.CHAT_INFO_SCREEN),
|
||||||
builder: (_) {
|
builder: (context) {
|
||||||
return ChatInfoScreen(
|
return ChatInfoScreen(
|
||||||
user: args,
|
user: args as User?,
|
||||||
|
messageTheme: StreamChatTheme.of(context).ownMessageTheme,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
case Routes.GROUP_INFO_SCREEN:
|
case Routes.GROUP_INFO_SCREEN:
|
||||||
return MaterialPageRoute(
|
return MaterialPageRoute(
|
||||||
settings: const RouteSettings(name: Routes.GROUP_INFO_SCREEN),
|
settings: const RouteSettings(name: Routes.GROUP_INFO_SCREEN),
|
||||||
builder: (_) {
|
builder: (context) {
|
||||||
return GroupInfoScreen();
|
return GroupInfoScreen(
|
||||||
|
messageTheme: StreamChatTheme.of(context).ownMessageTheme,
|
||||||
|
);
|
||||||
});
|
});
|
||||||
// Default case, should not reach here.
|
// Default case, should not reach here.
|
||||||
default:
|
default:
|
||||||
|
|||||||
@@ -2,15 +2,15 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
|
import 'package:stream_chat_flutter/stream_chat_flutter.dart';
|
||||||
|
|
||||||
class SearchTextField extends StatelessWidget {
|
class SearchTextField extends StatelessWidget {
|
||||||
final TextEditingController controller;
|
final TextEditingController? controller;
|
||||||
final ValueChanged<String> onChanged;
|
final ValueChanged<String>? onChanged;
|
||||||
final String hintText;
|
final String hintText;
|
||||||
final VoidCallback onTap;
|
final VoidCallback? onTap;
|
||||||
final bool showCloseButton;
|
final bool showCloseButton;
|
||||||
|
|
||||||
const SearchTextField({
|
const SearchTextField({
|
||||||
Key key,
|
Key? key,
|
||||||
@required this.controller,
|
required this.controller,
|
||||||
this.onChanged,
|
this.onChanged,
|
||||||
this.onTap,
|
this.onTap,
|
||||||
this.hintText = 'Search',
|
this.hintText = 'Search',
|
||||||
@@ -76,11 +76,11 @@ class SearchTextField extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
splashRadius: 24,
|
splashRadius: 24,
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
if (controller.text.isNotEmpty) {
|
if (controller!.text.isNotEmpty) {
|
||||||
Future.microtask(
|
Future.microtask(
|
||||||
() => [
|
() => [
|
||||||
controller.clear(),
|
controller!.clear(),
|
||||||
if (onChanged != null) onChanged(''),
|
if (onChanged != null) onChanged!(''),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import 'package:stream_chat_flutter/stream_chat_flutter.dart';
|
|||||||
|
|
||||||
class StreamVersion extends StatelessWidget {
|
class StreamVersion extends StatelessWidget {
|
||||||
const StreamVersion({
|
const StreamVersion({
|
||||||
Key key,
|
Key? key,
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -20,13 +20,13 @@ class StreamVersion extends StatelessWidget {
|
|||||||
return SizedBox();
|
return SizedBox();
|
||||||
}
|
}
|
||||||
|
|
||||||
final pubspec = snapshot.data;
|
final pubspec = snapshot.data!;
|
||||||
final yaml = loadYaml(pubspec);
|
final yaml = loadYaml(pubspec);
|
||||||
final streamChatDep =
|
final streamChatDep =
|
||||||
yaml['packages']['stream_chat_flutter']['version'];
|
yaml['packages']['stream_chat_flutter']['version'];
|
||||||
|
|
||||||
return Text(
|
return Text(
|
||||||
'Stream SDK v ${streamChatDep}',
|
'Stream SDK v $streamChatDep',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
color: StreamChatTheme.of(context).colorTheme.greyGainsboro,
|
color: StreamChatTheme.of(context).colorTheme.greyGainsboro,
|
||||||
|
|||||||
@@ -4,10 +4,10 @@ publish_to: 'none'
|
|||||||
version: 1.5.4+1
|
version: 1.5.4+1
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: ">=2.2.2 <3.0.0"
|
sdk: '>=2.12.0 <3.0.0'
|
||||||
|
|
||||||
dependencies:
|
dependencies:
|
||||||
flutter_app_badger: ^1.1.2
|
flutter_app_badger: ^1.2.0
|
||||||
flutter:
|
flutter:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
stream_chat_flutter:
|
stream_chat_flutter:
|
||||||
@@ -20,13 +20,14 @@ dependencies:
|
|||||||
url: https://github.com/GetStream/stream-chat-flutter.git
|
url: https://github.com/GetStream/stream-chat-flutter.git
|
||||||
ref: develop
|
ref: develop
|
||||||
path: packages/stream_chat_persistence
|
path: packages/stream_chat_persistence
|
||||||
flutter_local_notifications: ^2.0.2
|
flutter_local_notifications: ^5.0.0+4
|
||||||
flutter_svg: ^0.19.3
|
flutter_svg: ^0.22.0
|
||||||
flutter_secure_storage: ^3.3.5
|
flutter_secure_storage: ^4.2.0
|
||||||
yaml: ^2.2.1
|
yaml: ^3.1.0
|
||||||
uuid: ^2.2.2
|
uuid: ^3.0.4
|
||||||
streaming_shared_preferences: ^1.0.2
|
streaming_shared_preferences: ^2.0.0
|
||||||
lottie: ^0.7.0+1
|
lottie: ^1.0.1
|
||||||
|
collection: ^1.15.0-nullsafety.4
|
||||||
|
|
||||||
dependency_overrides:
|
dependency_overrides:
|
||||||
stream_chat:
|
stream_chat:
|
||||||
@@ -41,7 +42,7 @@ dependency_overrides:
|
|||||||
path: packages/stream_chat_flutter_core
|
path: packages/stream_chat_flutter_core
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_launcher_icons: ^0.8.1
|
flutter_launcher_icons: ^0.9.0
|
||||||
test: any
|
test: any
|
||||||
|
|
||||||
flutter:
|
flutter:
|
||||||
|
|||||||
@@ -1,30 +0,0 @@
|
|||||||
// This is a basic Flutter widget test.
|
|
||||||
//
|
|
||||||
// To perform an interaction with a widget in your test, use the WidgetTester
|
|
||||||
// utility that Flutter provides. For example, you can send tap and scroll
|
|
||||||
// gestures. You can also use WidgetTester to find child widgets in the widget
|
|
||||||
// tree, read text, and verify that the values of widget properties are correct.
|
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
|
||||||
|
|
||||||
import 'package:stream_chat_v1/main.dart';
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
testWidgets('Counter increments smoke test', (WidgetTester tester) async {
|
|
||||||
// Build our app and trigger a frame.
|
|
||||||
await tester.pumpWidget(MyApp());
|
|
||||||
|
|
||||||
// Verify that our counter starts at 0.
|
|
||||||
expect(find.text('0'), findsOneWidget);
|
|
||||||
expect(find.text('1'), findsNothing);
|
|
||||||
|
|
||||||
// Tap the '+' icon and trigger a frame.
|
|
||||||
await tester.tap(find.byIcon(Icons.add));
|
|
||||||
await tester.pump();
|
|
||||||
|
|
||||||
// Verify that our counter has incremented.
|
|
||||||
expect(find.text('0'), findsNothing);
|
|
||||||
expect(find.text('1'), findsOneWidget);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user