mirror of
https://github.com/Iconica-Development/flutter_chat.git
synced 2025-05-19 10:53:51 +02:00
fix: add remaining documentation and fix deep nesting
This commit is contained in:
parent
5d959184de
commit
244e1e1499
10 changed files with 136 additions and 103 deletions
3
.fvmrc
Normal file
3
.fvmrc
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
{
|
||||||
|
"flutter": "3.24.3"
|
||||||
|
}
|
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -49,3 +49,6 @@ web
|
||||||
windows
|
windows
|
||||||
|
|
||||||
pubspec_overrides.yaml
|
pubspec_overrides.yaml
|
||||||
|
|
||||||
|
# FVM Version Cache
|
||||||
|
.fvm/
|
|
@ -97,6 +97,7 @@ class ChatModel {
|
||||||
unreadMessageCount: unreadMessageCount ?? this.unreadMessageCount,
|
unreadMessageCount: unreadMessageCount ?? this.unreadMessageCount,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
/// Creates a map representation of this object
|
||||||
Map<String, dynamic> toMap() => {
|
Map<String, dynamic> toMap() => {
|
||||||
"users": users,
|
"users": users,
|
||||||
"isGroupChat": isGroupChat,
|
"isGroupChat": isGroupChat,
|
||||||
|
|
|
@ -16,6 +16,7 @@ class MessageModel {
|
||||||
required this.senderId,
|
required this.senderId,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/// Creates a message model instance given a map instance
|
||||||
factory MessageModel.fromMap(String id, Map<String, dynamic> map) =>
|
factory MessageModel.fromMap(String id, Map<String, dynamic> map) =>
|
||||||
MessageModel(
|
MessageModel(
|
||||||
chatId: map["chatId"],
|
chatId: map["chatId"],
|
||||||
|
@ -62,6 +63,7 @@ class MessageModel {
|
||||||
senderId: senderId ?? this.senderId,
|
senderId: senderId ?? this.senderId,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
/// Creates a map representation of this object
|
||||||
Map<String, dynamic> toMap() => {
|
Map<String, dynamic> toMap() => {
|
||||||
"chatId": chatId,
|
"chatId": chatId,
|
||||||
"text": text,
|
"text": text,
|
||||||
|
|
|
@ -14,6 +14,7 @@ class UserModel {
|
||||||
this.imageUrl,
|
this.imageUrl,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/// Creates a user based on a given map [data]
|
||||||
factory UserModel.fromMap(String id, Map<String, dynamic> data) => UserModel(
|
factory UserModel.fromMap(String id, Map<String, dynamic> data) => UserModel(
|
||||||
id: id,
|
id: id,
|
||||||
firstName: data["first_name"],
|
firstName: data["first_name"],
|
||||||
|
|
|
@ -4,20 +4,25 @@ 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";
|
||||||
|
|
||||||
|
/// Firebase implementation of the chat repository
|
||||||
class FirebaseChatRepository implements ChatRepositoryInterface {
|
class FirebaseChatRepository implements ChatRepositoryInterface {
|
||||||
|
/// Creates a firebase implementation of the chat repository
|
||||||
FirebaseChatRepository({
|
FirebaseChatRepository({
|
||||||
FirebaseFirestore? firestore,
|
FirebaseFirestore? firestore,
|
||||||
FirebaseStorage? storage,
|
FirebaseStorage? storage,
|
||||||
this.chatCollection = "chats",
|
String chatCollection = "chats",
|
||||||
this.messageCollection = "messages",
|
String messageCollection = "messages",
|
||||||
this.mediaPath = "chat",
|
String mediaPath = "chat",
|
||||||
}) : _firestore = firestore ?? FirebaseFirestore.instance,
|
}) : _mediaPath = mediaPath,
|
||||||
|
_messageCollection = messageCollection,
|
||||||
|
_chatCollection = chatCollection,
|
||||||
|
_firestore = firestore ?? FirebaseFirestore.instance,
|
||||||
_storage = storage ?? FirebaseStorage.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;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> createChat({
|
Future<void> createChat({
|
||||||
|
@ -36,17 +41,17 @@ class FirebaseChatRepository implements ChatRepositoryInterface {
|
||||||
"imageUrl": imageUrl,
|
"imageUrl": imageUrl,
|
||||||
"createdAt": DateTime.now().millisecondsSinceEpoch,
|
"createdAt": DateTime.now().millisecondsSinceEpoch,
|
||||||
};
|
};
|
||||||
await _firestore.collection(chatCollection).add(chatData);
|
await _firestore.collection(_chatCollection).add(chatData);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> deleteChat({required String chatId}) async {
|
Future<void> deleteChat({required String chatId}) async {
|
||||||
await _firestore.collection(chatCollection).doc(chatId).delete();
|
await _firestore.collection(_chatCollection).doc(chatId).delete();
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Stream<ChatModel> getChat({required String chatId}) => _firestore
|
Stream<ChatModel> getChat({required String chatId}) => _firestore
|
||||||
.collection(chatCollection)
|
.collection(_chatCollection)
|
||||||
.doc(chatId)
|
.doc(chatId)
|
||||||
.snapshots()
|
.snapshots()
|
||||||
.map((snapshot) {
|
.map((snapshot) {
|
||||||
|
@ -56,7 +61,7 @@ class FirebaseChatRepository implements ChatRepositoryInterface {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Stream<List<ChatModel>?> getChats({required String userId}) => _firestore
|
Stream<List<ChatModel>?> getChats({required String userId}) => _firestore
|
||||||
.collection(chatCollection)
|
.collection(_chatCollection)
|
||||||
.where("users", arrayContains: userId)
|
.where("users", arrayContains: userId)
|
||||||
.snapshots()
|
.snapshots()
|
||||||
.map(
|
.map(
|
||||||
|
@ -72,9 +77,9 @@ class FirebaseChatRepository implements ChatRepositoryInterface {
|
||||||
required String messageId,
|
required String messageId,
|
||||||
}) =>
|
}) =>
|
||||||
_firestore
|
_firestore
|
||||||
.collection(chatCollection)
|
.collection(_chatCollection)
|
||||||
.doc(chatId)
|
.doc(chatId)
|
||||||
.collection(messageCollection)
|
.collection(_messageCollection)
|
||||||
.doc(messageId)
|
.doc(messageId)
|
||||||
.snapshots()
|
.snapshots()
|
||||||
.map((snapshot) {
|
.map((snapshot) {
|
||||||
|
@ -93,9 +98,9 @@ class FirebaseChatRepository implements ChatRepositoryInterface {
|
||||||
required int page,
|
required int page,
|
||||||
}) =>
|
}) =>
|
||||||
_firestore
|
_firestore
|
||||||
.collection(chatCollection)
|
.collection(_chatCollection)
|
||||||
.doc(chatId)
|
.doc(chatId)
|
||||||
.collection(messageCollection)
|
.collection(_messageCollection)
|
||||||
.orderBy("timestamp")
|
.orderBy("timestamp")
|
||||||
.limit(pageSize)
|
.limit(pageSize)
|
||||||
.snapshots()
|
.snapshots()
|
||||||
|
@ -116,7 +121,7 @@ class FirebaseChatRepository implements ChatRepositoryInterface {
|
||||||
String? chatId,
|
String? chatId,
|
||||||
}) async* {
|
}) 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();
|
||||||
|
@ -157,15 +162,15 @@ class FirebaseChatRepository implements ChatRepositoryInterface {
|
||||||
);
|
);
|
||||||
|
|
||||||
await _firestore
|
await _firestore
|
||||||
.collection(chatCollection)
|
.collection(_chatCollection)
|
||||||
.doc(chatId)
|
.doc(chatId)
|
||||||
.collection(messageCollection)
|
.collection(_messageCollection)
|
||||||
.doc(messageId)
|
.doc(messageId)
|
||||||
.set(
|
.set(
|
||||||
message.toMap(),
|
message.toMap(),
|
||||||
);
|
);
|
||||||
|
|
||||||
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),
|
||||||
|
@ -177,7 +182,7 @@ class FirebaseChatRepository implements ChatRepositoryInterface {
|
||||||
@override
|
@override
|
||||||
Future<void> updateChat({required ChatModel chat}) async {
|
Future<void> updateChat({required ChatModel chat}) async {
|
||||||
await _firestore
|
await _firestore
|
||||||
.collection(chatCollection)
|
.collection(_chatCollection)
|
||||||
.doc(chat.id)
|
.doc(chat.id)
|
||||||
.update(chat.toMap());
|
.update(chat.toMap());
|
||||||
}
|
}
|
||||||
|
@ -187,7 +192,7 @@ class FirebaseChatRepository implements ChatRepositoryInterface {
|
||||||
required String path,
|
required String path,
|
||||||
required Uint8List image,
|
required Uint8List image,
|
||||||
}) async {
|
}) async {
|
||||||
var ref = _storage.ref().child(mediaPath).child(path);
|
var ref = _storage.ref().child(_mediaPath).child(path);
|
||||||
var uploadTask = ref.putData(image);
|
var uploadTask = ref.putData(image);
|
||||||
var snapshot = await uploadTask.whenComplete(() => {});
|
var snapshot = await uploadTask.whenComplete(() => {});
|
||||||
return snapshot.ref.getDownloadURL();
|
return snapshot.ref.getDownloadURL();
|
||||||
|
|
|
@ -1,17 +1,20 @@
|
||||||
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";
|
||||||
|
|
||||||
|
/// Firebase implementation of a user respository for chats.
|
||||||
class FirebaseUserRepository implements UserRepositoryInterface {
|
class FirebaseUserRepository implements UserRepositoryInterface {
|
||||||
|
/// Creates a firebase implementation of a user respository for chats.
|
||||||
FirebaseUserRepository({
|
FirebaseUserRepository({
|
||||||
FirebaseFirestore? firestore,
|
FirebaseFirestore? firestore,
|
||||||
this.userCollection = "users",
|
String userCollection = "users",
|
||||||
}) : _firestore = firestore ?? FirebaseFirestore.instance;
|
}) : _userCollection = userCollection,
|
||||||
|
_firestore = firestore ?? FirebaseFirestore.instance;
|
||||||
final FirebaseFirestore _firestore;
|
final FirebaseFirestore _firestore;
|
||||||
final String userCollection;
|
final String _userCollection;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Stream<List<UserModel>> getAllUsers() =>
|
Stream<List<UserModel>> getAllUsers() =>
|
||||||
_firestore.collection(userCollection).snapshots().map(
|
_firestore.collection(_userCollection).snapshots().map(
|
||||||
(querySnapshot) => querySnapshot.docs
|
(querySnapshot) => querySnapshot.docs
|
||||||
.map(
|
.map(
|
||||||
(doc) => UserModel.fromMap(
|
(doc) => UserModel.fromMap(
|
||||||
|
@ -24,7 +27,7 @@ class FirebaseUserRepository implements UserRepositoryInterface {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Stream<UserModel> getUser({required String userId}) =>
|
Stream<UserModel> getUser({required String userId}) =>
|
||||||
_firestore.collection(userCollection).doc(userId).snapshots().map(
|
_firestore.collection(_userCollection).doc(userId).snapshots().map(
|
||||||
(snapshot) => UserModel.fromMap(
|
(snapshot) => UserModel.fromMap(
|
||||||
snapshot.id,
|
snapshot.id,
|
||||||
snapshot.data()!,
|
snapshot.data()!,
|
||||||
|
|
|
@ -6,22 +6,36 @@ 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";
|
||||||
|
|
||||||
|
/// Type of screen, used in custom screen builders
|
||||||
enum ScreenType {
|
enum ScreenType {
|
||||||
|
/// Screen displaying an overview of chats
|
||||||
chatScreen(screen: ChatScreen),
|
chatScreen(screen: ChatScreen),
|
||||||
|
|
||||||
|
/// Screen displaying a single chat
|
||||||
chatDetailScreen(screen: ChatDetailScreen),
|
chatDetailScreen(screen: ChatDetailScreen),
|
||||||
|
|
||||||
|
/// Screen displaying the profile of a user within a chat
|
||||||
chatProfileScreen(screen: ChatProfileScreen),
|
chatProfileScreen(screen: ChatProfileScreen),
|
||||||
|
|
||||||
|
/// Screen with a form to create a new chat
|
||||||
newChatScreen(screen: NewChatScreen),
|
newChatScreen(screen: NewChatScreen),
|
||||||
|
|
||||||
|
/// Screen with a form to create a new group chat
|
||||||
newGroupChatScreen(screen: NewGroupChatScreen),
|
newGroupChatScreen(screen: NewGroupChatScreen),
|
||||||
|
|
||||||
|
/// Screen displaying all group chats
|
||||||
newGroupChatOverview(screen: NewGroupChatOverview);
|
newGroupChatOverview(screen: NewGroupChatOverview);
|
||||||
|
|
||||||
const ScreenType({
|
const ScreenType({
|
||||||
required this.screen,
|
required Type screen,
|
||||||
});
|
}) : _screen = screen;
|
||||||
|
|
||||||
final Type screen;
|
final Type _screen;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Extension for mapping widgets to [ScreenType]s
|
||||||
extension MapFromWidget on Widget {
|
extension MapFromWidget on Widget {
|
||||||
|
/// returns corresponding [ScreenType]
|
||||||
ScreenType get mapScreenType =>
|
ScreenType get mapScreenType =>
|
||||||
ScreenType.values.firstWhere((e) => e.screen == runtimeType);
|
ScreenType.values.firstWhere((e) => e._screen == runtimeType);
|
||||||
}
|
}
|
||||||
|
|
|
@ -35,6 +35,7 @@ class ChatProfileScreen extends StatelessWidget {
|
||||||
/// Callback function triggered when a user is tapped
|
/// Callback function triggered when a user is tapped
|
||||||
final Function(String)? onTapUser;
|
final Function(String)? onTapUser;
|
||||||
|
|
||||||
|
/// instance of a chat service
|
||||||
final ChatService service;
|
final ChatService service;
|
||||||
|
|
||||||
/// Callback function triggered when the start chat button is pressed
|
/// Callback function triggered when the start chat button is pressed
|
||||||
|
@ -135,6 +136,76 @@ class _Body extends StatelessWidget {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
var theme = Theme.of(context);
|
var theme = Theme.of(context);
|
||||||
|
|
||||||
|
var chatUserDisplay = Wrap(
|
||||||
|
children: [
|
||||||
|
...chat!.users.map(
|
||||||
|
(tappedUser) => Padding(
|
||||||
|
padding: const EdgeInsets.only(
|
||||||
|
bottom: 8,
|
||||||
|
right: 8,
|
||||||
|
),
|
||||||
|
child: InkWell(
|
||||||
|
onTap: () {
|
||||||
|
onTapUser?.call(tappedUser);
|
||||||
|
},
|
||||||
|
child: Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.start,
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
FutureBuilder<UserModel>(
|
||||||
|
future: service.getUser(userId: tappedUser).first,
|
||||||
|
builder: (context, snapshot) {
|
||||||
|
if (snapshot.connectionState == ConnectionState.waiting) {
|
||||||
|
return const CircularProgressIndicator();
|
||||||
|
}
|
||||||
|
|
||||||
|
var user = snapshot.data;
|
||||||
|
|
||||||
|
if (user == null) {
|
||||||
|
return const SizedBox.shrink();
|
||||||
|
}
|
||||||
|
|
||||||
|
return options.builders.userAvatarBuilder?.call(
|
||||||
|
context,
|
||||||
|
user,
|
||||||
|
44,
|
||||||
|
) ??
|
||||||
|
Avatar(
|
||||||
|
boxfit: BoxFit.cover,
|
||||||
|
user: User(
|
||||||
|
firstName: user.firstName,
|
||||||
|
lastName: user.lastName,
|
||||||
|
imageUrl:
|
||||||
|
user.imageUrl != null || user.imageUrl != ""
|
||||||
|
? user.imageUrl
|
||||||
|
: null,
|
||||||
|
),
|
||||||
|
size: 60,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
var targetUser = user ??
|
||||||
|
(
|
||||||
|
chat != null
|
||||||
|
? UserModel(
|
||||||
|
id: UniqueKey().toString(),
|
||||||
|
firstName: chat?.chatName,
|
||||||
|
imageUrl: chat?.imageUrl,
|
||||||
|
)
|
||||||
|
: UserModel(
|
||||||
|
id: UniqueKey().toString(),
|
||||||
|
firstName: options.translations.groupNameEmpty,
|
||||||
|
),
|
||||||
|
) as UserModel;
|
||||||
return Stack(
|
return Stack(
|
||||||
children: [
|
children: [
|
||||||
ListView(
|
ListView(
|
||||||
|
@ -145,20 +216,7 @@ class _Body extends StatelessWidget {
|
||||||
children: [
|
children: [
|
||||||
options.builders.userAvatarBuilder?.call(
|
options.builders.userAvatarBuilder?.call(
|
||||||
context,
|
context,
|
||||||
user ??
|
targetUser,
|
||||||
(
|
|
||||||
chat != null
|
|
||||||
? UserModel(
|
|
||||||
id: UniqueKey().toString(),
|
|
||||||
firstName: chat?.chatName,
|
|
||||||
imageUrl: chat?.imageUrl,
|
|
||||||
)
|
|
||||||
: UserModel(
|
|
||||||
id: UniqueKey().toString(),
|
|
||||||
firstName:
|
|
||||||
options.translations.groupNameEmpty,
|
|
||||||
),
|
|
||||||
) as UserModel,
|
|
||||||
60,
|
60,
|
||||||
) ??
|
) ??
|
||||||
Avatar(
|
Avatar(
|
||||||
|
@ -223,65 +281,7 @@ class _Body extends StatelessWidget {
|
||||||
const SizedBox(
|
const SizedBox(
|
||||||
height: 12,
|
height: 12,
|
||||||
),
|
),
|
||||||
Wrap(
|
chatUserDisplay,
|
||||||
children: [
|
|
||||||
...chat!.users.map(
|
|
||||||
(tappedUser) => Padding(
|
|
||||||
padding: const EdgeInsets.only(
|
|
||||||
bottom: 8,
|
|
||||||
right: 8,
|
|
||||||
),
|
|
||||||
child: InkWell(
|
|
||||||
onTap: () {
|
|
||||||
onTapUser?.call(tappedUser);
|
|
||||||
},
|
|
||||||
child: Column(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.start,
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
children: [
|
|
||||||
FutureBuilder<UserModel>(
|
|
||||||
future: service
|
|
||||||
.getUser(userId: tappedUser)
|
|
||||||
.first,
|
|
||||||
builder: (context, snapshot) {
|
|
||||||
if (snapshot.connectionState ==
|
|
||||||
ConnectionState.waiting) {
|
|
||||||
return const CircularProgressIndicator();
|
|
||||||
}
|
|
||||||
|
|
||||||
var user = snapshot.data;
|
|
||||||
|
|
||||||
if (user == null) {
|
|
||||||
return const SizedBox.shrink();
|
|
||||||
}
|
|
||||||
|
|
||||||
return options.builders.userAvatarBuilder
|
|
||||||
?.call(
|
|
||||||
context,
|
|
||||||
user,
|
|
||||||
44,
|
|
||||||
) ??
|
|
||||||
Avatar(
|
|
||||||
boxfit: BoxFit.cover,
|
|
||||||
user: User(
|
|
||||||
firstName: user.firstName,
|
|
||||||
lastName: user.lastName,
|
|
||||||
imageUrl: user.imageUrl != null ||
|
|
||||||
user.imageUrl != ""
|
|
||||||
? user.imageUrl
|
|
||||||
: null,
|
|
||||||
),
|
|
||||||
size: 60,
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
|
@ -50,6 +50,7 @@ Future<void> onPressSelectImage(
|
||||||
).then(
|
).then(
|
||||||
(image) async {
|
(image) async {
|
||||||
if (image == null) return;
|
if (image == null) return;
|
||||||
|
if (!context.mounted) return;
|
||||||
var messenger = ScaffoldMessenger.of(context)
|
var messenger = ScaffoldMessenger.of(context)
|
||||||
..showSnackBar(
|
..showSnackBar(
|
||||||
_getImageLoadingSnackbar(options.translations),
|
_getImageLoadingSnackbar(options.translations),
|
||||||
|
|
Loading…
Reference in a new issue