mirror of
https://github.com/Iconica-Development/flutter_chat.git
synced 2025-05-19 02:43:50 +02:00
feat: refactor
This commit is contained in:
parent
f55c43653c
commit
4df2adb984
27 changed files with 553 additions and 588 deletions
|
@ -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),
|
||||
);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
|
@ -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:
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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';
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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';
|
|
@ -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);
|
||||
}
|
|
@ -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,
|
||||
);
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
export 'chat_service.dart';
|
||||
export 'user_service.dart';
|
||||
export 'message_service.dart';
|
|
@ -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();
|
||||
}
|
|
@ -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:
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
},
|
||||
|
|
|
@ -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),
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue