migrate chatty

This commit is contained in:
Salvatore Giordano
2021-05-18 17:15:37 +02:00
parent 33622093e0
commit bc655e0dc4
27 changed files with 181 additions and 159 deletions
@@ -2,6 +2,6 @@
<Workspace <Workspace
version = "1.0"> version = "1.0">
<FileRef <FileRef
location = "group:Runner.xcodeproj"> location = "self:">
</FileRef> </FileRef>
</Workspace> </Workspace>
@@ -1,7 +1,7 @@
import 'package:stream_chatter/domain/models/auth_user.dart'; import 'package:stream_chatter/domain/models/auth_user.dart';
abstract class AuthRepository { abstract class AuthRepository {
Future<AuthUser> getAuthUser(); Future<AuthUser?> getAuthUser();
Future<AuthUser> signIn(); Future<AuthUser> signIn();
Future<void> logout(); Future<void> logout();
} }
@@ -1,5 +1,5 @@
import 'dart:io'; import 'dart:io';
abstract class ImagePickerRepository { abstract class ImagePickerRepository {
Future<File> pickImage(); Future<File?> pickImage();
} }
@@ -4,10 +4,17 @@ import 'package:stream_chatter/data/image_picker_repository.dart';
class ImagePickerImpl extends ImagePickerRepository { class ImagePickerImpl extends ImagePickerRepository {
@override @override
Future<File> pickImage() async { Future<File?> pickImage() async {
final picker = ImagePicker(); final picker = ImagePicker();
final pickedFile = final pickedFile = await picker.getImage(
await picker.getImage(source: ImageSource.gallery, maxWidth: 400); source: ImageSource.gallery,
maxWidth: 400,
);
if (pickedFile == null) {
return null;
}
return File(pickedFile.path); return File(pickedFile.path);
} }
} }
@@ -8,7 +8,7 @@ class StreamApiLocalImpl extends StreamApiRepository {
final StreamChatClient _client; final StreamChatClient _client;
@override @override
Future<ChatUser> connectUser(ChatUser user, String token) async { Future<ChatUser> connectUser(ChatUser user, String? token) async {
Map<String, dynamic> extraData = {}; Map<String, dynamic> extraData = {};
if (user.image != null) { if (user.image != null) {
extraData['image'] = user.image; extraData['image'] = user.image;
@@ -18,7 +18,7 @@ class StreamApiLocalImpl extends StreamApiRepository {
} }
await _client.disconnect(); await _client.disconnect();
await _client.connectUser( await _client.connectUser(
User(id: user.id, extraData: extraData), User(id: user.id!, extraData: extraData as Map<String, Object>),
token, token,
); );
return user; return user;
@@ -28,12 +28,12 @@ class StreamApiLocalImpl extends StreamApiRepository {
Future<List<ChatUser>> getChatUsers() async { Future<List<ChatUser>> getChatUsers() async {
final result = await _client.queryUsers(); final result = await _client.queryUsers();
final chatUsers = result.users final chatUsers = result.users
.where((element) => element.id != _client.state.user.id) .where((element) => element.id != _client.state.user!.id)
.map( .map(
(e) => ChatUser( (e) => ChatUser(
id: e.id, id: e.id,
name: e.name, name: e.name,
image: e.extraData['image'], image: e.extraData['image'] as String?,
), ),
) )
.toList(); .toList();
@@ -47,25 +47,25 @@ class StreamApiLocalImpl extends StreamApiRepository {
@override @override
Future<Channel> createGroupChat( Future<Channel> createGroupChat(
String channelId, String name, List<String> members, String channelId, String? name, List<String?>? members,
{String image}) async { {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!,
'members': [_client.state.user.id, ...members], 'members': [_client.state.user!.id, ...members!],
}); });
await channel.watch(); await channel.watch();
return channel; return channel;
} }
@override @override
Future<Channel> createSimpleChat(String friendId) async { Future<Channel> createSimpleChat(String? friendId) async {
final channel = _client.channel('messaging', final channel = _client.channel('messaging',
id: '${_client.state.user.id.hashCode}${friendId.hashCode}', id: '${_client.state.user!.id.hashCode}${friendId.hashCode}',
extraData: { extraData: {
'members': [ 'members': [
friendId, friendId,
_client.state.user.id, _client.state.user!.id,
], ],
}); });
await channel.watch(); await channel.watch();
@@ -84,6 +84,6 @@ class StreamApiLocalImpl extends StreamApiRepository {
User(id: userId), User(id: userId),
token, token,
); );
return _client.state.user.name != null && _client.state.user.name != userId; return _client.state.user!.name != null && _client.state.user!.name != userId;
} }
} }
@@ -4,7 +4,7 @@ import 'package:stream_chatter/data/upload_storage_repository.dart';
class UploadStorageLocalImpl extends UploadStorageRepository { class UploadStorageLocalImpl extends UploadStorageRepository {
@override @override
Future<String> uploadPhoto(File file, String path) async { Future<String> uploadPhoto(File? file, String path) async {
return 'https://lh3.googleusercontent.com/a-/AOh14GjhqGZ-V7tNXS1pOIp9vbBij4OS9JbzxXgxgy1t=s600-k-no-rp-mo'; return 'https://lh3.googleusercontent.com/a-/AOh14GjhqGZ-V7tNXS1pOIp9vbBij4OS9JbzxXgxgy1t=s600-k-no-rp-mo';
} }
} }
+9 -4
View File
@@ -7,7 +7,7 @@ class AuthImpl extends AuthRepository {
FirebaseAuth _auth = FirebaseAuth.instance; FirebaseAuth _auth = FirebaseAuth.instance;
@override @override
Future<AuthUser> getAuthUser() async { Future<AuthUser?> getAuthUser() async {
final user = _auth.currentUser; final user = _auth.currentUser;
if (user != null) { if (user != null) {
return AuthUser(user.uid); return AuthUser(user.uid);
@@ -19,16 +19,21 @@ class AuthImpl extends AuthRepository {
Future<AuthUser> signIn() async { Future<AuthUser> signIn() async {
try { try {
UserCredential userCredential; UserCredential userCredential;
final GoogleSignInAccount googleUser = await GoogleSignIn().signIn(); final GoogleSignInAccount? googleUser = await GoogleSignIn().signIn();
if (googleUser == null) {
throw Exception('login error');
}
final GoogleSignInAuthentication googleAuth = final GoogleSignInAuthentication googleAuth =
await googleUser.authentication; await googleUser.authentication;
final GoogleAuthCredential googleAuthCredential = final GoogleAuthCredential googleAuthCredential =
GoogleAuthProvider.credential( GoogleAuthProvider.credential(
accessToken: googleAuth.accessToken, accessToken: googleAuth.accessToken,
idToken: googleAuth.idToken, idToken: googleAuth.idToken,
); ) as GoogleAuthCredential;
userCredential = await _auth.signInWithCredential(googleAuthCredential); userCredential = await _auth.signInWithCredential(googleAuthCredential);
final user = userCredential.user; final user = userCredential.user!;
return AuthUser(user.uid); return AuthUser(user.uid);
} catch (e) { } catch (e) {
print(e); print(e);
@@ -13,6 +13,6 @@ class PersistentStorageImpl extends PersistentStorageRepository {
@override @override
Future<void> updateDarkMode(bool isDarkMode) async { Future<void> updateDarkMode(bool isDarkMode) async {
final preference = await SharedPreferences.getInstance(); final preference = await SharedPreferences.getInstance();
return await preference.setBool(_isDarkMode, isDarkMode); await preference.setBool(_isDarkMode, isDarkMode);
} }
} }
@@ -11,7 +11,7 @@ class StreamApiImpl extends StreamApiRepository {
final StreamChatClient _client; final StreamChatClient _client;
@override @override
Future<ChatUser> connectUser(ChatUser user, String token) async { Future<ChatUser> connectUser(ChatUser user, String? token) async {
Map<String, dynamic> extraData = {}; Map<String, dynamic> extraData = {};
if (user.image != null) { if (user.image != null) {
extraData['image'] = user.image; extraData['image'] = user.image;
@@ -21,7 +21,7 @@ class StreamApiImpl extends StreamApiRepository {
} }
await _client.disconnect(); await _client.disconnect();
await _client.connectUser( await _client.connectUser(
User(id: user.id, extraData: extraData), User(id: user.id!, extraData: extraData as Map<String, Object>),
token, token,
); );
return user; return user;
@@ -31,12 +31,12 @@ class StreamApiImpl extends StreamApiRepository {
Future<List<ChatUser>> getChatUsers() async { Future<List<ChatUser>> getChatUsers() async {
final result = await _client.queryUsers(); final result = await _client.queryUsers();
final chatUsers = result.users final chatUsers = result.users
.where((element) => element.id != _client.state.user.id) .where((element) => element.id != _client.state.user!.id)
.map( .map(
(e) => ChatUser( (e) => ChatUser(
id: e.id, id: e.id,
name: e.name, name: e.name,
image: e.extraData['image'], image: e.extraData['image'] as String?,
), ),
) )
.toList(); .toList();
@@ -44,10 +44,10 @@ class StreamApiImpl extends StreamApiRepository {
} }
@override @override
Future<String> getToken(String userId) async { Future<String?> getToken(String userId) async {
//TODO: use your own implementation in Production //TODO: use your own implementation in Production
final response = await http.post( final response = await http.post(
'your_backend_url', Uri.parse('your_backend_url'),
body: jsonEncode(<String, String>{'id': userId}), body: jsonEncode(<String, String>{'id': userId}),
headers: <String, String>{ headers: <String, String>{
'Content-Type': 'application/json; charset=UTF-8', 'Content-Type': 'application/json; charset=UTF-8',
@@ -62,25 +62,25 @@ class StreamApiImpl extends StreamApiRepository {
} }
@override @override
Future<Channel> createGroupChat(String id, String name, List<String> members, Future<Channel> createGroupChat(String id, String? name, List<String?>? members,
{String image}) async { {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!,
'members': [_client.state.user.id, ...members], 'members': [_client.state.user!.id, ...members!],
}); });
await channel.watch(); await channel.watch();
return channel; return channel;
} }
@override @override
Future<Channel> createSimpleChat(String friendId) async { Future<Channel> createSimpleChat(String? friendId) async {
final channel = _client.channel('messaging', final channel = _client.channel('messaging',
id: '${_client.state.user.id.hashCode}${friendId.hashCode}', id: '${_client.state.user!.id.hashCode}${friendId.hashCode}',
extraData: { extraData: {
'members': [ 'members': [
friendId, friendId,
_client.state.user.id, _client.state.user!.id,
], ],
}); });
await channel.watch(); await channel.watch();
@@ -99,6 +99,6 @@ class StreamApiImpl extends StreamApiRepository {
User(id: userId), User(id: userId),
token, token,
); );
return _client.state.user.name != null && _client.state.user.name != userId; return _client.state.user!.name != null && _client.state.user!.name != userId;
} }
} }
@@ -4,9 +4,9 @@ import 'package:stream_chatter/data/upload_storage_repository.dart';
class UploadStorageImpl extends UploadStorageRepository { class UploadStorageImpl extends UploadStorageRepository {
@override @override
Future<String> uploadPhoto(File file, String path) async { Future<String> uploadPhoto(File? file, String path) async {
final ref = firebase_storage.FirebaseStorage.instance.ref(path); final ref = firebase_storage.FirebaseStorage.instance.ref(path);
final uploadTask = ref.putFile(file); final uploadTask = ref.putFile(file!);
await uploadTask; await uploadTask;
return await ref.getDownloadURL(); return await ref.getDownloadURL();
} }
@@ -3,12 +3,12 @@ import 'package:stream_chat_flutter/stream_chat_flutter.dart';
abstract class StreamApiRepository { abstract class StreamApiRepository {
Future<List<ChatUser>> getChatUsers(); Future<List<ChatUser>> getChatUsers();
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( Future<Channel> createGroupChat(
String channelId, String name, List<String> members, String channelId, String? name, List<String?>? members,
{String image}); {String? image});
Future<Channel> createSimpleChat(String friendId); Future<Channel> createSimpleChat(String? friendId);
Future<void> logout(); Future<void> logout();
} }
@@ -1,5 +1,5 @@
import 'dart:io'; import 'dart:io';
abstract class UploadStorageRepository { abstract class UploadStorageRepository {
Future<String> uploadPhoto(File file, String path); Future<String> uploadPhoto(File? file, String path);
} }
@@ -1,6 +1,6 @@
class ChatUser { class ChatUser {
const ChatUser({this.name, this.image, this.id}); const ChatUser({this.name, this.image, this.id});
final String name; final String? name;
final String image; final String? image;
final String id; final String? id;
} }
@@ -7,9 +7,9 @@ import 'package:uuid/uuid.dart';
class CreateGroupInput { class CreateGroupInput {
CreateGroupInput({this.imageFile, this.name, this.members}); CreateGroupInput({this.imageFile, this.name, this.members});
final File imageFile; final File? imageFile;
final String name; final String? name;
final List<String> members; final List<String?>? members;
} }
class CreateGroupUseCase { class CreateGroupUseCase {
@@ -23,7 +23,7 @@ class CreateGroupUseCase {
Future<Channel> createGroup(CreateGroupInput input) async { Future<Channel> createGroup(CreateGroupInput input) async {
final channelId = Uuid().v4(); final channelId = Uuid().v4();
String image; String? image;
if (input.imageFile != null) { if (input.imageFile != null) {
image = await _uploadStorageRepository.uploadPhoto( image = await _uploadStorageRepository.uploadPhoto(
input.imageFile, 'channels/$channelId'); input.imageFile, 'channels/$channelId');
@@ -5,10 +5,12 @@ import 'package:stream_chatter/data/stream_api_repository.dart';
import 'package:stream_chatter/data/upload_storage_repository.dart'; import 'package:stream_chatter/data/upload_storage_repository.dart';
import 'package:stream_chatter/domain/models/chat_user.dart'; import 'package:stream_chatter/domain/models/chat_user.dart';
import '../exceptions/auth_exception.dart';
class ProfileInput { class ProfileInput {
ProfileInput({this.imageFile, this.name}); ProfileInput({this.imageFile, this.name});
final File imageFile; final File? imageFile;
final String name; final String? name;
} }
class ProfileSignInUseCase { class ProfileSignInUseCase {
@@ -24,13 +26,21 @@ class ProfileSignInUseCase {
Future<void> verify(ProfileInput input) async { Future<void> verify(ProfileInput input) async {
final auth = await _authRepository.getAuthUser(); final auth = await _authRepository.getAuthUser();
if (auth == null) {
throw AuthException(AuthErrorCode.not_auth);
}
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( image = await _uploadStorageRepository.uploadPhoto(
input.imageFile, 'users/${auth.id}'); input.imageFile, 'users/${auth.id}');
} }
await _streamApiRepository.connectUser( await _streamApiRepository.connectUser(
ChatUser(name: input.name, id: auth.id, image: image), token); ChatUser(
name: input.name,
id: auth.id,
image: image,
),
token);
} }
} }
@@ -1,9 +1,9 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
class AvatarImageView extends StatelessWidget { class AvatarImageView extends StatelessWidget {
const AvatarImageView({Key key, this.onTap, this.child}) : super(key: key); const AvatarImageView({Key? key, this.onTap, this.child}) : super(key: key);
final Widget child; final Widget? child;
final VoidCallback onTap; final VoidCallback? onTap;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@@ -5,8 +5,8 @@ class LoadingView extends StatelessWidget {
final Widget child; final Widget child;
const LoadingView({ const LoadingView({
Key key, Key? key,
@required this.child, required this.child,
this.isLoading = false, this.isLoading = false,
}) : super(key: key); }) : super(key: key);
@@ -1,3 +1,4 @@
import 'package:collection/collection.dart' show IterableExtension;
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
import 'package:jiffy/jiffy.dart'; import 'package:jiffy/jiffy.dart';
@@ -30,22 +31,22 @@ import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart';
/// Modify it to change the widget appearance. /// Modify it to change the widget appearance.
class MyChannelPreview extends StatelessWidget { class MyChannelPreview extends StatelessWidget {
/// Function called when tapping this widget /// Function called when tapping this widget
final void Function(Channel) onTap; final void Function(Channel)? onTap;
/// Function called when long pressing this widget /// Function called when long pressing this widget
final void Function(Channel) onLongPress; final void Function(Channel)? onLongPress;
/// Channel displayed /// Channel displayed
final Channel channel; final Channel channel;
/// The function called when the image is tapped /// The function called when the image is tapped
final VoidCallback onImageTap; final VoidCallback? onImageTap;
final String heroTag; final String? heroTag;
MyChannelPreview({ MyChannelPreview({
@required this.channel, required this.channel,
Key key, Key? key,
this.onTap, this.onTap,
this.onLongPress, this.onLongPress,
this.onImageTap, this.onImageTap,
@@ -59,24 +60,24 @@ class MyChannelPreview extends StatelessWidget {
initialData: channel.isMuted, initialData: channel.isMuted,
builder: (context, snapshot) { builder: (context, snapshot) {
return Opacity( return Opacity(
opacity: snapshot.data ? 0.5 : 1, opacity: snapshot.data! ? 0.5 : 1,
child: ListTile( child: ListTile(
contentPadding: const EdgeInsets.symmetric( contentPadding: const EdgeInsets.symmetric(
horizontal: 8, horizontal: 8,
), ),
onTap: () { onTap: () {
if (onTap != null) { if (onTap != null) {
onTap(channel); onTap!(channel);
} }
}, },
onLongPress: () { onLongPress: () {
if (onLongPress != null) { if (onLongPress != null) {
onLongPress(channel); onLongPress!(channel);
} }
}, },
leading: Material( leading: Material(
child: Hero( child: Hero(
tag: heroTag, tag: heroTag!,
child: StreamChannel( child: StreamChannel(
channel: channel, channel: channel,
child: ChannelImage( child: ChannelImage(
@@ -95,13 +96,13 @@ class MyChannelPreview extends StatelessWidget {
), ),
), ),
StreamBuilder<List<Member>>( StreamBuilder<List<Member>>(
stream: channel.state.membersStream, stream: channel.state!.membersStream,
initialData: channel.state.members, initialData: channel.state!.members,
builder: (context, snapshot) { builder: (context, snapshot) {
if (!snapshot.hasData || if (!snapshot.hasData ||
snapshot.data.isEmpty || snapshot.data!.isEmpty ||
!snapshot.data.any((Member e) => !snapshot.data!.any((Member e) =>
e.user.id == channel.client.state.user.id)) { e.user!.id == channel.client.state.user!.id)) {
return SizedBox(); return SizedBox();
} }
return ChannelUnreadIndicator( return ChannelUnreadIndicator(
@@ -116,26 +117,26 @@ class MyChannelPreview extends StatelessWidget {
Flexible(child: _buildSubtitle(context)), Flexible(child: _buildSubtitle(context)),
Builder( Builder(
builder: (context) { builder: (context) {
final lastMessage = channel.state.messages.lastWhere( final lastMessage =
channel.state!.messages.lastWhereOrNull(
(m) => !m.isDeleted && m.shadowed != true, (m) => !m.isDeleted && m.shadowed != true,
orElse: () => null,
); );
if (lastMessage?.user?.id == if (lastMessage?.user?.id ==
StreamChat.of(context).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) size: StreamChatTheme.of(context)
.channelPreviewTheme .channelPreviewTheme
.indicatorIconSize, .indicatorIconSize,
isMessageRead: channel.state.read isMessageRead: channel.state!.read
?.where((element) => ?.where((element) =>
element.user.id != element.user.id !=
channel.client.state.user.id) channel.client.state.user!.id)
?.where((element) => element.lastRead .where((element) => element.lastRead
.isAfter(lastMessage.createdAt)) .isAfter(lastMessage.createdAt))
?.isNotEmpty == .isNotEmpty ==
true, true,
), ),
); );
@@ -152,14 +153,14 @@ class MyChannelPreview extends StatelessWidget {
} }
Widget _buildDate(BuildContext context) { Widget _buildDate(BuildContext context) {
return StreamBuilder<DateTime>( return StreamBuilder<DateTime?>(
stream: channel.lastMessageAtStream, stream: channel.lastMessageAtStream,
initialData: channel.lastMessageAt, initialData: channel.lastMessageAt,
builder: (context, snapshot) { builder: (context, snapshot) {
if (!snapshot.hasData) { if (!snapshot.hasData) {
return SizedBox(); return SizedBox();
} }
final lastMessageAt = snapshot.data.toLocal(); final lastMessageAt = snapshot.data!.toLocal();
String stringDate; String stringDate;
final now = DateTime.now(); final now = DateTime.now();
@@ -198,11 +199,11 @@ class MyChannelPreview extends StatelessWidget {
' Channel is muted', ' Channel is muted',
style: StreamChatTheme.of(context) style: StreamChatTheme.of(context)
.channelPreviewTheme .channelPreviewTheme
.subtitle .subtitle!
.copyWith( .copyWith(
color: StreamChatTheme.of(context) color: StreamChatTheme.of(context)
.channelPreviewTheme .channelPreviewTheme
.subtitle .subtitle!
.color, .color,
), ),
), ),
@@ -212,21 +213,20 @@ class MyChannelPreview extends StatelessWidget {
return TypingIndicator( return TypingIndicator(
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: color:
StreamChatTheme.of(context).channelPreviewTheme.subtitle.color, StreamChatTheme.of(context).channelPreviewTheme.subtitle!.color,
), ),
); );
} }
Widget _buildLastMessage(BuildContext context) { Widget _buildLastMessage(BuildContext context) {
return StreamBuilder<List<Message>>( return StreamBuilder<List<Message>?>(
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( final lastMessage = snapshot.data
(m) => m.shadowed != true && !m.isDeleted, ?.lastWhereOrNull((m) => m.shadowed != true && !m.isDeleted);
orElse: () => null);
if (lastMessage == null) { if (lastMessage == null) {
return SizedBox(); return SizedBox();
} }
@@ -254,21 +254,21 @@ class MyChannelPreview extends StatelessWidget {
return Text.rich( return Text.rich(
_getDisplayText( _getDisplayText(
text, text!,
lastMessage.mentionedUsers, lastMessage.mentionedUsers,
lastMessage.attachments, lastMessage.attachments,
StreamChatTheme.of(context).channelPreviewTheme.subtitle.copyWith( StreamChatTheme.of(context).channelPreviewTheme.subtitle!.copyWith(
color: StreamChatTheme.of(context) color: StreamChatTheme.of(context)
.channelPreviewTheme .channelPreviewTheme
.subtitle .subtitle!
.color, .color,
fontStyle: (lastMessage.isSystem || lastMessage.isDeleted) fontStyle: (lastMessage.isSystem || lastMessage.isDeleted)
? FontStyle.italic ? FontStyle.italic
: FontStyle.normal), : FontStyle.normal),
StreamChatTheme.of(context).channelPreviewTheme.subtitle.copyWith( StreamChatTheme.of(context).channelPreviewTheme.subtitle!.copyWith(
color: StreamChatTheme.of(context) color: StreamChatTheme.of(context)
.channelPreviewTheme .channelPreviewTheme
.subtitle .subtitle!
.color, .color,
fontStyle: (lastMessage.isSystem || lastMessage.isDeleted) fontStyle: (lastMessage.isSystem || lastMessage.isDeleted)
? FontStyle.italic ? FontStyle.italic
@@ -321,8 +321,8 @@ class MyChannelPreview extends StatelessWidget {
class ChannelUnreadIndicator extends StatelessWidget { class ChannelUnreadIndicator extends StatelessWidget {
const ChannelUnreadIndicator({ const ChannelUnreadIndicator({
Key key, Key? key,
@required this.channel, required this.channel,
}) : super(key: key); }) : super(key: key);
final Channel channel; final Channel channel;
@@ -330,8 +330,8 @@ class ChannelUnreadIndicator extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return StreamBuilder<int>( return StreamBuilder<int>(
stream: channel.state.unreadCountStream, stream: channel.state!.unreadCountStream,
initialData: channel.state.unreadCount, initialData: channel.state!.unreadCount,
builder: (context, snapshot) { builder: (context, snapshot) {
if (!snapshot.hasData || snapshot.data == 0) { if (!snapshot.hasData || snapshot.data == 0) {
return SizedBox(); return SizedBox();
@@ -351,7 +351,7 @@ class ChannelUnreadIndicator extends StatelessWidget {
), ),
child: Center( child: Center(
child: Text( child: Text(
'${snapshot.data > 99 ? '99+' : snapshot.data}', '${snapshot.data! > 99 ? '99+' : snapshot.data}',
style: TextStyle( style: TextStyle(
fontSize: 11, fontSize: 11,
color: Colors.white, color: Colors.white,
+18 -19
View File
@@ -23,11 +23,10 @@ class ChatView extends StatelessWidget {
), ),
body: ChannelsBloc( body: ChannelsBloc(
child: ChannelListView( child: ChannelListView(
filter: { filter: Filter.in_(
'members': { 'members',
'\$in': [StreamChat.of(context).user?.id], [StreamChat.of(context).user!.id],
} ),
},
sort: [SortOption('last_message_at')], sort: [SortOption('last_message_at')],
channelPreviewBuilder: (context, channel) { channelPreviewBuilder: (context, channel) {
return Container( return Container(
@@ -36,22 +35,22 @@ class ChatView extends StatelessWidget {
channel: channel, channel: channel,
heroTag: channel.id, heroTag: channel.id,
onImageTap: () { onImageTap: () {
String name; String? name;
String image; String? image;
final currentUser = StreamChat.of(context).client.state.user; final currentUser = StreamChat.of(context).client.state.user;
if (channel.isGroup) { if (channel.isGroup) {
name = channel.extraData['name']; name = channel.extraData['name'];
image = channel.extraData['image']; image = channel.extraData['image'];
} else { } else {
final friend = channel.state.members final friend = channel.state!.members
.where((element) => element.userId != currentUser.id) .where((element) => element.userId != currentUser!.id)
.first .first
.user; .user!;
name = friend.name; name = friend.name;
image = friend.extraData['image']; image = friend.extraData['image'] as String?;
} }
return Navigator.of(context).push( Navigator.of(context).push(
PageRouteBuilder( PageRouteBuilder(
barrierColor: Colors.black45, barrierColor: Colors.black45,
barrierDismissible: true, barrierDismissible: true,
@@ -110,15 +109,15 @@ class ChannelPage extends StatelessWidget {
class ChatDetailView extends StatelessWidget { class ChatDetailView extends StatelessWidget {
const ChatDetailView({ const ChatDetailView({
Key key, Key? key,
this.image, this.image,
this.name, this.name,
this.channelId, this.channelId,
}) : super(key: key); }) : super(key: key);
final String image; final String? image;
final String name; final String? name;
final String channelId; final String? channelId;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@@ -135,10 +134,10 @@ class ChatDetailView extends StatelessWidget {
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
Hero( Hero(
tag: channelId, tag: channelId!,
child: ClipOval( child: ClipOval(
child: Image.network( child: Image.network(
image, image!,
height: 180, height: 180,
width: 180, width: 180,
fit: BoxFit.cover, fit: BoxFit.cover,
@@ -146,7 +145,7 @@ class ChatDetailView extends StatelessWidget {
), ),
), ),
Text( Text(
name, name!,
style: TextStyle( style: TextStyle(
fontWeight: FontWeight.w700, fontWeight: FontWeight.w700,
fontSize: 22, fontSize: 22,
@@ -143,9 +143,9 @@ class FriendsSelectionView extends StatelessWidget {
CircleAvatar( CircleAvatar(
radius: 30, radius: 30,
backgroundImage: NetworkImage( backgroundImage: NetworkImage(
chatUserState.chatUser.image), chatUserState.chatUser.image!),
), ),
Text(chatUserState.chatUser.name), Text(chatUserState.chatUser.name!),
], ],
), ),
Positioned( Positioned(
@@ -178,9 +178,9 @@ class FriendsSelectionView extends StatelessWidget {
}, },
leading: CircleAvatar( leading: CircleAvatar(
backgroundImage: backgroundImage:
NetworkImage(chatUserState.chatUser.image), NetworkImage(chatUserState.chatUser.image!),
), ),
title: Text(chatUserState.chatUser.name), title: Text(chatUserState.chatUser.name!),
trailing: isGroup trailing: isGroup
? Checkbox( ? Checkbox(
value: chatUserState.selected, value: chatUserState.selected,
@@ -13,8 +13,8 @@ class GroupSelectionState {
this.channel, this.channel,
this.isLoading = false, this.isLoading = false,
}); });
final File file; final File? file;
final Channel channel; final Channel? channel;
final bool isLoading; final bool isLoading;
} }
@@ -28,7 +28,7 @@ class GroupSelectionView extends StatelessWidget {
context, context,
Scaffold( Scaffold(
body: StreamChannel( body: StreamChannel(
channel: snapshot.channel, channel: snapshot.channel!,
child: ChannelPage(), child: ChannelPage(),
), ),
), ),
@@ -60,9 +60,9 @@ class GroupSelectionView extends StatelessWidget {
children: [ children: [
AvatarImageView( AvatarImageView(
onTap: context.read<GroupSelectionCubit>().pickImage, onTap: context.read<GroupSelectionCubit>().pickImage,
child: snapshot?.file != null child: snapshot.file != null
? Image.file( ? Image.file(
snapshot?.file, snapshot.file!,
fit: BoxFit.cover, fit: BoxFit.cover,
) )
: Icon( : Icon(
@@ -104,9 +104,9 @@ class GroupSelectionView extends StatelessWidget {
CircleAvatar( CircleAvatar(
radius: 30, radius: 30,
backgroundImage: backgroundImage:
NetworkImage(chatUserState.chatUser.image), NetworkImage(chatUserState.chatUser.image!),
), ),
Text(chatUserState.chatUser.name), Text(chatUserState.chatUser.name!),
], ],
), ),
); );
+6 -6
View File
@@ -36,7 +36,7 @@ class HomeView extends StatelessWidget {
class HomeNavigationBar extends StatelessWidget { class HomeNavigationBar extends StatelessWidget {
const HomeNavigationBar({ const HomeNavigationBar({
Key key, Key? key,
}) : super(key: key); }) : super(key: key);
@override @override
@@ -110,16 +110,16 @@ class HomeNavigationBar extends StatelessWidget {
class _HomeNavItem extends StatelessWidget { class _HomeNavItem extends StatelessWidget {
const _HomeNavItem({ const _HomeNavItem({
Key key, Key? key,
this.iconData, this.iconData,
this.text, this.text,
this.onTap, this.onTap,
this.selected = false, this.selected = false,
}) : super(key: key); }) : super(key: key);
final IconData iconData; final IconData? iconData;
final String text; final String? text;
final VoidCallback onTap; final VoidCallback? onTap;
final bool selected; final bool selected;
@override @override
@@ -135,7 +135,7 @@ class _HomeNavItem extends StatelessWidget {
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
Icon(iconData, color: color), Icon(iconData, color: color),
Text(text, style: TextStyle(color: color)), Text(text!, style: TextStyle(color: color)),
], ],
), ),
); );
@@ -10,8 +10,8 @@ import 'package:stream_chat_flutter/stream_chat_flutter.dart';
class SettingsView extends StatelessWidget { class SettingsView extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final user = StreamChat.of(context).client.state.user; final user = StreamChat.of(context).client.state.user!;
final image = user?.extraData['image']; final image = user.extraData['image'];
final textColor = Theme.of(context).appBarTheme.color; final textColor = Theme.of(context).appBarTheme.color;
return MultiBlocProvider( return MultiBlocProvider(
providers: [ providers: [
@@ -48,7 +48,7 @@ class SettingsView extends StatelessWidget {
onTap: () => null, onTap: () => null,
child: image != null child: image != null
? Image.network( ? Image.network(
image, image as String,
fit: BoxFit.cover, fit: BoxFit.cover,
) )
: Icon( : Icon(
@@ -11,7 +11,7 @@ class ProfileState {
this.success = false, this.success = false,
this.loading = false, this.loading = false,
}); });
final File file; final File? file;
final bool success; final bool success;
final bool loading; final bool loading;
} }
@@ -39,7 +39,7 @@ class ProfileVerifyView extends StatelessWidget {
onTap: context.read<ProfileVerifyCubit>().pickImage, onTap: context.read<ProfileVerifyCubit>().pickImage,
child: snapshot.file != null child: snapshot.file != null
? Image.file( ? Image.file(
snapshot.file, snapshot.file!,
fit: BoxFit.cover, fit: BoxFit.cover,
) )
: Icon( : Icon(
+10 -9
View File
@@ -4,22 +4,23 @@ publish_to: 'none'
version: 1.0.0+1 version: 1.0.0+1
environment: environment:
sdk: ">=2.7.0 <3.0.0" sdk: '>=2.12.0 <3.0.0'
dependencies: dependencies:
flutter: flutter:
sdk: flutter sdk: flutter
flutter_bloc: 6.1.2 flutter_bloc: ^7.0.0
stream_chat_flutter: 1.3.0-beta stream_chat_flutter: ^2.0.0-nullsafety.3
uuid: 2.2.2 uuid: ^3.0.4
firebase_core: 0.7.0 firebase_core: ^1.2.0
google_sign_in: 4.5.9 google_sign_in: ^5.0.3
firebase_auth: 0.20.0+1 firebase_auth: ^1.2.0
firebase_storage: 7.0.0 firebase_storage: ^8.1.0
shared_preferences: 0.5.12+4 shared_preferences: ^2.0.5
http: any http: any
collection: ^1.15.0-nullsafety.4
dev_dependencies: dev_dependencies:
flutter_test: flutter_test: