fix: conflicts

This commit is contained in:
Niels Gorter 2024-08-21 12:07:49 +02:00 committed by Freek van de Ven
commit 90a8ea6993
16 changed files with 203 additions and 212 deletions

View file

@ -12,4 +12,3 @@ jobs:
permissions: write-all permissions: write-all
with: with:
subfolder: '.' # add optional subfolder to run workflow in subfolder: '.' # add optional subfolder to run workflow in
flutter_version: 3.19.6

View file

@ -10,25 +10,25 @@ class LocalUserRepository implements UserRepositoryInterface {
BehaviorSubject<List<UserModel>>(); BehaviorSubject<List<UserModel>>();
final List<UserModel> _users = [ final List<UserModel> _users = [
UserModel( const UserModel(
id: "1", id: "1",
firstName: "John", firstName: "John",
lastName: "Doe", lastName: "Doe",
imageUrl: "https://picsum.photos/200/300", imageUrl: "https://picsum.photos/200/300",
), ),
UserModel( const UserModel(
id: "2", id: "2",
firstName: "Jane", firstName: "Jane",
lastName: "Doe", lastName: "Doe",
imageUrl: "https://picsum.photos/200/300", imageUrl: "https://picsum.photos/200/300",
), ),
UserModel( const UserModel(
id: "3", id: "3",
firstName: "Frans", firstName: "Frans",
lastName: "Timmermans", lastName: "Timmermans",
imageUrl: "https://picsum.photos/200/300", imageUrl: "https://picsum.photos/200/300",
), ),
UserModel( const UserModel(
id: "4", id: "4",
firstName: "Hendrik-Jan", firstName: "Hendrik-Jan",
lastName: "De derde", lastName: "De derde",

View file

@ -25,6 +25,22 @@ class ChatModel {
this.unreadMessageCount = 0, this.unreadMessageCount = 0,
}); });
/// The factory chat model that creates a chat model from a map
factory ChatModel.fromMap(String id, Map<String, dynamic> data) => ChatModel(
id: id,
users: List<String>.from(data["users"]),
isGroupChat: data["isGroupChat"],
chatName: data["chatName"],
description: data["description"],
imageUrl: data["imageUrl"],
canBeDeleted: data["canBeDeleted"] ?? true,
lastUsed: data["lastUsed"] != null
? DateTime.fromMillisecondsSinceEpoch(data["lastUsed"])
: null,
lastMessage: data["lastMessage"],
unreadMessageCount: data["unreadMessageCount"] ?? 0,
);
/// The chat id /// The chat id
final String id; final String id;
@ -81,34 +97,16 @@ class ChatModel {
unreadMessageCount: unreadMessageCount ?? this.unreadMessageCount, unreadMessageCount: unreadMessageCount ?? this.unreadMessageCount,
); );
/// The factory chat model that creates a chat model from a map
factory ChatModel.fromMap(String id, Map<String, dynamic> data) {
return ChatModel(
id: id,
users: List<String>.from(data['users']),
isGroupChat: data['isGroupChat'],
chatName: data['chatName'],
description: data['description'],
imageUrl: data['imageUrl'],
canBeDeleted: data['canBeDeleted'] ?? true,
lastUsed: data['lastUsed'] != null
? DateTime.fromMillisecondsSinceEpoch(data['lastUsed'])
: null,
lastMessage: data['lastMessage'],
unreadMessageCount: data['unreadMessageCount'] ?? 0,
);
}
Map<String, dynamic> toMap() => { Map<String, dynamic> toMap() => {
'users': users, "users": users,
'isGroupChat': isGroupChat, "isGroupChat": isGroupChat,
'chatName': chatName, "chatName": chatName,
'description': description, "description": description,
'imageUrl': imageUrl, "imageUrl": imageUrl,
'canBeDeleted': canBeDeleted, "canBeDeleted": canBeDeleted,
'lastUsed': lastUsed?.millisecondsSinceEpoch, "lastUsed": lastUsed?.millisecondsSinceEpoch,
'lastMessage': lastMessage, "lastMessage": lastMessage,
'unreadMessageCount': unreadMessageCount, "unreadMessageCount": unreadMessageCount,
}; };
} }

View file

@ -15,6 +15,17 @@ class MessageModel {
required this.timestamp, required this.timestamp,
required this.senderId, required this.senderId,
}); });
factory MessageModel.fromMap(String id, Map<String, dynamic> map) =>
MessageModel(
chatId: map["chatId"],
id: id,
text: map["text"],
imageUrl: map["imageUrl"],
timestamp: DateTime.fromMillisecondsSinceEpoch(map["timestamp"]),
senderId: map["senderId"],
);
/// The chat id /// The chat id
final String chatId; final String chatId;
@ -51,27 +62,14 @@ class MessageModel {
senderId: senderId ?? this.senderId, senderId: senderId ?? this.senderId,
); );
factory MessageModel.fromMap(String id, Map<String, dynamic> map) { Map<String, dynamic> toMap() => {
return MessageModel( "chatId": chatId,
chatId: map['chatId'], "text": text,
id: id, "imageUrl": imageUrl,
text: map['text'], "timestamp": timestamp.millisecondsSinceEpoch,
imageUrl: map['imageUrl'], "senderId": senderId,
timestamp: DateTime.fromMillisecondsSinceEpoch(map['timestamp']),
senderId: map['senderId'],
);
}
Map<String, dynamic> toMap() {
return {
'chatId': chatId,
'text': text,
'imageUrl': imageUrl,
'timestamp': timestamp.millisecondsSinceEpoch,
'senderId': senderId,
}; };
} }
}
/// Extension on [MessageModel] to check the message type /// Extension on [MessageModel] to check the message type
extension MessageType on MessageModel { extension MessageType on MessageModel {

View file

@ -14,6 +14,13 @@ class UserModel {
this.imageUrl, this.imageUrl,
}); });
factory UserModel.fromMap(String id, Map<String, dynamic> data) => UserModel(
id: id,
firstName: data["firstName"],
lastName: data["lastName"],
imageUrl: data["imageUrl"],
);
/// The user id /// The user id
final String id; final String id;
@ -25,15 +32,6 @@ class UserModel {
/// The user image url /// The user image url
final String? imageUrl; final String? imageUrl;
factory UserModel.fromMap(String id, Map<String, dynamic> data) {
return UserModel(
id: id,
firstName: data['firstName'],
lastName: data['lastName'],
imageUrl: data['imageUrl'],
);
}
} }
/// Extension on [UserModel] to get the user full name /// Extension on [UserModel] to get the user full name

View file

@ -188,9 +188,8 @@ class ChatService {
/// Returns a [Stream] of [int]. /// Returns a [Stream] of [int].
Stream<int> getUnreadMessagesCount({ Stream<int> getUnreadMessagesCount({
required String userId, required String userId,
}) { }) =>
return chatRepository.getUnreadMessagesCount(userId: userId); chatRepository.getUnreadMessagesCount(userId: userId);
}
/// Upload an image with the given parameters. /// Upload an image with the given parameters.
/// [path] is the image path. /// [path] is the image path.

View file

@ -1,2 +1,2 @@
export 'src/firebase_chat_repository.dart'; export "src/firebase_chat_repository.dart";
export 'src/firebase_user_repository.dart'; export "src/firebase_user_repository.dart";

View file

@ -1,25 +1,24 @@
import 'dart:typed_data'; import "dart:typed_data";
import 'package:chat_repository_interface/chat_repository_interface.dart'; import "package:chat_repository_interface/chat_repository_interface.dart";
import 'package:cloud_firestore/cloud_firestore.dart'; import "package:cloud_firestore/cloud_firestore.dart";
import 'package:firebase_storage/firebase_storage.dart'; import "package:firebase_storage/firebase_storage.dart";
class FirebaseChatRepository implements ChatRepositoryInterface { class FirebaseChatRepository implements ChatRepositoryInterface {
FirebaseChatRepository({
FirebaseFirestore? firestore,
FirebaseStorage? storage,
this.chatCollection = "chats",
this.messageCollection = "messages",
this.mediaPath = "chat",
}) : _firestore = firestore ?? FirebaseFirestore.instance,
_storage = storage ?? FirebaseStorage.instance;
final FirebaseFirestore _firestore; final FirebaseFirestore _firestore;
final FirebaseStorage _storage; final FirebaseStorage _storage;
final String chatCollection; final String chatCollection;
final String messageCollection; final String messageCollection;
final String mediaPath; final String mediaPath;
FirebaseChatRepository({
FirebaseFirestore? firestore,
FirebaseStorage? storage,
this.chatCollection = 'chats',
this.messageCollection = 'messages',
this.mediaPath = 'chat',
}) : _firestore = firestore ?? FirebaseFirestore.instance,
_storage = storage ?? FirebaseStorage.instance;
@override @override
Future<void> createChat({ Future<void> createChat({
required List<String> users, required List<String> users,
@ -29,13 +28,13 @@ class FirebaseChatRepository implements ChatRepositoryInterface {
String? imageUrl, String? imageUrl,
List<MessageModel>? messages, List<MessageModel>? messages,
}) async { }) async {
final chatData = { var chatData = {
'users': users, "users": users,
'isGroupChat': isGroupChat, "isGroupChat": isGroupChat,
'chatName': chatName, "chatName": chatName,
'description': description, "description": description,
'imageUrl': imageUrl, "imageUrl": imageUrl,
'createdAt': DateTime.now().millisecondsSinceEpoch, "createdAt": DateTime.now().millisecondsSinceEpoch,
}; };
await _firestore.collection(chatCollection).add(chatData); await _firestore.collection(chatCollection).add(chatData);
} }
@ -46,48 +45,45 @@ class FirebaseChatRepository implements ChatRepositoryInterface {
} }
@override @override
Stream<ChatModel> getChat({required String chatId}) { Stream<ChatModel> getChat({required String chatId}) => _firestore
return _firestore
.collection(chatCollection) .collection(chatCollection)
.doc(chatId) .doc(chatId)
.snapshots() .snapshots()
.map((snapshot) { .map((snapshot) {
var data = snapshot.data() as Map<String, dynamic>; var data = snapshot.data()!;
return ChatModel.fromMap(snapshot.id, data); return ChatModel.fromMap(snapshot.id, data);
}); });
}
@override @override
Stream<List<ChatModel>?> getChats({required String userId}) { Stream<List<ChatModel>?> getChats({required String userId}) => _firestore
return _firestore
.collection(chatCollection) .collection(chatCollection)
.where('users', arrayContains: userId) .where("users", arrayContains: userId)
.snapshots() .snapshots()
.map((querySnapshot) { .map(
return querySnapshot.docs.map((doc) { (querySnapshot) => querySnapshot.docs.map((doc) {
var data = doc.data(); var data = doc.data();
return ChatModel.fromMap(doc.id, data); return ChatModel.fromMap(doc.id, data);
}).toList(); }).toList(),
}); );
}
@override @override
Stream<MessageModel?> getMessage( Stream<MessageModel?> getMessage({
{required String chatId, required String messageId}) { required String chatId,
return _firestore required String messageId,
}) =>
_firestore
.collection(chatCollection) .collection(chatCollection)
.doc(chatId) .doc(chatId)
.collection(messageCollection) .collection(messageCollection)
.doc(messageId) .doc(messageId)
.snapshots() .snapshots()
.map((snapshot) { .map((snapshot) {
var data = snapshot.data() as Map<String, dynamic>; var data = snapshot.data()!;
return MessageModel.fromMap( return MessageModel.fromMap(
snapshot.id, snapshot.id,
data, data,
); );
}); });
}
@override @override
Stream<List<MessageModel>?> getMessages({ Stream<List<MessageModel>?> getMessages({
@ -95,41 +91,46 @@ class FirebaseChatRepository implements ChatRepositoryInterface {
required String userId, required String userId,
required int pageSize, required int pageSize,
required int page, required int page,
}) { }) =>
return _firestore _firestore
.collection(chatCollection) .collection(chatCollection)
.doc(chatId) .doc(chatId)
.collection(messageCollection) .collection(messageCollection)
.orderBy('timestamp') .orderBy("timestamp")
.limit(pageSize) .limit(pageSize)
.snapshots() .snapshots()
.map((query) => query.docs .map(
.map((snapshot) => MessageModel.fromMap( (query) => query.docs
.map(
(snapshot) => MessageModel.fromMap(
snapshot.id, snapshot.id,
snapshot.data(), snapshot.data(),
)) ),
.toList()); )
} .toList(),
);
@override @override
Stream<int> getUnreadMessagesCount( Stream<int> getUnreadMessagesCount({
{required String userId, String? chatId}) async* { required String userId,
String? chatId,
}) async* {
var query = _firestore var query = _firestore
.collection(chatCollection) .collection(chatCollection)
.where('users', arrayContains: userId) .where("users", arrayContains: userId)
.where('unreadMessageCount', isGreaterThan: 0) .where("unreadMessageCount", isGreaterThan: 0)
.snapshots(); .snapshots();
await for (var snapshot in query) { await for (var snapshot in query) {
var count = 0; var count = 0;
for (var doc in snapshot.docs) { for (var doc in snapshot.docs) {
var data = doc.data(); var data = doc.data();
var lastMessageKey = data['lastMessage']; var lastMessageKey = data["lastMessage"];
var message = var message =
await getMessage(chatId: doc.id, messageId: lastMessageKey).first; await getMessage(chatId: doc.id, messageId: lastMessageKey).first;
if (message?.senderId != userId) { if (message?.senderId != userId) {
count += data['unreadMessageCount'] as int; count += data["unreadMessageCount"] as int;
} }
} }
@ -152,7 +153,8 @@ class FirebaseChatRepository implements ChatRepositoryInterface {
text: text, text: text,
imageUrl: imageUrl, imageUrl: imageUrl,
timestamp: timestamp ?? DateTime.now(), timestamp: timestamp ?? DateTime.now(),
senderId: senderId); senderId: senderId,
);
await _firestore await _firestore
.collection(chatCollection) .collection(chatCollection)
@ -165,9 +167,9 @@ class FirebaseChatRepository implements ChatRepositoryInterface {
await _firestore.collection(chatCollection).doc(chatId).update( await _firestore.collection(chatCollection).doc(chatId).update(
{ {
'lastMessage': messageId, "lastMessage": messageId,
'unreadMessageCount': FieldValue.increment(1), "unreadMessageCount": FieldValue.increment(1),
'lastUsed': DateTime.now().millisecondsSinceEpoch, "lastUsed": DateTime.now().millisecondsSinceEpoch,
}, },
); );
} }
@ -181,11 +183,13 @@ class FirebaseChatRepository implements ChatRepositoryInterface {
} }
@override @override
Future<String> uploadImage( Future<String> uploadImage({
{required String path, required Uint8List image}) async { required String path,
final ref = _storage.ref().child(mediaPath).child(path); required Uint8List image,
final uploadTask = ref.putData(image); }) async {
final snapshot = await uploadTask.whenComplete(() => {}); var ref = _storage.ref().child(mediaPath).child(path);
return await snapshot.ref.getDownloadURL(); var uploadTask = ref.putData(image);
var snapshot = await uploadTask.whenComplete(() => {});
return snapshot.ref.getDownloadURL();
} }
} }

View file

@ -1,41 +1,33 @@
import 'package:chat_repository_interface/chat_repository_interface.dart'; import "package:chat_repository_interface/chat_repository_interface.dart";
import 'package:cloud_firestore/cloud_firestore.dart'; import "package:cloud_firestore/cloud_firestore.dart";
class FirebaseUserRepository implements UserRepositoryInterface { class FirebaseUserRepository implements UserRepositoryInterface {
FirebaseUserRepository({
FirebaseFirestore? firestore,
this.userCollection = "users",
}) : _firestore = firestore ?? FirebaseFirestore.instance;
final FirebaseFirestore _firestore; final FirebaseFirestore _firestore;
final String userCollection; final String userCollection;
FirebaseUserRepository({
FirebaseFirestore? firestore,
this.userCollection = 'users',
}) : _firestore = firestore ?? FirebaseFirestore.instance;
@override @override
Stream<List<UserModel>> getAllUsers() { Stream<List<UserModel>> getAllUsers() =>
return _firestore _firestore.collection(userCollection).snapshots().map(
.collection(userCollection) (querySnapshot) => querySnapshot.docs
.snapshots() .map(
.map((querySnapshot) { (doc) => UserModel.fromMap(
return querySnapshot.docs
.map((doc) => UserModel.fromMap(
doc.id, doc.id,
doc.data(), doc.data(),
)) ),
.toList(); )
}); .toList(),
} );
@override @override
Stream<UserModel> getUser({required String userId}) { Stream<UserModel> getUser({required String userId}) =>
return _firestore _firestore.collection(userCollection).doc(userId).snapshots().map(
.collection(userCollection) (snapshot) => UserModel.fromMap(
.doc(userId)
.snapshots()
.map((snapshot) {
return UserModel.fromMap(
snapshot.id, snapshot.id,
snapshot.data() as Map<String, dynamic>, snapshot.data()!,
),
); );
});
}
} }

View file

@ -4,7 +4,7 @@ publish_to: 'none'
version: 1.0.0+1 version: 1.0.0+1
environment: environment:
sdk: ^3.5.0 sdk: ^3.4.3
dependencies: dependencies:
flutter: flutter:

View file

@ -56,7 +56,7 @@ class ChatTranslations {
this.chatsTitle = "Chats", this.chatsTitle = "Chats",
this.chatsUnread = "unread", this.chatsUnread = "unread",
this.newChatButton = "Start chat", this.newChatButton = "Start chat",
this.newGroupChatButton = "Create a groupchat", this.newGroupChatButton = "Start a groupchat",
this.newChatTitle = "Start a chat", this.newChatTitle = "Start a chat",
this.image = "Image", this.image = "Image",
this.searchPlaceholder = "Search...", this.searchPlaceholder = "Search...",

View file

@ -1,10 +1,10 @@
import 'package:flutter/material.dart'; import "package:flutter/material.dart";
import 'package:flutter_chat/src/screens/chat_detail_screen.dart'; import "package:flutter_chat/src/screens/chat_detail_screen.dart";
import 'package:flutter_chat/src/screens/chat_profile_screen.dart'; import "package:flutter_chat/src/screens/chat_profile_screen.dart";
import 'package:flutter_chat/src/screens/chat_screen.dart'; import "package:flutter_chat/src/screens/chat_screen.dart";
import 'package:flutter_chat/src/screens/creation/new_chat_screen.dart'; import "package:flutter_chat/src/screens/creation/new_chat_screen.dart";
import 'package:flutter_chat/src/screens/creation/new_group_chat_overview.dart'; import "package:flutter_chat/src/screens/creation/new_group_chat_overview.dart";
import 'package:flutter_chat/src/screens/creation/new_group_chat_screen.dart'; import "package:flutter_chat/src/screens/creation/new_group_chat_screen.dart";
enum ScreenType { enum ScreenType {
chatScreen(screen: ChatScreen), chatScreen(screen: ChatScreen),
@ -22,7 +22,6 @@ enum ScreenType {
} }
extension MapFromWidget on Widget { extension MapFromWidget on Widget {
ScreenType get mapScreenType { ScreenType get mapScreenType =>
return ScreenType.values.firstWhere((e) => e.screen == this.runtimeType); ScreenType.values.firstWhere((e) => e.screen == runtimeType);
}
} }

View file

@ -117,7 +117,9 @@ class _NavigatorWrapper extends StatelessWidget {
route(context, chatProfileScreen(context, user, null)), route(context, chatProfileScreen(context, user, null)),
onUploadImage: (data) async { onUploadImage: (data) async {
var path = await chatService.uploadImage( var path = await chatService.uploadImage(
path: "chats/${chat.id}-$userId-${DateTime.now()}", image: data); path: "chats/${chat.id}-$userId-${DateTime.now()}",
image: data,
);
await chatService.sendMessage( await chatService.sendMessage(
messageId: "${chat.id}-$userId-${DateTime.now()}", messageId: "${chat.id}-$userId-${DateTime.now()}",
@ -191,7 +193,9 @@ class _NavigatorWrapper extends StatelessWidget {
String? path; String? path;
if (image != null) { if (image != null) {
path = await chatService.uploadImage( path = await chatService.uploadImage(
path: "groups/$title", image: image); path: "groups/$title",
image: image,
);
} }
var chat = await createGroupChat( var chat = await createGroupChat(
users, users,

View file

@ -63,7 +63,7 @@ class ChatProfileScreen extends StatelessWidget {
return options.builders.baseScreenBuilder!.call( return options.builders.baseScreenBuilder!.call(
context, context,
this.mapScreenType, mapScreenType,
_AppBar( _AppBar(
user: userModel, user: userModel,
chat: chatModel, chat: chatModel,
@ -288,7 +288,7 @@ class _Body extends StatelessWidget {
], ],
], ],
), ),
if (user?.id != currentUser) ...[ if (user != null && user?.id != currentUser) ...[
Align( Align(
alignment: Alignment.bottomCenter, alignment: Alignment.bottomCenter,
child: Padding( child: Padding(

View file

@ -60,7 +60,7 @@ class ChatScreen extends StatelessWidget {
return chatOptions.builders.baseScreenBuilder!.call( return chatOptions.builders.baseScreenBuilder!.call(
context, context,
this.mapScreenType, mapScreenType,
_AppBar( _AppBar(
userId: userId, userId: userId,
chatOptions: chatOptions, chatOptions: chatOptions,

View file

@ -50,7 +50,7 @@ class NewGroupChatOverview extends StatelessWidget {
return options.builders.baseScreenBuilder!.call( return options.builders.baseScreenBuilder!.call(
context, context,
this.mapScreenType, mapScreenType,
_AppBar( _AppBar(
options: options, options: options,
), ),