mirror of
https://github.com/Iconica-Development/flutter_chat.git
synced 2025-05-18 18:33:49 +02:00
fix: feedback
This commit is contained in:
parent
ec89961e07
commit
1f3dc09f44
42 changed files with 1509 additions and 1072 deletions
|
@ -19,7 +19,7 @@ migrate_working_dir/
|
||||||
# The .vscode folder contains launch configuration and tasks you configure in
|
# The .vscode folder contains launch configuration and tasks you configure in
|
||||||
# VS Code which you may wish to be included in version control, so this line
|
# VS Code which you may wish to be included in version control, so this line
|
||||||
# is commented out by default.
|
# is commented out by default.
|
||||||
#.vscode/
|
.vscode/
|
||||||
|
|
||||||
# Flutter/Dart/Pub related
|
# Flutter/Dart/Pub related
|
||||||
# Libraries should not include pubspec.lock, per https://dart.dev/guides/libraries/private-files#pubspeclock.
|
# Libraries should not include pubspec.lock, per https://dart.dev/guides/libraries/private-files#pubspeclock.
|
||||||
|
|
|
@ -1 +0,0 @@
|
||||||
TODO: Add your license here.
|
|
1
packages/chat_repository_interface/LICENSE
Symbolic link
1
packages/chat_repository_interface/LICENSE
Symbolic link
|
@ -0,0 +1 @@
|
||||||
|
../../LICENSE
|
|
@ -1,39 +0,0 @@
|
||||||
<!--
|
|
||||||
This README describes the package. If you publish this package to pub.dev,
|
|
||||||
this README's contents appear on the landing page for your package.
|
|
||||||
|
|
||||||
For information about how to write a good package README, see the guide for
|
|
||||||
[writing package pages](https://dart.dev/guides/libraries/writing-package-pages).
|
|
||||||
|
|
||||||
For general information about developing packages, see the Dart guide for
|
|
||||||
[creating packages](https://dart.dev/guides/libraries/create-library-packages)
|
|
||||||
and the Flutter guide for
|
|
||||||
[developing packages and plugins](https://flutter.dev/developing-packages).
|
|
||||||
-->
|
|
||||||
|
|
||||||
TODO: Put a short description of the package here that helps potential users
|
|
||||||
know whether this package might be useful for them.
|
|
||||||
|
|
||||||
## Features
|
|
||||||
|
|
||||||
TODO: List what your package can do. Maybe include images, gifs, or videos.
|
|
||||||
|
|
||||||
## Getting started
|
|
||||||
|
|
||||||
TODO: List prerequisites and provide or point to information on how to
|
|
||||||
start using the package.
|
|
||||||
|
|
||||||
## Usage
|
|
||||||
|
|
||||||
TODO: Include short and useful examples for package users. Add longer examples
|
|
||||||
to `/example` folder.
|
|
||||||
|
|
||||||
```dart
|
|
||||||
const like = 'sample';
|
|
||||||
```
|
|
||||||
|
|
||||||
## Additional information
|
|
||||||
|
|
||||||
TODO: Tell users more about the package: where to find more information, how to
|
|
||||||
contribute to the package, how to file issues, what response they can expect
|
|
||||||
from the package authors, and more.
|
|
|
@ -1,4 +1,9 @@
|
||||||
include: package:flutter_lints/flutter.yaml
|
include: package:flutter_iconica_analysis/components_options.yaml
|
||||||
|
|
||||||
# Additional information about this file can be found at
|
# Possible to overwrite the rules from the package
|
||||||
# https://dart.dev/guides/language/analysis-options
|
|
||||||
|
analyzer:
|
||||||
|
exclude:
|
||||||
|
|
||||||
|
linter:
|
||||||
|
rules:
|
|
@ -1,17 +1,15 @@
|
||||||
library chat_repository_interface;
|
|
||||||
|
|
||||||
// Interfaces
|
// Interfaces
|
||||||
export 'src/interfaces/chat_repostory_interface.dart';
|
export "src/interfaces/chat_repostory_interface.dart";
|
||||||
export 'src/interfaces/user_repository_interface.dart';
|
export "src/interfaces/user_repository_interface.dart";
|
||||||
|
|
||||||
// Local implementations
|
// Local implementations
|
||||||
export 'src/local/local_chat_repository.dart';
|
export "src/local/local_chat_repository.dart";
|
||||||
export 'src/local/local_user_repository.dart';
|
export "src/local/local_user_repository.dart";
|
||||||
|
|
||||||
// Models
|
// Models
|
||||||
export 'src/models/chat_model.dart';
|
export "src/models/chat_model.dart";
|
||||||
export 'src/models/message_model.dart';
|
export "src/models/message_model.dart";
|
||||||
export 'src/models/user_model.dart';
|
export "src/models/user_model.dart";
|
||||||
|
|
||||||
// Services
|
// Services
|
||||||
export 'src/services/chat_service.dart';
|
export "src/services/chat_service.dart";
|
||||||
|
|
|
@ -1,30 +1,53 @@
|
||||||
import 'dart:typed_data';
|
import "dart:typed_data";
|
||||||
|
|
||||||
import 'package:chat_repository_interface/src/models/chat_model.dart';
|
import "package:chat_repository_interface/src/models/chat_model.dart";
|
||||||
import 'package:chat_repository_interface/src/models/message_model.dart';
|
import "package:chat_repository_interface/src/models/message_model.dart";
|
||||||
import 'package:chat_repository_interface/src/models/user_model.dart';
|
import "package:chat_repository_interface/src/models/user_model.dart";
|
||||||
|
|
||||||
|
/// The chat repository interface
|
||||||
|
/// Implement this interface to create a chat
|
||||||
|
/// repository with a given data source.
|
||||||
abstract class ChatRepositoryInterface {
|
abstract class ChatRepositoryInterface {
|
||||||
String createChat({
|
/// Create a chat with the given parameters.
|
||||||
required List<UserModel> users,
|
/// [users] is a list of [UserModel] that will be part of the chat.
|
||||||
|
/// [chatName] is the name of the chat.
|
||||||
|
/// [description] is the description of the chat.
|
||||||
|
/// [imageUrl] is the image url of the chat.
|
||||||
|
/// [messages] is a list of [MessageModel] that will be part of the chat.
|
||||||
|
Future<void> createChat({
|
||||||
|
required List<String> users,
|
||||||
|
required bool isGroupChat,
|
||||||
String? chatName,
|
String? chatName,
|
||||||
String? description,
|
String? description,
|
||||||
String? imageUrl,
|
String? imageUrl,
|
||||||
List<MessageModel>? messages,
|
List<MessageModel>? messages,
|
||||||
});
|
});
|
||||||
|
|
||||||
Stream<ChatModel> updateChat({
|
/// Update the chat with the given parameters.
|
||||||
|
/// [chat] is the chat that will be updated.
|
||||||
|
Future<void> updateChat({
|
||||||
required ChatModel chat,
|
required ChatModel chat,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/// Get the chat with the given [chatId].
|
||||||
|
/// Returns a [ChatModel] stream.
|
||||||
Stream<ChatModel> getChat({
|
Stream<ChatModel> getChat({
|
||||||
required String chatId,
|
required String chatId,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/// Get the chats for the given [userId].
|
||||||
|
/// Returns a list of [ChatModel] stream.
|
||||||
Stream<List<ChatModel>?> getChats({
|
Stream<List<ChatModel>?> getChats({
|
||||||
required String userId,
|
required String userId,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/// Get the messages for the given [chatId].
|
||||||
|
/// Returns a list of [MessageModel] stream.
|
||||||
|
/// [pageSize] is the number of messages to be fetched.
|
||||||
|
/// [page] is the page number.
|
||||||
|
/// [userId] is the user id.
|
||||||
|
/// [chatId] is the chat id.
|
||||||
|
/// Returns a list of [MessageModel] stream.
|
||||||
Stream<List<MessageModel>?> getMessages({
|
Stream<List<MessageModel>?> getMessages({
|
||||||
required String chatId,
|
required String chatId,
|
||||||
required String userId,
|
required String userId,
|
||||||
|
@ -32,22 +55,46 @@ abstract class ChatRepositoryInterface {
|
||||||
required int page,
|
required int page,
|
||||||
});
|
});
|
||||||
|
|
||||||
bool sendMessage({
|
/// Get the message with the given [messageId].
|
||||||
|
/// [chatId] is the chat id.
|
||||||
|
/// Returns a [MessageModel] stream.
|
||||||
|
Stream<MessageModel?> getMessage({
|
||||||
|
required String chatId,
|
||||||
|
required String messageId,
|
||||||
|
});
|
||||||
|
|
||||||
|
/// Send a message with the given parameters.
|
||||||
|
/// [chatId] is the chat id.
|
||||||
|
/// [senderId] is the sender id.
|
||||||
|
/// [text] is the message text.
|
||||||
|
/// [imageUrl] is the image url.
|
||||||
|
Future<void> sendMessage({
|
||||||
required String chatId,
|
required String chatId,
|
||||||
required String senderId,
|
required String senderId,
|
||||||
|
required String messageId,
|
||||||
String? text,
|
String? text,
|
||||||
String? imageUrl,
|
String? imageUrl,
|
||||||
|
DateTime? timestamp,
|
||||||
});
|
});
|
||||||
|
|
||||||
bool deleteChat({
|
/// Delete the chat with the given [chatId].
|
||||||
|
Future<void> deleteChat({
|
||||||
required String chatId,
|
required String chatId,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/// Get the unread messages count for the given [userId].
|
||||||
|
/// [chatId] is the chat id. If not provided, it will return the
|
||||||
|
/// total unread messages count.
|
||||||
|
/// Returns an integer stream.
|
||||||
Stream<int> getUnreadMessagesCount({
|
Stream<int> getUnreadMessagesCount({
|
||||||
required String userId,
|
required String userId,
|
||||||
String? chatId,
|
String? chatId,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/// Upload an image with the given parameters.
|
||||||
|
/// [path] is the path of the image.
|
||||||
|
/// [image] is the image data.
|
||||||
|
/// Returns the image url.
|
||||||
Future<String> uploadImage({
|
Future<String> uploadImage({
|
||||||
required String path,
|
required String path,
|
||||||
required Uint8List image,
|
required Uint8List image,
|
||||||
|
|
|
@ -1,7 +1,14 @@
|
||||||
import 'package:chat_repository_interface/src/models/user_model.dart';
|
import "package:chat_repository_interface/src/models/user_model.dart";
|
||||||
|
|
||||||
|
/// The user repository interface
|
||||||
|
/// Implement this interface to create a user
|
||||||
|
/// repository with a given data source.
|
||||||
abstract class UserRepositoryInterface {
|
abstract class UserRepositoryInterface {
|
||||||
|
/// Get the user with the given [userId].
|
||||||
|
/// Returns a [UserModel] stream.
|
||||||
Stream<UserModel> getUser({required String userId});
|
Stream<UserModel> getUser({required String userId});
|
||||||
|
|
||||||
|
/// Get all the users.
|
||||||
|
/// Returns a list of [UserModel] stream.
|
||||||
Stream<List<UserModel>> getAllUsers();
|
Stream<List<UserModel>> getAllUsers();
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,114 +1,110 @@
|
||||||
import 'dart:async';
|
import "dart:async";
|
||||||
import 'dart:math';
|
import "dart:typed_data";
|
||||||
import 'dart:typed_data';
|
|
||||||
|
|
||||||
import 'package:chat_repository_interface/chat_repository_interface.dart';
|
import "package:chat_repository_interface/chat_repository_interface.dart";
|
||||||
import 'package:collection/collection.dart';
|
import "package:collection/collection.dart";
|
||||||
import 'package:rxdart/rxdart.dart';
|
import "package:rxdart/rxdart.dart";
|
||||||
|
|
||||||
|
/// The local chat repository
|
||||||
class LocalChatRepository implements ChatRepositoryInterface {
|
class LocalChatRepository implements ChatRepositoryInterface {
|
||||||
LocalChatRepository() {
|
/// The local chat repository constructor
|
||||||
var messages = <MessageModel>[];
|
LocalChatRepository();
|
||||||
|
|
||||||
for (var i = 0; i < 50; i++) {
|
final StreamController<List<ChatModel>> _chatsController =
|
||||||
var rnd = Random().nextInt(2);
|
|
||||||
|
|
||||||
messages.add(MessageModel(
|
|
||||||
id: i.toString(),
|
|
||||||
text: 'Message $i',
|
|
||||||
senderId: rnd == 0 ? '1' : '2',
|
|
||||||
timestamp: DateTime.now().add(Duration(seconds: i)),
|
|
||||||
imageUrl: null,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
_chats = [
|
|
||||||
ChatModel(
|
|
||||||
id: '1',
|
|
||||||
users: [UserModel(id: '1'), UserModel(id: '2')],
|
|
||||||
messages: messages,
|
|
||||||
lastMessage: messages.last,
|
|
||||||
unreadMessageCount: 50,
|
|
||||||
),
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
StreamController<List<ChatModel>> chatsController =
|
|
||||||
BehaviorSubject<List<ChatModel>>();
|
BehaviorSubject<List<ChatModel>>();
|
||||||
|
|
||||||
StreamController<ChatModel> chatController = BehaviorSubject<ChatModel>();
|
final StreamController<ChatModel> _chatController =
|
||||||
|
BehaviorSubject<ChatModel>();
|
||||||
|
|
||||||
StreamController<List<MessageModel>> messageController =
|
final StreamController<List<MessageModel>> _messageController =
|
||||||
BehaviorSubject<List<MessageModel>>();
|
BehaviorSubject<List<MessageModel>>();
|
||||||
|
|
||||||
List<ChatModel> _chats = [];
|
final List<ChatModel> _chats = [];
|
||||||
|
final Map<String, List<MessageModel>> _messages = {};
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String createChat(
|
Future<void> createChat({
|
||||||
{required List<UserModel> users,
|
required List<String> users,
|
||||||
|
required bool isGroupChat,
|
||||||
String? chatName,
|
String? chatName,
|
||||||
String? description,
|
String? description,
|
||||||
String? imageUrl,
|
String? imageUrl,
|
||||||
List<MessageModel>? messages}) {
|
List<MessageModel>? messages,
|
||||||
|
}) async {
|
||||||
var chat = ChatModel(
|
var chat = ChatModel(
|
||||||
id: DateTime.now().toString(),
|
id: DateTime.now().toString(),
|
||||||
|
isGroupChat: isGroupChat,
|
||||||
users: users,
|
users: users,
|
||||||
messages: messages ?? [],
|
|
||||||
chatName: chatName,
|
chatName: chatName,
|
||||||
description: description,
|
description: description,
|
||||||
imageUrl: imageUrl,
|
imageUrl: imageUrl,
|
||||||
);
|
);
|
||||||
|
|
||||||
_chats.add(chat);
|
_chats.add(chat);
|
||||||
chatsController.add(_chats);
|
_chatsController.add(_chats);
|
||||||
|
|
||||||
return chat.id;
|
if (messages != null) {
|
||||||
|
for (var message in messages) {
|
||||||
|
await sendMessage(
|
||||||
|
messageId: message.id,
|
||||||
|
chatId: chat.id,
|
||||||
|
senderId: message.senderId,
|
||||||
|
text: message.text,
|
||||||
|
imageUrl: message.imageUrl,
|
||||||
|
timestamp: message.timestamp,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Stream<ChatModel> updateChat({required ChatModel chat}) {
|
Future<void> updateChat({
|
||||||
|
required ChatModel chat,
|
||||||
|
}) async {
|
||||||
var index = _chats.indexWhere((e) => e.id == chat.id);
|
var index = _chats.indexWhere((e) => e.id == chat.id);
|
||||||
|
|
||||||
if (index != -1) {
|
if (index != -1) {
|
||||||
_chats[index] = chat;
|
_chats[index] = chat;
|
||||||
chatsController.add(_chats);
|
_chatsController.add(_chats);
|
||||||
}
|
}
|
||||||
|
|
||||||
return chatController.stream.where((e) => e.id == chat.id);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool deleteChat({required String chatId}) {
|
Future<void> deleteChat({
|
||||||
|
required String chatId,
|
||||||
|
}) async {
|
||||||
try {
|
try {
|
||||||
_chats.removeWhere((e) => e.id == chatId);
|
_chats.removeWhere((e) => e.id == chatId);
|
||||||
chatsController.add(_chats);
|
_chatsController.add(_chats);
|
||||||
|
} on Exception catch (_) {
|
||||||
return true;
|
rethrow;
|
||||||
} catch (e) {
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Stream<ChatModel> getChat({required String chatId}) {
|
Stream<ChatModel> getChat({
|
||||||
|
required String chatId,
|
||||||
|
}) {
|
||||||
var chat = _chats.firstWhereOrNull((e) => e.id == chatId);
|
var chat = _chats.firstWhereOrNull((e) => e.id == chatId);
|
||||||
|
|
||||||
if (chat != null) {
|
if (chat != null) {
|
||||||
chatController.add(chat);
|
_chatController.add(chat);
|
||||||
|
|
||||||
if (chat.imageUrl != null && chat.imageUrl!.isNotEmpty) {
|
if (chat.imageUrl?.isNotEmpty ?? false) {
|
||||||
chat.copyWith(imageUrl: 'https://picsum.photos/200/300');
|
chat.copyWith(imageUrl: "https://picsum.photos/200/300");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return chatController.stream;
|
return _chatController.stream;
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Stream<List<ChatModel>?> getChats({required String userId}) {
|
Stream<List<ChatModel>?> getChats({
|
||||||
chatsController.add(_chats);
|
required String userId,
|
||||||
|
}) {
|
||||||
|
_chatsController.add(_chats);
|
||||||
|
|
||||||
return chatsController.stream;
|
return _chatsController.stream;
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -123,11 +119,12 @@ class LocalChatRepository implements ChatRepositoryInterface {
|
||||||
chat = _chats.firstWhereOrNull((e) => e.id == chatId);
|
chat = _chats.firstWhereOrNull((e) => e.id == chatId);
|
||||||
|
|
||||||
if (chat != null) {
|
if (chat != null) {
|
||||||
var messages = List<MessageModel>.from(chat.messages);
|
var messages = List<MessageModel>.from(_messages[chatId] ?? []);
|
||||||
|
|
||||||
messages.sort((a, b) => a.timestamp.compareTo(b.timestamp));
|
messages.sort((a, b) => a.timestamp.compareTo(b.timestamp));
|
||||||
|
|
||||||
messageController.stream.first
|
unawaited(
|
||||||
|
_messageController.stream.first
|
||||||
.timeout(
|
.timeout(
|
||||||
const Duration(seconds: 1),
|
const Duration(seconds: 1),
|
||||||
)
|
)
|
||||||
|
@ -151,29 +148,46 @@ class LocalChatRepository implements ChatRepositoryInterface {
|
||||||
|
|
||||||
allMessages.sort((a, b) => a.timestamp.compareTo(b.timestamp));
|
allMessages.sort((a, b) => a.timestamp.compareTo(b.timestamp));
|
||||||
|
|
||||||
messageController.add(allMessages);
|
_messageController.add(allMessages);
|
||||||
}).onError((error, stackTrace) {
|
}).onError((error, stackTrace) {
|
||||||
messageController.add(messages.reversed
|
_messageController.add(
|
||||||
|
messages.reversed
|
||||||
.skip(page * pageSize)
|
.skip(page * pageSize)
|
||||||
.take(pageSize)
|
.take(pageSize)
|
||||||
.toList(growable: false)
|
.toList(growable: false)
|
||||||
.reversed
|
.reversed
|
||||||
.toList());
|
.toList(),
|
||||||
});
|
);
|
||||||
|
}),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return messageController.stream;
|
return _messageController.stream;
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool sendMessage(
|
Stream<MessageModel?> getMessage({
|
||||||
{required String chatId,
|
required String chatId,
|
||||||
|
required String messageId,
|
||||||
|
}) {
|
||||||
|
var message = _messages[chatId]?.firstWhereOrNull((e) => e.id == messageId);
|
||||||
|
|
||||||
|
return Stream.value(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> sendMessage({
|
||||||
|
required String chatId,
|
||||||
required String senderId,
|
required String senderId,
|
||||||
|
required String messageId,
|
||||||
String? text,
|
String? text,
|
||||||
String? imageUrl}) {
|
String? imageUrl,
|
||||||
|
DateTime? timestamp,
|
||||||
|
}) async {
|
||||||
var message = MessageModel(
|
var message = MessageModel(
|
||||||
id: DateTime.now().toString(),
|
chatId: chatId,
|
||||||
timestamp: DateTime.now(),
|
id: messageId,
|
||||||
|
timestamp: timestamp ?? DateTime.now(),
|
||||||
text: text,
|
text: text,
|
||||||
senderId: senderId,
|
senderId: senderId,
|
||||||
imageUrl: imageUrl,
|
imageUrl: imageUrl,
|
||||||
|
@ -181,34 +195,45 @@ class LocalChatRepository implements ChatRepositoryInterface {
|
||||||
|
|
||||||
var chat = _chats.firstWhereOrNull((e) => e.id == chatId);
|
var chat = _chats.firstWhereOrNull((e) => e.id == chatId);
|
||||||
|
|
||||||
if (chat == null) return false;
|
if (chat == null) throw Exception("Chat not found");
|
||||||
|
|
||||||
chat.messages.add(message);
|
var messages = List<MessageModel>.from(_messages[chatId] ?? []);
|
||||||
messageController.add(chat.messages);
|
messages.add(message);
|
||||||
|
_messages[chatId] = messages;
|
||||||
|
|
||||||
return true;
|
var newChat = chat.copyWith(
|
||||||
|
lastMessage: messageId,
|
||||||
|
unreadMessageCount: chat.unreadMessageCount + 1,
|
||||||
|
lastUsed: DateTime.now(),
|
||||||
|
);
|
||||||
|
|
||||||
|
_chats[_chats.indexWhere((e) => e.id == chatId)] = newChat;
|
||||||
|
|
||||||
|
_chatsController.add(_chats);
|
||||||
|
_messageController.add(_messages[chatId] ?? []);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Stream<int> getUnreadMessagesCount({required String userId, String? chatId}) {
|
Stream<int> getUnreadMessagesCount({
|
||||||
return chatsController.stream.map((chats) {
|
required String userId,
|
||||||
|
String? chatId,
|
||||||
|
}) =>
|
||||||
|
_chatsController.stream.map((chats) {
|
||||||
var count = 0;
|
var count = 0;
|
||||||
|
|
||||||
for (var chat in chats) {
|
for (var chat in chats) {
|
||||||
if (chat.users.any((e) => e.id == userId)) {
|
if (chat.users.contains(userId)) {
|
||||||
count += chat.unreadMessageCount;
|
count += chat.unreadMessageCount;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return count;
|
return count;
|
||||||
});
|
});
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<String> uploadImage({
|
Future<String> uploadImage({
|
||||||
required String path,
|
required String path,
|
||||||
required Uint8List image,
|
required Uint8List image,
|
||||||
}) {
|
}) =>
|
||||||
return Future.value('https://picsum.photos/200/300');
|
Future.value("https://picsum.photos/200/300");
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,47 +1,51 @@
|
||||||
import 'dart:async';
|
import "dart:async";
|
||||||
|
|
||||||
import 'package:chat_repository_interface/src/interfaces/user_repository_interface.dart';
|
import "package:chat_repository_interface/src/interfaces/user_repository_interface.dart";
|
||||||
import 'package:chat_repository_interface/src/models/user_model.dart';
|
import "package:chat_repository_interface/src/models/user_model.dart";
|
||||||
import 'package:rxdart/rxdart.dart';
|
import "package:rxdart/rxdart.dart";
|
||||||
|
|
||||||
|
/// The local user repository
|
||||||
class LocalUserRepository implements UserRepositoryInterface {
|
class LocalUserRepository implements UserRepositoryInterface {
|
||||||
final StreamController<List<UserModel>> _usersController =
|
final StreamController<List<UserModel>> _usersController =
|
||||||
BehaviorSubject<List<UserModel>>();
|
BehaviorSubject<List<UserModel>>();
|
||||||
|
|
||||||
final List<UserModel> _users = [
|
final List<UserModel> _users = [
|
||||||
UserModel(
|
UserModel(
|
||||||
id: '1',
|
id: "1",
|
||||||
firstName: 'John',
|
firstName: "John",
|
||||||
lastName: 'Doe',
|
lastName: "Doe",
|
||||||
imageUrl: 'https://picsum.photos/200/300',
|
imageUrl: "https://picsum.photos/200/300",
|
||||||
),
|
),
|
||||||
UserModel(
|
UserModel(
|
||||||
id: '2',
|
id: "2",
|
||||||
firstName: 'Jane',
|
firstName: "Jane",
|
||||||
lastName: 'Doe',
|
lastName: "Doe",
|
||||||
imageUrl: 'https://picsum.photos/200/300',
|
imageUrl: "https://picsum.photos/200/300",
|
||||||
),
|
),
|
||||||
UserModel(
|
UserModel(
|
||||||
id: '3',
|
id: "3",
|
||||||
firstName: 'Frans',
|
firstName: "Frans",
|
||||||
lastName: 'Timmermans',
|
lastName: "Timmermans",
|
||||||
imageUrl: 'https://picsum.photos/200/300',
|
imageUrl: "https://picsum.photos/200/300",
|
||||||
),
|
),
|
||||||
UserModel(
|
UserModel(
|
||||||
id: '4',
|
id: "4",
|
||||||
firstName: 'Hendrik-Jan',
|
firstName: "Hendrik-Jan",
|
||||||
lastName: 'De derde',
|
lastName: "De derde",
|
||||||
imageUrl: 'https://picsum.photos/200/300',
|
imageUrl: "https://picsum.photos/200/300",
|
||||||
),
|
),
|
||||||
];
|
];
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Stream<UserModel> getUser({required String userId}) {
|
Stream<UserModel> getUser({
|
||||||
return getAllUsers().map((users) => users.firstWhere(
|
required String userId,
|
||||||
|
}) =>
|
||||||
|
getAllUsers().map(
|
||||||
|
(users) => users.firstWhere(
|
||||||
(e) => e.id == userId,
|
(e) => e.id == userId,
|
||||||
orElse: () => throw Exception(),
|
orElse: () => throw Exception(),
|
||||||
));
|
),
|
||||||
}
|
);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Stream<List<UserModel>> getAllUsers() {
|
Stream<List<UserModel>> getAllUsers() {
|
||||||
|
|
|
@ -1,11 +1,21 @@
|
||||||
import 'package:chat_repository_interface/src/models/message_model.dart';
|
/// The chat model
|
||||||
import 'package:chat_repository_interface/src/models/user_model.dart';
|
/// A model that represents a chat.
|
||||||
|
/// [id] is the chat id.
|
||||||
|
/// [users] is a list of [UserModel] that are part of the chat.
|
||||||
|
/// [chatName] is the name of the chat.
|
||||||
|
/// [description] is the description of the chat.
|
||||||
|
/// [imageUrl] is the image url of the chat.
|
||||||
|
/// [canBeDeleted] is a boolean that indicates if the chat can be deleted.
|
||||||
|
/// [lastUsed] is the last time the chat was used.
|
||||||
|
/// [lastMessage] is the last message of the chat.
|
||||||
|
/// [unreadMessageCount] is the number of unread messages in the chat.
|
||||||
|
/// Returns a [ChatModel] instance.
|
||||||
class ChatModel {
|
class ChatModel {
|
||||||
ChatModel({
|
/// The chat model constructor
|
||||||
|
const ChatModel({
|
||||||
required this.id,
|
required this.id,
|
||||||
required this.users,
|
required this.users,
|
||||||
required this.messages,
|
required this.isGroupChat,
|
||||||
this.chatName,
|
this.chatName,
|
||||||
this.description,
|
this.description,
|
||||||
this.imageUrl,
|
this.imageUrl,
|
||||||
|
@ -15,35 +25,54 @@ class ChatModel {
|
||||||
this.unreadMessageCount = 0,
|
this.unreadMessageCount = 0,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/// The chat id
|
||||||
final String id;
|
final String id;
|
||||||
final List<MessageModel> messages;
|
|
||||||
final List<UserModel> users;
|
/// The chat users
|
||||||
|
final List<String> users;
|
||||||
|
|
||||||
|
/// The chat name
|
||||||
final String? chatName;
|
final String? chatName;
|
||||||
|
|
||||||
|
/// The chat description
|
||||||
final String? description;
|
final String? description;
|
||||||
|
|
||||||
|
/// The chat image url
|
||||||
final String? imageUrl;
|
final String? imageUrl;
|
||||||
|
|
||||||
|
/// A boolean that indicates if the chat can be deleted
|
||||||
final bool canBeDeleted;
|
final bool canBeDeleted;
|
||||||
|
|
||||||
|
/// The last time the chat was used
|
||||||
final DateTime? lastUsed;
|
final DateTime? lastUsed;
|
||||||
final MessageModel? lastMessage;
|
|
||||||
|
/// The last message of the chat
|
||||||
|
final String? lastMessage;
|
||||||
|
|
||||||
|
/// The number of unread messages in the chat
|
||||||
final int unreadMessageCount;
|
final int unreadMessageCount;
|
||||||
|
|
||||||
|
/// A boolean that indicates if the chat is a group chat
|
||||||
|
final bool isGroupChat;
|
||||||
|
|
||||||
|
/// The chat model copy with method
|
||||||
ChatModel copyWith({
|
ChatModel copyWith({
|
||||||
String? id,
|
String? id,
|
||||||
List<MessageModel>? messages,
|
List<String>? users,
|
||||||
List<UserModel>? users,
|
|
||||||
String? chatName,
|
String? chatName,
|
||||||
String? description,
|
String? description,
|
||||||
String? imageUrl,
|
String? imageUrl,
|
||||||
bool? canBeDeleted,
|
bool? canBeDeleted,
|
||||||
DateTime? lastUsed,
|
DateTime? lastUsed,
|
||||||
MessageModel? lastMessage,
|
String? lastMessage,
|
||||||
int? unreadMessageCount,
|
int? unreadMessageCount,
|
||||||
}) {
|
bool? isGroupChat,
|
||||||
return ChatModel(
|
}) =>
|
||||||
|
ChatModel(
|
||||||
id: id ?? this.id,
|
id: id ?? this.id,
|
||||||
messages: messages ?? this.messages,
|
|
||||||
users: users ?? this.users,
|
users: users ?? this.users,
|
||||||
chatName: chatName ?? this.chatName,
|
chatName: chatName ?? this.chatName,
|
||||||
|
isGroupChat: isGroupChat ?? this.isGroupChat,
|
||||||
description: description ?? this.description,
|
description: description ?? this.description,
|
||||||
imageUrl: imageUrl ?? this.imageUrl,
|
imageUrl: imageUrl ?? this.imageUrl,
|
||||||
canBeDeleted: canBeDeleted ?? this.canBeDeleted,
|
canBeDeleted: canBeDeleted ?? this.canBeDeleted,
|
||||||
|
@ -51,15 +80,13 @@ class ChatModel {
|
||||||
lastMessage: lastMessage ?? this.lastMessage,
|
lastMessage: lastMessage ?? this.lastMessage,
|
||||||
unreadMessageCount: unreadMessageCount ?? this.unreadMessageCount,
|
unreadMessageCount: unreadMessageCount ?? this.unreadMessageCount,
|
||||||
);
|
);
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
extension IsGroupChat on ChatModel {
|
|
||||||
bool get isGroupChat => users.length > 2;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The chat model extension
|
||||||
|
/// An extension that adds extra functionality to the chat model.
|
||||||
|
/// [getOtherUser] is a method that returns the other user in the chat.
|
||||||
extension GetOtherUser on ChatModel {
|
extension GetOtherUser on ChatModel {
|
||||||
UserModel getOtherUser(String userId) {
|
/// The get other user method
|
||||||
return users.firstWhere((user) => user.id != userId);
|
String getOtherUser(String userId) =>
|
||||||
}
|
users.firstWhere((user) => user != userId);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,14 @@
|
||||||
|
/// Message model
|
||||||
|
/// Represents a message in a chat
|
||||||
|
/// [id] is the message id.
|
||||||
|
/// [text] is the message text.
|
||||||
|
/// [imageUrl] is the message image url.
|
||||||
|
/// [timestamp] is the message timestamp.
|
||||||
|
/// [senderId] is the sender id.
|
||||||
class MessageModel {
|
class MessageModel {
|
||||||
MessageModel({
|
/// Message model constructor
|
||||||
|
const MessageModel({
|
||||||
|
required this.chatId,
|
||||||
required this.id,
|
required this.id,
|
||||||
required this.text,
|
required this.text,
|
||||||
required this.imageUrl,
|
required this.imageUrl,
|
||||||
|
@ -7,31 +16,47 @@ class MessageModel {
|
||||||
required this.senderId,
|
required this.senderId,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
final String chatId;
|
||||||
|
|
||||||
|
/// The message id
|
||||||
final String id;
|
final String id;
|
||||||
|
|
||||||
|
/// The message text
|
||||||
final String? text;
|
final String? text;
|
||||||
|
|
||||||
|
/// The message image url
|
||||||
final String? imageUrl;
|
final String? imageUrl;
|
||||||
|
|
||||||
|
/// The message timestamp
|
||||||
final DateTime timestamp;
|
final DateTime timestamp;
|
||||||
|
|
||||||
|
/// The sender id
|
||||||
final String senderId;
|
final String senderId;
|
||||||
|
|
||||||
|
/// The message model copy with method
|
||||||
MessageModel copyWith({
|
MessageModel copyWith({
|
||||||
|
String? chatId,
|
||||||
String? id,
|
String? id,
|
||||||
String? text,
|
String? text,
|
||||||
String? imageUrl,
|
String? imageUrl,
|
||||||
DateTime? timestamp,
|
DateTime? timestamp,
|
||||||
String? senderId,
|
String? senderId,
|
||||||
}) {
|
}) =>
|
||||||
return MessageModel(
|
MessageModel(
|
||||||
|
chatId: chatId ?? this.chatId,
|
||||||
id: id ?? this.id,
|
id: id ?? this.id,
|
||||||
text: text ?? this.text,
|
text: text ?? this.text,
|
||||||
imageUrl: imageUrl ?? this.imageUrl,
|
imageUrl: imageUrl ?? this.imageUrl,
|
||||||
timestamp: timestamp ?? this.timestamp,
|
timestamp: timestamp ?? this.timestamp,
|
||||||
senderId: senderId ?? this.senderId,
|
senderId: senderId ?? this.senderId,
|
||||||
);
|
);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Extension on [MessageModel] to check the message type
|
||||||
extension MessageType on MessageModel {
|
extension MessageType on MessageModel {
|
||||||
bool isTextMessage() => text != null;
|
/// Check if the message is a text message
|
||||||
|
bool get isTextMessage => text != null;
|
||||||
|
|
||||||
bool isImageMessage() => imageUrl != null;
|
/// Check if the message is an image message
|
||||||
|
bool get isImageMessage => imageUrl != null;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,18 +1,35 @@
|
||||||
|
/// User model
|
||||||
|
/// Represents a user in a chat
|
||||||
|
/// [id] is the user id.
|
||||||
|
/// [firstName] is the user first name.
|
||||||
|
/// [lastName] is the user last name.
|
||||||
|
/// [imageUrl] is the user image url.
|
||||||
|
/// [fullname] is the user full name.
|
||||||
class UserModel {
|
class UserModel {
|
||||||
UserModel({
|
/// User model constructor
|
||||||
|
const UserModel({
|
||||||
required this.id,
|
required this.id,
|
||||||
this.firstName,
|
this.firstName,
|
||||||
this.lastName,
|
this.lastName,
|
||||||
this.imageUrl,
|
this.imageUrl,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/// The user id
|
||||||
final String id;
|
final String id;
|
||||||
|
|
||||||
|
/// The user first name
|
||||||
final String? firstName;
|
final String? firstName;
|
||||||
|
|
||||||
|
/// The user last name
|
||||||
final String? lastName;
|
final String? lastName;
|
||||||
|
|
||||||
|
/// The user image url
|
||||||
final String? imageUrl;
|
final String? imageUrl;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Extension on [UserModel] to get the user full name
|
||||||
extension Fullname on UserModel {
|
extension Fullname on UserModel {
|
||||||
|
/// Get the user full name
|
||||||
String? get fullname {
|
String? get fullname {
|
||||||
if (firstName == null && lastName == null) {
|
if (firstName == null && lastName == null) {
|
||||||
return null;
|
return null;
|
||||||
|
|
|
@ -1,142 +1,191 @@
|
||||||
import 'dart:async';
|
import "dart:async";
|
||||||
import 'dart:typed_data';
|
import "dart:typed_data";
|
||||||
|
|
||||||
import 'package:chat_repository_interface/src/interfaces/chat_repostory_interface.dart';
|
import "package:chat_repository_interface/src/interfaces/chat_repostory_interface.dart";
|
||||||
import 'package:chat_repository_interface/src/interfaces/user_repository_interface.dart';
|
import "package:chat_repository_interface/src/interfaces/user_repository_interface.dart";
|
||||||
import 'package:chat_repository_interface/src/local/local_chat_repository.dart';
|
import "package:chat_repository_interface/src/local/local_chat_repository.dart";
|
||||||
import 'package:chat_repository_interface/src/local/local_user_repository.dart';
|
import "package:chat_repository_interface/src/local/local_user_repository.dart";
|
||||||
import 'package:chat_repository_interface/src/models/chat_model.dart';
|
import "package:chat_repository_interface/src/models/chat_model.dart";
|
||||||
import 'package:chat_repository_interface/src/models/message_model.dart';
|
import "package:chat_repository_interface/src/models/message_model.dart";
|
||||||
import 'package:chat_repository_interface/src/models/user_model.dart';
|
import "package:chat_repository_interface/src/models/user_model.dart";
|
||||||
import 'package:collection/collection.dart';
|
import "package:collection/collection.dart";
|
||||||
|
|
||||||
|
/// The chat service
|
||||||
|
/// Use this service to interact with the chat repository.
|
||||||
|
/// Optionally provide a [chatRepository] and [userRepository]
|
||||||
class ChatService {
|
class ChatService {
|
||||||
final ChatRepositoryInterface chatRepository;
|
/// Create a chat service with the given parameters.
|
||||||
final UserRepositoryInterface userRepository;
|
|
||||||
|
|
||||||
ChatService({
|
ChatService({
|
||||||
ChatRepositoryInterface? chatRepository,
|
ChatRepositoryInterface? chatRepository,
|
||||||
UserRepositoryInterface? userRepository,
|
UserRepositoryInterface? userRepository,
|
||||||
}) : chatRepository = chatRepository ?? LocalChatRepository(),
|
}) : chatRepository = chatRepository ?? LocalChatRepository(),
|
||||||
userRepository = userRepository ?? LocalUserRepository();
|
userRepository = userRepository ?? LocalUserRepository();
|
||||||
|
|
||||||
Stream<ChatModel> createChat({
|
/// The chat repository
|
||||||
|
final ChatRepositoryInterface chatRepository;
|
||||||
|
|
||||||
|
/// The user repository
|
||||||
|
final UserRepositoryInterface userRepository;
|
||||||
|
|
||||||
|
/// Create a chat with the given parameters.
|
||||||
|
/// [users] is a list of [UserModel] that will be part of the chat.
|
||||||
|
/// [chatName] is the name of the chat.
|
||||||
|
/// [description] is the description of the chat.
|
||||||
|
/// [imageUrl] is the image url of the chat.
|
||||||
|
/// [messages] is a list of [MessageModel] that will be part of the chat.
|
||||||
|
/// Returns a [ChatModel] stream.
|
||||||
|
Future<void> createChat({
|
||||||
required List<UserModel> users,
|
required List<UserModel> users,
|
||||||
|
required bool isGroupChat,
|
||||||
String? chatName,
|
String? chatName,
|
||||||
String? description,
|
String? description,
|
||||||
String? imageUrl,
|
String? imageUrl,
|
||||||
List<MessageModel>? messages,
|
List<MessageModel>? messages,
|
||||||
}) {
|
}) {
|
||||||
var chatId = chatRepository.createChat(
|
var userIds = users.map((e) => e.id).toList();
|
||||||
users: users,
|
|
||||||
|
return chatRepository.createChat(
|
||||||
|
isGroupChat: isGroupChat,
|
||||||
|
users: userIds,
|
||||||
chatName: chatName,
|
chatName: chatName,
|
||||||
description: description,
|
description: description,
|
||||||
imageUrl: imageUrl,
|
imageUrl: imageUrl,
|
||||||
messages: messages,
|
messages: messages,
|
||||||
);
|
);
|
||||||
|
|
||||||
return chatRepository.getChat(chatId: chatId);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get the chats for the given [userId].
|
||||||
|
/// Returns a list of [ChatModel] stream.
|
||||||
Stream<List<ChatModel>?> getChats({
|
Stream<List<ChatModel>?> getChats({
|
||||||
required String userId,
|
required String userId,
|
||||||
}) {
|
}) =>
|
||||||
return chatRepository.getChats(userId: userId);
|
chatRepository.getChats(userId: userId);
|
||||||
}
|
|
||||||
|
|
||||||
|
/// Get the chat with the given [chatId].
|
||||||
|
/// Returns a [ChatModel] stream.
|
||||||
Stream<ChatModel> getChat({
|
Stream<ChatModel> getChat({
|
||||||
required String chatId,
|
required String chatId,
|
||||||
}) {
|
}) =>
|
||||||
return chatRepository.getChat(chatId: chatId);
|
chatRepository.getChat(chatId: chatId);
|
||||||
}
|
|
||||||
|
|
||||||
|
/// Get the chat with the given [currentUser] and [otherUser].
|
||||||
|
/// Returns a [ChatModel] stream.
|
||||||
|
/// Returns null if the chat does not exist.
|
||||||
Future<ChatModel?> getChatByUser({
|
Future<ChatModel?> getChatByUser({
|
||||||
required String currentUser,
|
required String currentUser,
|
||||||
required String otherUser,
|
required String otherUser,
|
||||||
}) async {
|
}) async {
|
||||||
var chats = await chatRepository
|
var chats = await chatRepository.getChats(userId: currentUser).first;
|
||||||
.getChats(userId: currentUser)
|
|
||||||
.first
|
|
||||||
.timeout(const Duration(seconds: 1));
|
|
||||||
|
|
||||||
var personalChats =
|
var personalChats =
|
||||||
chats?.where((element) => element.users.length == 2).toList();
|
chats?.where((element) => element.users.length == 2).toList();
|
||||||
|
|
||||||
return personalChats?.firstWhereOrNull(
|
return personalChats?.firstWhereOrNull(
|
||||||
(element) => element.users.where((e) => e.id == otherUser).isNotEmpty,
|
(element) => element.users.where((e) => e == otherUser).isNotEmpty,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get the group chats with the given [currentUser] and [otherUsers].
|
||||||
|
/// Returns a [ChatModel] stream.
|
||||||
Future<ChatModel?> getGroupChatByUser({
|
Future<ChatModel?> getGroupChatByUser({
|
||||||
required String currentUser,
|
required String currentUser,
|
||||||
required List<UserModel> otherUsers,
|
required List<UserModel> otherUsers,
|
||||||
required String chatName,
|
required String chatName,
|
||||||
required String description,
|
required String description,
|
||||||
}) async {
|
}) async {
|
||||||
var chats = await chatRepository
|
try {
|
||||||
.getChats(userId: currentUser)
|
var chats = await chatRepository.getChats(userId: currentUser).first;
|
||||||
.first
|
|
||||||
.timeout(const Duration(seconds: 1));
|
|
||||||
|
|
||||||
var personalChats =
|
var personalChats =
|
||||||
chats?.where((element) => element.users.length > 2).toList();
|
chats?.where((element) => element.isGroupChat).toList();
|
||||||
|
|
||||||
try {
|
|
||||||
var groupChats = personalChats
|
var groupChats = personalChats
|
||||||
?.where((chats) => otherUsers.every(chats.users.contains))
|
?.where(
|
||||||
|
(chats) =>
|
||||||
|
otherUsers.every((user) => chats.users.contains(user.id)),
|
||||||
|
)
|
||||||
.toList();
|
.toList();
|
||||||
|
|
||||||
return groupChats?.firstWhereOrNull(
|
return groupChats?.firstWhereOrNull(
|
||||||
(element) =>
|
(element) =>
|
||||||
element.chatName == chatName && element.description == description,
|
element.chatName == chatName && element.description == description,
|
||||||
);
|
);
|
||||||
} catch (e) {
|
// ignore: avoid_catches_without_on_clauses
|
||||||
return null;
|
} catch (_) {
|
||||||
|
throw Exception("Chat not found");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get the message with the given [messageId].
|
||||||
|
/// [chatId] is the chat id.
|
||||||
|
/// Returns a [MessageModel] stream.
|
||||||
|
Stream<MessageModel?> getMessage({
|
||||||
|
required String chatId,
|
||||||
|
required String messageId,
|
||||||
|
}) =>
|
||||||
|
chatRepository.getMessage(chatId: chatId, messageId: messageId);
|
||||||
|
|
||||||
|
/// Get the messages for the given [chatId].
|
||||||
|
/// Returns a list of [MessageModel] stream.
|
||||||
|
/// [pageSize] is the number of messages to be fetched.
|
||||||
|
/// [page] is the page number.
|
||||||
|
/// [userId] is the user id.
|
||||||
|
/// [chatId] is the chat id.
|
||||||
|
/// Returns a list of [MessageModel] stream.
|
||||||
Stream<List<MessageModel>?> getMessages({
|
Stream<List<MessageModel>?> getMessages({
|
||||||
required String userId,
|
required String userId,
|
||||||
required String chatId,
|
required String chatId,
|
||||||
required int pageSize,
|
required int pageSize,
|
||||||
required int page,
|
required int page,
|
||||||
}) {
|
}) =>
|
||||||
return chatRepository.getMessages(
|
chatRepository.getMessages(
|
||||||
userId: userId,
|
userId: userId,
|
||||||
chatId: chatId,
|
chatId: chatId,
|
||||||
pageSize: pageSize,
|
pageSize: pageSize,
|
||||||
page: page,
|
page: page,
|
||||||
);
|
);
|
||||||
}
|
|
||||||
|
|
||||||
bool sendMessage({
|
/// Send a message with the given parameters.
|
||||||
|
/// [chatId] is the chat id.
|
||||||
|
/// [senderId] is the sender id.
|
||||||
|
/// [text] is the message text.
|
||||||
|
/// [imageUrl] is the image url.
|
||||||
|
Future<void> sendMessage({
|
||||||
required String chatId,
|
required String chatId,
|
||||||
String? text,
|
|
||||||
required String senderId,
|
required String senderId,
|
||||||
|
required String messageId,
|
||||||
|
String? text,
|
||||||
String? imageUrl,
|
String? imageUrl,
|
||||||
}) {
|
}) =>
|
||||||
return chatRepository.sendMessage(
|
chatRepository.sendMessage(
|
||||||
chatId: chatId,
|
chatId: chatId,
|
||||||
|
messageId: messageId,
|
||||||
text: text,
|
text: text,
|
||||||
senderId: senderId,
|
senderId: senderId,
|
||||||
imageUrl: imageUrl,
|
imageUrl: imageUrl,
|
||||||
);
|
);
|
||||||
}
|
|
||||||
|
|
||||||
bool deleteChat({
|
/// Delete the chat with the given parameters.
|
||||||
|
/// [chatId] is the chat id.
|
||||||
|
Future<void> deleteChat({
|
||||||
required String chatId,
|
required String chatId,
|
||||||
}) {
|
}) =>
|
||||||
return chatRepository.deleteChat(chatId: chatId);
|
chatRepository.deleteChat(chatId: chatId);
|
||||||
}
|
|
||||||
|
|
||||||
Stream<UserModel> getUser({required String userId}) {
|
/// Get user with the given [userId].
|
||||||
return userRepository.getUser(userId: userId);
|
/// Returns a [UserModel] stream.
|
||||||
}
|
Stream<UserModel> getUser({required String userId}) =>
|
||||||
|
userRepository.getUser(userId: userId);
|
||||||
|
|
||||||
Stream<List<UserModel>> getAllUsers() {
|
/// Get all the users.
|
||||||
return userRepository.getAllUsers();
|
/// Returns a list of [UserModel] stream.
|
||||||
}
|
Stream<List<UserModel>> getAllUsers() => userRepository.getAllUsers();
|
||||||
|
|
||||||
|
/// Get the unread messages count for the given [userId] and or [chatId].
|
||||||
|
/// [userId] is the user id.
|
||||||
|
/// [chatId] is the chat id. If not provided, it will return the
|
||||||
|
/// total unread messages count.
|
||||||
|
/// Returns a [Stream] of [int].
|
||||||
Stream<int> getUnreadMessagesCount({
|
Stream<int> getUnreadMessagesCount({
|
||||||
required String userId,
|
required String userId,
|
||||||
String? chatId,
|
String? chatId,
|
||||||
|
@ -151,16 +200,22 @@ class ChatService {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Upload an image with the given parameters.
|
||||||
|
/// [path] is the image path.
|
||||||
|
/// [image] is the image bytes.
|
||||||
|
/// Returns a [Future] of [String].
|
||||||
Future<String> uploadImage({
|
Future<String> uploadImage({
|
||||||
required String path,
|
required String path,
|
||||||
required Uint8List image,
|
required Uint8List image,
|
||||||
}) {
|
}) =>
|
||||||
return chatRepository.uploadImage(
|
chatRepository.uploadImage(
|
||||||
path: path,
|
path: path,
|
||||||
image: image,
|
image: image,
|
||||||
);
|
);
|
||||||
}
|
|
||||||
|
|
||||||
|
/// Mark the chat as read with the given parameters.
|
||||||
|
/// [chatId] is the chat id.
|
||||||
|
/// Returns a [Future] of [void].
|
||||||
Future<void> markAsRead({
|
Future<void> markAsRead({
|
||||||
required String chatId,
|
required String chatId,
|
||||||
}) async {
|
}) async {
|
||||||
|
@ -171,6 +226,6 @@ class ChatService {
|
||||||
unreadMessageCount: 0,
|
unreadMessageCount: 0,
|
||||||
);
|
);
|
||||||
|
|
||||||
chatRepository.updateChat(chat: newChat);
|
await chatRepository.updateChat(chat: newChat);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,41 +17,9 @@ dependencies:
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
flutter_lints: ^3.0.0
|
flutter_iconica_analysis:
|
||||||
|
git:
|
||||||
|
url: https://github.com/Iconica-Development/flutter_iconica_analysis
|
||||||
|
ref: 7.0.0
|
||||||
|
|
||||||
# For information on the generic Dart part of this file, see the
|
|
||||||
# following page: https://dart.dev/tools/pub/pubspec
|
|
||||||
|
|
||||||
# The following section is specific to Flutter packages.
|
|
||||||
flutter:
|
flutter:
|
||||||
|
|
||||||
# To add assets to your package, add an assets section, like this:
|
|
||||||
# assets:
|
|
||||||
# - images/a_dot_burr.jpeg
|
|
||||||
# - images/a_dot_ham.jpeg
|
|
||||||
#
|
|
||||||
# For details regarding assets in packages, see
|
|
||||||
# https://flutter.dev/assets-and-images/#from-packages
|
|
||||||
#
|
|
||||||
# An image asset can refer to one or more resolution-specific "variants", see
|
|
||||||
# https://flutter.dev/assets-and-images/#resolution-aware
|
|
||||||
|
|
||||||
# To add custom fonts to your package, add a fonts section here,
|
|
||||||
# in this "flutter" section. Each entry in this list should have a
|
|
||||||
# "family" key with the font family name, and a "fonts" key with a
|
|
||||||
# list giving the asset and other descriptors for the font. For
|
|
||||||
# example:
|
|
||||||
# fonts:
|
|
||||||
# - family: Schyler
|
|
||||||
# fonts:
|
|
||||||
# - asset: fonts/Schyler-Regular.ttf
|
|
||||||
# - asset: fonts/Schyler-Italic.ttf
|
|
||||||
# style: italic
|
|
||||||
# - family: Trajan Pro
|
|
||||||
# fonts:
|
|
||||||
# - asset: fonts/TrajanPro.ttf
|
|
||||||
# - asset: fonts/TrajanPro_Bold.ttf
|
|
||||||
# weight: 700
|
|
||||||
#
|
|
||||||
# For details regarding fonts in packages, see
|
|
||||||
# https://flutter.dev/custom-fonts/#from-packages
|
|
||||||
|
|
2
packages/firebase_chat_repository/.gitignore
vendored
2
packages/firebase_chat_repository/.gitignore
vendored
|
@ -19,7 +19,7 @@ migrate_working_dir/
|
||||||
# The .vscode folder contains launch configuration and tasks you configure in
|
# The .vscode folder contains launch configuration and tasks you configure in
|
||||||
# VS Code which you may wish to be included in version control, so this line
|
# VS Code which you may wish to be included in version control, so this line
|
||||||
# is commented out by default.
|
# is commented out by default.
|
||||||
#.vscode/
|
.vscode/
|
||||||
|
|
||||||
# Flutter/Dart/Pub related
|
# Flutter/Dart/Pub related
|
||||||
# Libraries should not include pubspec.lock, per https://dart.dev/guides/libraries/private-files#pubspeclock.
|
# Libraries should not include pubspec.lock, per https://dart.dev/guides/libraries/private-files#pubspeclock.
|
||||||
|
|
|
@ -1 +0,0 @@
|
||||||
TODO: Add your license here.
|
|
1
packages/firebase_chat_repository/LICENSE
Symbolic link
1
packages/firebase_chat_repository/LICENSE
Symbolic link
|
@ -0,0 +1 @@
|
||||||
|
../../LICENSE
|
|
@ -1,4 +1,9 @@
|
||||||
include: package:flutter_lints/flutter.yaml
|
include: package:flutter_iconica_analysis/components_options.yaml
|
||||||
|
|
||||||
# Additional information about this file can be found at
|
# Possible to overwrite the rules from the package
|
||||||
# https://dart.dev/guides/language/analysis-options
|
|
||||||
|
analyzer:
|
||||||
|
exclude:
|
||||||
|
|
||||||
|
linter:
|
||||||
|
rules:
|
|
@ -1,5 +1,3 @@
|
||||||
library firebase_chat_repository;
|
|
||||||
|
|
||||||
/// A Calculator.
|
/// A Calculator.
|
||||||
class Calculator {
|
class Calculator {
|
||||||
/// Returns [value] plus 1.
|
/// Returns [value] plus 1.
|
||||||
|
|
|
@ -14,41 +14,9 @@ dependencies:
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
flutter_lints: ^3.0.0
|
flutter_iconica_analysis:
|
||||||
|
git:
|
||||||
|
url: https://github.com/Iconica-Development/flutter_iconica_analysis
|
||||||
|
ref: 7.0.0
|
||||||
|
|
||||||
# For information on the generic Dart part of this file, see the
|
|
||||||
# following page: https://dart.dev/tools/pub/pubspec
|
|
||||||
|
|
||||||
# The following section is specific to Flutter packages.
|
|
||||||
flutter:
|
flutter:
|
||||||
|
|
||||||
# To add assets to your package, add an assets section, like this:
|
|
||||||
# assets:
|
|
||||||
# - images/a_dot_burr.jpeg
|
|
||||||
# - images/a_dot_ham.jpeg
|
|
||||||
#
|
|
||||||
# For details regarding assets in packages, see
|
|
||||||
# https://flutter.dev/assets-and-images/#from-packages
|
|
||||||
#
|
|
||||||
# An image asset can refer to one or more resolution-specific "variants", see
|
|
||||||
# https://flutter.dev/assets-and-images/#resolution-aware
|
|
||||||
|
|
||||||
# To add custom fonts to your package, add a fonts section here,
|
|
||||||
# in this "flutter" section. Each entry in this list should have a
|
|
||||||
# "family" key with the font family name, and a "fonts" key with a
|
|
||||||
# list giving the asset and other descriptors for the font. For
|
|
||||||
# example:
|
|
||||||
# fonts:
|
|
||||||
# - family: Schyler
|
|
||||||
# fonts:
|
|
||||||
# - asset: fonts/Schyler-Regular.ttf
|
|
||||||
# - asset: fonts/Schyler-Italic.ttf
|
|
||||||
# style: italic
|
|
||||||
# - family: Trajan Pro
|
|
||||||
# fonts:
|
|
||||||
# - asset: fonts/TrajanPro.ttf
|
|
||||||
# - asset: fonts/TrajanPro_Bold.ttf
|
|
||||||
# weight: 700
|
|
||||||
#
|
|
||||||
# For details regarding fonts in packages, see
|
|
||||||
# https://flutter.dev/custom-fonts/#from-packages
|
|
||||||
|
|
2
packages/flutter_chat/.gitignore
vendored
2
packages/flutter_chat/.gitignore
vendored
|
@ -19,7 +19,7 @@ migrate_working_dir/
|
||||||
# The .vscode folder contains launch configuration and tasks you configure in
|
# The .vscode folder contains launch configuration and tasks you configure in
|
||||||
# VS Code which you may wish to be included in version control, so this line
|
# VS Code which you may wish to be included in version control, so this line
|
||||||
# is commented out by default.
|
# is commented out by default.
|
||||||
#.vscode/
|
.vscode/
|
||||||
|
|
||||||
# Flutter/Dart/Pub related
|
# Flutter/Dart/Pub related
|
||||||
# Libraries should not include pubspec.lock, per https://dart.dev/guides/libraries/private-files#pubspeclock.
|
# Libraries should not include pubspec.lock, per https://dart.dev/guides/libraries/private-files#pubspeclock.
|
||||||
|
|
|
@ -1,4 +1,9 @@
|
||||||
include: package:flutter_lints/flutter.yaml
|
include: package:flutter_iconica_analysis/components_options.yaml
|
||||||
|
|
||||||
# Additional information about this file can be found at
|
# Possible to overwrite the rules from the package
|
||||||
# https://dart.dev/guides/language/analysis-options
|
|
||||||
|
analyzer:
|
||||||
|
exclude:
|
||||||
|
|
||||||
|
linter:
|
||||||
|
rules:
|
|
@ -1,26 +1,41 @@
|
||||||
import "package:flutter/material.dart";
|
import 'package:flutter/material.dart';
|
||||||
import "package:flutter_chat/flutter_chat.dart";
|
import 'package:flutter_chat/flutter_chat.dart';
|
||||||
|
|
||||||
void main(List<String> args) async {
|
void main() {
|
||||||
WidgetsFlutterBinding.ensureInitialized();
|
runApp(const MyApp());
|
||||||
|
|
||||||
runApp(const App());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class App extends StatelessWidget {
|
class MyApp extends StatelessWidget {
|
||||||
const App({super.key});
|
const MyApp({super.key});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) => const MaterialApp(
|
Widget build(BuildContext context) {
|
||||||
home: Home(),
|
return MaterialApp(
|
||||||
|
title: 'Flutter Demo',
|
||||||
|
theme: ThemeData(
|
||||||
|
useMaterial3: true,
|
||||||
|
),
|
||||||
|
home: const MyHomePage(),
|
||||||
);
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class Home extends StatelessWidget {
|
class MyHomePage extends StatefulWidget {
|
||||||
const Home({super.key});
|
const MyHomePage({super.key});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) => const Center(
|
State<MyHomePage> createState() => _MyHomePageState();
|
||||||
child: FlutterChatEntryWidget(userId: '1'),
|
}
|
||||||
);
|
|
||||||
|
class _MyHomePageState extends State<MyHomePage> {
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Scaffold(
|
||||||
|
appBar: AppBar(),
|
||||||
|
body: const Center(),
|
||||||
|
floatingActionButton: const FlutterChatEntryWidget(
|
||||||
|
userId: '1',
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,40 +1,16 @@
|
||||||
name: example
|
name: example
|
||||||
description: "A new Flutter project."
|
description: "A new Flutter project."
|
||||||
# The following line prevents the package from being accidentally published to
|
publish_to: 'none'
|
||||||
# pub.dev using `flutter pub publish`. This is preferred for private packages.
|
|
||||||
publish_to: 'none' # Remove this line if you wish to publish to pub.dev
|
|
||||||
|
|
||||||
# The following defines the version and build number for your application.
|
|
||||||
# A version number is three numbers separated by dots, like 1.2.43
|
|
||||||
# followed by an optional build number separated by a +.
|
|
||||||
# Both the version and the builder number may be overridden in flutter
|
|
||||||
# build by specifying --build-name and --build-number, respectively.
|
|
||||||
# In Android, build-name is used as versionName while build-number used as versionCode.
|
|
||||||
# Read more about Android versioning at https://developer.android.com/studio/publish/versioning
|
|
||||||
# In iOS, build-name is used as CFBundleShortVersionString while build-number is used as CFBundleVersion.
|
|
||||||
# Read more about iOS versioning at
|
|
||||||
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
|
|
||||||
# In Windows, build-name is used as the major, minor, and patch parts
|
|
||||||
# of the product and file versions while build-number is used as the build suffix.
|
|
||||||
version: 1.0.0+1
|
version: 1.0.0+1
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: '>=3.4.3 <4.0.0'
|
sdk: ^3.5.0
|
||||||
|
|
||||||
# Dependencies specify other packages that your package needs in order to work.
|
|
||||||
# To automatically upgrade your package dependencies to the latest versions
|
|
||||||
# consider running `flutter pub upgrade --major-versions`. Alternatively,
|
|
||||||
# dependencies can be manually updated by changing the version numbers below to
|
|
||||||
# the latest version available on pub.dev. To see which dependencies have newer
|
|
||||||
# versions available, run `flutter pub outdated`.
|
|
||||||
dependencies:
|
dependencies:
|
||||||
flutter:
|
flutter:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
|
|
||||||
|
cupertino_icons: ^1.0.8
|
||||||
# The following adds the Cupertino Icons font to your application.
|
|
||||||
# Use with the CupertinoIcons class for iOS style icons.
|
|
||||||
cupertino_icons: ^1.0.6
|
|
||||||
flutter_chat:
|
flutter_chat:
|
||||||
path: ../
|
path: ../
|
||||||
|
|
||||||
|
@ -42,51 +18,7 @@ dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
|
|
||||||
# The "flutter_lints" package below contains a set of recommended lints to
|
flutter_lints: ^4.0.0
|
||||||
# encourage good coding practices. The lint set provided by the package is
|
|
||||||
# activated in the `analysis_options.yaml` file located at the root of your
|
|
||||||
# package. See that file for information about deactivating specific lint
|
|
||||||
# rules and activating additional ones.
|
|
||||||
flutter_lints: ^3.0.0
|
|
||||||
|
|
||||||
# For information on the generic Dart part of this file, see the
|
|
||||||
# following page: https://dart.dev/tools/pub/pubspec
|
|
||||||
|
|
||||||
# The following section is specific to Flutter packages.
|
|
||||||
flutter:
|
flutter:
|
||||||
|
|
||||||
# The following line ensures that the Material Icons font is
|
|
||||||
# included with your application, so that you can use the icons in
|
|
||||||
# the material Icons class.
|
|
||||||
uses-material-design: true
|
uses-material-design: true
|
||||||
|
|
||||||
# To add assets to your application, add an assets section, like this:
|
|
||||||
# assets:
|
|
||||||
# - images/a_dot_burr.jpeg
|
|
||||||
# - images/a_dot_ham.jpeg
|
|
||||||
|
|
||||||
# An image asset can refer to one or more resolution-specific "variants", see
|
|
||||||
# https://flutter.dev/assets-and-images/#resolution-aware
|
|
||||||
|
|
||||||
# For details regarding adding assets from package dependencies, see
|
|
||||||
# https://flutter.dev/assets-and-images/#from-packages
|
|
||||||
|
|
||||||
# To add custom fonts to your application, add a fonts section here,
|
|
||||||
# in this "flutter" section. Each entry in this list should have a
|
|
||||||
# "family" key with the font family name, and a "fonts" key with a
|
|
||||||
# list giving the asset and other descriptors for the font. For
|
|
||||||
# example:
|
|
||||||
# fonts:
|
|
||||||
# - family: Schyler
|
|
||||||
# fonts:
|
|
||||||
# - asset: fonts/Schyler-Regular.ttf
|
|
||||||
# - asset: fonts/Schyler-Italic.ttf
|
|
||||||
# style: italic
|
|
||||||
# - family: Trajan Pro
|
|
||||||
# fonts:
|
|
||||||
# - asset: fonts/TrajanPro.ttf
|
|
||||||
# - asset: fonts/TrajanPro_Bold.ttf
|
|
||||||
# weight: 700
|
|
||||||
#
|
|
||||||
# For details regarding fonts from package dependencies,
|
|
||||||
# see https://flutter.dev/custom-fonts/#from-packages
|
|
||||||
|
|
30
packages/flutter_chat/example/test/widget_test.dart
Normal file
30
packages/flutter_chat/example/test/widget_test.dart
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
// This is a basic Flutter widget test.
|
||||||
|
//
|
||||||
|
// To perform an interaction with a widget in your test, use the WidgetTester
|
||||||
|
// utility in the flutter_test package. For example, you can send tap and scroll
|
||||||
|
// gestures. You can also use WidgetTester to find child widgets in the widget
|
||||||
|
// tree, read text, and verify that the values of widget properties are correct.
|
||||||
|
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
|
||||||
|
import 'package:example/main.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
testWidgets('Counter increments smoke test', (WidgetTester tester) async {
|
||||||
|
// Build our app and trigger a frame.
|
||||||
|
await tester.pumpWidget(const MyApp());
|
||||||
|
|
||||||
|
// Verify that our counter starts at 0.
|
||||||
|
expect(find.text('0'), findsOneWidget);
|
||||||
|
expect(find.text('1'), findsNothing);
|
||||||
|
|
||||||
|
// Tap the '+' icon and trigger a frame.
|
||||||
|
await tester.tap(find.byIcon(Icons.add));
|
||||||
|
await tester.pump();
|
||||||
|
|
||||||
|
// Verify that our counter has incremented.
|
||||||
|
expect(find.text('0'), findsNothing);
|
||||||
|
expect(find.text('1'), findsOneWidget);
|
||||||
|
});
|
||||||
|
}
|
|
@ -1,11 +1,22 @@
|
||||||
library flutter_chat;
|
// ignore_for_file: prefer_double_quotes
|
||||||
|
|
||||||
// Core
|
// Core
|
||||||
export 'package:chat_repository_interface/chat_repository_interface.dart';
|
export 'package:chat_repository_interface/chat_repository_interface.dart';
|
||||||
|
|
||||||
// Screens
|
|
||||||
export "src/config/chat_options.dart";
|
|
||||||
|
|
||||||
// User story
|
// User story
|
||||||
export "package:flutter_chat/src/flutter_chat_entry_widget.dart";
|
export "package:flutter_chat/src/flutter_chat_entry_widget.dart";
|
||||||
export "package:flutter_chat/src/flutter_chat_navigator_userstory.dart";
|
export "package:flutter_chat/src/flutter_chat_navigator_userstory.dart";
|
||||||
|
|
||||||
|
// Options
|
||||||
|
export "src/config/chat_builders.dart";
|
||||||
|
export "src/config/chat_options.dart";
|
||||||
|
export "src/config/chat_translations.dart";
|
||||||
|
|
||||||
|
// Screens
|
||||||
|
export "src/screens/chat_detail_screen.dart";
|
||||||
|
export "src/screens/chat_profile_screen.dart";
|
||||||
|
export "src/screens/chat_screen.dart";
|
||||||
|
export "src/screens/creation/new_chat_screen.dart";
|
||||||
|
export "src/screens/creation/new_group_chat_overview.dart";
|
||||||
|
export "src/screens/creation/new_group_chat_screen.dart";
|
||||||
|
export "src/services/date_formatter.dart";
|
||||||
|
|
|
@ -1,8 +1,10 @@
|
||||||
import 'package:chat_repository_interface/chat_repository_interface.dart';
|
import "package:chat_repository_interface/chat_repository_interface.dart";
|
||||||
import 'package:flutter/material.dart';
|
import "package:flutter/material.dart";
|
||||||
import 'package:flutter_chat/src/config/chat_translations.dart';
|
import "package:flutter_chat/src/config/chat_translations.dart";
|
||||||
|
|
||||||
|
/// The chat builders
|
||||||
class ChatBuilders {
|
class ChatBuilders {
|
||||||
|
/// The chat builders constructor
|
||||||
const ChatBuilders({
|
const ChatBuilders({
|
||||||
this.chatScreenScaffoldBuilder,
|
this.chatScreenScaffoldBuilder,
|
||||||
this.newChatScreenScaffoldBuilder,
|
this.newChatScreenScaffoldBuilder,
|
||||||
|
@ -23,76 +25,112 @@ class ChatBuilders {
|
||||||
this.loadingWidgetBuilder,
|
this.loadingWidgetBuilder,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/// The chat screen scaffold builder
|
||||||
final ScaffoldBuilder? chatScreenScaffoldBuilder;
|
final ScaffoldBuilder? chatScreenScaffoldBuilder;
|
||||||
|
|
||||||
|
/// The new chat screen scaffold builder
|
||||||
final ScaffoldBuilder? newChatScreenScaffoldBuilder;
|
final ScaffoldBuilder? newChatScreenScaffoldBuilder;
|
||||||
|
|
||||||
|
/// The new group chat overview scaffold builder
|
||||||
final ScaffoldBuilder? newGroupChatOverviewScaffoldBuilder;
|
final ScaffoldBuilder? newGroupChatOverviewScaffoldBuilder;
|
||||||
|
|
||||||
|
/// The new group chat screen scaffold builder
|
||||||
final ScaffoldBuilder? newGroupChatScreenScaffoldBuilder;
|
final ScaffoldBuilder? newGroupChatScreenScaffoldBuilder;
|
||||||
|
|
||||||
|
/// The chat detail scaffold builder
|
||||||
final ScaffoldBuilder? chatDetailScaffoldBuilder;
|
final ScaffoldBuilder? chatDetailScaffoldBuilder;
|
||||||
|
|
||||||
|
/// The chat profile scaffold builder
|
||||||
final ScaffoldBuilder? chatProfileScaffoldBuilder;
|
final ScaffoldBuilder? chatProfileScaffoldBuilder;
|
||||||
|
|
||||||
|
/// The message input builder
|
||||||
final TextInputBuilder? messageInputBuilder;
|
final TextInputBuilder? messageInputBuilder;
|
||||||
|
|
||||||
|
/// The chat row container builder
|
||||||
final ContainerBuilder? chatRowContainerBuilder;
|
final ContainerBuilder? chatRowContainerBuilder;
|
||||||
|
|
||||||
|
/// The group avatar builder
|
||||||
final GroupAvatarBuilder? groupAvatarBuilder;
|
final GroupAvatarBuilder? groupAvatarBuilder;
|
||||||
|
|
||||||
|
/// The user avatar builder
|
||||||
final UserAvatarBuilder? userAvatarBuilder;
|
final UserAvatarBuilder? userAvatarBuilder;
|
||||||
|
|
||||||
|
/// The delete chat dialog builder
|
||||||
final Future<bool?> Function(BuildContext, ChatModel)?
|
final Future<bool?> Function(BuildContext, ChatModel)?
|
||||||
deleteChatDialogBuilder;
|
deleteChatDialogBuilder;
|
||||||
|
|
||||||
|
/// The new chat button builder
|
||||||
final ButtonBuilder? newChatButtonBuilder;
|
final ButtonBuilder? newChatButtonBuilder;
|
||||||
|
|
||||||
|
/// The no users placeholder builder
|
||||||
final NoUsersPlaceholderBuilder? noUsersPlaceholderBuilder;
|
final NoUsersPlaceholderBuilder? noUsersPlaceholderBuilder;
|
||||||
|
|
||||||
|
/// The chat title builder
|
||||||
final Widget Function(String chatTitle)? chatTitleBuilder;
|
final Widget Function(String chatTitle)? chatTitleBuilder;
|
||||||
|
|
||||||
|
/// The username builder
|
||||||
final Widget Function(String userFullName)? usernameBuilder;
|
final Widget Function(String userFullName)? usernameBuilder;
|
||||||
|
|
||||||
|
/// The image picker container builder
|
||||||
final ImagePickerContainerBuilder? imagePickerContainerBuilder;
|
final ImagePickerContainerBuilder? imagePickerContainerBuilder;
|
||||||
|
|
||||||
|
/// The loading widget builder
|
||||||
final Widget? Function(BuildContext context)? loadingWidgetBuilder;
|
final Widget? Function(BuildContext context)? loadingWidgetBuilder;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The button builder
|
||||||
typedef ButtonBuilder = Widget Function(
|
typedef ButtonBuilder = Widget Function(
|
||||||
BuildContext context,
|
BuildContext context,
|
||||||
VoidCallback onPressed,
|
VoidCallback onPressed,
|
||||||
ChatTranslations translations,
|
ChatTranslations translations,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
/// The image picker container builder
|
||||||
typedef ImagePickerContainerBuilder = Widget Function(
|
typedef ImagePickerContainerBuilder = Widget Function(
|
||||||
|
BuildContext context,
|
||||||
VoidCallback onClose,
|
VoidCallback onClose,
|
||||||
ChatTranslations translations,
|
ChatTranslations translations,
|
||||||
BuildContext context,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
|
/// The text input builder
|
||||||
typedef TextInputBuilder = Widget Function(
|
typedef TextInputBuilder = Widget Function(
|
||||||
|
BuildContext context,
|
||||||
TextEditingController textEditingController,
|
TextEditingController textEditingController,
|
||||||
Widget suffixIcon,
|
Widget suffixIcon,
|
||||||
ChatTranslations translations,
|
ChatTranslations translations,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
/// The scaffold builder
|
||||||
typedef ScaffoldBuilder = Scaffold Function(
|
typedef ScaffoldBuilder = Scaffold Function(
|
||||||
AppBar appBar,
|
BuildContext context,
|
||||||
|
PreferredSizeWidget appBar,
|
||||||
Widget body,
|
Widget body,
|
||||||
Color backgroundColor,
|
Color backgroundColor,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
/// The container builder
|
||||||
typedef ContainerBuilder = Widget Function(
|
typedef ContainerBuilder = Widget Function(
|
||||||
|
BuildContext context,
|
||||||
Widget child,
|
Widget child,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
/// The group avatar builder
|
||||||
typedef GroupAvatarBuilder = Widget Function(
|
typedef GroupAvatarBuilder = Widget Function(
|
||||||
|
BuildContext context,
|
||||||
String groupName,
|
String groupName,
|
||||||
String? imageUrl,
|
String? imageUrl,
|
||||||
double size,
|
double size,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
/// The user avatar builder
|
||||||
typedef UserAvatarBuilder = Widget Function(
|
typedef UserAvatarBuilder = Widget Function(
|
||||||
|
BuildContext context,
|
||||||
UserModel user,
|
UserModel user,
|
||||||
double size,
|
double size,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
/// The no users placeholder builder
|
||||||
typedef NoUsersPlaceholderBuilder = Widget Function(
|
typedef NoUsersPlaceholderBuilder = Widget Function(
|
||||||
|
BuildContext context,
|
||||||
ChatTranslations translations,
|
ChatTranslations translations,
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,20 +1,13 @@
|
||||||
import 'dart:ui';
|
import "dart:ui";
|
||||||
|
|
||||||
import 'package:flutter_chat/src/config/chat_builders.dart';
|
import "package:flutter_chat/src/config/chat_builders.dart";
|
||||||
import 'package:flutter_chat/src/config/chat_translations.dart';
|
import "package:flutter_chat/src/config/chat_translations.dart";
|
||||||
|
|
||||||
|
/// The chat options
|
||||||
|
/// Use this class to configure the chat options.
|
||||||
class ChatOptions {
|
class ChatOptions {
|
||||||
final String Function(bool showFullDate, DateTime date)? dateformat;
|
/// The chat options constructor
|
||||||
final ChatTranslations translations;
|
const ChatOptions({
|
||||||
final ChatBuilders builders;
|
|
||||||
final bool groupChatEnabled;
|
|
||||||
final bool showTimes;
|
|
||||||
final Color iconEnabledColor;
|
|
||||||
final Color iconDisabledColor;
|
|
||||||
final Function? onNoChats;
|
|
||||||
final int pageSize;
|
|
||||||
|
|
||||||
ChatOptions({
|
|
||||||
this.dateformat,
|
this.dateformat,
|
||||||
this.groupChatEnabled = true,
|
this.groupChatEnabled = true,
|
||||||
this.showTimes = true,
|
this.showTimes = true,
|
||||||
|
@ -25,4 +18,32 @@ class ChatOptions {
|
||||||
this.onNoChats,
|
this.onNoChats,
|
||||||
this.pageSize = 20,
|
this.pageSize = 20,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/// [dateformat] is a function that formats the date.
|
||||||
|
// ignore: avoid_positional_boolean_parameters
|
||||||
|
final String Function(bool showFullDate, DateTime date)? dateformat;
|
||||||
|
|
||||||
|
/// [translations] is the chat translations.
|
||||||
|
final ChatTranslations translations;
|
||||||
|
|
||||||
|
/// [builders] is the chat builders.
|
||||||
|
final ChatBuilders builders;
|
||||||
|
|
||||||
|
/// [groupChatEnabled] is a boolean that indicates if group chat is enabled.
|
||||||
|
final bool groupChatEnabled;
|
||||||
|
|
||||||
|
/// [showTimes] is a boolean that indicates if the chat times are shown.
|
||||||
|
final bool showTimes;
|
||||||
|
|
||||||
|
/// [iconEnabledColor] is the color of the enabled icon.
|
||||||
|
final Color iconEnabledColor;
|
||||||
|
|
||||||
|
/// [iconDisabledColor] is the color of the disabled icon.
|
||||||
|
final Color iconDisabledColor;
|
||||||
|
|
||||||
|
/// [onNoChats] is a function that is triggered when there are no chats.
|
||||||
|
final Function? onNoChats;
|
||||||
|
|
||||||
|
/// [pageSize] is the number of chats to load at a time.
|
||||||
|
final int pageSize;
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,8 @@
|
||||||
//
|
//
|
||||||
// SPDX-License-Identifier: BSD-3-Clause
|
// SPDX-License-Identifier: BSD-3-Clause
|
||||||
|
|
||||||
|
// ignore_for_file: public_member_api_docs
|
||||||
|
|
||||||
/// Class that holds all the translations for the chat component view and
|
/// Class that holds all the translations for the chat component view and
|
||||||
/// the corresponding userstory
|
/// the corresponding userstory
|
||||||
class ChatTranslations {
|
class ChatTranslations {
|
||||||
|
|
|
@ -61,14 +61,14 @@ class _FlutterChatEntryWidgetState extends State<FlutterChatEntryWidget> {
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) => GestureDetector(
|
Widget build(BuildContext context) => InkWell(
|
||||||
onTap: () async =>
|
onTap: () async =>
|
||||||
widget.onTap?.call() ??
|
widget.onTap?.call() ??
|
||||||
Navigator.of(context).pushReplacement(
|
Navigator.of(context).pushReplacement(
|
||||||
MaterialPageRoute(
|
MaterialPageRoute(
|
||||||
builder: (context) => FlutterChatNavigatorUserstory(
|
builder: (context) => FlutterChatNavigatorUserstory(
|
||||||
userId: widget.userId,
|
userId: widget.userId,
|
||||||
chatService: chatService!,
|
chatService: chatService,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
|
@ -2,8 +2,11 @@
|
||||||
//
|
//
|
||||||
// SPDX-License-Identifier: BSD-3-Clause
|
// SPDX-License-Identifier: BSD-3-Clause
|
||||||
|
|
||||||
|
import "dart:async";
|
||||||
|
|
||||||
|
import "package:chat_repository_interface/chat_repository_interface.dart";
|
||||||
import "package:flutter/material.dart";
|
import "package:flutter/material.dart";
|
||||||
import "package:flutter_chat/flutter_chat.dart";
|
import "package:flutter_chat/src/config/chat_options.dart";
|
||||||
import "package:flutter_chat/src/screens/chat_detail_screen.dart";
|
import "package:flutter_chat/src/screens/chat_detail_screen.dart";
|
||||||
import "package:flutter_chat/src/screens/chat_profile_screen.dart";
|
import "package:flutter_chat/src/screens/chat_profile_screen.dart";
|
||||||
import "package:flutter_chat/src/screens/chat_screen.dart";
|
import "package:flutter_chat/src/screens/chat_screen.dart";
|
||||||
|
@ -11,17 +14,27 @@ import "package:flutter_chat/src/screens/creation/new_chat_screen.dart";
|
||||||
import "package:flutter_chat/src/screens/creation/new_group_chat_overview.dart";
|
import "package:flutter_chat/src/screens/creation/new_group_chat_overview.dart";
|
||||||
import "package:flutter_chat/src/screens/creation/new_group_chat_screen.dart";
|
import "package:flutter_chat/src/screens/creation/new_group_chat_screen.dart";
|
||||||
|
|
||||||
|
/// The flutter chat navigator userstory
|
||||||
|
/// [userId] is the id of the user
|
||||||
|
/// [chatService] is the chat service
|
||||||
|
/// [chatOptions] are the chat options
|
||||||
|
/// This widget is the entry point for the chat UI
|
||||||
class FlutterChatNavigatorUserstory extends StatefulWidget {
|
class FlutterChatNavigatorUserstory extends StatefulWidget {
|
||||||
|
/// Constructs a [FlutterChatNavigatorUserstory].
|
||||||
const FlutterChatNavigatorUserstory({
|
const FlutterChatNavigatorUserstory({
|
||||||
super.key,
|
|
||||||
required this.userId,
|
required this.userId,
|
||||||
this.chatService,
|
this.chatService,
|
||||||
this.chatOptions,
|
this.chatOptions,
|
||||||
|
super.key,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/// The user ID of the person currently looking at the chat
|
||||||
final String userId;
|
final String userId;
|
||||||
|
|
||||||
|
/// The chat service associated with the widget.
|
||||||
final ChatService? chatService;
|
final ChatService? chatService;
|
||||||
|
|
||||||
|
/// The chat options
|
||||||
final ChatOptions? chatOptions;
|
final ChatOptions? chatOptions;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -37,112 +50,147 @@ class _FlutterChatNavigatorUserstoryState
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
chatService = widget.chatService ?? ChatService();
|
chatService = widget.chatService ?? ChatService();
|
||||||
chatOptions = widget.chatOptions ?? ChatOptions();
|
chatOptions = widget.chatOptions ?? const ChatOptions();
|
||||||
super.initState();
|
super.initState();
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) => chatScreen();
|
Widget build(BuildContext context) => Navigator(
|
||||||
|
key: const ValueKey(
|
||||||
Widget chatScreen() {
|
"chat_navigator",
|
||||||
return ChatScreen(
|
),
|
||||||
|
onGenerateRoute: (settings) => MaterialPageRoute(
|
||||||
|
builder: (context) => _NavigatorWrapper(
|
||||||
userId: widget.userId,
|
userId: widget.userId,
|
||||||
chatService: chatService,
|
chatService: chatService,
|
||||||
chatOptions: chatOptions,
|
chatOptions: chatOptions,
|
||||||
onPressChat: (chat) {
|
),
|
||||||
return route(chatDetailScreen(chat));
|
),
|
||||||
},
|
|
||||||
onDeleteChat: (chat) {
|
|
||||||
chatService.deleteChat(chatId: chat.id);
|
|
||||||
},
|
|
||||||
onPressStartChat: () {
|
|
||||||
return route(newChatScreen());
|
|
||||||
},
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget chatDetailScreen(ChatModel chat) => ChatDetailScreen(
|
class _NavigatorWrapper extends StatelessWidget {
|
||||||
userId: widget.userId,
|
const _NavigatorWrapper({
|
||||||
|
required this.userId,
|
||||||
|
required this.chatService,
|
||||||
|
required this.chatOptions,
|
||||||
|
});
|
||||||
|
|
||||||
|
final String userId;
|
||||||
|
final ChatService chatService;
|
||||||
|
final ChatOptions chatOptions;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) => chatScreen(context);
|
||||||
|
|
||||||
|
Widget chatScreen(BuildContext context) => ChatScreen(
|
||||||
|
userId: userId,
|
||||||
|
chatService: chatService,
|
||||||
|
chatOptions: chatOptions,
|
||||||
|
onPressChat: (chat) => route(context, chatDetailScreen(context, chat)),
|
||||||
|
onDeleteChat: (chat) async {
|
||||||
|
await chatService.deleteChat(chatId: chat.id);
|
||||||
|
},
|
||||||
|
onPressStartChat: () => route(context, newChatScreen(context)),
|
||||||
|
);
|
||||||
|
|
||||||
|
Widget chatDetailScreen(BuildContext context, ChatModel chat) =>
|
||||||
|
ChatDetailScreen(
|
||||||
|
userId: userId,
|
||||||
chatService: chatService,
|
chatService: chatService,
|
||||||
chatOptions: chatOptions,
|
chatOptions: chatOptions,
|
||||||
chat: chat,
|
chat: chat,
|
||||||
onReadChat: (chat) => chatService.markAsRead(
|
onReadChat: (chat) async => chatService.markAsRead(
|
||||||
chatId: chat.id,
|
chatId: chat.id,
|
||||||
),
|
),
|
||||||
onPressChatTitle: (chat) {
|
onPressChatTitle: (chat) async {
|
||||||
if (chat.isGroupChat) {
|
if (chat.isGroupChat) {
|
||||||
return route(chatProfileScreen(null, chat));
|
return route(context, chatProfileScreen(context, null, chat));
|
||||||
}
|
}
|
||||||
|
|
||||||
var otherUser = chat.getOtherUser(widget.userId);
|
var otherUserId = chat.getOtherUser(userId);
|
||||||
|
var otherUser = await chatService.getUser(userId: otherUserId).first;
|
||||||
|
|
||||||
return route(chatProfileScreen(otherUser, null));
|
if (!context.mounted) return;
|
||||||
},
|
return route(context, chatProfileScreen(context, otherUser, null));
|
||||||
onPressUserProfile: (user) {
|
|
||||||
return route(chatProfileScreen(user, null));
|
|
||||||
},
|
},
|
||||||
|
onPressUserProfile: (user) =>
|
||||||
|
route(context, chatProfileScreen(context, user, null)),
|
||||||
onUploadImage: (data) async {
|
onUploadImage: (data) async {
|
||||||
var path = await chatService.uploadImage(path: 'chats', image: data);
|
var path = await chatService.uploadImage(path: "chats", image: data);
|
||||||
|
|
||||||
chatService.sendMessage(
|
await chatService.sendMessage(
|
||||||
|
messageId: "${chat.id}-$userId-${DateTime.now()}",
|
||||||
chatId: chat.id,
|
chatId: chat.id,
|
||||||
senderId: widget.userId,
|
senderId: userId,
|
||||||
imageUrl: path,
|
imageUrl: path,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
onMessageSubmit: (text) {
|
onMessageSubmit: (text) async {
|
||||||
chatService.sendMessage(
|
await chatService.sendMessage(
|
||||||
|
messageId: "${chat.id}-$userId-${DateTime.now()}",
|
||||||
chatId: chat.id,
|
chatId: chat.id,
|
||||||
senderId: widget.userId,
|
senderId: userId,
|
||||||
text: text,
|
text: text,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
Widget chatProfileScreen(UserModel? user, ChatModel? chat) =>
|
Widget chatProfileScreen(
|
||||||
|
BuildContext context,
|
||||||
|
UserModel? user,
|
||||||
|
ChatModel? chat,
|
||||||
|
) =>
|
||||||
ChatProfileScreen(
|
ChatProfileScreen(
|
||||||
|
service: chatService,
|
||||||
options: chatOptions,
|
options: chatOptions,
|
||||||
userId: widget.userId,
|
userId: userId,
|
||||||
userModel: user,
|
userModel: user,
|
||||||
chatModel: chat,
|
chatModel: chat,
|
||||||
onTapUser: (user) {
|
onTapUser: (userId) async {
|
||||||
route(chatProfileScreen(user, null));
|
var user = await chatService.getUser(userId: userId).first;
|
||||||
|
|
||||||
|
if (!context.mounted) return;
|
||||||
|
route(context, chatProfileScreen(context, user, null));
|
||||||
},
|
},
|
||||||
onPressStartChat: (user) async {
|
onPressStartChat: (userId) async {
|
||||||
var chat = await createChat(user.id);
|
var chat = await createChat(userId);
|
||||||
return route(chatDetailScreen(chat));
|
|
||||||
|
if (!context.mounted) return;
|
||||||
|
return route(context, chatDetailScreen(context, chat));
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
Widget newChatScreen() => NewChatScreen(
|
Widget newChatScreen(BuildContext context) => NewChatScreen(
|
||||||
userId: widget.userId,
|
userId: userId,
|
||||||
chatService: chatService,
|
chatService: chatService,
|
||||||
chatOptions: chatOptions,
|
chatOptions: chatOptions,
|
||||||
onPressCreateGroupChat: () {
|
onPressCreateGroupChat: () =>
|
||||||
return route(newGroupChatScreen());
|
route(context, newGroupChatScreen(context)),
|
||||||
},
|
|
||||||
onPressCreateChat: (user) async {
|
onPressCreateChat: (user) async {
|
||||||
var chat = await createChat(user.id);
|
var chat = await createChat(user.id);
|
||||||
return route(chatDetailScreen(chat));
|
|
||||||
|
if (!context.mounted) return;
|
||||||
|
return route(context, chatDetailScreen(context, chat));
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
Widget newGroupChatScreen() => NewGroupChatScreen(
|
Widget newGroupChatScreen(BuildContext context) => NewGroupChatScreen(
|
||||||
userId: widget.userId,
|
userId: userId,
|
||||||
chatService: chatService,
|
chatService: chatService,
|
||||||
chatOptions: chatOptions,
|
chatOptions: chatOptions,
|
||||||
onContinue: (users) {
|
onContinue: (users) =>
|
||||||
return route(newGroupChatOverview(users));
|
route(context, newGroupChatOverview(context, users)),
|
||||||
},
|
|
||||||
);
|
);
|
||||||
|
|
||||||
Widget newGroupChatOverview(List<UserModel> users) => NewGroupChatOverview(
|
Widget newGroupChatOverview(BuildContext context, List<UserModel> users) =>
|
||||||
|
NewGroupChatOverview(
|
||||||
options: chatOptions,
|
options: chatOptions,
|
||||||
users: users,
|
users: users,
|
||||||
onComplete: (users, title, description, image) async {
|
onComplete: (users, title, description, image) async {
|
||||||
String? path;
|
String? path;
|
||||||
if (image != null) {
|
if (image != null) {
|
||||||
path = await chatService.uploadImage(path: 'groups', image: image);
|
path = await chatService.uploadImage(path: "groups", image: image);
|
||||||
}
|
}
|
||||||
var chat = await createGroupChat(
|
var chat = await createGroupChat(
|
||||||
users,
|
users,
|
||||||
|
@ -150,7 +198,9 @@ class _FlutterChatNavigatorUserstoryState
|
||||||
description,
|
description,
|
||||||
path,
|
path,
|
||||||
);
|
);
|
||||||
return route(chatDetailScreen(chat));
|
|
||||||
|
if (!context.mounted) return;
|
||||||
|
return route(context, chatDetailScreen(context, chat));
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -161,30 +211,43 @@ class _FlutterChatNavigatorUserstoryState
|
||||||
String? imageUrl,
|
String? imageUrl,
|
||||||
) async {
|
) async {
|
||||||
ChatModel? chat;
|
ChatModel? chat;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
chat = await chatService.getGroupChatByUser(
|
chat = await chatService.getGroupChatByUser(
|
||||||
currentUser: widget.userId,
|
currentUser: userId,
|
||||||
otherUsers: userModels,
|
otherUsers: userModels,
|
||||||
chatName: title,
|
chatName: title,
|
||||||
description: description,
|
description: description,
|
||||||
);
|
);
|
||||||
} catch (e) {
|
} on Exception catch (_) {
|
||||||
chat = null;
|
chat = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (chat == null) {
|
if (chat == null) {
|
||||||
var currentUser = await chatService.getUser(userId: widget.userId).first;
|
var currentUser = await chatService.getUser(userId: userId).first;
|
||||||
var otherUsers = await Future.wait(
|
var otherUsers = await Future.wait(
|
||||||
userModels.map((e) => chatService.getUser(userId: e.id).first),
|
userModels.map((e) => chatService.getUser(userId: e.id).first),
|
||||||
);
|
);
|
||||||
|
|
||||||
chat = await chatService.createChat(
|
await chatService.createChat(
|
||||||
|
isGroupChat: true,
|
||||||
users: [currentUser, ...otherUsers],
|
users: [currentUser, ...otherUsers],
|
||||||
chatName: title,
|
chatName: title,
|
||||||
description: description,
|
description: description,
|
||||||
imageUrl: imageUrl,
|
imageUrl: imageUrl,
|
||||||
).first;
|
);
|
||||||
|
|
||||||
|
var chat = await chatService.getGroupChatByUser(
|
||||||
|
currentUser: userId,
|
||||||
|
otherUsers: otherUsers,
|
||||||
|
chatName: title,
|
||||||
|
description: description,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (chat == null) {
|
||||||
|
throw Exception("Chat not created");
|
||||||
|
}
|
||||||
|
|
||||||
|
return chat;
|
||||||
}
|
}
|
||||||
|
|
||||||
return chat;
|
return chat;
|
||||||
|
@ -195,28 +258,42 @@ class _FlutterChatNavigatorUserstoryState
|
||||||
|
|
||||||
try {
|
try {
|
||||||
chat = await chatService.getChatByUser(
|
chat = await chatService.getChatByUser(
|
||||||
currentUser: widget.userId,
|
currentUser: userId,
|
||||||
otherUser: otherUserId,
|
otherUser: otherUserId,
|
||||||
);
|
);
|
||||||
} catch (e) {
|
} on Exception catch (_) {
|
||||||
chat = null;
|
chat = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (chat == null) {
|
if (chat == null) {
|
||||||
var currentUser = await chatService.getUser(userId: widget.userId).first;
|
var currentUser = await chatService.getUser(userId: userId).first;
|
||||||
var otherUser = await chatService.getUser(userId: otherUserId).first;
|
var otherUser = await chatService.getUser(userId: otherUserId).first;
|
||||||
|
|
||||||
chat = await chatService.createChat(
|
await chatService.createChat(
|
||||||
|
isGroupChat: false,
|
||||||
users: [currentUser, otherUser],
|
users: [currentUser, otherUser],
|
||||||
).first;
|
);
|
||||||
|
|
||||||
|
var chat = await chatService.getChatByUser(
|
||||||
|
currentUser: userId,
|
||||||
|
otherUser: otherUserId,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (chat == null) {
|
||||||
|
throw Exception("Chat not created");
|
||||||
}
|
}
|
||||||
|
|
||||||
return chat;
|
return chat;
|
||||||
}
|
}
|
||||||
|
|
||||||
void route(Widget screen) {
|
return chat;
|
||||||
|
}
|
||||||
|
|
||||||
|
void route(BuildContext context, Widget screen) {
|
||||||
|
unawaited(
|
||||||
Navigator.of(context).push(
|
Navigator.of(context).push(
|
||||||
MaterialPageRoute(builder: (context) => screen),
|
MaterialPageRoute(builder: (context) => screen),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,16 +1,19 @@
|
||||||
import 'dart:typed_data';
|
import "dart:async";
|
||||||
|
import "dart:typed_data";
|
||||||
|
|
||||||
import 'package:cached_network_image/cached_network_image.dart';
|
import "package:cached_network_image/cached_network_image.dart";
|
||||||
import 'package:chat_repository_interface/chat_repository_interface.dart';
|
import "package:chat_repository_interface/chat_repository_interface.dart";
|
||||||
import 'package:flutter/material.dart';
|
import "package:flutter/material.dart";
|
||||||
import 'package:flutter_chat/src/screens/creation/widgets/image_picker.dart';
|
import "package:flutter_chat/src/config/chat_options.dart";
|
||||||
import 'package:flutter_chat/src/config/chat_options.dart';
|
import "package:flutter_chat/src/screens/creation/widgets/image_picker.dart";
|
||||||
import 'package:flutter_chat/src/services/date_formatter.dart';
|
import "package:flutter_chat/src/services/date_formatter.dart";
|
||||||
import 'package:flutter_profile/flutter_profile.dart';
|
import "package:flutter_profile/flutter_profile.dart";
|
||||||
|
|
||||||
|
/// Chat detail screen
|
||||||
|
/// Seen when a user clicks on a chat
|
||||||
class ChatDetailScreen extends StatefulWidget {
|
class ChatDetailScreen extends StatefulWidget {
|
||||||
|
/// Constructs a [ChatDetailScreen].
|
||||||
const ChatDetailScreen({
|
const ChatDetailScreen({
|
||||||
super.key,
|
|
||||||
required this.userId,
|
required this.userId,
|
||||||
required this.chatService,
|
required this.chatService,
|
||||||
required this.chatOptions,
|
required this.chatOptions,
|
||||||
|
@ -20,16 +23,34 @@ class ChatDetailScreen extends StatefulWidget {
|
||||||
required this.onUploadImage,
|
required this.onUploadImage,
|
||||||
required this.onMessageSubmit,
|
required this.onMessageSubmit,
|
||||||
required this.onReadChat,
|
required this.onReadChat,
|
||||||
|
super.key,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/// The user ID of the person currently looking at the chat
|
||||||
final String userId;
|
final String userId;
|
||||||
|
|
||||||
|
/// The chat service associated with the widget.
|
||||||
final ChatService chatService;
|
final ChatService chatService;
|
||||||
|
|
||||||
|
/// The chat options
|
||||||
final ChatOptions chatOptions;
|
final ChatOptions chatOptions;
|
||||||
|
|
||||||
|
/// The chat model currently being viewed
|
||||||
final ChatModel chat;
|
final ChatModel chat;
|
||||||
|
|
||||||
|
/// Callback function triggered when the chat title is pressed.
|
||||||
final Function(ChatModel) onPressChatTitle;
|
final Function(ChatModel) onPressChatTitle;
|
||||||
|
|
||||||
|
/// Callback function triggered when the user profile is pressed.
|
||||||
final Function(UserModel) onPressUserProfile;
|
final Function(UserModel) onPressUserProfile;
|
||||||
|
|
||||||
|
/// Callback function triggered when an image is uploaded.
|
||||||
final Function(Uint8List image) onUploadImage;
|
final Function(Uint8List image) onUploadImage;
|
||||||
|
|
||||||
|
/// Callback function triggered when a message is submitted.
|
||||||
final Function(String text) onMessageSubmit;
|
final Function(String text) onMessageSubmit;
|
||||||
|
|
||||||
|
/// Callback function triggered when the chat is read.
|
||||||
final Function(ChatModel chat) onReadChat;
|
final Function(ChatModel chat) onReadChat;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -37,7 +58,7 @@ class ChatDetailScreen extends StatefulWidget {
|
||||||
}
|
}
|
||||||
|
|
||||||
class _ChatDetailScreenState extends State<ChatDetailScreen> {
|
class _ChatDetailScreenState extends State<ChatDetailScreen> {
|
||||||
late String chatTitle;
|
String? chatTitle;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
|
@ -45,25 +66,35 @@ class _ChatDetailScreenState extends State<ChatDetailScreen> {
|
||||||
chatTitle = widget.chat.chatName ??
|
chatTitle = widget.chat.chatName ??
|
||||||
widget.chatOptions.translations.groupNameEmpty;
|
widget.chatOptions.translations.groupNameEmpty;
|
||||||
} else {
|
} else {
|
||||||
chatTitle = widget.chat.users
|
WidgetsBinding.instance.addPostFrameCallback((_) async {
|
||||||
.firstWhere((element) => element.id != widget.userId)
|
await _getTitle();
|
||||||
.fullname ??
|
});
|
||||||
widget.chatOptions.translations.anonymousUser;
|
|
||||||
}
|
}
|
||||||
super.initState();
|
super.initState();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> _getTitle() async {
|
||||||
|
var userId =
|
||||||
|
widget.chat.users.firstWhere((element) => element != widget.userId);
|
||||||
|
var user = await widget.chatService.getUser(userId: userId).first;
|
||||||
|
|
||||||
|
chatTitle = user.fullname ?? widget.chatOptions.translations.anonymousUser;
|
||||||
|
|
||||||
|
setState(() {});
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
var theme = Theme.of(context);
|
var theme = Theme.of(context);
|
||||||
|
|
||||||
return widget.chatOptions.builders.chatDetailScaffoldBuilder?.call(
|
return widget.chatOptions.builders.chatDetailScaffoldBuilder?.call(
|
||||||
|
context,
|
||||||
_AppBar(
|
_AppBar(
|
||||||
chatTitle: chatTitle,
|
chatTitle: chatTitle,
|
||||||
chatOptions: widget.chatOptions,
|
chatOptions: widget.chatOptions,
|
||||||
onPressChatTitle: widget.onPressChatTitle,
|
onPressChatTitle: widget.onPressChatTitle,
|
||||||
chatModel: widget.chat,
|
chatModel: widget.chat,
|
||||||
) as AppBar,
|
),
|
||||||
_Body(
|
_Body(
|
||||||
chatService: widget.chatService,
|
chatService: widget.chatService,
|
||||||
options: widget.chatOptions,
|
options: widget.chatOptions,
|
||||||
|
@ -105,7 +136,7 @@ class _AppBar extends StatelessWidget implements PreferredSizeWidget {
|
||||||
required this.chatModel,
|
required this.chatModel,
|
||||||
});
|
});
|
||||||
|
|
||||||
final String chatTitle;
|
final String? chatTitle;
|
||||||
final ChatOptions chatOptions;
|
final ChatOptions chatOptions;
|
||||||
final Function(ChatModel) onPressChatTitle;
|
final Function(ChatModel) onPressChatTitle;
|
||||||
final ChatModel chatModel;
|
final ChatModel chatModel;
|
||||||
|
@ -128,9 +159,9 @@ class _AppBar extends StatelessWidget implements PreferredSizeWidget {
|
||||||
),
|
),
|
||||||
title: GestureDetector(
|
title: GestureDetector(
|
||||||
onTap: () => onPressChatTitle.call(chatModel),
|
onTap: () => onPressChatTitle.call(chatModel),
|
||||||
child: chatOptions.builders.chatTitleBuilder?.call(chatTitle) ??
|
child: chatOptions.builders.chatTitleBuilder?.call(chatTitle ?? "") ??
|
||||||
Text(
|
Text(
|
||||||
chatTitle,
|
chatTitle ?? "",
|
||||||
overflow: TextOverflow.ellipsis,
|
overflow: TextOverflow.ellipsis,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -167,10 +198,10 @@ class _Body extends StatefulWidget {
|
||||||
}
|
}
|
||||||
|
|
||||||
class _BodyState extends State<_Body> {
|
class _BodyState extends State<_Body> {
|
||||||
ScrollController controller = ScrollController();
|
final ScrollController controller = ScrollController();
|
||||||
bool showIndicator = false;
|
bool showIndicator = false;
|
||||||
late int pageSize;
|
late int pageSize;
|
||||||
var page = 0;
|
int page = 0;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
|
@ -178,10 +209,38 @@ class _BodyState extends State<_Body> {
|
||||||
super.initState();
|
super.initState();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
controller.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
var theme = Theme.of(context);
|
var theme = Theme.of(context);
|
||||||
|
|
||||||
|
void handleScroll(PointerMoveEvent event) {
|
||||||
|
if (!showIndicator &&
|
||||||
|
controller.offset >= controller.position.maxScrollExtent &&
|
||||||
|
!controller.position.outOfRange) {
|
||||||
|
setState(() {
|
||||||
|
showIndicator = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
setState(() {
|
||||||
|
page++;
|
||||||
|
});
|
||||||
|
|
||||||
|
Future.delayed(const Duration(seconds: 2), () {
|
||||||
|
if (mounted) {
|
||||||
|
setState(() {
|
||||||
|
showIndicator = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return Stack(
|
return Stack(
|
||||||
children: [
|
children: [
|
||||||
Column(
|
Column(
|
||||||
|
@ -208,28 +267,7 @@ class _BodyState extends State<_Body> {
|
||||||
});
|
});
|
||||||
|
|
||||||
return Listener(
|
return Listener(
|
||||||
onPointerMove: (event) {
|
onPointerMove: handleScroll,
|
||||||
if (!showIndicator &&
|
|
||||||
controller.offset >=
|
|
||||||
controller.position.maxScrollExtent &&
|
|
||||||
!controller.position.outOfRange) {
|
|
||||||
setState(() {
|
|
||||||
showIndicator = true;
|
|
||||||
});
|
|
||||||
|
|
||||||
setState(() {
|
|
||||||
page++;
|
|
||||||
});
|
|
||||||
|
|
||||||
Future.delayed(const Duration(seconds: 2), () {
|
|
||||||
if (mounted) {
|
|
||||||
setState(() {
|
|
||||||
showIndicator = false;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
child: ListView(
|
child: ListView(
|
||||||
shrinkWrap: true,
|
shrinkWrap: true,
|
||||||
controller: controller,
|
controller: controller,
|
||||||
|
@ -250,6 +288,7 @@ class _BodyState extends State<_Body> {
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
for (var i = 0; i < messages.length; i++) ...[
|
for (var i = 0; i < messages.length; i++) ...[
|
||||||
|
if (widget.chat.id == messages[i].chatId) ...[
|
||||||
_ChatBubble(
|
_ChatBubble(
|
||||||
key: ValueKey(messages[i].id),
|
key: ValueKey(messages[i].id),
|
||||||
message: messages[i],
|
message: messages[i],
|
||||||
|
@ -260,11 +299,13 @@ class _BodyState extends State<_Body> {
|
||||||
onPressUserProfile: widget.onPressUserProfile,
|
onPressUserProfile: widget.onPressUserProfile,
|
||||||
options: widget.options,
|
options: widget.options,
|
||||||
),
|
),
|
||||||
]
|
],
|
||||||
|
],
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}),
|
},
|
||||||
|
),
|
||||||
),
|
),
|
||||||
_ChatBottom(
|
_ChatBottom(
|
||||||
chat: widget.chat,
|
chat: widget.chat,
|
||||||
|
@ -350,6 +391,7 @@ class _ChatBottomState extends State<_ChatBottom> {
|
||||||
child: SizedBox(
|
child: SizedBox(
|
||||||
height: 45,
|
height: 45,
|
||||||
child: widget.options.builders.messageInputBuilder?.call(
|
child: widget.options.builders.messageInputBuilder?.call(
|
||||||
|
context,
|
||||||
_textEditingController,
|
_textEditingController,
|
||||||
Row(
|
Row(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
@ -412,9 +454,7 @@ class _ChatBottomState extends State<_ChatBottom> {
|
||||||
horizontal: 30,
|
horizontal: 30,
|
||||||
),
|
),
|
||||||
hintText: widget.options.translations.messagePlaceholder,
|
hintText: widget.options.translations.messagePlaceholder,
|
||||||
hintStyle: theme.textTheme.bodyMedium!.copyWith(
|
hintStyle: theme.textTheme.bodyMedium,
|
||||||
color: theme.textTheme.bodyMedium!.color!.withOpacity(0.5),
|
|
||||||
),
|
|
||||||
fillColor: Colors.white,
|
fillColor: Colors.white,
|
||||||
filled: true,
|
filled: true,
|
||||||
border: const OutlineInputBorder(
|
border: const OutlineInputBorder(
|
||||||
|
@ -520,7 +560,7 @@ class _ChatBubbleState extends State<_ChatBubble> {
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
if (isNewDate || isSameSender) ...[
|
if (isNewDate || isSameSender) ...[
|
||||||
GestureDetector(
|
InkWell(
|
||||||
onTap: () => widget.onPressUserProfile(user),
|
onTap: () => widget.onPressUserProfile(user),
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.only(left: 10.0),
|
padding: const EdgeInsets.only(left: 10.0),
|
||||||
|
@ -529,6 +569,7 @@ class _ChatBubbleState extends State<_ChatBubble> {
|
||||||
image: user.imageUrl!,
|
image: user.imageUrl!,
|
||||||
)
|
)
|
||||||
: widget.options.builders.userAvatarBuilder?.call(
|
: widget.options.builders.userAvatarBuilder?.call(
|
||||||
|
context,
|
||||||
user,
|
user,
|
||||||
40,
|
40,
|
||||||
) ??
|
) ??
|
||||||
|
@ -538,9 +579,8 @@ class _ChatBubbleState extends State<_ChatBubble> {
|
||||||
user: User(
|
user: User(
|
||||||
firstName: user.firstName,
|
firstName: user.firstName,
|
||||||
lastName: user.lastName,
|
lastName: user.lastName,
|
||||||
imageUrl: user.imageUrl != ""
|
imageUrl:
|
||||||
? user.imageUrl
|
user.imageUrl != "" ? user.imageUrl : null,
|
||||||
: null,
|
|
||||||
),
|
),
|
||||||
size: 40,
|
size: 40,
|
||||||
),
|
),
|
||||||
|
@ -568,8 +608,7 @@ class _ChatBubbleState extends State<_ChatBubble> {
|
||||||
user.fullname ?? "",
|
user.fullname ?? "",
|
||||||
) ??
|
) ??
|
||||||
Text(
|
Text(
|
||||||
user.fullname ??
|
user.fullname ?? translations.anonymousUser,
|
||||||
translations.anonymousUser,
|
|
||||||
style: theme.textTheme.titleMedium,
|
style: theme.textTheme.titleMedium,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -588,7 +627,7 @@ class _ChatBubbleState extends State<_ChatBubble> {
|
||||||
],
|
],
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.only(top: 3.0),
|
padding: const EdgeInsets.only(top: 3.0),
|
||||||
child: widget.message.isTextMessage()
|
child: widget.message.isTextMessage
|
||||||
? Row(
|
? Row(
|
||||||
crossAxisAlignment: CrossAxisAlignment.end,
|
crossAxisAlignment: CrossAxisAlignment.end,
|
||||||
mainAxisAlignment:
|
mainAxisAlignment:
|
||||||
|
@ -617,7 +656,7 @@ class _ChatBubbleState extends State<_ChatBubble> {
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
: widget.message.isImageMessage()
|
: widget.message.isImageMessage
|
||||||
? CachedNetworkImage(
|
? CachedNetworkImage(
|
||||||
imageUrl: widget.message.imageUrl ?? "",
|
imageUrl: widget.message.imageUrl ?? "",
|
||||||
)
|
)
|
||||||
|
@ -630,7 +669,8 @@ class _ChatBubbleState extends State<_ChatBubble> {
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
});
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,37 +1,57 @@
|
||||||
import 'package:chat_repository_interface/chat_repository_interface.dart';
|
import "package:chat_repository_interface/chat_repository_interface.dart";
|
||||||
import 'package:flutter/material.dart';
|
import "package:flutter/material.dart";
|
||||||
import 'package:flutter_chat/src/config/chat_options.dart';
|
import "package:flutter_chat/src/config/chat_options.dart";
|
||||||
import 'package:flutter_profile/flutter_profile.dart';
|
import "package:flutter_profile/flutter_profile.dart";
|
||||||
|
|
||||||
|
/// The chat profile screen
|
||||||
|
/// Seen when a user taps on a chat profile
|
||||||
|
/// Also used for group chats
|
||||||
class ChatProfileScreen extends StatelessWidget {
|
class ChatProfileScreen extends StatelessWidget {
|
||||||
|
/// Constructs a [ChatProfileScreen]
|
||||||
const ChatProfileScreen({
|
const ChatProfileScreen({
|
||||||
super.key,
|
|
||||||
required this.options,
|
required this.options,
|
||||||
required this.userId,
|
required this.userId,
|
||||||
required this.userModel,
|
required this.userModel,
|
||||||
|
required this.service,
|
||||||
required this.chatModel,
|
required this.chatModel,
|
||||||
required this.onTapUser,
|
required this.onTapUser,
|
||||||
required this.onPressStartChat,
|
required this.onPressStartChat,
|
||||||
|
super.key,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/// The chat options
|
||||||
final ChatOptions options;
|
final ChatOptions options;
|
||||||
|
|
||||||
|
/// The user ID of the person currently looking at the chat
|
||||||
final String userId;
|
final String userId;
|
||||||
|
|
||||||
|
/// The user model of the persons profile to be viewed
|
||||||
final UserModel? userModel;
|
final UserModel? userModel;
|
||||||
|
|
||||||
|
/// The chat model of the chat being viewed
|
||||||
final ChatModel? chatModel;
|
final ChatModel? chatModel;
|
||||||
final Function(UserModel)? onTapUser;
|
|
||||||
final Function(UserModel)? onPressStartChat;
|
/// Callback function triggered when a user is tapped
|
||||||
|
final Function(String)? onTapUser;
|
||||||
|
|
||||||
|
final ChatService service;
|
||||||
|
|
||||||
|
/// Callback function triggered when the start chat button is pressed
|
||||||
|
final Function(String)? onPressStartChat;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
var theme = Theme.of(context);
|
var theme = Theme.of(context);
|
||||||
|
|
||||||
return options.builders.chatProfileScaffoldBuilder?.call(
|
return options.builders.chatProfileScaffoldBuilder?.call(
|
||||||
|
context,
|
||||||
_AppBar(
|
_AppBar(
|
||||||
user: userModel,
|
user: userModel,
|
||||||
chat: chatModel,
|
chat: chatModel,
|
||||||
options: options,
|
options: options,
|
||||||
) as AppBar,
|
),
|
||||||
_Body(
|
_Body(
|
||||||
|
service: service,
|
||||||
currentUser: userId,
|
currentUser: userId,
|
||||||
options: options,
|
options: options,
|
||||||
user: userModel,
|
user: userModel,
|
||||||
|
@ -50,6 +70,7 @@ class ChatProfileScreen extends StatelessWidget {
|
||||||
body: _Body(
|
body: _Body(
|
||||||
currentUser: userId,
|
currentUser: userId,
|
||||||
options: options,
|
options: options,
|
||||||
|
service: service,
|
||||||
user: userModel,
|
user: userModel,
|
||||||
chat: chatModel,
|
chat: chatModel,
|
||||||
onTapUser: onTapUser,
|
onTapUser: onTapUser,
|
||||||
|
@ -78,7 +99,7 @@ class _AppBar extends StatelessWidget implements PreferredSizeWidget {
|
||||||
const IconThemeData(color: Colors.white),
|
const IconThemeData(color: Colors.white),
|
||||||
title: Text(
|
title: Text(
|
||||||
user != null
|
user != null
|
||||||
? '${user!.fullname}'
|
? "${user!.fullname}"
|
||||||
: chat != null
|
: chat != null
|
||||||
? chat?.chatName ?? options.translations.groupNameEmpty
|
? chat?.chatName ?? options.translations.groupNameEmpty
|
||||||
: "",
|
: "",
|
||||||
|
@ -93,6 +114,7 @@ class _AppBar extends StatelessWidget implements PreferredSizeWidget {
|
||||||
class _Body extends StatelessWidget {
|
class _Body extends StatelessWidget {
|
||||||
const _Body({
|
const _Body({
|
||||||
required this.options,
|
required this.options,
|
||||||
|
required this.service,
|
||||||
required this.user,
|
required this.user,
|
||||||
required this.chat,
|
required this.chat,
|
||||||
required this.onPressStartChat,
|
required this.onPressStartChat,
|
||||||
|
@ -101,10 +123,11 @@ class _Body extends StatelessWidget {
|
||||||
});
|
});
|
||||||
|
|
||||||
final ChatOptions options;
|
final ChatOptions options;
|
||||||
|
final ChatService service;
|
||||||
final UserModel? user;
|
final UserModel? user;
|
||||||
final ChatModel? chat;
|
final ChatModel? chat;
|
||||||
final Function(UserModel)? onTapUser;
|
final Function(String)? onTapUser;
|
||||||
final Function(UserModel)? onPressStartChat;
|
final Function(String)? onPressStartChat;
|
||||||
final String currentUser;
|
final String currentUser;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -119,6 +142,7 @@ class _Body extends StatelessWidget {
|
||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
options.builders.userAvatarBuilder?.call(
|
options.builders.userAvatarBuilder?.call(
|
||||||
|
context,
|
||||||
user ??
|
user ??
|
||||||
(
|
(
|
||||||
chat != null
|
chat != null
|
||||||
|
@ -185,8 +209,7 @@ class _Body extends StatelessWidget {
|
||||||
),
|
),
|
||||||
Text(
|
Text(
|
||||||
chat!.description ?? "",
|
chat!.description ?? "",
|
||||||
style: theme.textTheme.bodyMedium!
|
style: theme.textTheme.bodyMedium,
|
||||||
.copyWith(color: Colors.black),
|
|
||||||
),
|
),
|
||||||
const SizedBox(
|
const SizedBox(
|
||||||
height: 12,
|
height: 12,
|
||||||
|
@ -206,7 +229,7 @@ class _Body extends StatelessWidget {
|
||||||
bottom: 8,
|
bottom: 8,
|
||||||
right: 8,
|
right: 8,
|
||||||
),
|
),
|
||||||
child: GestureDetector(
|
child: InkWell(
|
||||||
onTap: () {
|
onTap: () {
|
||||||
onTapUser?.call(tappedUser);
|
onTapUser?.call(tappedUser);
|
||||||
},
|
},
|
||||||
|
@ -214,22 +237,41 @@ class _Body extends StatelessWidget {
|
||||||
mainAxisAlignment: MainAxisAlignment.start,
|
mainAxisAlignment: MainAxisAlignment.start,
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
options.builders.userAvatarBuilder?.call(
|
FutureBuilder<UserModel>(
|
||||||
tappedUser,
|
future: service
|
||||||
|
.getUser(userId: tappedUser)
|
||||||
|
.first,
|
||||||
|
builder: (context, snapshot) {
|
||||||
|
if (snapshot.connectionState ==
|
||||||
|
ConnectionState.waiting) {
|
||||||
|
return const CircularProgressIndicator();
|
||||||
|
}
|
||||||
|
|
||||||
|
var user = snapshot.data;
|
||||||
|
|
||||||
|
if (user == null) {
|
||||||
|
return const SizedBox.shrink();
|
||||||
|
}
|
||||||
|
|
||||||
|
return options.builders.userAvatarBuilder
|
||||||
|
?.call(
|
||||||
|
context,
|
||||||
|
user,
|
||||||
44,
|
44,
|
||||||
) ??
|
) ??
|
||||||
Avatar(
|
Avatar(
|
||||||
boxfit: BoxFit.cover,
|
boxfit: BoxFit.cover,
|
||||||
user: User(
|
user: User(
|
||||||
firstName: tappedUser.firstName,
|
firstName: user.firstName,
|
||||||
lastName: tappedUser.lastName,
|
lastName: user.lastName,
|
||||||
imageUrl:
|
imageUrl: user.imageUrl != null ||
|
||||||
tappedUser.imageUrl != null ||
|
user.imageUrl != ""
|
||||||
tappedUser.imageUrl != ""
|
? user.imageUrl
|
||||||
? tappedUser.imageUrl
|
|
||||||
: null,
|
: null,
|
||||||
),
|
),
|
||||||
size: 60,
|
size: 60,
|
||||||
|
);
|
||||||
|
},
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
@ -244,7 +286,7 @@ class _Body extends StatelessWidget {
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
if (user != null && user!.id != currentUser) ...[
|
if (user?.id != currentUser) ...[
|
||||||
Align(
|
Align(
|
||||||
alignment: Alignment.bottomCenter,
|
alignment: Alignment.bottomCenter,
|
||||||
child: Padding(
|
child: Padding(
|
||||||
|
@ -254,7 +296,7 @@ class _Body extends StatelessWidget {
|
||||||
),
|
),
|
||||||
child: FilledButton(
|
child: FilledButton(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
onPressStartChat?.call(user!);
|
onPressStartChat?.call(user!.id);
|
||||||
},
|
},
|
||||||
child: Row(
|
child: Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
|
|
@ -1,23 +1,31 @@
|
||||||
import 'package:chat_repository_interface/chat_repository_interface.dart';
|
import "package:chat_repository_interface/chat_repository_interface.dart";
|
||||||
import 'package:flutter/material.dart';
|
import "package:flutter/material.dart";
|
||||||
import 'package:flutter_chat/src/config/chat_options.dart';
|
import "package:flutter_chat/src/config/chat_options.dart";
|
||||||
import 'package:flutter_chat/src/config/chat_translations.dart';
|
import "package:flutter_chat/src/config/chat_translations.dart";
|
||||||
import 'package:flutter_chat/src/services/date_formatter.dart';
|
import "package:flutter_chat/src/services/date_formatter.dart";
|
||||||
import "package:flutter_profile/flutter_profile.dart";
|
import "package:flutter_profile/flutter_profile.dart";
|
||||||
|
|
||||||
|
/// The chat screen
|
||||||
|
/// Seen when a user is chatting
|
||||||
class ChatScreen extends StatelessWidget {
|
class ChatScreen extends StatelessWidget {
|
||||||
|
/// Constructs a [ChatScreen]
|
||||||
const ChatScreen({
|
const ChatScreen({
|
||||||
super.key,
|
|
||||||
required this.userId,
|
required this.userId,
|
||||||
required this.chatService,
|
required this.chatService,
|
||||||
required this.chatOptions,
|
required this.chatOptions,
|
||||||
required this.onPressChat,
|
required this.onPressChat,
|
||||||
required this.onDeleteChat,
|
required this.onDeleteChat,
|
||||||
this.onPressStartChat,
|
this.onPressStartChat,
|
||||||
|
super.key,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/// The user ID of the person currently looking at the chat
|
||||||
final String userId;
|
final String userId;
|
||||||
|
|
||||||
|
/// The chat service
|
||||||
final ChatService chatService;
|
final ChatService chatService;
|
||||||
|
|
||||||
|
/// The chat options
|
||||||
final ChatOptions chatOptions;
|
final ChatOptions chatOptions;
|
||||||
|
|
||||||
/// Callback function for starting a chat.
|
/// Callback function for starting a chat.
|
||||||
|
@ -26,17 +34,19 @@ class ChatScreen extends StatelessWidget {
|
||||||
/// Callback function for pressing on a chat.
|
/// Callback function for pressing on a chat.
|
||||||
final void Function(ChatModel chat) onPressChat;
|
final void Function(ChatModel chat) onPressChat;
|
||||||
|
|
||||||
|
/// Callback function for deleting a chat.
|
||||||
final void Function(ChatModel chat) onDeleteChat;
|
final void Function(ChatModel chat) onDeleteChat;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
var theme = Theme.of(context);
|
var theme = Theme.of(context);
|
||||||
return chatOptions.builders.chatScreenScaffoldBuilder?.call(
|
return chatOptions.builders.chatScreenScaffoldBuilder?.call(
|
||||||
|
context,
|
||||||
_AppBar(
|
_AppBar(
|
||||||
userId: userId,
|
userId: userId,
|
||||||
chatOptions: chatOptions,
|
chatOptions: chatOptions,
|
||||||
chatService: chatService,
|
chatService: chatService,
|
||||||
) as AppBar,
|
),
|
||||||
_Body(
|
_Body(
|
||||||
userId: userId,
|
userId: userId,
|
||||||
chatOptions: chatOptions,
|
chatOptions: chatOptions,
|
||||||
|
@ -134,7 +144,7 @@ class _Body extends StatefulWidget {
|
||||||
}
|
}
|
||||||
|
|
||||||
class _BodyState extends State<_Body> {
|
class _BodyState extends State<_Body> {
|
||||||
ScrollController controller = ScrollController();
|
final ScrollController controller = ScrollController();
|
||||||
bool _hasCalledOnNoChats = false;
|
bool _hasCalledOnNoChats = false;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -152,7 +162,6 @@ class _BodyState extends State<_Body> {
|
||||||
StreamBuilder<List<ChatModel>?>(
|
StreamBuilder<List<ChatModel>?>(
|
||||||
stream: widget.chatService.getChats(userId: widget.userId),
|
stream: widget.chatService.getChats(userId: widget.userId),
|
||||||
builder: (BuildContext context, snapshot) {
|
builder: (BuildContext context, snapshot) {
|
||||||
// if the stream is done, empty and noChats is set we should call that
|
|
||||||
if (snapshot.connectionState == ConnectionState.done &&
|
if (snapshot.connectionState == ConnectionState.done &&
|
||||||
(snapshot.data?.isEmpty ?? true) ||
|
(snapshot.data?.isEmpty ?? true) ||
|
||||||
(snapshot.data != null && snapshot.data!.isEmpty)) {
|
(snapshot.data != null && snapshot.data!.isEmpty)) {
|
||||||
|
@ -160,6 +169,7 @@ class _BodyState extends State<_Body> {
|
||||||
!_hasCalledOnNoChats) {
|
!_hasCalledOnNoChats) {
|
||||||
_hasCalledOnNoChats = true; // Set the flag to true
|
_hasCalledOnNoChats = true; // Set the flag to true
|
||||||
WidgetsBinding.instance.addPostFrameCallback((_) async {
|
WidgetsBinding.instance.addPostFrameCallback((_) async {
|
||||||
|
// ignore: avoid_dynamic_calls
|
||||||
await widget.chatOptions.onNoChats!.call();
|
await widget.chatOptions.onNoChats!.call();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -172,7 +182,7 @@ class _BodyState extends State<_Body> {
|
||||||
}
|
}
|
||||||
return Column(
|
return Column(
|
||||||
children: [
|
children: [
|
||||||
for (ChatModel chat in (snapshot.data ?? [])) ...[
|
for (ChatModel chat in snapshot.data ?? []) ...[
|
||||||
DecoratedBox(
|
DecoratedBox(
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
border: Border(
|
border: Border(
|
||||||
|
@ -186,17 +196,19 @@ class _BodyState extends State<_Body> {
|
||||||
builder: (context) => !chat.canBeDeleted
|
builder: (context) => !chat.canBeDeleted
|
||||||
? Dismissible(
|
? Dismissible(
|
||||||
confirmDismiss: (_) async {
|
confirmDismiss: (_) async {
|
||||||
widget.chatOptions.builders
|
await widget.chatOptions.builders
|
||||||
.deleteChatDialogBuilder
|
.deleteChatDialogBuilder
|
||||||
?.call(context, chat) ??
|
?.call(context, chat) ??
|
||||||
_deleteDialog(
|
_deleteDialog(
|
||||||
chat,
|
chat,
|
||||||
translations,
|
translations,
|
||||||
|
// ignore: use_build_context_synchronously
|
||||||
context,
|
context,
|
||||||
);
|
);
|
||||||
return _deleteDialog(
|
return _deleteDialog(
|
||||||
chat,
|
chat,
|
||||||
translations,
|
translations,
|
||||||
|
// ignore: use_build_context_synchronously
|
||||||
context,
|
context,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
@ -230,16 +242,18 @@ class _BodyState extends State<_Body> {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
key: ValueKey(
|
key: ValueKey(
|
||||||
chat.id.toString(),
|
chat.id,
|
||||||
),
|
),
|
||||||
child: ChatListItem(
|
child: _ChatItem(
|
||||||
|
service: widget.chatService,
|
||||||
chat: chat,
|
chat: chat,
|
||||||
chatOptions: widget.chatOptions,
|
chatOptions: widget.chatOptions,
|
||||||
userId: widget.userId,
|
userId: widget.userId,
|
||||||
onPressChat: widget.onPressChat,
|
onPressChat: widget.onPressChat,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
: ChatListItem(
|
: _ChatItem(
|
||||||
|
service: widget.chatService,
|
||||||
chat: chat,
|
chat: chat,
|
||||||
chatOptions: widget.chatOptions,
|
chatOptions: widget.chatOptions,
|
||||||
userId: widget.userId,
|
userId: widget.userId,
|
||||||
|
@ -274,7 +288,7 @@ class _BodyState extends State<_Body> {
|
||||||
borderRadius: BorderRadius.circular(56),
|
borderRadius: BorderRadius.circular(56),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
onPressed: widget.onPressStartChat!,
|
onPressed: widget.onPressStartChat,
|
||||||
child: Text(
|
child: Text(
|
||||||
translations.newChatButton,
|
translations.newChatButton,
|
||||||
style: theme.textTheme.displayLarge,
|
style: theme.textTheme.displayLarge,
|
||||||
|
@ -286,17 +300,18 @@ class _BodyState extends State<_Body> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class ChatListItem extends StatelessWidget {
|
class _ChatItem extends StatelessWidget {
|
||||||
const ChatListItem({
|
const _ChatItem({
|
||||||
required this.chat,
|
required this.chat,
|
||||||
required this.chatOptions,
|
required this.chatOptions,
|
||||||
|
required this.service,
|
||||||
required this.userId,
|
required this.userId,
|
||||||
required this.onPressChat,
|
required this.onPressChat,
|
||||||
super.key,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
final ChatModel chat;
|
final ChatModel chat;
|
||||||
final ChatOptions chatOptions;
|
final ChatOptions chatOptions;
|
||||||
|
final ChatService service;
|
||||||
final String userId;
|
final String userId;
|
||||||
final Function(ChatModel chat) onPressChat;
|
final Function(ChatModel chat) onPressChat;
|
||||||
|
|
||||||
|
@ -306,15 +321,17 @@ class ChatListItem extends StatelessWidget {
|
||||||
options: chatOptions,
|
options: chatOptions,
|
||||||
);
|
);
|
||||||
var theme = Theme.of(context);
|
var theme = Theme.of(context);
|
||||||
return GestureDetector(
|
return InkWell(
|
||||||
onTap: () {
|
onTap: () {
|
||||||
onPressChat(chat);
|
onPressChat(chat);
|
||||||
},
|
},
|
||||||
child: chatOptions.builders.chatRowContainerBuilder?.call(
|
child: chatOptions.builders.chatRowContainerBuilder?.call(
|
||||||
|
context,
|
||||||
_ChatListItem(
|
_ChatListItem(
|
||||||
chat: chat,
|
chat: chat,
|
||||||
options: chatOptions,
|
options: chatOptions,
|
||||||
dateFormatter: dateFormatter,
|
dateFormatter: dateFormatter,
|
||||||
|
chatService: service,
|
||||||
currentUserId: userId,
|
currentUserId: userId,
|
||||||
),
|
),
|
||||||
) ??
|
) ??
|
||||||
|
@ -334,6 +351,7 @@ class ChatListItem extends StatelessWidget {
|
||||||
chat: chat,
|
chat: chat,
|
||||||
options: chatOptions,
|
options: chatOptions,
|
||||||
dateFormatter: dateFormatter,
|
dateFormatter: dateFormatter,
|
||||||
|
chatService: service,
|
||||||
currentUserId: userId,
|
currentUserId: userId,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -348,27 +366,46 @@ class _ChatListItem extends StatelessWidget {
|
||||||
required this.options,
|
required this.options,
|
||||||
required this.dateFormatter,
|
required this.dateFormatter,
|
||||||
required this.currentUserId,
|
required this.currentUserId,
|
||||||
|
required this.chatService,
|
||||||
});
|
});
|
||||||
|
|
||||||
final ChatModel chat;
|
final ChatModel chat;
|
||||||
final ChatOptions options;
|
final ChatOptions options;
|
||||||
final DateFormatter dateFormatter;
|
final DateFormatter dateFormatter;
|
||||||
final String currentUserId;
|
final String currentUserId;
|
||||||
|
final ChatService chatService;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
var translations = options.translations;
|
var translations = options.translations;
|
||||||
if (chat.isGroupChat) {
|
if (chat.isGroupChat) {
|
||||||
|
return StreamBuilder<MessageModel?>(
|
||||||
|
stream: chat.lastMessage != null
|
||||||
|
? chatService.getMessage(
|
||||||
|
chatId: chat.id,
|
||||||
|
messageId: chat.lastMessage!,
|
||||||
|
)
|
||||||
|
: const Stream.empty(),
|
||||||
|
builder: (context, snapshot) {
|
||||||
|
if (snapshot.connectionState == ConnectionState.waiting) {
|
||||||
|
return const Center(
|
||||||
|
child: CircularProgressIndicator(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
var data = snapshot.data;
|
||||||
|
|
||||||
return _ChatRow(
|
return _ChatRow(
|
||||||
title: chat.chatName ?? translations.groupNameEmpty,
|
title: chat.chatName ?? translations.groupNameEmpty,
|
||||||
unreadMessages: chat.unreadMessageCount,
|
unreadMessages: chat.unreadMessageCount,
|
||||||
subTitle: chat.lastMessage != null
|
subTitle: data != null
|
||||||
? chat.lastMessage!.isTextMessage()
|
? data.isTextMessage
|
||||||
? chat.lastMessage!.text
|
? data.text
|
||||||
: "📷 "
|
: "📷 "
|
||||||
"${translations.image}"
|
"${translations.image}"
|
||||||
: "",
|
: "",
|
||||||
avatar: options.builders.groupAvatarBuilder?.call(
|
avatar: options.builders.groupAvatarBuilder?.call(
|
||||||
|
context,
|
||||||
chat.chatName ?? translations.groupNameEmpty,
|
chat.chatName ?? translations.groupNameEmpty,
|
||||||
chat.imageUrl,
|
chat.imageUrl,
|
||||||
40.0,
|
40.0,
|
||||||
|
@ -390,14 +427,48 @@ class _ChatListItem extends StatelessWidget {
|
||||||
)
|
)
|
||||||
: null,
|
: null,
|
||||||
);
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
var otherUser = chat.users.firstWhere(
|
var otherUser = chat.users.firstWhere(
|
||||||
(element) => element.id != currentUserId,
|
(element) => element != currentUserId,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
return StreamBuilder<UserModel>(
|
||||||
|
stream: chatService.getUser(userId: otherUser),
|
||||||
|
builder: (context, snapshot) {
|
||||||
|
if (snapshot.connectionState == ConnectionState.waiting) {
|
||||||
|
return const Center(
|
||||||
|
child: CircularProgressIndicator(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
var otherUser = snapshot.data;
|
||||||
|
|
||||||
|
if (otherUser == null) {
|
||||||
|
return const SizedBox();
|
||||||
|
}
|
||||||
|
|
||||||
|
return StreamBuilder<MessageModel?>(
|
||||||
|
stream: chat.lastMessage != null
|
||||||
|
? chatService.getMessage(
|
||||||
|
chatId: chat.id,
|
||||||
|
messageId: chat.lastMessage!,
|
||||||
|
)
|
||||||
|
: const Stream.empty(),
|
||||||
|
builder: (context, snapshot) {
|
||||||
|
if (snapshot.connectionState == ConnectionState.waiting) {
|
||||||
|
return const Center(
|
||||||
|
child: CircularProgressIndicator(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
var data = snapshot.data;
|
||||||
|
|
||||||
return _ChatRow(
|
return _ChatRow(
|
||||||
unreadMessages: chat.unreadMessageCount,
|
unreadMessages: chat.unreadMessageCount,
|
||||||
avatar: options.builders.userAvatarBuilder?.call(
|
avatar: options.builders.userAvatarBuilder?.call(
|
||||||
|
context,
|
||||||
otherUser,
|
otherUser,
|
||||||
40.0,
|
40.0,
|
||||||
) ??
|
) ??
|
||||||
|
@ -406,16 +477,17 @@ class _ChatListItem extends StatelessWidget {
|
||||||
user: User(
|
user: User(
|
||||||
firstName: otherUser.firstName,
|
firstName: otherUser.firstName,
|
||||||
lastName: otherUser.lastName,
|
lastName: otherUser.lastName,
|
||||||
imageUrl: otherUser.imageUrl != null || otherUser.imageUrl != ""
|
imageUrl:
|
||||||
|
otherUser.imageUrl != null || otherUser.imageUrl != ""
|
||||||
? otherUser.imageUrl
|
? otherUser.imageUrl
|
||||||
: null,
|
: null,
|
||||||
),
|
),
|
||||||
size: 40.0,
|
size: 40.0,
|
||||||
),
|
),
|
||||||
title: otherUser.fullname ?? translations.anonymousUser,
|
title: otherUser.fullname ?? translations.anonymousUser,
|
||||||
subTitle: chat.lastMessage != null
|
subTitle: data != null
|
||||||
? chat.lastMessage!.isTextMessage()
|
? data.isTextMessage
|
||||||
? chat.lastMessage!.text
|
? data.text
|
||||||
: "📷 "
|
: "📷 "
|
||||||
"${translations.image}"
|
"${translations.image}"
|
||||||
: "",
|
: "",
|
||||||
|
@ -425,6 +497,10 @@ class _ChatListItem extends StatelessWidget {
|
||||||
)
|
)
|
||||||
: null,
|
: null,
|
||||||
);
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -490,7 +566,6 @@ class _ChatRow extends StatelessWidget {
|
||||||
this.lastUsed,
|
this.lastUsed,
|
||||||
this.subTitle,
|
this.subTitle,
|
||||||
this.avatar,
|
this.avatar,
|
||||||
super.key,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
/// The title of the chat.
|
/// The title of the chat.
|
||||||
|
@ -535,11 +610,7 @@ class _ChatRow extends StatelessWidget {
|
||||||
padding: const EdgeInsets.only(top: 3.0),
|
padding: const EdgeInsets.only(top: 3.0),
|
||||||
child: Text(
|
child: Text(
|
||||||
subTitle!,
|
subTitle!,
|
||||||
style: unreadMessages > 0
|
style: theme.textTheme.bodySmall,
|
||||||
? theme.textTheme.bodySmall!.copyWith(
|
|
||||||
fontWeight: FontWeight.w800,
|
|
||||||
)
|
|
||||||
: theme.textTheme.bodySmall,
|
|
||||||
overflow: TextOverflow.ellipsis,
|
overflow: TextOverflow.ellipsis,
|
||||||
maxLines: 2,
|
maxLines: 2,
|
||||||
),
|
),
|
||||||
|
|
|
@ -1,11 +1,14 @@
|
||||||
import 'package:chat_repository_interface/chat_repository_interface.dart';
|
import "package:chat_repository_interface/chat_repository_interface.dart";
|
||||||
import 'package:flutter/material.dart';
|
import "package:flutter/material.dart";
|
||||||
import 'package:flutter_chat/src/config/chat_options.dart';
|
import "package:flutter_chat/src/config/chat_options.dart";
|
||||||
import 'package:flutter_chat/src/screens/creation/widgets/search_field.dart';
|
import "package:flutter_chat/src/screens/creation/widgets/search_field.dart";
|
||||||
import 'package:flutter_chat/src/screens/creation/widgets/search_icon.dart';
|
import "package:flutter_chat/src/screens/creation/widgets/search_icon.dart";
|
||||||
import 'package:flutter_chat/src/screens/creation/widgets/user_list.dart';
|
import "package:flutter_chat/src/screens/creation/widgets/user_list.dart";
|
||||||
|
|
||||||
|
/// New chat screen
|
||||||
|
/// This screen is used to create a new chat
|
||||||
class NewChatScreen extends StatefulWidget {
|
class NewChatScreen extends StatefulWidget {
|
||||||
|
/// Constructs a [NewChatScreen]
|
||||||
const NewChatScreen({
|
const NewChatScreen({
|
||||||
required this.userId,
|
required this.userId,
|
||||||
required this.chatService,
|
required this.chatService,
|
||||||
|
@ -15,10 +18,19 @@ class NewChatScreen extends StatefulWidget {
|
||||||
super.key,
|
super.key,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/// The user ID of the person currently looking at the chat
|
||||||
final String userId;
|
final String userId;
|
||||||
|
|
||||||
|
/// The chat service associated with the widget.
|
||||||
final ChatService chatService;
|
final ChatService chatService;
|
||||||
|
|
||||||
|
/// The chat options
|
||||||
final ChatOptions chatOptions;
|
final ChatOptions chatOptions;
|
||||||
|
|
||||||
|
/// Callback function triggered when the create group chat button is pressed
|
||||||
final VoidCallback onPressCreateGroupChat;
|
final VoidCallback onPressCreateGroupChat;
|
||||||
|
|
||||||
|
/// Callback function triggered when a user is tapped
|
||||||
final Function(UserModel) onPressCreateChat;
|
final Function(UserModel) onPressCreateChat;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -35,6 +47,7 @@ class _NewChatScreenState extends State<NewChatScreen> {
|
||||||
var theme = Theme.of(context);
|
var theme = Theme.of(context);
|
||||||
|
|
||||||
return widget.chatOptions.builders.newChatScreenScaffoldBuilder?.call(
|
return widget.chatOptions.builders.newChatScreenScaffoldBuilder?.call(
|
||||||
|
context,
|
||||||
_AppBar(
|
_AppBar(
|
||||||
chatOptions: widget.chatOptions,
|
chatOptions: widget.chatOptions,
|
||||||
isSearching: _isSearching,
|
isSearching: _isSearching,
|
||||||
|
@ -55,7 +68,7 @@ class _NewChatScreenState extends State<NewChatScreen> {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
focusNode: _textFieldFocusNode,
|
focusNode: _textFieldFocusNode,
|
||||||
) as AppBar,
|
),
|
||||||
_Body(
|
_Body(
|
||||||
chatOptions: widget.chatOptions,
|
chatOptions: widget.chatOptions,
|
||||||
chatService: widget.chatService,
|
chatService: widget.chatService,
|
||||||
|
@ -218,7 +231,7 @@ class _Body extends StatelessWidget {
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
return chatOptions.builders.noUsersPlaceholderBuilder
|
return chatOptions.builders.noUsersPlaceholderBuilder
|
||||||
?.call(translations) ??
|
?.call(context, translations) ??
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.symmetric(vertical: 20),
|
padding: const EdgeInsets.symmetric(vertical: 20),
|
||||||
child: Align(
|
child: Align(
|
||||||
|
|
|
@ -1,32 +1,46 @@
|
||||||
import 'dart:typed_data';
|
import "dart:typed_data";
|
||||||
|
|
||||||
import 'package:chat_repository_interface/chat_repository_interface.dart';
|
import "package:chat_repository_interface/chat_repository_interface.dart";
|
||||||
import 'package:flutter/material.dart';
|
import "package:flutter/material.dart";
|
||||||
import 'package:flutter_chat/src/config/chat_options.dart';
|
import "package:flutter_chat/src/config/chat_options.dart";
|
||||||
import 'package:flutter_chat/src/screens/creation/widgets/image_picker.dart';
|
import "package:flutter_chat/src/screens/creation/widgets/image_picker.dart";
|
||||||
import 'package:flutter_profile/flutter_profile.dart';
|
import "package:flutter_profile/flutter_profile.dart";
|
||||||
|
|
||||||
|
/// New group chat overview
|
||||||
|
/// Seen after the user has selected the users they
|
||||||
|
/// want to add to the group chat
|
||||||
class NewGroupChatOverview extends StatelessWidget {
|
class NewGroupChatOverview extends StatelessWidget {
|
||||||
|
/// Constructs a [NewGroupChatOverview]
|
||||||
const NewGroupChatOverview({
|
const NewGroupChatOverview({
|
||||||
super.key,
|
|
||||||
required this.options,
|
required this.options,
|
||||||
required this.users,
|
required this.users,
|
||||||
required this.onComplete,
|
required this.onComplete,
|
||||||
|
super.key,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/// The chat options
|
||||||
final ChatOptions options;
|
final ChatOptions options;
|
||||||
|
|
||||||
|
/// The users to be added to the group chat
|
||||||
final List<UserModel> users;
|
final List<UserModel> users;
|
||||||
final Function(List<UserModel> users, String chatName, String description,
|
|
||||||
Uint8List? image) onComplete;
|
/// Callback function triggered when the group chat is created
|
||||||
|
final Function(
|
||||||
|
List<UserModel> users,
|
||||||
|
String chatName,
|
||||||
|
String description,
|
||||||
|
Uint8List? image,
|
||||||
|
) onComplete;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
var theme = Theme.of(context);
|
var theme = Theme.of(context);
|
||||||
|
|
||||||
return options.builders.newGroupChatOverviewScaffoldBuilder?.call(
|
return options.builders.newGroupChatOverviewScaffoldBuilder?.call(
|
||||||
|
context,
|
||||||
_AppBar(
|
_AppBar(
|
||||||
options: options,
|
options: options,
|
||||||
) as AppBar,
|
),
|
||||||
_Body(
|
_Body(
|
||||||
options: options,
|
options: options,
|
||||||
users: users,
|
users: users,
|
||||||
|
@ -80,8 +94,12 @@ class _Body extends StatefulWidget {
|
||||||
|
|
||||||
final ChatOptions options;
|
final ChatOptions options;
|
||||||
final List<UserModel> users;
|
final List<UserModel> users;
|
||||||
final Function(List<UserModel> users, String chatName, String description,
|
final Function(
|
||||||
Uint8List? image) onComplete;
|
List<UserModel> users,
|
||||||
|
String chatName,
|
||||||
|
String description,
|
||||||
|
Uint8List? image,
|
||||||
|
) onComplete;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<_Body> createState() => _BodyState();
|
State<_Body> createState() => _BodyState();
|
||||||
|
@ -92,10 +110,10 @@ class _BodyState extends State<_Body> {
|
||||||
final TextEditingController _bioController = TextEditingController();
|
final TextEditingController _bioController = TextEditingController();
|
||||||
Uint8List? image;
|
Uint8List? image;
|
||||||
|
|
||||||
var formKey = GlobalKey<FormState>();
|
GlobalKey<FormState> formKey = GlobalKey<FormState>();
|
||||||
var isPressed = false;
|
bool isPressed = false;
|
||||||
|
|
||||||
var users = <UserModel>[];
|
List<UserModel> users = <UserModel>[];
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
|
@ -123,7 +141,7 @@ class _BodyState extends State<_Body> {
|
||||||
Center(
|
Center(
|
||||||
child: Stack(
|
child: Stack(
|
||||||
children: [
|
children: [
|
||||||
GestureDetector(
|
InkWell(
|
||||||
onTap: () async => onPressSelectImage(
|
onTap: () async => onPressSelectImage(
|
||||||
context,
|
context,
|
||||||
widget.options,
|
widget.options,
|
||||||
|
@ -162,7 +180,7 @@ class _BodyState extends State<_Body> {
|
||||||
borderRadius: BorderRadius.circular(40),
|
borderRadius: BorderRadius.circular(40),
|
||||||
),
|
),
|
||||||
child: Center(
|
child: Center(
|
||||||
child: GestureDetector(
|
child: InkWell(
|
||||||
onTap: () {
|
onTap: () {
|
||||||
setState(() {
|
setState(() {
|
||||||
image = null;
|
image = null;
|
||||||
|
@ -198,10 +216,7 @@ class _BodyState extends State<_Body> {
|
||||||
fillColor: Colors.white,
|
fillColor: Colors.white,
|
||||||
filled: true,
|
filled: true,
|
||||||
hintText: translations.groupNameHintText,
|
hintText: translations.groupNameHintText,
|
||||||
hintStyle: theme.textTheme.bodyMedium!.copyWith(
|
hintStyle: theme.textTheme.bodyMedium,
|
||||||
color:
|
|
||||||
theme.textTheme.bodyMedium!.color!.withOpacity(0.5),
|
|
||||||
),
|
|
||||||
enabledBorder: OutlineInputBorder(
|
enabledBorder: OutlineInputBorder(
|
||||||
borderRadius: BorderRadius.circular(12),
|
borderRadius: BorderRadius.circular(12),
|
||||||
borderSide: const BorderSide(
|
borderSide: const BorderSide(
|
||||||
|
@ -245,10 +260,7 @@ class _BodyState extends State<_Body> {
|
||||||
fillColor: Colors.white,
|
fillColor: Colors.white,
|
||||||
filled: true,
|
filled: true,
|
||||||
hintText: translations.groupBioHintText,
|
hintText: translations.groupBioHintText,
|
||||||
hintStyle: theme.textTheme.bodyMedium!.copyWith(
|
hintStyle: theme.textTheme.bodyMedium,
|
||||||
color:
|
|
||||||
theme.textTheme.bodyMedium!.color!.withOpacity(0.5),
|
|
||||||
),
|
|
||||||
enabledBorder: OutlineInputBorder(
|
enabledBorder: OutlineInputBorder(
|
||||||
borderRadius: BorderRadius.circular(12),
|
borderRadius: BorderRadius.circular(12),
|
||||||
borderSide: const BorderSide(
|
borderSide: const BorderSide(
|
||||||
|
@ -357,8 +369,7 @@ class _SelectedUser extends StatelessWidget {
|
||||||
final Function(UserModel) onRemove;
|
final Function(UserModel) onRemove;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) => InkWell(
|
||||||
return GestureDetector(
|
|
||||||
onTap: () {
|
onTap: () {
|
||||||
onRemove(user);
|
onRemove(user);
|
||||||
},
|
},
|
||||||
|
@ -367,6 +378,7 @@ class _SelectedUser extends StatelessWidget {
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.all(8),
|
padding: const EdgeInsets.all(8),
|
||||||
child: options.builders.userAvatarBuilder?.call(
|
child: options.builders.userAvatarBuilder?.call(
|
||||||
|
context,
|
||||||
user,
|
user,
|
||||||
40,
|
40,
|
||||||
) ??
|
) ??
|
||||||
|
@ -391,5 +403,4 @@ class _SelectedUser extends StatelessWidget {
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,11 +1,14 @@
|
||||||
import 'package:chat_repository_interface/chat_repository_interface.dart';
|
import "package:chat_repository_interface/chat_repository_interface.dart";
|
||||||
import 'package:flutter/material.dart';
|
import "package:flutter/material.dart";
|
||||||
import 'package:flutter_chat/src/config/chat_options.dart';
|
import "package:flutter_chat/src/config/chat_options.dart";
|
||||||
import 'package:flutter_chat/src/screens/creation/widgets/search_field.dart';
|
import "package:flutter_chat/src/screens/creation/widgets/search_field.dart";
|
||||||
import 'package:flutter_chat/src/screens/creation/widgets/search_icon.dart';
|
import "package:flutter_chat/src/screens/creation/widgets/search_icon.dart";
|
||||||
import 'package:flutter_chat/src/screens/creation/widgets/user_list.dart';
|
import "package:flutter_chat/src/screens/creation/widgets/user_list.dart";
|
||||||
|
|
||||||
|
/// New group chat screen
|
||||||
|
/// This screen is used to create a new group chat
|
||||||
class NewGroupChatScreen extends StatefulWidget {
|
class NewGroupChatScreen extends StatefulWidget {
|
||||||
|
/// Constructs a [NewGroupChatScreen]
|
||||||
const NewGroupChatScreen({
|
const NewGroupChatScreen({
|
||||||
required this.userId,
|
required this.userId,
|
||||||
required this.chatService,
|
required this.chatService,
|
||||||
|
@ -14,9 +17,16 @@ class NewGroupChatScreen extends StatefulWidget {
|
||||||
super.key,
|
super.key,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/// The user ID of the person currently looking at the chat
|
||||||
final String userId;
|
final String userId;
|
||||||
|
|
||||||
|
/// The chat service associated with the widget.
|
||||||
final ChatService chatService;
|
final ChatService chatService;
|
||||||
|
|
||||||
|
/// The chat options
|
||||||
final ChatOptions chatOptions;
|
final ChatOptions chatOptions;
|
||||||
|
|
||||||
|
/// Callback function triggered when the continue button is pressed
|
||||||
final Function(List<UserModel>) onContinue;
|
final Function(List<UserModel>) onContinue;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -35,6 +45,7 @@ class _NewGroupChatScreenState extends State<NewGroupChatScreen> {
|
||||||
var theme = Theme.of(context);
|
var theme = Theme.of(context);
|
||||||
|
|
||||||
return widget.chatOptions.builders.newGroupChatScreenScaffoldBuilder?.call(
|
return widget.chatOptions.builders.newGroupChatScreenScaffoldBuilder?.call(
|
||||||
|
context,
|
||||||
_AppBar(
|
_AppBar(
|
||||||
chatOptions: widget.chatOptions,
|
chatOptions: widget.chatOptions,
|
||||||
isSearching: _isSearching,
|
isSearching: _isSearching,
|
||||||
|
@ -55,7 +66,7 @@ class _NewGroupChatScreenState extends State<NewGroupChatScreen> {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
focusNode: _textFieldFocusNode,
|
focusNode: _textFieldFocusNode,
|
||||||
) as AppBar,
|
),
|
||||||
_Body(
|
_Body(
|
||||||
onSelectedUser: handleUserTap,
|
onSelectedUser: handleUserTap,
|
||||||
selectedUsers: selectedUsers,
|
selectedUsers: selectedUsers,
|
||||||
|
@ -219,7 +230,7 @@ class _Body extends StatelessWidget {
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
return chatOptions.builders.noUsersPlaceholderBuilder
|
return chatOptions.builders.noUsersPlaceholderBuilder
|
||||||
?.call(translations) ??
|
?.call(context, translations) ??
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.symmetric(vertical: 20),
|
padding: const EdgeInsets.symmetric(vertical: 20),
|
||||||
child: Align(
|
child: Align(
|
||||||
|
|
|
@ -1,10 +1,11 @@
|
||||||
import 'dart:typed_data';
|
import "dart:typed_data";
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import "package:flutter/material.dart";
|
||||||
import 'package:flutter_chat/src/config/chat_options.dart';
|
import "package:flutter_chat/src/config/chat_options.dart";
|
||||||
import 'package:flutter_chat/src/config/chat_translations.dart';
|
import "package:flutter_chat/src/config/chat_translations.dart";
|
||||||
import 'package:flutter_image_picker/flutter_image_picker.dart';
|
import "package:flutter_image_picker/flutter_image_picker.dart";
|
||||||
|
|
||||||
|
/// The function to call when the user selects an image
|
||||||
Future<void> onPressSelectImage(
|
Future<void> onPressSelectImage(
|
||||||
BuildContext context,
|
BuildContext context,
|
||||||
ChatOptions options,
|
ChatOptions options,
|
||||||
|
@ -14,9 +15,9 @@ Future<void> onPressSelectImage(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (BuildContext context) =>
|
builder: (BuildContext context) =>
|
||||||
options.builders.imagePickerContainerBuilder?.call(
|
options.builders.imagePickerContainerBuilder?.call(
|
||||||
|
context,
|
||||||
() => Navigator.of(context).pop(),
|
() => Navigator.of(context).pop(),
|
||||||
options.translations,
|
options.translations,
|
||||||
context,
|
|
||||||
) ??
|
) ??
|
||||||
Container(
|
Container(
|
||||||
padding: const EdgeInsets.all(8.0),
|
padding: const EdgeInsets.all(8.0),
|
||||||
|
@ -38,9 +39,7 @@ Future<void> onPressSelectImage(
|
||||||
onPressed: () => Navigator.of(context).pop(),
|
onPressed: () => Navigator.of(context).pop(),
|
||||||
child: Text(
|
child: Text(
|
||||||
options.translations.cancelImagePickerBtn,
|
options.translations.cancelImagePickerBtn,
|
||||||
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
|
style: Theme.of(context).textTheme.bodyMedium,
|
||||||
decoration: TextDecoration.underline,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
|
@ -1,20 +1,31 @@
|
||||||
import 'package:flutter/material.dart';
|
import "package:flutter/material.dart";
|
||||||
import 'package:flutter_chat/src/config/chat_options.dart';
|
import "package:flutter_chat/src/config/chat_options.dart";
|
||||||
|
|
||||||
|
/// The search field widget
|
||||||
class SearchField extends StatelessWidget {
|
class SearchField extends StatelessWidget {
|
||||||
|
/// Constructs a [SearchField]
|
||||||
const SearchField({
|
const SearchField({
|
||||||
super.key,
|
|
||||||
required this.chatOptions,
|
required this.chatOptions,
|
||||||
required this.isSearching,
|
required this.isSearching,
|
||||||
required this.onSearch,
|
required this.onSearch,
|
||||||
required this.focusNode,
|
required this.focusNode,
|
||||||
required this.text,
|
required this.text,
|
||||||
|
super.key,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/// The chat options
|
||||||
final ChatOptions chatOptions;
|
final ChatOptions chatOptions;
|
||||||
|
|
||||||
|
/// Whether the search field is currently in use
|
||||||
final bool isSearching;
|
final bool isSearching;
|
||||||
|
|
||||||
|
/// Callback function triggered when the search field is used
|
||||||
final Function(String query) onSearch;
|
final Function(String query) onSearch;
|
||||||
|
|
||||||
|
/// The focus node of the search field
|
||||||
final FocusNode focusNode;
|
final FocusNode focusNode;
|
||||||
|
|
||||||
|
/// The text to display in the search field
|
||||||
final String text;
|
final String text;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -22,24 +33,25 @@ class SearchField extends StatelessWidget {
|
||||||
var theme = Theme.of(context);
|
var theme = Theme.of(context);
|
||||||
var translations = chatOptions.translations;
|
var translations = chatOptions.translations;
|
||||||
|
|
||||||
return isSearching
|
if (isSearching) {
|
||||||
? TextField(
|
return TextField(
|
||||||
focusNode: focusNode,
|
focusNode: focusNode,
|
||||||
onChanged: onSearch,
|
onChanged: onSearch,
|
||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
hintText: translations.searchPlaceholder,
|
hintText: translations.searchPlaceholder,
|
||||||
hintStyle:
|
hintStyle: theme.textTheme.bodyMedium,
|
||||||
theme.textTheme.bodyMedium!.copyWith(color: Colors.white),
|
|
||||||
focusedBorder: UnderlineInputBorder(
|
focusedBorder: UnderlineInputBorder(
|
||||||
borderSide: BorderSide(
|
borderSide: BorderSide(
|
||||||
color: theme.colorScheme.primary,
|
color: theme.colorScheme.primary,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
style: theme.textTheme.bodySmall!.copyWith(color: Colors.white),
|
style: theme.textTheme.bodySmall,
|
||||||
cursorColor: theme.textSelectionTheme.cursorColor ?? Colors.white,
|
cursorColor: theme.textSelectionTheme.cursorColor ?? Colors.white,
|
||||||
)
|
);
|
||||||
: Text(
|
}
|
||||||
|
|
||||||
|
return Text(
|
||||||
text,
|
text,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,13 +1,18 @@
|
||||||
import 'package:flutter/material.dart';
|
import "package:flutter/material.dart";
|
||||||
|
|
||||||
|
/// A widget representing a search icon.
|
||||||
class SearchIcon extends StatelessWidget {
|
class SearchIcon extends StatelessWidget {
|
||||||
|
/// Constructs a [SearchIcon].
|
||||||
const SearchIcon({
|
const SearchIcon({
|
||||||
super.key,
|
|
||||||
required this.isSearching,
|
required this.isSearching,
|
||||||
required this.onPressed,
|
required this.onPressed,
|
||||||
|
super.key,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/// Whether the search icon is currently in use
|
||||||
final bool isSearching;
|
final bool isSearching;
|
||||||
|
|
||||||
|
/// Callback function triggered when the search icon is pressed
|
||||||
final VoidCallback onPressed;
|
final VoidCallback onPressed;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|
|
@ -1,11 +1,12 @@
|
||||||
import 'package:chat_repository_interface/chat_repository_interface.dart';
|
import "package:chat_repository_interface/chat_repository_interface.dart";
|
||||||
import 'package:flutter/material.dart';
|
import "package:flutter/material.dart";
|
||||||
import 'package:flutter_chat/src/config/chat_options.dart';
|
import "package:flutter_chat/src/config/chat_options.dart";
|
||||||
import 'package:flutter_profile/flutter_profile.dart';
|
import "package:flutter_profile/flutter_profile.dart";
|
||||||
|
|
||||||
|
/// The user list widget
|
||||||
class UserList extends StatefulWidget {
|
class UserList extends StatefulWidget {
|
||||||
|
/// Constructs a [UserList]
|
||||||
const UserList({
|
const UserList({
|
||||||
super.key,
|
|
||||||
required this.users,
|
required this.users,
|
||||||
required this.currentUser,
|
required this.currentUser,
|
||||||
required this.query,
|
required this.query,
|
||||||
|
@ -14,15 +15,31 @@ class UserList extends StatefulWidget {
|
||||||
this.creatingGroup = false,
|
this.creatingGroup = false,
|
||||||
this.selectedUsers = const [],
|
this.selectedUsers = const [],
|
||||||
this.onSelectedUser,
|
this.onSelectedUser,
|
||||||
|
super.key,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/// The list of users
|
||||||
final List<UserModel> users;
|
final List<UserModel> users;
|
||||||
|
|
||||||
|
/// The query to search for
|
||||||
final String query;
|
final String query;
|
||||||
|
|
||||||
|
/// The current user
|
||||||
final String currentUser;
|
final String currentUser;
|
||||||
|
|
||||||
|
/// The chat options
|
||||||
final ChatOptions options;
|
final ChatOptions options;
|
||||||
|
|
||||||
|
/// Whether the user is creating a group
|
||||||
final bool creatingGroup;
|
final bool creatingGroup;
|
||||||
|
|
||||||
|
/// Callback function triggered when a chat is created
|
||||||
final Function(UserModel)? onPressCreateChat;
|
final Function(UserModel)? onPressCreateChat;
|
||||||
|
|
||||||
|
/// The selected users
|
||||||
final List<UserModel> selectedUsers;
|
final List<UserModel> selectedUsers;
|
||||||
|
|
||||||
|
/// Callback function triggered when a user is selected
|
||||||
final Function(UserModel)? onSelectedUser;
|
final Function(UserModel)? onSelectedUser;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -71,10 +88,11 @@ class _UserListState extends State<UserList> {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
child: widget.options.builders.chatRowContainerBuilder?.call(
|
child: widget.options.builders.chatRowContainerBuilder?.call(
|
||||||
|
context,
|
||||||
Row(
|
Row(
|
||||||
children: [
|
children: [
|
||||||
widget.options.builders.userAvatarBuilder
|
widget.options.builders.userAvatarBuilder
|
||||||
?.call(user, 44) ??
|
?.call(context, user, 44) ??
|
||||||
Avatar(
|
Avatar(
|
||||||
boxfit: BoxFit.cover,
|
boxfit: BoxFit.cover,
|
||||||
user: User(
|
user: User(
|
||||||
|
@ -122,7 +140,7 @@ class _UserListState extends State<UserList> {
|
||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
widget.options.builders.userAvatarBuilder
|
widget.options.builders.userAvatarBuilder
|
||||||
?.call(user, 44) ??
|
?.call(context, user, 44) ??
|
||||||
Avatar(
|
Avatar(
|
||||||
boxfit: BoxFit.cover,
|
boxfit: BoxFit.cover,
|
||||||
user: User(
|
user: User(
|
||||||
|
@ -162,7 +180,7 @@ class _UserListState extends State<UserList> {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
void handlePersonalChatTap(UserModel user) async {
|
Future<void> handlePersonalChatTap(UserModel user) async {
|
||||||
if (!isPressed) {
|
if (!isPressed) {
|
||||||
setState(() {
|
setState(() {
|
||||||
isPressed = true;
|
isPressed = true;
|
||||||
|
|
|
@ -5,10 +5,14 @@
|
||||||
import "package:flutter_chat/src/config/chat_options.dart";
|
import "package:flutter_chat/src/config/chat_options.dart";
|
||||||
import "package:intl/intl.dart";
|
import "package:intl/intl.dart";
|
||||||
|
|
||||||
|
/// The date formatter
|
||||||
class DateFormatter {
|
class DateFormatter {
|
||||||
|
/// Constructs a [DateFormatter]
|
||||||
DateFormatter({
|
DateFormatter({
|
||||||
required this.options,
|
required this.options,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/// The chat options
|
||||||
final ChatOptions options;
|
final ChatOptions options;
|
||||||
final _now = DateTime.now();
|
final _now = DateTime.now();
|
||||||
|
|
||||||
|
@ -46,6 +50,7 @@ class DateFormatter {
|
||||||
|
|
||||||
bool _isThisYear(DateTime date) => date.year == _now.year;
|
bool _isThisYear(DateTime date) => date.year == _now.year;
|
||||||
|
|
||||||
|
/// Formats the date
|
||||||
String format({
|
String format({
|
||||||
required DateTime date,
|
required DateTime date,
|
||||||
bool showFullDate = false,
|
bool showFullDate = false,
|
||||||
|
|
|
@ -1,10 +1,11 @@
|
||||||
name: flutter_chat
|
name: flutter_chat
|
||||||
description: "A new Flutter package project."
|
description: "A new Flutter package project."
|
||||||
version: 0.0.1
|
version: 0.0.1
|
||||||
homepage:
|
homepage: https://www.iconica.app
|
||||||
publish_to: https://forgejo.internal.iconica.nl/api/packages/internal/pub
|
publish_to: https://forgejo.internal.iconica.nl/api/packages/internal/pub
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: '>=3.4.3 <4.0.0'
|
sdk: ">=3.4.3 <4.0.0"
|
||||||
flutter: ">=1.17.0"
|
flutter: ">=1.17.0"
|
||||||
|
|
||||||
dependencies:
|
dependencies:
|
||||||
|
@ -20,7 +21,7 @@ dependencies:
|
||||||
ref: 1.0.5
|
ref: 1.0.5
|
||||||
flutter_profile:
|
flutter_profile:
|
||||||
git:
|
git:
|
||||||
ref: 1.5.0
|
ref: 1.6.0
|
||||||
url: https://github.com/Iconica-Development/flutter_profile
|
url: https://github.com/Iconica-Development/flutter_profile
|
||||||
chat_repository_interface:
|
chat_repository_interface:
|
||||||
path: ../chat_repository_interface
|
path: ../chat_repository_interface
|
||||||
|
@ -28,41 +29,9 @@ dependencies:
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
flutter_lints: ^3.0.0
|
flutter_iconica_analysis:
|
||||||
|
git:
|
||||||
|
url: https://github.com/Iconica-Development/flutter_iconica_analysis
|
||||||
|
ref: 7.0.0
|
||||||
|
|
||||||
# For information on the generic Dart part of this file, see the
|
|
||||||
# following page: https://dart.dev/tools/pub/pubspec
|
|
||||||
|
|
||||||
# The following section is specific to Flutter packages.
|
|
||||||
flutter:
|
flutter:
|
||||||
|
|
||||||
# To add assets to your package, add an assets section, like this:
|
|
||||||
# assets:
|
|
||||||
# - images/a_dot_burr.jpeg
|
|
||||||
# - images/a_dot_ham.jpeg
|
|
||||||
#
|
|
||||||
# For details regarding assets in packages, see
|
|
||||||
# https://flutter.dev/assets-and-images/#from-packages
|
|
||||||
#
|
|
||||||
# An image asset can refer to one or more resolution-specific "variants", see
|
|
||||||
# https://flutter.dev/assets-and-images/#resolution-aware
|
|
||||||
|
|
||||||
# To add custom fonts to your package, add a fonts section here,
|
|
||||||
# in this "flutter" section. Each entry in this list should have a
|
|
||||||
# "family" key with the font family name, and a "fonts" key with a
|
|
||||||
# list giving the asset and other descriptors for the font. For
|
|
||||||
# example:
|
|
||||||
# fonts:
|
|
||||||
# - family: Schyler
|
|
||||||
# fonts:
|
|
||||||
# - asset: fonts/Schyler-Regular.ttf
|
|
||||||
# - asset: fonts/Schyler-Italic.ttf
|
|
||||||
# style: italic
|
|
||||||
# - family: Trajan Pro
|
|
||||||
# fonts:
|
|
||||||
# - asset: fonts/TrajanPro.ttf
|
|
||||||
# - asset: fonts/TrajanPro_Bold.ttf
|
|
||||||
# weight: 700
|
|
||||||
#
|
|
||||||
# For details regarding fonts in packages, see
|
|
||||||
# https://flutter.dev/custom-fonts/#from-packages
|
|
||||||
|
|
Loading…
Reference in a new issue