feat: refactor

This commit is contained in:
Stein Milder 2022-12-16 13:10:57 +01:00
parent f55c43653c
commit 4df2adb984
27 changed files with 553 additions and 588 deletions

View file

@ -4,29 +4,5 @@
library flutter_community_chat;
import 'package:flutter/material.dart';
import 'package:flutter_community_chat/service/chat_service.dart';
import 'package:flutter_community_chat_interface/flutter_community_chat_interface.dart';
import 'package:flutter_community_chat_view/flutter_community_chat_view.dart';
export 'package:flutter_community_chat_view/flutter_community_chat_view.dart';
export 'package:flutter_community_chat/service/chat_service.dart';
export 'package:flutter_community_chat_interface/flutter_community_chat_interface.dart';
class CommunityChat extends StatelessWidget {
const CommunityChat({
required this.chatService,
super.key,
});
final ChatService chatService;
@override
Widget build(BuildContext context) => ChatScreen(
chats: chatService.dataProvider.getChatsStream(),
onPressStartChat: () => chatService.onPressStartChat(context),
onPressChat: (chat) => chatService.onPressChat(context, chat),
onDeleteChat: (ChatModel chat) => chatService.deleteChat(chat),
options: chatService.options,
translations: chatService.translations(context),
);
}

View file

@ -1,130 +0,0 @@
import 'dart:typed_data';
import 'package:flutter/material.dart';
import 'package:flutter_community_chat/ui/components/image_loading_snackbar.dart';
import 'package:flutter_community_chat_interface/flutter_community_chat_interface.dart';
import 'package:flutter_community_chat_view/flutter_community_chat_view.dart';
import 'package:flutter_image_picker/flutter_image_picker.dart';
abstract class ChatService {
ChatService({
required this.options,
required this.imagePickerConfig,
required this.dataProvider,
});
final CommunityChatInterface dataProvider;
final ChatOptions options;
final ImagePickerConfig imagePickerConfig;
bool _isFetchingUsers = false;
ImagePickerTheme imagePickerTheme(BuildContext context) =>
const ImagePickerTheme();
ChatTranslations translations(BuildContext context);
Future<void> _push(BuildContext context, Widget widget) =>
Navigator.of(context).push(
MaterialPageRoute(builder: (context) => widget),
);
void _pop(BuildContext context) => Navigator.of(context).pop();
Future<void> onPressStartChat(BuildContext context) async {
if (!_isFetchingUsers) {
_isFetchingUsers = true;
await dataProvider.getChatUsers().then(
(users) {
_isFetchingUsers = false;
_push(
context,
buildNewChatScreen(context, users),
);
},
);
}
}
Widget buildNewChatScreen(
BuildContext context,
List<ChatUserModel> users,
) =>
NewChatScreen(
options: options,
translations: translations(context),
onPressCreateChat: (user) => onPressChat(
context,
PersonalChatModel(user: user),
popBeforePush: true,
),
users: users,
);
Future<void> onPressChat(
BuildContext context,
ChatModel chat, {
bool popBeforePush = false,
}) =>
dataProvider.setChat(chat).then((_) {
if (popBeforePush) {
_pop(context);
}
_push(
context,
buildChatDetailScreen(context, chat),
);
});
Widget buildChatDetailScreen(
BuildContext context,
ChatModel chat,
) =>
ChatDetailScreen(
options: options,
translations: translations(context),
chat: chat,
chatMessages: dataProvider.getMessagesStream(),
onPressSelectImage: (ChatModel chat) => onPressSelectImage(
context,
chat,
),
onMessageSubmit: (ChatModel chat, String content) =>
dataProvider.sendTextMessage(content),
);
Future<void> onPressSelectImage(
BuildContext context,
ChatModel chat,
) =>
showModalBottomSheet<Uint8List?>(
context: context,
builder: (BuildContext context) => options.imagePickerContainerBuilder(
ImagePicker(
customButton: options.closeImagePickerButtonBuilder(
context,
() => Navigator.of(context).pop(),
translations(context),
),
imagePickerConfig: imagePickerConfig,
imagePickerTheme: imagePickerTheme(context),
),
),
).then(
(image) async {
var messenger = ScaffoldMessenger.of(context);
messenger.showSnackBar(
getImageLoadingSnackbar(
translations(context),
),
);
if (image != null) {
await dataProvider.sendImageMessage(image);
}
messenger.hideCurrentSnackBar();
},
);
Future<void> deleteChat(ChatModel chat) => dataProvider.deleteChat(chat);
}

View file

@ -179,7 +179,7 @@ packages:
description:
path: "packages/flutter_community_chat_interface"
ref: HEAD
resolved-ref: "6a9e88ec8d07118e1543b1a82aa5482d6832cbf8"
resolved-ref: c644e1affb1dd4f570bf0e4ae2e950f5e9d83c37
url: "https://github.com/Iconica-Development/flutter_community_chat.git"
source: git
version: "0.0.1"
@ -188,7 +188,7 @@ packages:
description:
path: "packages/flutter_community_chat_view"
ref: HEAD
resolved-ref: "6a9e88ec8d07118e1543b1a82aa5482d6832cbf8"
resolved-ref: c644e1affb1dd4f570bf0e4ae2e950f5e9d83c37
url: "https://github.com/Iconica-Development/flutter_community_chat.git"
source: git
version: "0.0.1"
@ -202,7 +202,7 @@ packages:
source: git
version: "1.0.0"
flutter_image_picker:
dependency: "direct main"
dependency: transitive
description:
path: "."
ref: "1.0.3"
@ -319,7 +319,7 @@ packages:
name: matcher
url: "https://pub.dartlang.org"
source: hosted
version: "0.12.13"
version: "0.12.14"
material_color_utilities:
dependency: transitive
description:
@ -361,7 +361,7 @@ packages:
name: path
url: "https://pub.dartlang.org"
source: hosted
version: "1.8.2"
version: "1.8.3"
path_provider:
dependency: transitive
description:
@ -478,7 +478,7 @@ packages:
name: sqflite
url: "https://pub.dartlang.org"
source: hosted
version: "2.2.0+3"
version: "2.2.2"
sqflite_common:
dependency: transitive
description:
@ -527,7 +527,7 @@ packages:
name: test_api
url: "https://pub.dartlang.org"
source: hosted
version: "0.4.16"
version: "0.4.17"
typed_data:
dependency: transitive
description:

View file

@ -19,10 +19,6 @@ dependencies:
git:
url: https://github.com/Iconica-Development/flutter_community_chat.git
path: packages/flutter_community_chat_interface
flutter_image_picker:
git:
url: https://github.com/Iconica-Development/flutter_image_picker
ref: 1.0.3
dev_dependencies:
flutter_lints: ^2.0.0

View file

@ -7,22 +7,9 @@ class FirebaseChatOptions {
this.chatsCollectionName = 'chats',
this.messagesCollectionName = 'messages',
this.usersCollectionName = 'users',
this.userFilter,
});
final String chatsCollectionName;
final String messagesCollectionName;
final String usersCollectionName;
final FirebaseUserFilter? userFilter;
}
class FirebaseUserFilter {
const FirebaseUserFilter({
required this.field,
required this.expectedValue,
});
final String field;
final Object expectedValue;
}

View file

@ -4,84 +4,4 @@
library flutter_community_chat_firebase;
import 'dart:async';
import 'dart:typed_data';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:firebase_core/firebase_core.dart';
import 'package:firebase_storage/firebase_storage.dart';
import 'package:flutter_community_chat_firebase/config/firebase_chat_options.dart';
import 'package:flutter_community_chat_firebase/service/service.dart';
import 'package:flutter_community_chat_interface/flutter_community_chat_interface.dart';
export 'package:flutter_community_chat_firebase/service/service.dart';
class FirebaseCommunityChatDataProvider extends CommunityChatInterface {
late final FirebaseUserService _userService;
late final FirebaseMessageService _messageService;
late final FirebaseChatService _chatService;
final FirebaseChatOptions firebaseChatOptions;
FirebaseCommunityChatDataProvider({
this.firebaseChatOptions = const FirebaseChatOptions(),
FirebaseApp? app,
FirebaseUserService? firebaseUserService,
FirebaseMessageService? firebaseMessageService,
FirebaseChatService? firebaseChatService,
}) {
var appInstance = app ?? Firebase.app();
var db = FirebaseFirestore.instanceFor(app: appInstance);
var storage = FirebaseStorage.instanceFor(app: appInstance);
var auth = FirebaseAuth.instanceFor(app: appInstance);
_userService = firebaseUserService ??
FirebaseUserService(
db: db,
auth: auth,
options: firebaseChatOptions,
);
_chatService = firebaseChatService ??
FirebaseChatService(
db: db,
storage: storage,
userService: _userService,
options: firebaseChatOptions,
);
_messageService = firebaseMessageService ??
FirebaseMessageService(
db: db,
storage: storage,
userService: _userService,
chatService: _chatService,
options: firebaseChatOptions,
);
}
@override
Stream<List<ChatMessageModel>> getMessagesStream() =>
_messageService.getMessagesStream();
@override
Future<List<ChatUserModel>> getChatUsers() => _userService.getAllUsers();
@override
Stream<List<ChatModel>> getChatsStream() => _chatService.getChatsStream();
@override
Future<void> sendTextMessage(String text) =>
_messageService.sendTextMessage(text);
@override
Future<void> sendImageMessage(Uint8List image) =>
_messageService.sendImageMessage(image);
@override
Future<void> setChat(ChatModel chat) async =>
await _messageService.setChat(chat);
@override
Future<void> deleteChat(ChatModel chat) async =>
await _chatService.deleteChat(chat);
}

View file

@ -4,31 +4,38 @@
import 'dart:async';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:firebase_core/firebase_core.dart';
import 'package:firebase_storage/firebase_storage.dart';
import 'package:flutter/material.dart';
import 'package:flutter_community_chat_firebase/config/firebase_chat_options.dart';
import 'package:flutter_community_chat_firebase/dto/firebase_chat_document.dart';
import 'package:flutter_community_chat_interface/flutter_community_chat_interface.dart';
import 'firebase_user_service.dart';
class FirebaseChatService {
class FirebaseChatService implements ChatService {
late FirebaseFirestore _db;
late FirebaseStorage _storage;
late ChatUserService _userService;
late FirebaseChatOptions _options;
FirebaseChatService({
required this.db,
required this.storage,
required this.userService,
required this.options,
});
required ChatUserService userService,
FirebaseApp? app,
FirebaseChatOptions? options,
}) {
var appInstance = app ?? Firebase.app();
FirebaseFirestore db;
FirebaseStorage storage;
FirebaseUserService userService;
FirebaseChatOptions options;
_db = FirebaseFirestore.instanceFor(app: appInstance);
_storage = FirebaseStorage.instanceFor(app: appInstance);
_userService = userService;
_options = options ?? const FirebaseChatOptions();
}
StreamSubscription<QuerySnapshot> _addChatSubscription(
List<String> chatIds,
Function(List<ChatModel>) onReceivedChats,
Function(List<PersonalChatModel>) onReceivedChats,
) {
var snapshots = db
.collection(options.chatsCollectionName)
var snapshots = _db
.collection(_options.chatsCollectionName)
.where(
FieldPath.documentId,
whereIn: chatIds,
@ -41,8 +48,8 @@ class FirebaseChatService {
.snapshots();
return snapshots.listen((snapshot) async {
var currentUser = await userService.getCurrentUser();
List<ChatModel> chats = [];
var currentUser = await _userService.getCurrentUser();
List<PersonalChatModel> chats = [];
for (var chatDoc in snapshot.docs) {
var chatData = chatDoc.data();
@ -51,7 +58,7 @@ class FirebaseChatService {
if (chatData.lastMessage != null) {
var messageData = chatData.lastMessage!;
var sender = await userService.getUser(messageData.sender);
var sender = await _userService.getUser(messageData.sender);
if (sender != null) {
var timestamp = DateTime.fromMillisecondsSinceEpoch(
@ -77,7 +84,7 @@ class FirebaseChatService {
var otherUserId = List<String>.from(chatData.users).firstWhere(
(element) => element != currentUser?.id,
);
var otherUser = await userService.getUser(otherUserId);
var otherUser = await _userService.getUser(otherUserId);
if (otherUser != null) {
chats.add(
@ -117,14 +124,15 @@ class FirebaseChatService {
return result;
}
Stream<List<ChatModel>> _getSpecificChatsStream(List<String> chatIds) {
late StreamController<List<ChatModel>> controller;
Stream<List<PersonalChatModel>> _getSpecificChatsStream(
List<String> chatIds) {
late StreamController<List<PersonalChatModel>> controller;
List<StreamSubscription<QuerySnapshot>> subscriptions = [];
var splittedChatIds = _splitChatIds(chatIds: chatIds);
controller = StreamController<List<ChatModel>>(
controller = StreamController<List<PersonalChatModel>>(
onListen: () {
var chats = <int, List<ChatModel>>{};
var chats = <int, List<PersonalChatModel>>{};
for (var chatIdPair in splittedChatIds.asMap().entries) {
subscriptions.add(
@ -133,7 +141,7 @@ class FirebaseChatService {
(data) {
chats[chatIdPair.key] = data;
List<ChatModel> mergedChats = [];
List<PersonalChatModel> mergedChats = [];
mergedChats.addAll(
chats.values.expand((element) => element),
@ -160,15 +168,17 @@ class FirebaseChatService {
return controller.stream;
}
Stream<List<ChatModel>> getChatsStream() {
late StreamController<List<ChatModel>> controller;
@override
Stream<List<PersonalChatModel>> getChatsStream() {
late StreamController<List<PersonalChatModel>> controller;
StreamSubscription? userChatsSubscription;
StreamSubscription? chatsSubscription;
controller = StreamController(
onListen: () async {
var currentUser = await userService.getCurrentUser();
userChatsSubscription = db
.collection(options.usersCollectionName)
debugPrint('Start listening to chats');
var currentUser = await _userService.getCurrentUser();
userChatsSubscription = _db
.collection(_options.usersCollectionName)
.doc(currentUser?.id)
.collection('chats')
.snapshots()
@ -185,37 +195,34 @@ class FirebaseChatService {
onCancel: () {
chatsSubscription?.cancel();
userChatsSubscription?.cancel();
debugPrint('Stop listening to chats');
},
);
return controller.stream;
}
Future<ChatModel?> getChatByUser(ChatUserModel user) async {
var currentUser = await userService.getCurrentUser();
var chatCollection = await db
.collection(options.usersCollectionName)
@override
Future<PersonalChatModel> getOrCreateChatByUser(ChatUserModel user) async {
var currentUser = await _userService.getCurrentUser();
var collection = await _db
.collection(_options.usersCollectionName)
.doc(currentUser?.id)
.collection('chats')
.where('users', arrayContains: user.id)
.get();
for (var element in chatCollection.docs) {
var data = element.data();
if (data.containsKey('users') && data['users'] is List) {
if (data['users'].contains(user.id)) {
return PersonalChatModel(
id: element.id,
user: user,
);
}
}
}
var doc = collection.docs.isNotEmpty ? collection.docs.first : null;
return null;
return PersonalChatModel(
id: doc?.id,
user: user,
);
}
@override
Future<void> deleteChat(ChatModel chat) async {
var chatCollection = await db
.collection(options.chatsCollectionName)
var chatCollection = await _db
.collection(_options.chatsCollectionName)
.doc(chat.id)
.withConverter(
fromFirestore: (snapshot, _) =>
@ -228,8 +235,8 @@ class FirebaseChatService {
if (chatData != null) {
for (var userId in chatData.users) {
db
.collection(options.usersCollectionName)
_db
.collection(_options.usersCollectionName)
.doc(userId)
.collection('chats')
.doc(chat.id)
@ -237,9 +244,12 @@ class FirebaseChatService {
}
if (chat.id != null) {
await db.collection(options.chatsCollectionName).doc(chat.id).delete();
await storage
.ref(options.chatsCollectionName)
await _db
.collection(_options.chatsCollectionName)
.doc(chat.id)
.delete();
await _storage
.ref(_options.chatsCollectionName)
.child(chat.id!)
.listAll()
.then((value) {
@ -250,4 +260,48 @@ class FirebaseChatService {
}
}
}
@override
Future<PersonalChatModel> storeChatIfNot(PersonalChatModel chat) async {
if (chat.id == null) {
var currentUser = await _userService.getCurrentUser();
if (currentUser?.id == null || chat.user.id == null) {
return chat;
}
List<String> userIds = [
currentUser!.id!,
chat.user.id!,
];
var reference = await _db
.collection(_options.chatsCollectionName)
.withConverter(
fromFirestore: (snapshot, _) =>
FirebaseChatDocument.fromJson(snapshot.data()!, snapshot.id),
toFirestore: (chat, _) => chat.toJson(),
)
.add(
FirebaseChatDocument(
personal: true,
users: userIds,
lastUsed: Timestamp.now(),
),
);
for (var userId in userIds) {
await _db
.collection(_options.usersCollectionName)
.doc(userId)
.collection('chats')
.doc(reference.id)
.set({'users': userIds});
}
chat.id = reference.id;
}
return chat;
}
}

View file

@ -5,56 +5,40 @@
import 'dart:async';
import 'dart:typed_data';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:firebase_core/firebase_core.dart';
import 'package:firebase_storage/firebase_storage.dart';
import 'package:flutter/material.dart';
import 'package:flutter_community_chat_firebase/config/firebase_chat_options.dart';
import 'package:flutter_community_chat_firebase/dto/firebase_chat_document.dart';
import 'package:flutter_community_chat_firebase/dto/firebase_message_document.dart';
import 'package:flutter_community_chat_firebase/service/firebase_chat_service.dart';
import 'package:flutter_community_chat_firebase/service/firebase_user_service.dart';
import 'package:flutter_community_chat_interface/flutter_community_chat_interface.dart';
import 'package:uuid/uuid.dart';
class FirebaseMessageService {
FirebaseMessageService({
required this.db,
required this.storage,
required this.userService,
required this.chatService,
required this.options,
});
class FirebaseMessageService implements MessageService {
late final FirebaseFirestore _db;
late final FirebaseStorage _storage;
late final ChatUserService _userService;
late FirebaseChatOptions _options;
final FirebaseFirestore db;
final FirebaseStorage storage;
final FirebaseUserService userService;
final FirebaseChatService chatService;
FirebaseChatOptions options;
late StreamController<List<ChatMessageModel>> _controller;
StreamSubscription<QuerySnapshot>? _subscription;
ChatModel? _chat;
Future<void> setChat(ChatModel chat) async {
if (chat.id == null && chat is PersonalChatModel) {
var chatWithUser = await chatService.getChatByUser(chat.user);
FirebaseMessageService({
required ChatUserService userService,
FirebaseApp? app,
FirebaseChatOptions? options,
}) {
var appInstance = app ?? Firebase.app();
if (chatWithUser != null) {
_chat = chatWithUser;
return;
}
}
_chat = chat;
_db = FirebaseFirestore.instanceFor(app: appInstance);
_storage = FirebaseStorage.instanceFor(app: appInstance);
_userService = userService;
_options = options ?? const FirebaseChatOptions();
}
Future<void> _beforeSendMessage() async {
if (_chat != null) {
_chat = await createChatIfNotExists(_chat!);
}
}
Future<void> _sendMessage(ChatModel chat, Map<String, dynamic> data) async {
var currentUser = await _userService.getCurrentUser();
Future<void> _sendMessage(Map<String, dynamic> data) async {
var currentUser = await userService.getCurrentUser();
if (_chat?.id == null || currentUser == null) {
if (chat.id == null || currentUser == null) {
return;
}
@ -64,15 +48,15 @@ class FirebaseMessageService {
...data
};
var chatReference = db
var chatReference = _db
.collection(
options.chatsCollectionName,
_options.chatsCollectionName,
)
.doc(_chat!.id);
.doc(chat.id);
await chatReference
.collection(
options.messagesCollectionName,
_options.messagesCollectionName,
)
.add(message);
@ -80,35 +64,54 @@ class FirebaseMessageService {
'last_used': DateTime.now(),
'last_message': message,
});
if (chat.id != null && _controller.hasListener && (_subscription == null)) {
_subscription = _startListeningForMessages(chat);
}
}
Future<void> sendTextMessage(String text) => _beforeSendMessage().then(
(_) => _sendMessage({'text': text}),
);
Future<void> sendImageMessage(Uint8List image) => _beforeSendMessage().then(
(_) {
if (_chat?.id == null) {
return null;
}
var ref = storage.ref(
'${options.chatsCollectionName}/${_chat!.id}/${const Uuid().v4()}');
return ref.putData(image).then(
(_) => ref.getDownloadURL().then(
(url) {
_sendMessage({'image_url': url});
},
),
);
@override
Future<void> sendTextMessage({
required String text,
required ChatModel chat,
}) =>
_sendMessage(
chat,
{
'text': text,
},
);
Query<FirebaseMessageDocument> _getMessagesQuery(String chatId) => db
.collection(options.chatsCollectionName)
.doc(chatId)
.collection(options.messagesCollectionName)
@override
Future<void> sendImageMessage({
required ChatModel chat,
required Uint8List image,
}) async {
if (chat.id == null) {
return;
}
var ref = _storage
.ref('${_options.chatsCollectionName}/${chat.id}/${const Uuid().v4()}');
return ref.putData(image).then(
(_) => ref.getDownloadURL().then(
(url) {
_sendMessage(
chat,
{
'image_url': url,
},
);
},
),
);
}
Query<FirebaseMessageDocument> _getMessagesQuery(ChatModel chat) => _db
.collection(_options.chatsCollectionName)
.doc(chat.id)
.collection(_options.messagesCollectionName)
.orderBy('timestamp', descending: false)
.withConverter<FirebaseMessageDocument>(
fromFirestore: (snapshot, _) =>
@ -116,15 +119,18 @@ class FirebaseMessageService {
toFirestore: (user, _) => user.toJson(),
);
Stream<List<ChatMessageModel>> getMessagesStream() {
@override
Stream<List<ChatMessageModel>> getMessagesStream(ChatModel chat) {
_controller = StreamController<List<ChatMessageModel>>(
onListen: () {
if (_chat?.id != null) {
_subscription = _startListeningForMessages(_chat!);
if (chat.id != null) {
_subscription = _startListeningForMessages(chat);
}
},
onCancel: () {
_subscription?.cancel();
_subscription = null;
debugPrint('Canceling messages stream');
},
);
@ -132,7 +138,9 @@ class FirebaseMessageService {
}
StreamSubscription<QuerySnapshot> _startListeningForMessages(ChatModel chat) {
var snapshots = _getMessagesQuery(chat.id!).snapshots();
debugPrint('Start listening for messages in chat ${chat.id}');
var snapshots = _getMessagesQuery(chat).snapshots();
return snapshots.listen(
(snapshot) async {
@ -141,7 +149,7 @@ class FirebaseMessageService {
for (var messageDoc in snapshot.docs) {
var messageData = messageDoc.data();
var sender = await userService.getUser(messageData.sender);
var sender = await _userService.getUser(messageData.sender);
if (sender != null) {
var timestamp = DateTime.fromMillisecondsSinceEpoch(
@ -168,54 +176,4 @@ class FirebaseMessageService {
},
);
}
Future<ChatModel?> createChatIfNotExists(ChatModel chat) async {
if (chat.id == null) {
if (chat is! PersonalChatModel) {
return null;
}
var currentUser = await userService.getCurrentUser();
if (currentUser?.id == null || chat.user.id == null) {
return null;
}
List<String> userIds = [
currentUser!.id!,
chat.user.id!,
];
var reference = await db
.collection(options.chatsCollectionName)
.withConverter(
fromFirestore: (snapshot, _) =>
FirebaseChatDocument.fromJson(snapshot.data()!, snapshot.id),
toFirestore: (chat, _) => chat.toJson(),
)
.add(
FirebaseChatDocument(
personal: true,
users: userIds,
lastUsed: Timestamp.now(),
),
);
for (var userId in userIds) {
await db
.collection(options.usersCollectionName)
.doc(userId)
.collection('chats')
.doc(reference.id)
.set({'users': userIds});
}
chat.id = reference.id;
_subscription?.cancel();
_subscription = _startListeningForMessages(chat);
}
return chat;
}
}

View file

@ -4,25 +4,32 @@
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:firebase_core/firebase_core.dart';
import 'package:flutter_community_chat_firebase/config/firebase_chat_options.dart';
import 'package:flutter_community_chat_firebase/dto/firebase_user_document.dart';
import 'package:flutter_community_chat_interface/flutter_community_chat_interface.dart';
class FirebaseUserService {
class FirebaseUserService implements ChatUserService {
FirebaseUserService({
required this.db,
required this.auth,
required this.options,
});
FirebaseApp? app,
FirebaseChatOptions? options,
}) {
var appInstance = app ?? Firebase.app();
_db = FirebaseFirestore.instanceFor(app: appInstance);
_auth = FirebaseAuth.instanceFor(app: appInstance);
_options = options ?? const FirebaseChatOptions();
}
late FirebaseFirestore _db;
late FirebaseAuth _auth;
late FirebaseChatOptions _options;
FirebaseFirestore db;
FirebaseAuth auth;
FirebaseChatOptions options;
ChatUserModel? _currentUser;
final Map<String, ChatUserModel> _users = {};
CollectionReference<FirebaseUserDocument> get _userCollection => db
.collection(options.usersCollectionName)
CollectionReference<FirebaseUserDocument> get _userCollection => _db
.collection(_options.usersCollectionName)
.withConverter<FirebaseUserDocument>(
fromFirestore: (snapshot, _) => FirebaseUserDocument.fromJson(
snapshot.data()!,
@ -31,6 +38,7 @@ class FirebaseUserService {
toFirestore: (user, _) => user.toJson(),
);
@override
Future<ChatUserModel?> getUser(String id) async {
if (_users.containsKey(id)) {
return _users[id]!;
@ -54,11 +62,13 @@ class FirebaseUserService {
});
}
@override
Future<ChatUserModel?> getCurrentUser() async =>
_currentUser == null && auth.currentUser?.uid != null
? _currentUser = await getUser(auth.currentUser!.uid)
_currentUser == null && _auth.currentUser?.uid != null
? _currentUser = await getUser(_auth.currentUser!.uid)
: _currentUser;
@override
Future<List<ChatUserModel>> getAllUsers() async {
var currentUser = await getCurrentUser();
@ -67,13 +77,6 @@ class FirebaseUserService {
isNotEqualTo: currentUser?.id,
);
if (options.userFilter != null) {
query = query.where(
options.userFilter!.field,
isEqualTo: options.userFilter!.expectedValue,
);
}
var data = await query.get();
return data.docs.map((user) {

View file

@ -14,7 +14,7 @@ packages:
name: _flutterfire_internals
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.9"
version: "1.0.10"
analyzer:
dependency: transitive
description:
@ -84,21 +84,21 @@ packages:
name: cloud_firestore
url: "https://pub.dartlang.org"
source: hosted
version: "4.1.0"
version: "4.2.0"
cloud_firestore_platform_interface:
dependency: transitive
description:
name: cloud_firestore_platform_interface
url: "https://pub.dartlang.org"
source: hosted
version: "5.9.0"
version: "5.9.1"
cloud_firestore_web:
dependency: transitive
description:
name: cloud_firestore_web
url: "https://pub.dartlang.org"
source: hosted
version: "3.1.0"
version: "3.1.1"
code_builder:
dependency: transitive
description:
@ -154,28 +154,28 @@ packages:
name: firebase_auth
url: "https://pub.dartlang.org"
source: hosted
version: "4.1.4"
version: "4.2.1"
firebase_auth_platform_interface:
dependency: transitive
description:
name: firebase_auth_platform_interface
url: "https://pub.dartlang.org"
source: hosted
version: "6.11.3"
version: "6.11.5"
firebase_auth_web:
dependency: transitive
description:
name: firebase_auth_web
url: "https://pub.dartlang.org"
source: hosted
version: "5.1.3"
version: "5.2.1"
firebase_core:
dependency: "direct main"
description:
name: firebase_core
url: "https://pub.dartlang.org"
source: hosted
version: "2.3.0"
version: "2.4.0"
firebase_core_platform_interface:
dependency: transitive
description:
@ -189,28 +189,28 @@ packages:
name: firebase_core_web
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.1"
version: "2.0.2"
firebase_storage:
dependency: "direct main"
description:
name: firebase_storage
url: "https://pub.dartlang.org"
source: hosted
version: "11.0.6"
version: "11.0.8"
firebase_storage_platform_interface:
dependency: transitive
description:
name: firebase_storage_platform_interface
url: "https://pub.dartlang.org"
source: hosted
version: "4.1.24"
version: "4.1.25"
firebase_storage_web:
dependency: transitive
description:
name: firebase_storage_web
url: "https://pub.dartlang.org"
source: hosted
version: "3.3.16"
version: "3.3.17"
fixnum:
dependency: transitive
description:
@ -228,7 +228,7 @@ packages:
description:
path: "packages/flutter_community_chat_interface"
ref: HEAD
resolved-ref: "6a9e88ec8d07118e1543b1a82aa5482d6832cbf8"
resolved-ref: c644e1affb1dd4f570bf0e4ae2e950f5e9d83c37
url: "https://github.com/Iconica-Development/flutter_community_chat.git"
source: git
version: "0.0.1"

View file

@ -4,11 +4,6 @@
library flutter_community_chat_interface;
export 'src/community_chat_interface.dart';
export 'src/model/chat.dart';
export 'src/model/chat_image_message.dart';
export 'src/model/chat_message.dart';
export 'src/model/chat_text_message.dart';
export 'src/model/chat_user.dart';
export 'src/model/group_chat.dart';
export 'src/model/personal_chat.dart';
export 'package:flutter_community_chat_interface/src/chat_data_provider.dart';
export 'package:flutter_community_chat_interface/src/model/model.dart';
export 'package:flutter_community_chat_interface/src/service/service.dart';

View file

@ -0,0 +1,19 @@
// SPDX-FileCopyrightText: 2022 Iconica
//
// SPDX-License-Identifier: BSD-3-Clause
import 'package:flutter_community_chat_interface/flutter_community_chat_interface.dart';
import 'package:flutter_data_interface/flutter_data_interface.dart';
class ChatDataProvider extends DataInterface {
ChatDataProvider({
required this.chatService,
required this.userService,
required this.messageService,
}) : super(token: _token);
static final Object _token = Object();
final ChatUserService userService;
final ChatService chatService;
final MessageService messageService;
}

View file

@ -1,22 +0,0 @@
// SPDX-FileCopyrightText: 2022 Iconica
//
// SPDX-License-Identifier: BSD-3-Clause
import 'dart:typed_data';
import 'package:flutter_community_chat_interface/flutter_community_chat_interface.dart';
import 'package:flutter_data_interface/flutter_data_interface.dart';
abstract class CommunityChatInterface extends DataInterface {
CommunityChatInterface() : super(token: _token);
static final Object _token = Object();
Future<void> setChat(ChatModel chat);
Future<void> sendTextMessage(String text);
Future<void> sendImageMessage(Uint8List image);
Stream<List<ChatMessageModel>> getMessagesStream();
Stream<List<ChatModel>> getChatsStream();
Future<List<ChatUserModel>> getChatUsers();
Future<void> deleteChat(ChatModel chat);
}

View file

@ -0,0 +1,7 @@
export 'chat.dart';
export 'chat_image_message.dart';
export 'chat_text_message.dart';
export 'chat_user.dart';
export 'group_chat.dart';
export 'personal_chat.dart';
export 'chat_message.dart';

View file

@ -0,0 +1,8 @@
import 'package:flutter_community_chat_interface/flutter_community_chat_interface.dart';
abstract class ChatService {
Stream<List<PersonalChatModel>> getChatsStream();
Future<PersonalChatModel> getOrCreateChatByUser(ChatUserModel user);
Future<void> deleteChat(PersonalChatModel chat);
Future<PersonalChatModel> storeChatIfNot(PersonalChatModel chat);
}

View file

@ -0,0 +1,18 @@
import 'dart:typed_data';
import 'package:flutter_community_chat_interface/flutter_community_chat_interface.dart';
abstract class MessageService {
Future<void> sendTextMessage({
required ChatModel chat,
required String text,
});
Future<void> sendImageMessage({
required ChatModel chat,
required Uint8List image,
});
Stream<List<ChatMessageModel>> getMessagesStream(
ChatModel chat,
);
}

View file

@ -0,0 +1,3 @@
export 'chat_service.dart';
export 'user_service.dart';
export 'message_service.dart';

View file

@ -0,0 +1,7 @@
import 'package:flutter_community_chat_interface/flutter_community_chat_interface.dart';
abstract class ChatUserService {
Future<ChatUserModel?> getUser(String id);
Future<ChatUserModel?> getCurrentUser();
Future<List<ChatUserModel>> getAllUsers();
}

View file

@ -161,7 +161,7 @@ packages:
name: matcher
url: "https://pub.dartlang.org"
source: hosted
version: "0.12.13"
version: "0.12.14"
material_color_utilities:
dependency: transitive
description:
@ -196,7 +196,7 @@ packages:
name: path
url: "https://pub.dartlang.org"
source: hosted
version: "1.8.2"
version: "1.8.3"
pub_semver:
dependency: transitive
description:
@ -257,7 +257,7 @@ packages:
name: test_api
url: "https://pub.dartlang.org"
source: hosted
version: "0.4.16"
version: "0.4.17"
typed_data:
dependency: transitive
description:

View file

@ -113,6 +113,13 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "3.1.1"
cross_file:
dependency: transitive
description:
name: cross_file
url: "https://pub.dartlang.org"
source: hosted
version: "0.3.3+2"
crypto:
dependency: transitive
description:
@ -186,7 +193,7 @@ packages:
description:
path: "packages/flutter_community_chat_interface"
ref: HEAD
resolved-ref: "6a9e88ec8d07118e1543b1a82aa5482d6832cbf8"
resolved-ref: c644e1affb1dd4f570bf0e4ae2e950f5e9d83c37
url: "https://github.com/Iconica-Development/flutter_community_chat.git"
source: git
version: "0.0.1"
@ -206,6 +213,15 @@ packages:
url: "https://github.com/Iconica-Development/flutter_data_interface.git"
source: git
version: "1.0.0"
flutter_image_picker:
dependency: transitive
description:
path: "."
ref: "1.0.3"
resolved-ref: "20814755cca74296600a0ae3e016e46979e66a7e"
url: "https://github.com/Iconica-Development/flutter_image_picker"
source: git
version: "1.0.3"
flutter_lints:
dependency: "direct dev"
description:
@ -213,11 +229,23 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.1"
flutter_plugin_android_lifecycle:
dependency: transitive
description:
name: flutter_plugin_android_lifecycle
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.7"
flutter_test:
dependency: "direct dev"
description: flutter
source: sdk
version: "0.0.0"
flutter_web_plugins:
dependency: transitive
description: flutter
source: sdk
version: "0.0.0"
glob:
dependency: transitive
description:
@ -239,6 +267,41 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "4.0.2"
image_picker:
dependency: transitive
description:
name: image_picker
url: "https://pub.dartlang.org"
source: hosted
version: "0.8.6"
image_picker_android:
dependency: transitive
description:
name: image_picker_android
url: "https://pub.dartlang.org"
source: hosted
version: "0.8.5+3"
image_picker_for_web:
dependency: transitive
description:
name: image_picker_for_web
url: "https://pub.dartlang.org"
source: hosted
version: "2.1.10"
image_picker_ios:
dependency: transitive
description:
name: image_picker_ios
url: "https://pub.dartlang.org"
source: hosted
version: "0.8.6+1"
image_picker_platform_interface:
dependency: transitive
description:
name: image_picker_platform_interface
url: "https://pub.dartlang.org"
source: hosted
version: "2.6.2"
intl:
dependency: transitive
description:
@ -246,6 +309,13 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "0.17.0"
js:
dependency: transitive
description:
name: js
url: "https://pub.dartlang.org"
source: hosted
version: "0.6.4"
lints:
dependency: transitive
description:
@ -425,7 +495,7 @@ packages:
name: sqflite
url: "https://pub.dartlang.org"
source: hosted
version: "2.2.0+3"
version: "2.2.2"
sqflite_common:
dependency: transitive
description:

View file

@ -16,9 +16,9 @@ class ChatBottom extends StatefulWidget {
super.key,
});
final Future<void> Function(ChatModel chat, String text) onMessageSubmit;
final Future<void> Function(String text) onMessageSubmit;
final TextInputBuilder messageInputBuilder;
final Function(ChatModel)? onPressSelectImage;
final VoidCallback? onPressSelectImage;
final ChatModel chat;
final ChatTranslations translations;
@ -44,17 +44,16 @@ class _ChatBottomState extends State<ChatBottom> {
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
if (widget.onPressSelectImage != null)
IconButton(
onPressed: () => widget.onPressSelectImage!(widget.chat),
icon: const Icon(Icons.image),
),
IconButton(
onPressed: widget.onPressSelectImage,
icon: const Icon(Icons.image),
),
IconButton(
onPressed: () {
var value = _textEditingController.text;
if (value.isNotEmpty) {
widget.onMessageSubmit(widget.chat, value);
widget.onMessageSubmit(value);
_textEditingController.clear();
}
},

View file

@ -1,5 +1,5 @@
import 'package:flutter/material.dart';
import 'package:flutter_community_chat/flutter_community_chat.dart';
import 'package:flutter_community_chat_view/flutter_community_chat_view.dart';
SnackBar getImageLoadingSnackbar(ChatTranslations translations) => SnackBar(
duration: const Duration(minutes: 1),

View file

@ -6,6 +6,7 @@ import 'package:flutter/material.dart';
import 'package:flutter_community_chat_interface/flutter_community_chat_interface.dart';
import 'package:flutter_community_chat_view/flutter_community_chat_view.dart';
import 'package:flutter_community_chat_view/src/components/chat_image.dart';
import 'package:flutter_image_picker/flutter_image_picker.dart';
class ChatOptions {
const ChatOptions({
@ -13,7 +14,6 @@ class ChatOptions {
this.messageInputBuilder = _createMessageInput,
this.chatRowContainerBuilder = _createChatRowContainer,
this.imagePickerContainerBuilder = _createImagePickerContainer,
this.closeImagePickerButtonBuilder = _createCloseImagePickerButton,
this.scaffoldBuilder = _createScaffold,
this.userAvatarBuilder = _createUserAvatar,
this.noChatsPlaceholderBuilder = _createNoChatsPlaceholder,
@ -22,8 +22,7 @@ class ChatOptions {
final ButtonBuilder newChatButtonBuilder;
final TextInputBuilder messageInputBuilder;
final ContainerBuilder chatRowContainerBuilder;
final ContainerBuilder imagePickerContainerBuilder;
final ButtonBuilder closeImagePickerButtonBuilder;
final ImagePickerContainerBuilder imagePickerContainerBuilder;
final ScaffoldBuilder scaffoldBuilder;
final UserAvatarBuilder userAvatarBuilder;
final NoChatsPlaceholderBuilder noChatsPlaceholderBuilder;
@ -70,23 +69,19 @@ Widget _createChatRowContainer(
);
Widget _createImagePickerContainer(
Widget imagePicker,
VoidCallback onClose,
ChatTranslations translations,
) =>
Container(
padding: const EdgeInsets.all(8.0),
color: Colors.black,
child: imagePicker,
);
Widget _createCloseImagePickerButton(
BuildContext context,
VoidCallback onPressed,
ChatTranslations translations,
) =>
ElevatedButton(
onPressed: onPressed,
child: Text(
translations.cancelImagePickerBtn,
child: ImagePicker(
customButton: ElevatedButton(
onPressed: onClose,
child: Text(
translations.cancelImagePickerBtn,
),
),
),
);
@ -138,6 +133,11 @@ typedef ContainerBuilder = Widget Function(
Widget child,
);
typedef ImagePickerContainerBuilder = Widget Function(
VoidCallback onClose,
ChatTranslations translations,
);
typedef ScaffoldBuilder = Scaffold Function(
AppBar appBar,
Widget body,

View file

@ -2,86 +2,114 @@
//
// SPDX-License-Identifier: BSD-3-Clause
import 'dart:typed_data';
import 'package:flutter/material.dart';
import 'package:flutter_community_chat_interface/flutter_community_chat_interface.dart';
import 'package:flutter_community_chat_view/flutter_community_chat_view.dart';
import 'package:flutter_community_chat_view/src/components/chat_bottom.dart';
import 'package:flutter_community_chat_view/src/components/chat_detail_row.dart';
import 'package:flutter_community_chat_view/src/components/image_loading_snackbar.dart';
class ChatDetailScreen extends StatelessWidget {
const ChatDetailScreen({
required this.options,
required this.chat,
required this.onMessageSubmit,
required this.onUploadImage,
this.translations = const ChatTranslations(),
this.chat,
this.chatMessages,
this.onPressSelectImage,
this.onPressChatTitle,
super.key,
});
final ChatModel chat;
final PersonalChatModel? chat;
final ChatOptions options;
final ChatTranslations translations;
final Stream<List<ChatMessageModel>>? chatMessages;
final Function(ChatModel)? onPressSelectImage;
final Future<void> Function(ChatModel chat, String text) onMessageSubmit;
final Future<void> Function(ChatModel chat)? onPressChatTitle;
final Future<void> Function(Uint8List image) onUploadImage;
final Future<void> Function(String text) onMessageSubmit;
final VoidCallback? onPressChatTitle;
@override
Widget build(BuildContext context) => Scaffold(
appBar: AppBar(
centerTitle: true,
title: GestureDetector(
onTap: () =>
onPressChatTitle != null ? onPressChatTitle!(chat) : {},
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
if (chat is PersonalChatModel)
options.userAvatarBuilder(
(chat as PersonalChatModel).user,
36.0,
),
Expanded(
child: Padding(
padding: const EdgeInsets.only(left: 15.5),
child: Text(
(chat as PersonalChatModel).user.fullName,
style: const TextStyle(fontSize: 18),
Widget build(BuildContext context) {
Future<void> onPressSelectImage() => showModalBottomSheet<Uint8List?>(
context: context,
builder: (BuildContext context) =>
options.imagePickerContainerBuilder(
() => Navigator.of(context).pop(),
translations,
),
).then(
(image) async {
var messenger = ScaffoldMessenger.of(context)
..showSnackBar(
getImageLoadingSnackbar(translations),
);
if (image != null) {
await onUploadImage(image);
}
messenger.hideCurrentSnackBar();
},
);
return Scaffold(
appBar: AppBar(
centerTitle: true,
title: GestureDetector(
onTap: onPressChatTitle,
child: Row(
mainAxisSize: MainAxisSize.min,
children: chat == null
? []
: [
options.userAvatarBuilder(
chat!.user,
36.0,
),
),
),
],
),
Expanded(
child: Padding(
padding: const EdgeInsets.only(left: 15.5),
child: Text(
chat!.user.fullName,
style: const TextStyle(fontSize: 18),
),
),
),
],
),
),
body: Column(
children: [
Expanded(
child: StreamBuilder<List<ChatMessageModel>>(
stream: chatMessages,
builder: (BuildContext context, snapshot) => ListView(
reverse: true,
padding: const EdgeInsets.only(top: 24.0),
children: [
for (var message
in (snapshot.data ?? chat.messages ?? []).reversed)
ChatDetailRow(
message: message,
),
],
),
),
body: Column(
children: [
Expanded(
child: StreamBuilder<List<ChatMessageModel>>(
stream: chatMessages,
builder: (BuildContext context, snapshot) => ListView(
reverse: true,
padding: const EdgeInsets.only(top: 24.0),
children: [
for (var message
in (snapshot.data ?? chat?.messages ?? []).reversed)
ChatDetailRow(
message: message,
),
],
),
),
),
if (chat != null)
ChatBottom(
chat: chat,
chat: chat!,
messageInputBuilder: options.messageInputBuilder,
onPressSelectImage: onPressSelectImage,
onMessageSubmit: onMessageSubmit,
translations: translations,
),
],
),
);
],
),
);
}
}

View file

@ -20,10 +20,10 @@ class ChatScreen extends StatefulWidget {
final ChatOptions options;
final ChatTranslations translations;
final Stream<List<ChatModel>> chats;
final Stream<List<PersonalChatModel>> chats;
final VoidCallback? onPressStartChat;
final void Function(ChatModel chat) onDeleteChat;
final void Function(ChatModel chat) onPressChat;
final void Function(PersonalChatModel chat) onDeleteChat;
final void Function(PersonalChatModel chat) onPressChat;
@override
State<ChatScreen> createState() => _ChatScreenState();
@ -43,11 +43,11 @@ class _ChatScreenState extends State<ChatScreen> {
child: ListView(
padding: const EdgeInsets.only(top: 15.0),
children: [
StreamBuilder<List<ChatModel>>(
StreamBuilder<List<PersonalChatModel>>(
stream: widget.chats,
builder: (BuildContext context, snapshot) => Column(
children: [
for (ChatModel chat in snapshot.data ?? [])
for (PersonalChatModel chat in snapshot.data ?? [])
Builder(
builder: (context) => Dismissible(
confirmDismiss: (_) => showDialog(
@ -100,15 +100,11 @@ class _ChatScreenState extends State<ChatScreen> {
onTap: () => widget.onPressChat(chat),
child: widget.options.chatRowContainerBuilder(
ChatRow(
avatar: chat is PersonalChatModel
? widget.options.userAvatarBuilder(
chat.user,
40.0,
)
: Container(),
title: chat is PersonalChatModel
? chat.user.fullName
: (chat as GroupChatModel).title,
avatar: widget.options.userAvatarBuilder(
chat.user,
40.0,
),
title: chat.user.fullName,
subTitle: chat.lastMessage != null
? chat.lastMessage
is ChatTextMessageModel

View file

@ -113,6 +113,13 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "3.1.1"
cross_file:
dependency: transitive
description:
name: cross_file
url: "https://pub.dartlang.org"
source: hosted
version: "0.3.3+2"
crypto:
dependency: transitive
description:
@ -179,7 +186,7 @@ packages:
description:
path: "packages/flutter_community_chat_interface"
ref: HEAD
resolved-ref: "6a9e88ec8d07118e1543b1a82aa5482d6832cbf8"
resolved-ref: c644e1affb1dd4f570bf0e4ae2e950f5e9d83c37
url: "https://github.com/Iconica-Development/flutter_community_chat.git"
source: git
version: "0.0.1"
@ -192,6 +199,15 @@ packages:
url: "https://github.com/Iconica-Development/flutter_data_interface.git"
source: git
version: "1.0.0"
flutter_image_picker:
dependency: "direct main"
description:
path: "."
ref: "1.0.3"
resolved-ref: "20814755cca74296600a0ae3e016e46979e66a7e"
url: "https://github.com/Iconica-Development/flutter_image_picker"
source: git
version: "1.0.3"
flutter_lints:
dependency: "direct dev"
description:
@ -199,11 +215,23 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.1"
flutter_plugin_android_lifecycle:
dependency: transitive
description:
name: flutter_plugin_android_lifecycle
url: "https://pub.dartlang.org"
source: hosted
version: "2.0.7"
flutter_test:
dependency: "direct dev"
description: flutter
source: sdk
version: "0.0.0"
flutter_web_plugins:
dependency: transitive
description: flutter
source: sdk
version: "0.0.0"
glob:
dependency: transitive
description:
@ -225,6 +253,41 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "4.0.2"
image_picker:
dependency: transitive
description:
name: image_picker
url: "https://pub.dartlang.org"
source: hosted
version: "0.8.6"
image_picker_android:
dependency: transitive
description:
name: image_picker_android
url: "https://pub.dartlang.org"
source: hosted
version: "0.8.5+3"
image_picker_for_web:
dependency: transitive
description:
name: image_picker_for_web
url: "https://pub.dartlang.org"
source: hosted
version: "2.1.10"
image_picker_ios:
dependency: transitive
description:
name: image_picker_ios
url: "https://pub.dartlang.org"
source: hosted
version: "0.8.6+1"
image_picker_platform_interface:
dependency: transitive
description:
name: image_picker_platform_interface
url: "https://pub.dartlang.org"
source: hosted
version: "2.6.2"
intl:
dependency: "direct main"
description:
@ -232,6 +295,13 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "0.17.0"
js:
dependency: transitive
description:
name: js
url: "https://pub.dartlang.org"
source: hosted
version: "0.6.4"
lints:
dependency: transitive
description:
@ -411,7 +481,7 @@ packages:
name: sqflite
url: "https://pub.dartlang.org"
source: hosted
version: "2.2.0+3"
version: "2.2.2"
sqflite_common:
dependency: transitive
description:

View file

@ -21,7 +21,10 @@ dependencies:
url: https://github.com/Iconica-Development/flutter_community_chat.git
path: packages/flutter_community_chat_interface
cached_network_image: ^3.2.2
flutter_image_picker:
git:
url: https://github.com/Iconica-Development/flutter_image_picker
ref: 1.0.3
dev_dependencies:
flutter_test:
sdk: flutter