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
|
||||
# VS Code which you may wish to be included in version control, so this line
|
||||
# is commented out by default.
|
||||
#.vscode/
|
||||
.vscode/
|
||||
|
||||
# Flutter/Dart/Pub related
|
||||
# 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
|
||||
# https://dart.dev/guides/language/analysis-options
|
||||
# Possible to overwrite the rules from the package
|
||||
|
||||
analyzer:
|
||||
exclude:
|
||||
|
||||
linter:
|
||||
rules:
|
|
@ -1,17 +1,15 @@
|
|||
library chat_repository_interface;
|
||||
|
||||
// Interfaces
|
||||
export 'src/interfaces/chat_repostory_interface.dart';
|
||||
export 'src/interfaces/user_repository_interface.dart';
|
||||
export "src/interfaces/chat_repostory_interface.dart";
|
||||
export "src/interfaces/user_repository_interface.dart";
|
||||
|
||||
// Local implementations
|
||||
export 'src/local/local_chat_repository.dart';
|
||||
export 'src/local/local_user_repository.dart';
|
||||
export "src/local/local_chat_repository.dart";
|
||||
export "src/local/local_user_repository.dart";
|
||||
|
||||
// Models
|
||||
export 'src/models/chat_model.dart';
|
||||
export 'src/models/message_model.dart';
|
||||
export 'src/models/user_model.dart';
|
||||
export "src/models/chat_model.dart";
|
||||
export "src/models/message_model.dart";
|
||||
export "src/models/user_model.dart";
|
||||
|
||||
// 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/message_model.dart';
|
||||
import 'package:chat_repository_interface/src/models/user_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/user_model.dart";
|
||||
|
||||
/// The chat repository interface
|
||||
/// Implement this interface to create a chat
|
||||
/// repository with a given data source.
|
||||
abstract class ChatRepositoryInterface {
|
||||
String createChat({
|
||||
required List<UserModel> users,
|
||||
/// 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.
|
||||
Future<void> createChat({
|
||||
required List<String> users,
|
||||
required bool isGroupChat,
|
||||
String? chatName,
|
||||
String? description,
|
||||
String? imageUrl,
|
||||
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,
|
||||
});
|
||||
|
||||
/// Get the chat with the given [chatId].
|
||||
/// Returns a [ChatModel] stream.
|
||||
Stream<ChatModel> getChat({
|
||||
required String chatId,
|
||||
});
|
||||
|
||||
/// Get the chats for the given [userId].
|
||||
/// Returns a list of [ChatModel] stream.
|
||||
Stream<List<ChatModel>?> getChats({
|
||||
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({
|
||||
required String chatId,
|
||||
required String userId,
|
||||
|
@ -32,22 +55,46 @@ abstract class ChatRepositoryInterface {
|
|||
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 senderId,
|
||||
required String messageId,
|
||||
String? text,
|
||||
String? imageUrl,
|
||||
DateTime? timestamp,
|
||||
});
|
||||
|
||||
bool deleteChat({
|
||||
/// Delete the chat with the given [chatId].
|
||||
Future<void> deleteChat({
|
||||
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({
|
||||
required String userId,
|
||||
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({
|
||||
required String path,
|
||||
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 {
|
||||
/// Get the user with the given [userId].
|
||||
/// Returns a [UserModel] stream.
|
||||
Stream<UserModel> getUser({required String userId});
|
||||
|
||||
/// Get all the users.
|
||||
/// Returns a list of [UserModel] stream.
|
||||
Stream<List<UserModel>> getAllUsers();
|
||||
}
|
||||
|
|
|
@ -1,114 +1,110 @@
|
|||
import 'dart:async';
|
||||
import 'dart:math';
|
||||
import 'dart:typed_data';
|
||||
import "dart:async";
|
||||
import "dart:typed_data";
|
||||
|
||||
import 'package:chat_repository_interface/chat_repository_interface.dart';
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:rxdart/rxdart.dart';
|
||||
import "package:chat_repository_interface/chat_repository_interface.dart";
|
||||
import "package:collection/collection.dart";
|
||||
import "package:rxdart/rxdart.dart";
|
||||
|
||||
/// The local chat repository
|
||||
class LocalChatRepository implements ChatRepositoryInterface {
|
||||
LocalChatRepository() {
|
||||
var messages = <MessageModel>[];
|
||||
/// The local chat repository constructor
|
||||
LocalChatRepository();
|
||||
|
||||
for (var i = 0; i < 50; i++) {
|
||||
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 =
|
||||
final StreamController<List<ChatModel>> _chatsController =
|
||||
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>>();
|
||||
|
||||
List<ChatModel> _chats = [];
|
||||
final List<ChatModel> _chats = [];
|
||||
final Map<String, List<MessageModel>> _messages = {};
|
||||
|
||||
@override
|
||||
String createChat(
|
||||
{required List<UserModel> users,
|
||||
String? chatName,
|
||||
String? description,
|
||||
String? imageUrl,
|
||||
List<MessageModel>? messages}) {
|
||||
Future<void> createChat({
|
||||
required List<String> users,
|
||||
required bool isGroupChat,
|
||||
String? chatName,
|
||||
String? description,
|
||||
String? imageUrl,
|
||||
List<MessageModel>? messages,
|
||||
}) async {
|
||||
var chat = ChatModel(
|
||||
id: DateTime.now().toString(),
|
||||
isGroupChat: isGroupChat,
|
||||
users: users,
|
||||
messages: messages ?? [],
|
||||
chatName: chatName,
|
||||
description: description,
|
||||
imageUrl: imageUrl,
|
||||
);
|
||||
|
||||
_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
|
||||
Stream<ChatModel> updateChat({required ChatModel chat}) {
|
||||
Future<void> updateChat({
|
||||
required ChatModel chat,
|
||||
}) async {
|
||||
var index = _chats.indexWhere((e) => e.id == chat.id);
|
||||
|
||||
if (index != -1) {
|
||||
_chats[index] = chat;
|
||||
chatsController.add(_chats);
|
||||
_chatsController.add(_chats);
|
||||
}
|
||||
|
||||
return chatController.stream.where((e) => e.id == chat.id);
|
||||
}
|
||||
|
||||
@override
|
||||
bool deleteChat({required String chatId}) {
|
||||
Future<void> deleteChat({
|
||||
required String chatId,
|
||||
}) async {
|
||||
try {
|
||||
_chats.removeWhere((e) => e.id == chatId);
|
||||
chatsController.add(_chats);
|
||||
|
||||
return true;
|
||||
} catch (e) {
|
||||
return false;
|
||||
_chatsController.add(_chats);
|
||||
} on Exception catch (_) {
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Stream<ChatModel> getChat({required String chatId}) {
|
||||
Stream<ChatModel> getChat({
|
||||
required String chatId,
|
||||
}) {
|
||||
var chat = _chats.firstWhereOrNull((e) => e.id == chatId);
|
||||
|
||||
if (chat != null) {
|
||||
chatController.add(chat);
|
||||
_chatController.add(chat);
|
||||
|
||||
if (chat.imageUrl != null && chat.imageUrl!.isNotEmpty) {
|
||||
chat.copyWith(imageUrl: 'https://picsum.photos/200/300');
|
||||
if (chat.imageUrl?.isNotEmpty ?? false) {
|
||||
chat.copyWith(imageUrl: "https://picsum.photos/200/300");
|
||||
}
|
||||
}
|
||||
|
||||
return chatController.stream;
|
||||
return _chatController.stream;
|
||||
}
|
||||
|
||||
@override
|
||||
Stream<List<ChatModel>?> getChats({required String userId}) {
|
||||
chatsController.add(_chats);
|
||||
Stream<List<ChatModel>?> getChats({
|
||||
required String userId,
|
||||
}) {
|
||||
_chatsController.add(_chats);
|
||||
|
||||
return chatsController.stream;
|
||||
return _chatsController.stream;
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -123,57 +119,75 @@ class LocalChatRepository implements ChatRepositoryInterface {
|
|||
chat = _chats.firstWhereOrNull((e) => e.id == chatId);
|
||||
|
||||
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));
|
||||
|
||||
messageController.stream.first
|
||||
.timeout(
|
||||
const Duration(seconds: 1),
|
||||
)
|
||||
.then((oldMessages) {
|
||||
var newMessages = messages.reversed
|
||||
.skip(page * pageSize)
|
||||
.take(pageSize)
|
||||
.toList(growable: false)
|
||||
.reversed
|
||||
.toList();
|
||||
unawaited(
|
||||
_messageController.stream.first
|
||||
.timeout(
|
||||
const Duration(seconds: 1),
|
||||
)
|
||||
.then((oldMessages) {
|
||||
var newMessages = messages.reversed
|
||||
.skip(page * pageSize)
|
||||
.take(pageSize)
|
||||
.toList(growable: false)
|
||||
.reversed
|
||||
.toList();
|
||||
|
||||
if (newMessages.isEmpty) return;
|
||||
if (newMessages.isEmpty) return;
|
||||
|
||||
var allMessages = [...oldMessages, ...newMessages];
|
||||
var allMessages = [...oldMessages, ...newMessages];
|
||||
|
||||
allMessages = allMessages
|
||||
.toSet()
|
||||
.toList()
|
||||
.cast<MessageModel>()
|
||||
.toList(growable: false);
|
||||
allMessages = allMessages
|
||||
.toSet()
|
||||
.toList()
|
||||
.cast<MessageModel>()
|
||||
.toList(growable: false);
|
||||
|
||||
allMessages.sort((a, b) => a.timestamp.compareTo(b.timestamp));
|
||||
allMessages.sort((a, b) => a.timestamp.compareTo(b.timestamp));
|
||||
|
||||
messageController.add(allMessages);
|
||||
}).onError((error, stackTrace) {
|
||||
messageController.add(messages.reversed
|
||||
.skip(page * pageSize)
|
||||
.take(pageSize)
|
||||
.toList(growable: false)
|
||||
.reversed
|
||||
.toList());
|
||||
});
|
||||
_messageController.add(allMessages);
|
||||
}).onError((error, stackTrace) {
|
||||
_messageController.add(
|
||||
messages.reversed
|
||||
.skip(page * pageSize)
|
||||
.take(pageSize)
|
||||
.toList(growable: false)
|
||||
.reversed
|
||||
.toList(),
|
||||
);
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
return messageController.stream;
|
||||
return _messageController.stream;
|
||||
}
|
||||
|
||||
@override
|
||||
bool sendMessage(
|
||||
{required String chatId,
|
||||
required String senderId,
|
||||
String? text,
|
||||
String? imageUrl}) {
|
||||
Stream<MessageModel?> getMessage({
|
||||
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 messageId,
|
||||
String? text,
|
||||
String? imageUrl,
|
||||
DateTime? timestamp,
|
||||
}) async {
|
||||
var message = MessageModel(
|
||||
id: DateTime.now().toString(),
|
||||
timestamp: DateTime.now(),
|
||||
chatId: chatId,
|
||||
id: messageId,
|
||||
timestamp: timestamp ?? DateTime.now(),
|
||||
text: text,
|
||||
senderId: senderId,
|
||||
imageUrl: imageUrl,
|
||||
|
@ -181,34 +195,45 @@ class LocalChatRepository implements ChatRepositoryInterface {
|
|||
|
||||
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);
|
||||
messageController.add(chat.messages);
|
||||
var messages = List<MessageModel>.from(_messages[chatId] ?? []);
|
||||
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
|
||||
Stream<int> getUnreadMessagesCount({required String userId, String? chatId}) {
|
||||
return chatsController.stream.map((chats) {
|
||||
var count = 0;
|
||||
Stream<int> getUnreadMessagesCount({
|
||||
required String userId,
|
||||
String? chatId,
|
||||
}) =>
|
||||
_chatsController.stream.map((chats) {
|
||||
var count = 0;
|
||||
|
||||
for (var chat in chats) {
|
||||
if (chat.users.any((e) => e.id == userId)) {
|
||||
count += chat.unreadMessageCount;
|
||||
for (var chat in chats) {
|
||||
if (chat.users.contains(userId)) {
|
||||
count += chat.unreadMessageCount;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return count;
|
||||
});
|
||||
}
|
||||
return count;
|
||||
});
|
||||
|
||||
@override
|
||||
Future<String> uploadImage({
|
||||
required String path,
|
||||
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/models/user_model.dart';
|
||||
import 'package:rxdart/rxdart.dart';
|
||||
import "package:chat_repository_interface/src/interfaces/user_repository_interface.dart";
|
||||
import "package:chat_repository_interface/src/models/user_model.dart";
|
||||
import "package:rxdart/rxdart.dart";
|
||||
|
||||
/// The local user repository
|
||||
class LocalUserRepository implements UserRepositoryInterface {
|
||||
final StreamController<List<UserModel>> _usersController =
|
||||
BehaviorSubject<List<UserModel>>();
|
||||
|
||||
final List<UserModel> _users = [
|
||||
UserModel(
|
||||
id: '1',
|
||||
firstName: 'John',
|
||||
lastName: 'Doe',
|
||||
imageUrl: 'https://picsum.photos/200/300',
|
||||
id: "1",
|
||||
firstName: "John",
|
||||
lastName: "Doe",
|
||||
imageUrl: "https://picsum.photos/200/300",
|
||||
),
|
||||
UserModel(
|
||||
id: '2',
|
||||
firstName: 'Jane',
|
||||
lastName: 'Doe',
|
||||
imageUrl: 'https://picsum.photos/200/300',
|
||||
id: "2",
|
||||
firstName: "Jane",
|
||||
lastName: "Doe",
|
||||
imageUrl: "https://picsum.photos/200/300",
|
||||
),
|
||||
UserModel(
|
||||
id: '3',
|
||||
firstName: 'Frans',
|
||||
lastName: 'Timmermans',
|
||||
imageUrl: 'https://picsum.photos/200/300',
|
||||
id: "3",
|
||||
firstName: "Frans",
|
||||
lastName: "Timmermans",
|
||||
imageUrl: "https://picsum.photos/200/300",
|
||||
),
|
||||
UserModel(
|
||||
id: '4',
|
||||
firstName: 'Hendrik-Jan',
|
||||
lastName: 'De derde',
|
||||
imageUrl: 'https://picsum.photos/200/300',
|
||||
id: "4",
|
||||
firstName: "Hendrik-Jan",
|
||||
lastName: "De derde",
|
||||
imageUrl: "https://picsum.photos/200/300",
|
||||
),
|
||||
];
|
||||
|
||||
@override
|
||||
Stream<UserModel> getUser({required String userId}) {
|
||||
return getAllUsers().map((users) => users.firstWhere(
|
||||
Stream<UserModel> getUser({
|
||||
required String userId,
|
||||
}) =>
|
||||
getAllUsers().map(
|
||||
(users) => users.firstWhere(
|
||||
(e) => e.id == userId,
|
||||
orElse: () => throw Exception(),
|
||||
));
|
||||
}
|
||||
),
|
||||
);
|
||||
|
||||
@override
|
||||
Stream<List<UserModel>> getAllUsers() {
|
||||
|
|
|
@ -1,11 +1,21 @@
|
|||
import 'package:chat_repository_interface/src/models/message_model.dart';
|
||||
import 'package:chat_repository_interface/src/models/user_model.dart';
|
||||
|
||||
/// The chat model
|
||||
/// 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 {
|
||||
ChatModel({
|
||||
/// The chat model constructor
|
||||
const ChatModel({
|
||||
required this.id,
|
||||
required this.users,
|
||||
required this.messages,
|
||||
required this.isGroupChat,
|
||||
this.chatName,
|
||||
this.description,
|
||||
this.imageUrl,
|
||||
|
@ -15,51 +25,68 @@ class ChatModel {
|
|||
this.unreadMessageCount = 0,
|
||||
});
|
||||
|
||||
/// The chat 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;
|
||||
|
||||
/// The chat description
|
||||
final String? description;
|
||||
|
||||
/// The chat image url
|
||||
final String? imageUrl;
|
||||
|
||||
/// A boolean that indicates if the chat can be deleted
|
||||
final bool canBeDeleted;
|
||||
|
||||
/// The last time the chat was used
|
||||
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;
|
||||
|
||||
/// A boolean that indicates if the chat is a group chat
|
||||
final bool isGroupChat;
|
||||
|
||||
/// The chat model copy with method
|
||||
ChatModel copyWith({
|
||||
String? id,
|
||||
List<MessageModel>? messages,
|
||||
List<UserModel>? users,
|
||||
List<String>? users,
|
||||
String? chatName,
|
||||
String? description,
|
||||
String? imageUrl,
|
||||
bool? canBeDeleted,
|
||||
DateTime? lastUsed,
|
||||
MessageModel? lastMessage,
|
||||
String? lastMessage,
|
||||
int? unreadMessageCount,
|
||||
}) {
|
||||
return ChatModel(
|
||||
id: id ?? this.id,
|
||||
messages: messages ?? this.messages,
|
||||
users: users ?? this.users,
|
||||
chatName: chatName ?? this.chatName,
|
||||
description: description ?? this.description,
|
||||
imageUrl: imageUrl ?? this.imageUrl,
|
||||
canBeDeleted: canBeDeleted ?? this.canBeDeleted,
|
||||
lastUsed: lastUsed ?? this.lastUsed,
|
||||
lastMessage: lastMessage ?? this.lastMessage,
|
||||
unreadMessageCount: unreadMessageCount ?? this.unreadMessageCount,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
extension IsGroupChat on ChatModel {
|
||||
bool get isGroupChat => users.length > 2;
|
||||
bool? isGroupChat,
|
||||
}) =>
|
||||
ChatModel(
|
||||
id: id ?? this.id,
|
||||
users: users ?? this.users,
|
||||
chatName: chatName ?? this.chatName,
|
||||
isGroupChat: isGroupChat ?? this.isGroupChat,
|
||||
description: description ?? this.description,
|
||||
imageUrl: imageUrl ?? this.imageUrl,
|
||||
canBeDeleted: canBeDeleted ?? this.canBeDeleted,
|
||||
lastUsed: lastUsed ?? this.lastUsed,
|
||||
lastMessage: lastMessage ?? this.lastMessage,
|
||||
unreadMessageCount: unreadMessageCount ?? this.unreadMessageCount,
|
||||
);
|
||||
}
|
||||
|
||||
/// 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 {
|
||||
UserModel getOtherUser(String userId) {
|
||||
return users.firstWhere((user) => user.id != userId);
|
||||
}
|
||||
/// The get other user method
|
||||
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 {
|
||||
MessageModel({
|
||||
/// Message model constructor
|
||||
const MessageModel({
|
||||
required this.chatId,
|
||||
required this.id,
|
||||
required this.text,
|
||||
required this.imageUrl,
|
||||
|
@ -7,31 +16,47 @@ class MessageModel {
|
|||
required this.senderId,
|
||||
});
|
||||
|
||||
final String chatId;
|
||||
|
||||
/// The message id
|
||||
final String id;
|
||||
|
||||
/// The message text
|
||||
final String? text;
|
||||
|
||||
/// The message image url
|
||||
final String? imageUrl;
|
||||
|
||||
/// The message timestamp
|
||||
final DateTime timestamp;
|
||||
|
||||
/// The sender id
|
||||
final String senderId;
|
||||
|
||||
/// The message model copy with method
|
||||
MessageModel copyWith({
|
||||
String? chatId,
|
||||
String? id,
|
||||
String? text,
|
||||
String? imageUrl,
|
||||
DateTime? timestamp,
|
||||
String? senderId,
|
||||
}) {
|
||||
return MessageModel(
|
||||
id: id ?? this.id,
|
||||
text: text ?? this.text,
|
||||
imageUrl: imageUrl ?? this.imageUrl,
|
||||
timestamp: timestamp ?? this.timestamp,
|
||||
senderId: senderId ?? this.senderId,
|
||||
);
|
||||
}
|
||||
}) =>
|
||||
MessageModel(
|
||||
chatId: chatId ?? this.chatId,
|
||||
id: id ?? this.id,
|
||||
text: text ?? this.text,
|
||||
imageUrl: imageUrl ?? this.imageUrl,
|
||||
timestamp: timestamp ?? this.timestamp,
|
||||
senderId: senderId ?? this.senderId,
|
||||
);
|
||||
}
|
||||
|
||||
/// Extension on [MessageModel] to check the message type
|
||||
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 {
|
||||
UserModel({
|
||||
/// User model constructor
|
||||
const UserModel({
|
||||
required this.id,
|
||||
this.firstName,
|
||||
this.lastName,
|
||||
this.imageUrl,
|
||||
});
|
||||
|
||||
/// The user id
|
||||
final String id;
|
||||
|
||||
/// The user first name
|
||||
final String? firstName;
|
||||
|
||||
/// The user last name
|
||||
final String? lastName;
|
||||
|
||||
/// The user image url
|
||||
final String? imageUrl;
|
||||
}
|
||||
|
||||
/// Extension on [UserModel] to get the user full name
|
||||
extension Fullname on UserModel {
|
||||
/// Get the user full name
|
||||
String? get fullname {
|
||||
if (firstName == null && lastName == null) {
|
||||
return null;
|
||||
|
|
|
@ -1,142 +1,191 @@
|
|||
import 'dart:async';
|
||||
import 'dart:typed_data';
|
||||
import "dart:async";
|
||||
import "dart:typed_data";
|
||||
|
||||
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/local/local_chat_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/message_model.dart';
|
||||
import 'package:chat_repository_interface/src/models/user_model.dart';
|
||||
import 'package:collection/collection.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/local/local_chat_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/message_model.dart";
|
||||
import "package:chat_repository_interface/src/models/user_model.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 {
|
||||
final ChatRepositoryInterface chatRepository;
|
||||
final UserRepositoryInterface userRepository;
|
||||
|
||||
/// Create a chat service with the given parameters.
|
||||
ChatService({
|
||||
ChatRepositoryInterface? chatRepository,
|
||||
UserRepositoryInterface? userRepository,
|
||||
}) : chatRepository = chatRepository ?? LocalChatRepository(),
|
||||
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 bool isGroupChat,
|
||||
String? chatName,
|
||||
String? description,
|
||||
String? imageUrl,
|
||||
List<MessageModel>? messages,
|
||||
}) {
|
||||
var chatId = chatRepository.createChat(
|
||||
users: users,
|
||||
var userIds = users.map((e) => e.id).toList();
|
||||
|
||||
return chatRepository.createChat(
|
||||
isGroupChat: isGroupChat,
|
||||
users: userIds,
|
||||
chatName: chatName,
|
||||
description: description,
|
||||
imageUrl: imageUrl,
|
||||
messages: messages,
|
||||
);
|
||||
|
||||
return chatRepository.getChat(chatId: chatId);
|
||||
}
|
||||
|
||||
/// Get the chats for the given [userId].
|
||||
/// Returns a list of [ChatModel] stream.
|
||||
Stream<List<ChatModel>?> getChats({
|
||||
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({
|
||||
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({
|
||||
required String currentUser,
|
||||
required String otherUser,
|
||||
}) async {
|
||||
var chats = await chatRepository
|
||||
.getChats(userId: currentUser)
|
||||
.first
|
||||
.timeout(const Duration(seconds: 1));
|
||||
var chats = await chatRepository.getChats(userId: currentUser).first;
|
||||
|
||||
var personalChats =
|
||||
chats?.where((element) => element.users.length == 2).toList();
|
||||
|
||||
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({
|
||||
required String currentUser,
|
||||
required List<UserModel> otherUsers,
|
||||
required String chatName,
|
||||
required String description,
|
||||
}) async {
|
||||
var chats = await chatRepository
|
||||
.getChats(userId: currentUser)
|
||||
.first
|
||||
.timeout(const Duration(seconds: 1));
|
||||
|
||||
var personalChats =
|
||||
chats?.where((element) => element.users.length > 2).toList();
|
||||
|
||||
try {
|
||||
var chats = await chatRepository.getChats(userId: currentUser).first;
|
||||
|
||||
var personalChats =
|
||||
chats?.where((element) => element.isGroupChat).toList();
|
||||
|
||||
var groupChats = personalChats
|
||||
?.where((chats) => otherUsers.every(chats.users.contains))
|
||||
?.where(
|
||||
(chats) =>
|
||||
otherUsers.every((user) => chats.users.contains(user.id)),
|
||||
)
|
||||
.toList();
|
||||
|
||||
return groupChats?.firstWhereOrNull(
|
||||
(element) =>
|
||||
element.chatName == chatName && element.description == description,
|
||||
);
|
||||
} catch (e) {
|
||||
return null;
|
||||
// ignore: avoid_catches_without_on_clauses
|
||||
} 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({
|
||||
required String userId,
|
||||
required String chatId,
|
||||
required int pageSize,
|
||||
required int page,
|
||||
}) {
|
||||
return chatRepository.getMessages(
|
||||
userId: userId,
|
||||
chatId: chatId,
|
||||
pageSize: pageSize,
|
||||
page: page,
|
||||
);
|
||||
}
|
||||
}) =>
|
||||
chatRepository.getMessages(
|
||||
userId: userId,
|
||||
chatId: chatId,
|
||||
pageSize: pageSize,
|
||||
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,
|
||||
String? text,
|
||||
required String senderId,
|
||||
required String messageId,
|
||||
String? text,
|
||||
String? imageUrl,
|
||||
}) {
|
||||
return chatRepository.sendMessage(
|
||||
chatId: chatId,
|
||||
text: text,
|
||||
senderId: senderId,
|
||||
imageUrl: imageUrl,
|
||||
);
|
||||
}
|
||||
}) =>
|
||||
chatRepository.sendMessage(
|
||||
chatId: chatId,
|
||||
messageId: messageId,
|
||||
text: text,
|
||||
senderId: senderId,
|
||||
imageUrl: imageUrl,
|
||||
);
|
||||
|
||||
bool deleteChat({
|
||||
/// Delete the chat with the given parameters.
|
||||
/// [chatId] is the chat id.
|
||||
Future<void> deleteChat({
|
||||
required String chatId,
|
||||
}) {
|
||||
return chatRepository.deleteChat(chatId: chatId);
|
||||
}
|
||||
}) =>
|
||||
chatRepository.deleteChat(chatId: chatId);
|
||||
|
||||
Stream<UserModel> getUser({required String userId}) {
|
||||
return userRepository.getUser(userId: userId);
|
||||
}
|
||||
/// Get user with the given [userId].
|
||||
/// Returns a [UserModel] stream.
|
||||
Stream<UserModel> getUser({required String userId}) =>
|
||||
userRepository.getUser(userId: userId);
|
||||
|
||||
Stream<List<UserModel>> getAllUsers() {
|
||||
return userRepository.getAllUsers();
|
||||
}
|
||||
/// Get all the users.
|
||||
/// 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({
|
||||
required String userId,
|
||||
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({
|
||||
required String path,
|
||||
required Uint8List image,
|
||||
}) {
|
||||
return chatRepository.uploadImage(
|
||||
path: path,
|
||||
image: image,
|
||||
);
|
||||
}
|
||||
}) =>
|
||||
chatRepository.uploadImage(
|
||||
path: path,
|
||||
image: image,
|
||||
);
|
||||
|
||||
/// Mark the chat as read with the given parameters.
|
||||
/// [chatId] is the chat id.
|
||||
/// Returns a [Future] of [void].
|
||||
Future<void> markAsRead({
|
||||
required String chatId,
|
||||
}) async {
|
||||
|
@ -171,6 +226,6 @@ class ChatService {
|
|||
unreadMessageCount: 0,
|
||||
);
|
||||
|
||||
chatRepository.updateChat(chat: newChat);
|
||||
await chatRepository.updateChat(chat: newChat);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,41 +17,9 @@ dependencies:
|
|||
dev_dependencies:
|
||||
flutter_test:
|
||||
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:
|
||||
|
||||
# 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
|
||||
# VS Code which you may wish to be included in version control, so this line
|
||||
# is commented out by default.
|
||||
#.vscode/
|
||||
.vscode/
|
||||
|
||||
# Flutter/Dart/Pub related
|
||||
# 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
|
||||
# https://dart.dev/guides/language/analysis-options
|
||||
# Possible to overwrite the rules from the package
|
||||
|
||||
analyzer:
|
||||
exclude:
|
||||
|
||||
linter:
|
||||
rules:
|
|
@ -1,5 +1,3 @@
|
|||
library firebase_chat_repository;
|
||||
|
||||
/// A Calculator.
|
||||
class Calculator {
|
||||
/// Returns [value] plus 1.
|
||||
|
|
|
@ -14,41 +14,9 @@ dependencies:
|
|||
dev_dependencies:
|
||||
flutter_test:
|
||||
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:
|
||||
|
||||
# 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
|
||||
# VS Code which you may wish to be included in version control, so this line
|
||||
# is commented out by default.
|
||||
#.vscode/
|
||||
.vscode/
|
||||
|
||||
# Flutter/Dart/Pub related
|
||||
# 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
|
||||
# https://dart.dev/guides/language/analysis-options
|
||||
# Possible to overwrite the rules from the package
|
||||
|
||||
analyzer:
|
||||
exclude:
|
||||
|
||||
linter:
|
||||
rules:
|
|
@ -1,26 +1,41 @@
|
|||
import "package:flutter/material.dart";
|
||||
import "package:flutter_chat/flutter_chat.dart";
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_chat/flutter_chat.dart';
|
||||
|
||||
void main(List<String> args) async {
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
runApp(const App());
|
||||
void main() {
|
||||
runApp(const MyApp());
|
||||
}
|
||||
|
||||
class App extends StatelessWidget {
|
||||
const App({super.key});
|
||||
class MyApp extends StatelessWidget {
|
||||
const MyApp({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) => const MaterialApp(
|
||||
home: Home(),
|
||||
);
|
||||
Widget build(BuildContext context) {
|
||||
return MaterialApp(
|
||||
title: 'Flutter Demo',
|
||||
theme: ThemeData(
|
||||
useMaterial3: true,
|
||||
),
|
||||
home: const MyHomePage(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class Home extends StatelessWidget {
|
||||
const Home({super.key});
|
||||
class MyHomePage extends StatefulWidget {
|
||||
const MyHomePage({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) => const Center(
|
||||
child: FlutterChatEntryWidget(userId: '1'),
|
||||
);
|
||||
State<MyHomePage> createState() => _MyHomePageState();
|
||||
}
|
||||
|
||||
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
|
||||
description: "A new Flutter project."
|
||||
# The following line prevents the package from being accidentally published to
|
||||
# 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.
|
||||
publish_to: 'none'
|
||||
version: 1.0.0+1
|
||||
|
||||
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:
|
||||
flutter:
|
||||
sdk: flutter
|
||||
|
||||
|
||||
# The following adds the Cupertino Icons font to your application.
|
||||
# Use with the CupertinoIcons class for iOS style icons.
|
||||
cupertino_icons: ^1.0.6
|
||||
cupertino_icons: ^1.0.8
|
||||
flutter_chat:
|
||||
path: ../
|
||||
|
||||
|
@ -42,51 +18,7 @@ dev_dependencies:
|
|||
flutter_test:
|
||||
sdk: flutter
|
||||
|
||||
# The "flutter_lints" package below contains a set of recommended lints to
|
||||
# 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
|
||||
flutter_lints: ^4.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:
|
||||
|
||||
# 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
|
||||
|
||||
# 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
|
||||
export 'package:chat_repository_interface/chat_repository_interface.dart';
|
||||
|
||||
// Screens
|
||||
export "src/config/chat_options.dart";
|
||||
|
||||
// User story
|
||||
export "package:flutter_chat/src/flutter_chat_entry_widget.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:flutter/material.dart';
|
||||
import 'package:flutter_chat/src/config/chat_translations.dart';
|
||||
import "package:chat_repository_interface/chat_repository_interface.dart";
|
||||
import "package:flutter/material.dart";
|
||||
import "package:flutter_chat/src/config/chat_translations.dart";
|
||||
|
||||
/// The chat builders
|
||||
class ChatBuilders {
|
||||
/// The chat builders constructor
|
||||
const ChatBuilders({
|
||||
this.chatScreenScaffoldBuilder,
|
||||
this.newChatScreenScaffoldBuilder,
|
||||
|
@ -23,76 +25,112 @@ class ChatBuilders {
|
|||
this.loadingWidgetBuilder,
|
||||
});
|
||||
|
||||
/// The chat screen scaffold builder
|
||||
final ScaffoldBuilder? chatScreenScaffoldBuilder;
|
||||
|
||||
/// The new chat screen scaffold builder
|
||||
final ScaffoldBuilder? newChatScreenScaffoldBuilder;
|
||||
|
||||
/// The new group chat overview scaffold builder
|
||||
final ScaffoldBuilder? newGroupChatOverviewScaffoldBuilder;
|
||||
|
||||
/// The new group chat screen scaffold builder
|
||||
final ScaffoldBuilder? newGroupChatScreenScaffoldBuilder;
|
||||
|
||||
/// The chat detail scaffold builder
|
||||
final ScaffoldBuilder? chatDetailScaffoldBuilder;
|
||||
|
||||
/// The chat profile scaffold builder
|
||||
final ScaffoldBuilder? chatProfileScaffoldBuilder;
|
||||
|
||||
/// The message input builder
|
||||
final TextInputBuilder? messageInputBuilder;
|
||||
|
||||
/// The chat row container builder
|
||||
final ContainerBuilder? chatRowContainerBuilder;
|
||||
|
||||
/// The group avatar builder
|
||||
final GroupAvatarBuilder? groupAvatarBuilder;
|
||||
|
||||
/// The user avatar builder
|
||||
final UserAvatarBuilder? userAvatarBuilder;
|
||||
|
||||
/// The delete chat dialog builder
|
||||
final Future<bool?> Function(BuildContext, ChatModel)?
|
||||
deleteChatDialogBuilder;
|
||||
|
||||
/// The new chat button builder
|
||||
final ButtonBuilder? newChatButtonBuilder;
|
||||
|
||||
/// The no users placeholder builder
|
||||
final NoUsersPlaceholderBuilder? noUsersPlaceholderBuilder;
|
||||
|
||||
/// The chat title builder
|
||||
final Widget Function(String chatTitle)? chatTitleBuilder;
|
||||
|
||||
/// The username builder
|
||||
final Widget Function(String userFullName)? usernameBuilder;
|
||||
|
||||
/// The image picker container builder
|
||||
final ImagePickerContainerBuilder? imagePickerContainerBuilder;
|
||||
|
||||
/// The loading widget builder
|
||||
final Widget? Function(BuildContext context)? loadingWidgetBuilder;
|
||||
}
|
||||
|
||||
/// The button builder
|
||||
typedef ButtonBuilder = Widget Function(
|
||||
BuildContext context,
|
||||
VoidCallback onPressed,
|
||||
ChatTranslations translations,
|
||||
);
|
||||
|
||||
/// The image picker container builder
|
||||
typedef ImagePickerContainerBuilder = Widget Function(
|
||||
BuildContext context,
|
||||
VoidCallback onClose,
|
||||
ChatTranslations translations,
|
||||
BuildContext context,
|
||||
);
|
||||
|
||||
/// The text input builder
|
||||
typedef TextInputBuilder = Widget Function(
|
||||
BuildContext context,
|
||||
TextEditingController textEditingController,
|
||||
Widget suffixIcon,
|
||||
ChatTranslations translations,
|
||||
);
|
||||
|
||||
/// The scaffold builder
|
||||
typedef ScaffoldBuilder = Scaffold Function(
|
||||
AppBar appBar,
|
||||
BuildContext context,
|
||||
PreferredSizeWidget appBar,
|
||||
Widget body,
|
||||
Color backgroundColor,
|
||||
);
|
||||
|
||||
/// The container builder
|
||||
typedef ContainerBuilder = Widget Function(
|
||||
BuildContext context,
|
||||
Widget child,
|
||||
);
|
||||
|
||||
/// The group avatar builder
|
||||
typedef GroupAvatarBuilder = Widget Function(
|
||||
BuildContext context,
|
||||
String groupName,
|
||||
String? imageUrl,
|
||||
double size,
|
||||
);
|
||||
|
||||
/// The user avatar builder
|
||||
typedef UserAvatarBuilder = Widget Function(
|
||||
BuildContext context,
|
||||
UserModel user,
|
||||
double size,
|
||||
);
|
||||
|
||||
/// The no users placeholder builder
|
||||
typedef NoUsersPlaceholderBuilder = Widget Function(
|
||||
BuildContext context,
|
||||
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_translations.dart';
|
||||
import "package:flutter_chat/src/config/chat_builders.dart";
|
||||
import "package:flutter_chat/src/config/chat_translations.dart";
|
||||
|
||||
/// The chat options
|
||||
/// Use this class to configure the chat options.
|
||||
class ChatOptions {
|
||||
final String Function(bool showFullDate, DateTime date)? dateformat;
|
||||
final ChatTranslations translations;
|
||||
final ChatBuilders builders;
|
||||
final bool groupChatEnabled;
|
||||
final bool showTimes;
|
||||
final Color iconEnabledColor;
|
||||
final Color iconDisabledColor;
|
||||
final Function? onNoChats;
|
||||
final int pageSize;
|
||||
|
||||
ChatOptions({
|
||||
/// The chat options constructor
|
||||
const ChatOptions({
|
||||
this.dateformat,
|
||||
this.groupChatEnabled = true,
|
||||
this.showTimes = true,
|
||||
|
@ -25,4 +18,32 @@ class ChatOptions {
|
|||
this.onNoChats,
|
||||
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
|
||||
|
||||
// ignore_for_file: public_member_api_docs
|
||||
|
||||
/// Class that holds all the translations for the chat component view and
|
||||
/// the corresponding userstory
|
||||
class ChatTranslations {
|
||||
|
|
|
@ -61,14 +61,14 @@ class _FlutterChatEntryWidgetState extends State<FlutterChatEntryWidget> {
|
|||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) => GestureDetector(
|
||||
Widget build(BuildContext context) => InkWell(
|
||||
onTap: () async =>
|
||||
widget.onTap?.call() ??
|
||||
Navigator.of(context).pushReplacement(
|
||||
MaterialPageRoute(
|
||||
builder: (context) => FlutterChatNavigatorUserstory(
|
||||
userId: widget.userId,
|
||||
chatService: chatService!,
|
||||
chatService: chatService,
|
||||
),
|
||||
),
|
||||
),
|
||||
|
|
|
@ -2,8 +2,11 @@
|
|||
//
|
||||
// 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_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_profile_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_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 {
|
||||
/// Constructs a [FlutterChatNavigatorUserstory].
|
||||
const FlutterChatNavigatorUserstory({
|
||||
super.key,
|
||||
required this.userId,
|
||||
this.chatService,
|
||||
this.chatOptions,
|
||||
super.key,
|
||||
});
|
||||
|
||||
/// The user ID of the person currently looking at the chat
|
||||
final String userId;
|
||||
|
||||
/// The chat service associated with the widget.
|
||||
final ChatService? chatService;
|
||||
|
||||
/// The chat options
|
||||
final ChatOptions? chatOptions;
|
||||
|
||||
@override
|
||||
|
@ -37,112 +50,147 @@ class _FlutterChatNavigatorUserstoryState
|
|||
@override
|
||||
void initState() {
|
||||
chatService = widget.chatService ?? ChatService();
|
||||
chatOptions = widget.chatOptions ?? ChatOptions();
|
||||
chatOptions = widget.chatOptions ?? const ChatOptions();
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) => chatScreen();
|
||||
Widget build(BuildContext context) => Navigator(
|
||||
key: const ValueKey(
|
||||
"chat_navigator",
|
||||
),
|
||||
onGenerateRoute: (settings) => MaterialPageRoute(
|
||||
builder: (context) => _NavigatorWrapper(
|
||||
userId: widget.userId,
|
||||
chatService: chatService,
|
||||
chatOptions: chatOptions,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget chatScreen() {
|
||||
return ChatScreen(
|
||||
userId: widget.userId,
|
||||
chatService: chatService,
|
||||
chatOptions: chatOptions,
|
||||
onPressChat: (chat) {
|
||||
return route(chatDetailScreen(chat));
|
||||
},
|
||||
onDeleteChat: (chat) {
|
||||
chatService.deleteChat(chatId: chat.id);
|
||||
},
|
||||
onPressStartChat: () {
|
||||
return route(newChatScreen());
|
||||
},
|
||||
);
|
||||
}
|
||||
class _NavigatorWrapper extends StatelessWidget {
|
||||
const _NavigatorWrapper({
|
||||
required this.userId,
|
||||
required this.chatService,
|
||||
required this.chatOptions,
|
||||
});
|
||||
|
||||
Widget chatDetailScreen(ChatModel chat) => ChatDetailScreen(
|
||||
userId: widget.userId,
|
||||
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,
|
||||
chatOptions: chatOptions,
|
||||
chat: chat,
|
||||
onReadChat: (chat) => chatService.markAsRead(
|
||||
onReadChat: (chat) async => chatService.markAsRead(
|
||||
chatId: chat.id,
|
||||
),
|
||||
onPressChatTitle: (chat) {
|
||||
onPressChatTitle: (chat) async {
|
||||
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));
|
||||
},
|
||||
onPressUserProfile: (user) {
|
||||
return route(chatProfileScreen(user, null));
|
||||
if (!context.mounted) return;
|
||||
return route(context, chatProfileScreen(context, otherUser, null));
|
||||
},
|
||||
onPressUserProfile: (user) =>
|
||||
route(context, chatProfileScreen(context, user, null)),
|
||||
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,
|
||||
senderId: widget.userId,
|
||||
senderId: userId,
|
||||
imageUrl: path,
|
||||
);
|
||||
},
|
||||
onMessageSubmit: (text) {
|
||||
chatService.sendMessage(
|
||||
onMessageSubmit: (text) async {
|
||||
await chatService.sendMessage(
|
||||
messageId: "${chat.id}-$userId-${DateTime.now()}",
|
||||
chatId: chat.id,
|
||||
senderId: widget.userId,
|
||||
senderId: userId,
|
||||
text: text,
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
Widget chatProfileScreen(UserModel? user, ChatModel? chat) =>
|
||||
Widget chatProfileScreen(
|
||||
BuildContext context,
|
||||
UserModel? user,
|
||||
ChatModel? chat,
|
||||
) =>
|
||||
ChatProfileScreen(
|
||||
service: chatService,
|
||||
options: chatOptions,
|
||||
userId: widget.userId,
|
||||
userId: userId,
|
||||
userModel: user,
|
||||
chatModel: chat,
|
||||
onTapUser: (user) {
|
||||
route(chatProfileScreen(user, null));
|
||||
onTapUser: (userId) async {
|
||||
var user = await chatService.getUser(userId: userId).first;
|
||||
|
||||
if (!context.mounted) return;
|
||||
route(context, chatProfileScreen(context, user, null));
|
||||
},
|
||||
onPressStartChat: (user) async {
|
||||
var chat = await createChat(user.id);
|
||||
return route(chatDetailScreen(chat));
|
||||
onPressStartChat: (userId) async {
|
||||
var chat = await createChat(userId);
|
||||
|
||||
if (!context.mounted) return;
|
||||
return route(context, chatDetailScreen(context, chat));
|
||||
},
|
||||
);
|
||||
|
||||
Widget newChatScreen() => NewChatScreen(
|
||||
userId: widget.userId,
|
||||
Widget newChatScreen(BuildContext context) => NewChatScreen(
|
||||
userId: userId,
|
||||
chatService: chatService,
|
||||
chatOptions: chatOptions,
|
||||
onPressCreateGroupChat: () {
|
||||
return route(newGroupChatScreen());
|
||||
},
|
||||
onPressCreateGroupChat: () =>
|
||||
route(context, newGroupChatScreen(context)),
|
||||
onPressCreateChat: (user) async {
|
||||
var chat = await createChat(user.id);
|
||||
return route(chatDetailScreen(chat));
|
||||
|
||||
if (!context.mounted) return;
|
||||
return route(context, chatDetailScreen(context, chat));
|
||||
},
|
||||
);
|
||||
|
||||
Widget newGroupChatScreen() => NewGroupChatScreen(
|
||||
userId: widget.userId,
|
||||
Widget newGroupChatScreen(BuildContext context) => NewGroupChatScreen(
|
||||
userId: userId,
|
||||
chatService: chatService,
|
||||
chatOptions: chatOptions,
|
||||
onContinue: (users) {
|
||||
return route(newGroupChatOverview(users));
|
||||
},
|
||||
onContinue: (users) =>
|
||||
route(context, newGroupChatOverview(context, users)),
|
||||
);
|
||||
|
||||
Widget newGroupChatOverview(List<UserModel> users) => NewGroupChatOverview(
|
||||
Widget newGroupChatOverview(BuildContext context, List<UserModel> users) =>
|
||||
NewGroupChatOverview(
|
||||
options: chatOptions,
|
||||
users: users,
|
||||
onComplete: (users, title, description, image) async {
|
||||
String? path;
|
||||
if (image != null) {
|
||||
path = await chatService.uploadImage(path: 'groups', image: image);
|
||||
path = await chatService.uploadImage(path: "groups", image: image);
|
||||
}
|
||||
var chat = await createGroupChat(
|
||||
users,
|
||||
|
@ -150,7 +198,9 @@ class _FlutterChatNavigatorUserstoryState
|
|||
description,
|
||||
path,
|
||||
);
|
||||
return route(chatDetailScreen(chat));
|
||||
|
||||
if (!context.mounted) return;
|
||||
return route(context, chatDetailScreen(context, chat));
|
||||
},
|
||||
);
|
||||
|
||||
|
@ -161,30 +211,43 @@ class _FlutterChatNavigatorUserstoryState
|
|||
String? imageUrl,
|
||||
) async {
|
||||
ChatModel? chat;
|
||||
|
||||
try {
|
||||
chat = await chatService.getGroupChatByUser(
|
||||
currentUser: widget.userId,
|
||||
currentUser: userId,
|
||||
otherUsers: userModels,
|
||||
chatName: title,
|
||||
description: description,
|
||||
);
|
||||
} catch (e) {
|
||||
} on Exception catch (_) {
|
||||
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(
|
||||
userModels.map((e) => chatService.getUser(userId: e.id).first),
|
||||
);
|
||||
|
||||
chat = await chatService.createChat(
|
||||
await chatService.createChat(
|
||||
isGroupChat: true,
|
||||
users: [currentUser, ...otherUsers],
|
||||
chatName: title,
|
||||
description: description,
|
||||
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;
|
||||
|
@ -195,28 +258,42 @@ class _FlutterChatNavigatorUserstoryState
|
|||
|
||||
try {
|
||||
chat = await chatService.getChatByUser(
|
||||
currentUser: widget.userId,
|
||||
currentUser: userId,
|
||||
otherUser: otherUserId,
|
||||
);
|
||||
} catch (e) {
|
||||
} on Exception catch (_) {
|
||||
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;
|
||||
|
||||
chat = await chatService.createChat(
|
||||
await chatService.createChat(
|
||||
isGroupChat: false,
|
||||
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) {
|
||||
Navigator.of(context).push(
|
||||
MaterialPageRoute(builder: (context) => screen),
|
||||
void route(BuildContext context, Widget screen) {
|
||||
unawaited(
|
||||
Navigator.of(context).push(
|
||||
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:chat_repository_interface/chat_repository_interface.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/services/date_formatter.dart';
|
||||
import 'package:flutter_profile/flutter_profile.dart';
|
||||
import "package:cached_network_image/cached_network_image.dart";
|
||||
import "package:chat_repository_interface/chat_repository_interface.dart";
|
||||
import "package:flutter/material.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_profile/flutter_profile.dart";
|
||||
|
||||
/// Chat detail screen
|
||||
/// Seen when a user clicks on a chat
|
||||
class ChatDetailScreen extends StatefulWidget {
|
||||
/// Constructs a [ChatDetailScreen].
|
||||
const ChatDetailScreen({
|
||||
super.key,
|
||||
required this.userId,
|
||||
required this.chatService,
|
||||
required this.chatOptions,
|
||||
|
@ -20,16 +23,34 @@ class ChatDetailScreen extends StatefulWidget {
|
|||
required this.onUploadImage,
|
||||
required this.onMessageSubmit,
|
||||
required this.onReadChat,
|
||||
super.key,
|
||||
});
|
||||
|
||||
/// The user ID of the person currently looking at the chat
|
||||
final String userId;
|
||||
|
||||
/// The chat service associated with the widget.
|
||||
final ChatService chatService;
|
||||
|
||||
/// The chat options
|
||||
final ChatOptions chatOptions;
|
||||
|
||||
/// The chat model currently being viewed
|
||||
final ChatModel chat;
|
||||
|
||||
/// Callback function triggered when the chat title is pressed.
|
||||
final Function(ChatModel) onPressChatTitle;
|
||||
|
||||
/// Callback function triggered when the user profile is pressed.
|
||||
final Function(UserModel) onPressUserProfile;
|
||||
|
||||
/// Callback function triggered when an image is uploaded.
|
||||
final Function(Uint8List image) onUploadImage;
|
||||
|
||||
/// Callback function triggered when a message is submitted.
|
||||
final Function(String text) onMessageSubmit;
|
||||
|
||||
/// Callback function triggered when the chat is read.
|
||||
final Function(ChatModel chat) onReadChat;
|
||||
|
||||
@override
|
||||
|
@ -37,7 +58,7 @@ class ChatDetailScreen extends StatefulWidget {
|
|||
}
|
||||
|
||||
class _ChatDetailScreenState extends State<ChatDetailScreen> {
|
||||
late String chatTitle;
|
||||
String? chatTitle;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
|
@ -45,25 +66,35 @@ class _ChatDetailScreenState extends State<ChatDetailScreen> {
|
|||
chatTitle = widget.chat.chatName ??
|
||||
widget.chatOptions.translations.groupNameEmpty;
|
||||
} else {
|
||||
chatTitle = widget.chat.users
|
||||
.firstWhere((element) => element.id != widget.userId)
|
||||
.fullname ??
|
||||
widget.chatOptions.translations.anonymousUser;
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) async {
|
||||
await _getTitle();
|
||||
});
|
||||
}
|
||||
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
|
||||
Widget build(BuildContext context) {
|
||||
var theme = Theme.of(context);
|
||||
|
||||
return widget.chatOptions.builders.chatDetailScaffoldBuilder?.call(
|
||||
context,
|
||||
_AppBar(
|
||||
chatTitle: chatTitle,
|
||||
chatOptions: widget.chatOptions,
|
||||
onPressChatTitle: widget.onPressChatTitle,
|
||||
chatModel: widget.chat,
|
||||
) as AppBar,
|
||||
),
|
||||
_Body(
|
||||
chatService: widget.chatService,
|
||||
options: widget.chatOptions,
|
||||
|
@ -105,7 +136,7 @@ class _AppBar extends StatelessWidget implements PreferredSizeWidget {
|
|||
required this.chatModel,
|
||||
});
|
||||
|
||||
final String chatTitle;
|
||||
final String? chatTitle;
|
||||
final ChatOptions chatOptions;
|
||||
final Function(ChatModel) onPressChatTitle;
|
||||
final ChatModel chatModel;
|
||||
|
@ -128,9 +159,9 @@ class _AppBar extends StatelessWidget implements PreferredSizeWidget {
|
|||
),
|
||||
title: GestureDetector(
|
||||
onTap: () => onPressChatTitle.call(chatModel),
|
||||
child: chatOptions.builders.chatTitleBuilder?.call(chatTitle) ??
|
||||
child: chatOptions.builders.chatTitleBuilder?.call(chatTitle ?? "") ??
|
||||
Text(
|
||||
chatTitle,
|
||||
chatTitle ?? "",
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
|
@ -167,10 +198,10 @@ class _Body extends StatefulWidget {
|
|||
}
|
||||
|
||||
class _BodyState extends State<_Body> {
|
||||
ScrollController controller = ScrollController();
|
||||
final ScrollController controller = ScrollController();
|
||||
bool showIndicator = false;
|
||||
late int pageSize;
|
||||
var page = 0;
|
||||
int page = 0;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
|
@ -178,78 +209,86 @@ class _BodyState extends State<_Body> {
|
|||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
controller.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext 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(
|
||||
children: [
|
||||
Column(
|
||||
children: [
|
||||
Expanded(
|
||||
child: StreamBuilder<List<MessageModel>?>(
|
||||
stream: widget.chatService.getMessages(
|
||||
userId: widget.currentUserId,
|
||||
chatId: widget.chat.id,
|
||||
pageSize: pageSize,
|
||||
page: page,
|
||||
),
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.connectionState == ConnectionState.waiting) {
|
||||
return const Center(
|
||||
child: CircularProgressIndicator(),
|
||||
);
|
||||
}
|
||||
stream: widget.chatService.getMessages(
|
||||
userId: widget.currentUserId,
|
||||
chatId: widget.chat.id,
|
||||
pageSize: pageSize,
|
||||
page: page,
|
||||
),
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.connectionState == ConnectionState.waiting) {
|
||||
return const Center(
|
||||
child: CircularProgressIndicator(),
|
||||
);
|
||||
}
|
||||
|
||||
var messages = snapshot.data?.reversed.toList() ?? [];
|
||||
var messages = snapshot.data?.reversed.toList() ?? [];
|
||||
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) async {
|
||||
await widget.onReadChat(widget.chat);
|
||||
});
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) async {
|
||||
await widget.onReadChat(widget.chat);
|
||||
});
|
||||
|
||||
return Listener(
|
||||
onPointerMove: (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;
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
child: ListView(
|
||||
shrinkWrap: true,
|
||||
controller: controller,
|
||||
physics: const AlwaysScrollableScrollPhysics(),
|
||||
reverse: messages.isNotEmpty,
|
||||
padding: const EdgeInsets.only(top: 24.0),
|
||||
children: [
|
||||
if (messages.isEmpty && !showIndicator) ...[
|
||||
Center(
|
||||
child: Text(
|
||||
widget.chat.isGroupChat
|
||||
? widget.options.translations
|
||||
.writeFirstMessageInGroupChat
|
||||
: widget.options.translations
|
||||
.writeMessageToStartChat,
|
||||
style: theme.textTheme.bodySmall,
|
||||
),
|
||||
return Listener(
|
||||
onPointerMove: handleScroll,
|
||||
child: ListView(
|
||||
shrinkWrap: true,
|
||||
controller: controller,
|
||||
physics: const AlwaysScrollableScrollPhysics(),
|
||||
reverse: messages.isNotEmpty,
|
||||
padding: const EdgeInsets.only(top: 24.0),
|
||||
children: [
|
||||
if (messages.isEmpty && !showIndicator) ...[
|
||||
Center(
|
||||
child: Text(
|
||||
widget.chat.isGroupChat
|
||||
? widget.options.translations
|
||||
.writeFirstMessageInGroupChat
|
||||
: widget.options.translations
|
||||
.writeMessageToStartChat,
|
||||
style: theme.textTheme.bodySmall,
|
||||
),
|
||||
],
|
||||
for (var i = 0; i < messages.length; i++) ...[
|
||||
),
|
||||
],
|
||||
for (var i = 0; i < messages.length; i++) ...[
|
||||
if (widget.chat.id == messages[i].chatId) ...[
|
||||
_ChatBubble(
|
||||
key: ValueKey(messages[i].id),
|
||||
message: messages[i],
|
||||
|
@ -260,11 +299,13 @@ class _BodyState extends State<_Body> {
|
|||
onPressUserProfile: widget.onPressUserProfile,
|
||||
options: widget.options,
|
||||
),
|
||||
]
|
||||
],
|
||||
],
|
||||
),
|
||||
);
|
||||
}),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
_ChatBottom(
|
||||
chat: widget.chat,
|
||||
|
@ -350,6 +391,7 @@ class _ChatBottomState extends State<_ChatBottom> {
|
|||
child: SizedBox(
|
||||
height: 45,
|
||||
child: widget.options.builders.messageInputBuilder?.call(
|
||||
context,
|
||||
_textEditingController,
|
||||
Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
|
@ -412,9 +454,7 @@ class _ChatBottomState extends State<_ChatBottom> {
|
|||
horizontal: 30,
|
||||
),
|
||||
hintText: widget.options.translations.messagePlaceholder,
|
||||
hintStyle: theme.textTheme.bodyMedium!.copyWith(
|
||||
color: theme.textTheme.bodyMedium!.color!.withOpacity(0.5),
|
||||
),
|
||||
hintStyle: theme.textTheme.bodyMedium,
|
||||
fillColor: Colors.white,
|
||||
filled: true,
|
||||
border: const OutlineInputBorder(
|
||||
|
@ -502,135 +542,135 @@ class _ChatBubbleState extends State<_ChatBubble> {
|
|||
widget.previousMessage?.timestamp.minute;
|
||||
var hasHeader = isNewDate || isSameSender;
|
||||
return StreamBuilder<UserModel>(
|
||||
stream: widget.chatService.getUser(userId: widget.message.senderId),
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.connectionState == ConnectionState.waiting) {
|
||||
return const Center(
|
||||
child: CircularProgressIndicator(),
|
||||
);
|
||||
}
|
||||
stream: widget.chatService.getUser(userId: widget.message.senderId),
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.connectionState == ConnectionState.waiting) {
|
||||
return const Center(
|
||||
child: CircularProgressIndicator(),
|
||||
);
|
||||
}
|
||||
|
||||
var user = snapshot.data!;
|
||||
var user = snapshot.data!;
|
||||
|
||||
return Padding(
|
||||
padding: EdgeInsets.only(
|
||||
top: isNewDate || isSameSender ? 25.0 : 0,
|
||||
),
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
if (isNewDate || isSameSender) ...[
|
||||
GestureDetector(
|
||||
onTap: () => widget.onPressUserProfile(user),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(left: 10.0),
|
||||
child: user.imageUrl?.isNotEmpty ?? false
|
||||
? _ChatImage(
|
||||
image: user.imageUrl!,
|
||||
)
|
||||
: widget.options.builders.userAvatarBuilder?.call(
|
||||
user,
|
||||
40,
|
||||
) ??
|
||||
Avatar(
|
||||
key: ValueKey(user.id),
|
||||
boxfit: BoxFit.cover,
|
||||
user: User(
|
||||
firstName: user.firstName,
|
||||
lastName: user.lastName,
|
||||
imageUrl: user.imageUrl != ""
|
||||
? user.imageUrl
|
||||
: null,
|
||||
),
|
||||
size: 40,
|
||||
),
|
||||
),
|
||||
),
|
||||
] else ...[
|
||||
const SizedBox(
|
||||
width: 50,
|
||||
),
|
||||
],
|
||||
Expanded(
|
||||
return Padding(
|
||||
padding: EdgeInsets.only(
|
||||
top: isNewDate || isSameSender ? 25.0 : 0,
|
||||
),
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
if (isNewDate || isSameSender) ...[
|
||||
InkWell(
|
||||
onTap: () => widget.onPressUserProfile(user),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 22.0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
children: [
|
||||
if (isNewDate || isSameSender) ...[
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Expanded(
|
||||
child: widget.options.builders.usernameBuilder
|
||||
?.call(
|
||||
user.fullname ?? "",
|
||||
) ??
|
||||
Text(
|
||||
user.fullname ??
|
||||
translations.anonymousUser,
|
||||
style: theme.textTheme.titleMedium,
|
||||
),
|
||||
padding: const EdgeInsets.only(left: 10.0),
|
||||
child: user.imageUrl?.isNotEmpty ?? false
|
||||
? _ChatImage(
|
||||
image: user.imageUrl!,
|
||||
)
|
||||
: widget.options.builders.userAvatarBuilder?.call(
|
||||
context,
|
||||
user,
|
||||
40,
|
||||
) ??
|
||||
Avatar(
|
||||
key: ValueKey(user.id),
|
||||
boxfit: BoxFit.cover,
|
||||
user: User(
|
||||
firstName: user.firstName,
|
||||
lastName: user.lastName,
|
||||
imageUrl:
|
||||
user.imageUrl != "" ? user.imageUrl : null,
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 5.0),
|
||||
child: Text(
|
||||
dateFormatter.format(
|
||||
date: widget.message.timestamp,
|
||||
showFullDate: true,
|
||||
),
|
||||
style: theme.textTheme.labelSmall,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 3.0),
|
||||
child: widget.message.isTextMessage()
|
||||
? Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.end,
|
||||
mainAxisAlignment:
|
||||
MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Flexible(
|
||||
child: Text(
|
||||
widget.message.text ?? "",
|
||||
style: theme.textTheme.bodySmall,
|
||||
),
|
||||
),
|
||||
if (widget.options.showTimes &&
|
||||
!isSameMinute &&
|
||||
!isNewDate &&
|
||||
!hasHeader)
|
||||
Text(
|
||||
dateFormatter
|
||||
.format(
|
||||
date: widget.message.timestamp,
|
||||
showFullDate: true,
|
||||
)
|
||||
.split(" ")
|
||||
.last,
|
||||
style: theme.textTheme.labelSmall,
|
||||
textAlign: TextAlign.end,
|
||||
),
|
||||
],
|
||||
)
|
||||
: widget.message.isImageMessage()
|
||||
? CachedNetworkImage(
|
||||
imageUrl: widget.message.imageUrl ?? "",
|
||||
)
|
||||
: const SizedBox.shrink(),
|
||||
),
|
||||
],
|
||||
),
|
||||
size: 40,
|
||||
),
|
||||
),
|
||||
),
|
||||
] else ...[
|
||||
const SizedBox(
|
||||
width: 50,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
});
|
||||
Expanded(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 22.0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
children: [
|
||||
if (isNewDate || isSameSender) ...[
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Expanded(
|
||||
child: widget.options.builders.usernameBuilder
|
||||
?.call(
|
||||
user.fullname ?? "",
|
||||
) ??
|
||||
Text(
|
||||
user.fullname ?? translations.anonymousUser,
|
||||
style: theme.textTheme.titleMedium,
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 5.0),
|
||||
child: Text(
|
||||
dateFormatter.format(
|
||||
date: widget.message.timestamp,
|
||||
showFullDate: true,
|
||||
),
|
||||
style: theme.textTheme.labelSmall,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 3.0),
|
||||
child: widget.message.isTextMessage
|
||||
? Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.end,
|
||||
mainAxisAlignment:
|
||||
MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Flexible(
|
||||
child: Text(
|
||||
widget.message.text ?? "",
|
||||
style: theme.textTheme.bodySmall,
|
||||
),
|
||||
),
|
||||
if (widget.options.showTimes &&
|
||||
!isSameMinute &&
|
||||
!isNewDate &&
|
||||
!hasHeader)
|
||||
Text(
|
||||
dateFormatter
|
||||
.format(
|
||||
date: widget.message.timestamp,
|
||||
showFullDate: true,
|
||||
)
|
||||
.split(" ")
|
||||
.last,
|
||||
style: theme.textTheme.labelSmall,
|
||||
textAlign: TextAlign.end,
|
||||
),
|
||||
],
|
||||
)
|
||||
: widget.message.isImageMessage
|
||||
? CachedNetworkImage(
|
||||
imageUrl: widget.message.imageUrl ?? "",
|
||||
)
|
||||
: const SizedBox.shrink(),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,37 +1,57 @@
|
|||
import 'package:chat_repository_interface/chat_repository_interface.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_chat/src/config/chat_options.dart';
|
||||
import 'package:flutter_profile/flutter_profile.dart';
|
||||
import "package:chat_repository_interface/chat_repository_interface.dart";
|
||||
import "package:flutter/material.dart";
|
||||
import "package:flutter_chat/src/config/chat_options.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 {
|
||||
/// Constructs a [ChatProfileScreen]
|
||||
const ChatProfileScreen({
|
||||
super.key,
|
||||
required this.options,
|
||||
required this.userId,
|
||||
required this.userModel,
|
||||
required this.service,
|
||||
required this.chatModel,
|
||||
required this.onTapUser,
|
||||
required this.onPressStartChat,
|
||||
super.key,
|
||||
});
|
||||
|
||||
/// The chat options
|
||||
final ChatOptions options;
|
||||
|
||||
/// The user ID of the person currently looking at the chat
|
||||
final String userId;
|
||||
|
||||
/// The user model of the persons profile to be viewed
|
||||
final UserModel? userModel;
|
||||
|
||||
/// The chat model of the chat being viewed
|
||||
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
|
||||
Widget build(BuildContext context) {
|
||||
var theme = Theme.of(context);
|
||||
|
||||
return options.builders.chatProfileScaffoldBuilder?.call(
|
||||
context,
|
||||
_AppBar(
|
||||
user: userModel,
|
||||
chat: chatModel,
|
||||
options: options,
|
||||
) as AppBar,
|
||||
),
|
||||
_Body(
|
||||
service: service,
|
||||
currentUser: userId,
|
||||
options: options,
|
||||
user: userModel,
|
||||
|
@ -50,6 +70,7 @@ class ChatProfileScreen extends StatelessWidget {
|
|||
body: _Body(
|
||||
currentUser: userId,
|
||||
options: options,
|
||||
service: service,
|
||||
user: userModel,
|
||||
chat: chatModel,
|
||||
onTapUser: onTapUser,
|
||||
|
@ -78,7 +99,7 @@ class _AppBar extends StatelessWidget implements PreferredSizeWidget {
|
|||
const IconThemeData(color: Colors.white),
|
||||
title: Text(
|
||||
user != null
|
||||
? '${user!.fullname}'
|
||||
? "${user!.fullname}"
|
||||
: chat != null
|
||||
? chat?.chatName ?? options.translations.groupNameEmpty
|
||||
: "",
|
||||
|
@ -93,6 +114,7 @@ class _AppBar extends StatelessWidget implements PreferredSizeWidget {
|
|||
class _Body extends StatelessWidget {
|
||||
const _Body({
|
||||
required this.options,
|
||||
required this.service,
|
||||
required this.user,
|
||||
required this.chat,
|
||||
required this.onPressStartChat,
|
||||
|
@ -101,10 +123,11 @@ class _Body extends StatelessWidget {
|
|||
});
|
||||
|
||||
final ChatOptions options;
|
||||
final ChatService service;
|
||||
final UserModel? user;
|
||||
final ChatModel? chat;
|
||||
final Function(UserModel)? onTapUser;
|
||||
final Function(UserModel)? onPressStartChat;
|
||||
final Function(String)? onTapUser;
|
||||
final Function(String)? onPressStartChat;
|
||||
final String currentUser;
|
||||
|
||||
@override
|
||||
|
@ -119,6 +142,7 @@ class _Body extends StatelessWidget {
|
|||
child: Column(
|
||||
children: [
|
||||
options.builders.userAvatarBuilder?.call(
|
||||
context,
|
||||
user ??
|
||||
(
|
||||
chat != null
|
||||
|
@ -185,8 +209,7 @@ class _Body extends StatelessWidget {
|
|||
),
|
||||
Text(
|
||||
chat!.description ?? "",
|
||||
style: theme.textTheme.bodyMedium!
|
||||
.copyWith(color: Colors.black),
|
||||
style: theme.textTheme.bodyMedium,
|
||||
),
|
||||
const SizedBox(
|
||||
height: 12,
|
||||
|
@ -206,7 +229,7 @@ class _Body extends StatelessWidget {
|
|||
bottom: 8,
|
||||
right: 8,
|
||||
),
|
||||
child: GestureDetector(
|
||||
child: InkWell(
|
||||
onTap: () {
|
||||
onTapUser?.call(tappedUser);
|
||||
},
|
||||
|
@ -214,23 +237,42 @@ class _Body extends StatelessWidget {
|
|||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
options.builders.userAvatarBuilder?.call(
|
||||
tappedUser,
|
||||
44,
|
||||
) ??
|
||||
Avatar(
|
||||
boxfit: BoxFit.cover,
|
||||
user: User(
|
||||
firstName: tappedUser.firstName,
|
||||
lastName: tappedUser.lastName,
|
||||
imageUrl:
|
||||
tappedUser.imageUrl != null ||
|
||||
tappedUser.imageUrl != ""
|
||||
? tappedUser.imageUrl
|
||||
FutureBuilder<UserModel>(
|
||||
future: service
|
||||
.getUser(userId: tappedUser)
|
||||
.first,
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.connectionState ==
|
||||
ConnectionState.waiting) {
|
||||
return const CircularProgressIndicator();
|
||||
}
|
||||
|
||||
var user = snapshot.data;
|
||||
|
||||
if (user == null) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
|
||||
return options.builders.userAvatarBuilder
|
||||
?.call(
|
||||
context,
|
||||
user,
|
||||
44,
|
||||
) ??
|
||||
Avatar(
|
||||
boxfit: BoxFit.cover,
|
||||
user: User(
|
||||
firstName: user.firstName,
|
||||
lastName: user.lastName,
|
||||
imageUrl: user.imageUrl != null ||
|
||||
user.imageUrl != ""
|
||||
? user.imageUrl
|
||||
: null,
|
||||
),
|
||||
size: 60,
|
||||
),
|
||||
),
|
||||
size: 60,
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
@ -244,7 +286,7 @@ class _Body extends StatelessWidget {
|
|||
],
|
||||
],
|
||||
),
|
||||
if (user != null && user!.id != currentUser) ...[
|
||||
if (user?.id != currentUser) ...[
|
||||
Align(
|
||||
alignment: Alignment.bottomCenter,
|
||||
child: Padding(
|
||||
|
@ -254,7 +296,7 @@ class _Body extends StatelessWidget {
|
|||
),
|
||||
child: FilledButton(
|
||||
onPressed: () {
|
||||
onPressStartChat?.call(user!);
|
||||
onPressStartChat?.call(user!.id);
|
||||
},
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
|
|
|
@ -1,23 +1,31 @@
|
|||
import 'package:chat_repository_interface/chat_repository_interface.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_chat/src/config/chat_options.dart';
|
||||
import 'package:flutter_chat/src/config/chat_translations.dart';
|
||||
import 'package:flutter_chat/src/services/date_formatter.dart';
|
||||
import "package:chat_repository_interface/chat_repository_interface.dart";
|
||||
import "package:flutter/material.dart";
|
||||
import "package:flutter_chat/src/config/chat_options.dart";
|
||||
import "package:flutter_chat/src/config/chat_translations.dart";
|
||||
import "package:flutter_chat/src/services/date_formatter.dart";
|
||||
import "package:flutter_profile/flutter_profile.dart";
|
||||
|
||||
/// The chat screen
|
||||
/// Seen when a user is chatting
|
||||
class ChatScreen extends StatelessWidget {
|
||||
/// Constructs a [ChatScreen]
|
||||
const ChatScreen({
|
||||
super.key,
|
||||
required this.userId,
|
||||
required this.chatService,
|
||||
required this.chatOptions,
|
||||
required this.onPressChat,
|
||||
required this.onDeleteChat,
|
||||
this.onPressStartChat,
|
||||
super.key,
|
||||
});
|
||||
|
||||
/// The user ID of the person currently looking at the chat
|
||||
final String userId;
|
||||
|
||||
/// The chat service
|
||||
final ChatService chatService;
|
||||
|
||||
/// The chat options
|
||||
final ChatOptions chatOptions;
|
||||
|
||||
/// Callback function for starting a chat.
|
||||
|
@ -26,17 +34,19 @@ class ChatScreen extends StatelessWidget {
|
|||
/// Callback function for pressing on a chat.
|
||||
final void Function(ChatModel chat) onPressChat;
|
||||
|
||||
/// Callback function for deleting a chat.
|
||||
final void Function(ChatModel chat) onDeleteChat;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
var theme = Theme.of(context);
|
||||
return chatOptions.builders.chatScreenScaffoldBuilder?.call(
|
||||
context,
|
||||
_AppBar(
|
||||
userId: userId,
|
||||
chatOptions: chatOptions,
|
||||
chatService: chatService,
|
||||
) as AppBar,
|
||||
),
|
||||
_Body(
|
||||
userId: userId,
|
||||
chatOptions: chatOptions,
|
||||
|
@ -134,7 +144,7 @@ class _Body extends StatefulWidget {
|
|||
}
|
||||
|
||||
class _BodyState extends State<_Body> {
|
||||
ScrollController controller = ScrollController();
|
||||
final ScrollController controller = ScrollController();
|
||||
bool _hasCalledOnNoChats = false;
|
||||
|
||||
@override
|
||||
|
@ -152,7 +162,6 @@ class _BodyState extends State<_Body> {
|
|||
StreamBuilder<List<ChatModel>?>(
|
||||
stream: widget.chatService.getChats(userId: widget.userId),
|
||||
builder: (BuildContext context, snapshot) {
|
||||
// if the stream is done, empty and noChats is set we should call that
|
||||
if (snapshot.connectionState == ConnectionState.done &&
|
||||
(snapshot.data?.isEmpty ?? true) ||
|
||||
(snapshot.data != null && snapshot.data!.isEmpty)) {
|
||||
|
@ -160,6 +169,7 @@ class _BodyState extends State<_Body> {
|
|||
!_hasCalledOnNoChats) {
|
||||
_hasCalledOnNoChats = true; // Set the flag to true
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) async {
|
||||
// ignore: avoid_dynamic_calls
|
||||
await widget.chatOptions.onNoChats!.call();
|
||||
});
|
||||
}
|
||||
|
@ -172,7 +182,7 @@ class _BodyState extends State<_Body> {
|
|||
}
|
||||
return Column(
|
||||
children: [
|
||||
for (ChatModel chat in (snapshot.data ?? [])) ...[
|
||||
for (ChatModel chat in snapshot.data ?? []) ...[
|
||||
DecoratedBox(
|
||||
decoration: BoxDecoration(
|
||||
border: Border(
|
||||
|
@ -186,17 +196,19 @@ class _BodyState extends State<_Body> {
|
|||
builder: (context) => !chat.canBeDeleted
|
||||
? Dismissible(
|
||||
confirmDismiss: (_) async {
|
||||
widget.chatOptions.builders
|
||||
await widget.chatOptions.builders
|
||||
.deleteChatDialogBuilder
|
||||
?.call(context, chat) ??
|
||||
_deleteDialog(
|
||||
chat,
|
||||
translations,
|
||||
// ignore: use_build_context_synchronously
|
||||
context,
|
||||
);
|
||||
return _deleteDialog(
|
||||
chat,
|
||||
translations,
|
||||
// ignore: use_build_context_synchronously
|
||||
context,
|
||||
);
|
||||
},
|
||||
|
@ -230,16 +242,18 @@ class _BodyState extends State<_Body> {
|
|||
),
|
||||
),
|
||||
key: ValueKey(
|
||||
chat.id.toString(),
|
||||
chat.id,
|
||||
),
|
||||
child: ChatListItem(
|
||||
child: _ChatItem(
|
||||
service: widget.chatService,
|
||||
chat: chat,
|
||||
chatOptions: widget.chatOptions,
|
||||
userId: widget.userId,
|
||||
onPressChat: widget.onPressChat,
|
||||
),
|
||||
)
|
||||
: ChatListItem(
|
||||
: _ChatItem(
|
||||
service: widget.chatService,
|
||||
chat: chat,
|
||||
chatOptions: widget.chatOptions,
|
||||
userId: widget.userId,
|
||||
|
@ -274,7 +288,7 @@ class _BodyState extends State<_Body> {
|
|||
borderRadius: BorderRadius.circular(56),
|
||||
),
|
||||
),
|
||||
onPressed: widget.onPressStartChat!,
|
||||
onPressed: widget.onPressStartChat,
|
||||
child: Text(
|
||||
translations.newChatButton,
|
||||
style: theme.textTheme.displayLarge,
|
||||
|
@ -286,17 +300,18 @@ class _BodyState extends State<_Body> {
|
|||
}
|
||||
}
|
||||
|
||||
class ChatListItem extends StatelessWidget {
|
||||
const ChatListItem({
|
||||
class _ChatItem extends StatelessWidget {
|
||||
const _ChatItem({
|
||||
required this.chat,
|
||||
required this.chatOptions,
|
||||
required this.service,
|
||||
required this.userId,
|
||||
required this.onPressChat,
|
||||
super.key,
|
||||
});
|
||||
|
||||
final ChatModel chat;
|
||||
final ChatOptions chatOptions;
|
||||
final ChatService service;
|
||||
final String userId;
|
||||
final Function(ChatModel chat) onPressChat;
|
||||
|
||||
|
@ -306,15 +321,17 @@ class ChatListItem extends StatelessWidget {
|
|||
options: chatOptions,
|
||||
);
|
||||
var theme = Theme.of(context);
|
||||
return GestureDetector(
|
||||
return InkWell(
|
||||
onTap: () {
|
||||
onPressChat(chat);
|
||||
},
|
||||
child: chatOptions.builders.chatRowContainerBuilder?.call(
|
||||
context,
|
||||
_ChatListItem(
|
||||
chat: chat,
|
||||
options: chatOptions,
|
||||
dateFormatter: dateFormatter,
|
||||
chatService: service,
|
||||
currentUserId: userId,
|
||||
),
|
||||
) ??
|
||||
|
@ -334,6 +351,7 @@ class ChatListItem extends StatelessWidget {
|
|||
chat: chat,
|
||||
options: chatOptions,
|
||||
dateFormatter: dateFormatter,
|
||||
chatService: service,
|
||||
currentUserId: userId,
|
||||
),
|
||||
),
|
||||
|
@ -348,82 +366,140 @@ class _ChatListItem extends StatelessWidget {
|
|||
required this.options,
|
||||
required this.dateFormatter,
|
||||
required this.currentUserId,
|
||||
required this.chatService,
|
||||
});
|
||||
|
||||
final ChatModel chat;
|
||||
final ChatOptions options;
|
||||
final DateFormatter dateFormatter;
|
||||
final String currentUserId;
|
||||
final ChatService chatService;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
var translations = options.translations;
|
||||
if (chat.isGroupChat) {
|
||||
return _ChatRow(
|
||||
title: chat.chatName ?? translations.groupNameEmpty,
|
||||
unreadMessages: chat.unreadMessageCount,
|
||||
subTitle: chat.lastMessage != null
|
||||
? chat.lastMessage!.isTextMessage()
|
||||
? chat.lastMessage!.text
|
||||
: "📷 "
|
||||
"${translations.image}"
|
||||
: "",
|
||||
avatar: options.builders.groupAvatarBuilder?.call(
|
||||
chat.chatName ?? translations.groupNameEmpty,
|
||||
chat.imageUrl,
|
||||
40.0,
|
||||
) ??
|
||||
Avatar(
|
||||
boxfit: BoxFit.cover,
|
||||
user: User(
|
||||
firstName: chat.chatName,
|
||||
lastName: null,
|
||||
imageUrl: chat.imageUrl != null || chat.imageUrl != ""
|
||||
? chat.imageUrl
|
||||
: null,
|
||||
),
|
||||
size: 40.0,
|
||||
),
|
||||
lastUsed: chat.lastUsed != null
|
||||
? dateFormatter.format(
|
||||
date: chat.lastUsed!,
|
||||
return StreamBuilder<MessageModel?>(
|
||||
stream: chat.lastMessage != null
|
||||
? chatService.getMessage(
|
||||
chatId: chat.id,
|
||||
messageId: chat.lastMessage!,
|
||||
)
|
||||
: null,
|
||||
: const Stream.empty(),
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.connectionState == ConnectionState.waiting) {
|
||||
return const Center(
|
||||
child: CircularProgressIndicator(),
|
||||
);
|
||||
}
|
||||
|
||||
var data = snapshot.data;
|
||||
|
||||
return _ChatRow(
|
||||
title: chat.chatName ?? translations.groupNameEmpty,
|
||||
unreadMessages: chat.unreadMessageCount,
|
||||
subTitle: data != null
|
||||
? data.isTextMessage
|
||||
? data.text
|
||||
: "📷 "
|
||||
"${translations.image}"
|
||||
: "",
|
||||
avatar: options.builders.groupAvatarBuilder?.call(
|
||||
context,
|
||||
chat.chatName ?? translations.groupNameEmpty,
|
||||
chat.imageUrl,
|
||||
40.0,
|
||||
) ??
|
||||
Avatar(
|
||||
boxfit: BoxFit.cover,
|
||||
user: User(
|
||||
firstName: chat.chatName,
|
||||
lastName: null,
|
||||
imageUrl: chat.imageUrl != null || chat.imageUrl != ""
|
||||
? chat.imageUrl
|
||||
: null,
|
||||
),
|
||||
size: 40.0,
|
||||
),
|
||||
lastUsed: chat.lastUsed != null
|
||||
? dateFormatter.format(
|
||||
date: chat.lastUsed!,
|
||||
)
|
||||
: null,
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
var otherUser = chat.users.firstWhere(
|
||||
(element) => element.id != currentUserId,
|
||||
(element) => element != currentUserId,
|
||||
);
|
||||
|
||||
return _ChatRow(
|
||||
unreadMessages: chat.unreadMessageCount,
|
||||
avatar: options.builders.userAvatarBuilder?.call(
|
||||
otherUser,
|
||||
40.0,
|
||||
) ??
|
||||
Avatar(
|
||||
boxfit: BoxFit.cover,
|
||||
user: User(
|
||||
firstName: otherUser.firstName,
|
||||
lastName: otherUser.lastName,
|
||||
imageUrl: otherUser.imageUrl != null || otherUser.imageUrl != ""
|
||||
? otherUser.imageUrl
|
||||
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(
|
||||
unreadMessages: chat.unreadMessageCount,
|
||||
avatar: options.builders.userAvatarBuilder?.call(
|
||||
context,
|
||||
otherUser,
|
||||
40.0,
|
||||
) ??
|
||||
Avatar(
|
||||
boxfit: BoxFit.cover,
|
||||
user: User(
|
||||
firstName: otherUser.firstName,
|
||||
lastName: otherUser.lastName,
|
||||
imageUrl:
|
||||
otherUser.imageUrl != null || otherUser.imageUrl != ""
|
||||
? otherUser.imageUrl
|
||||
: null,
|
||||
),
|
||||
size: 40.0,
|
||||
),
|
||||
title: otherUser.fullname ?? translations.anonymousUser,
|
||||
subTitle: data != null
|
||||
? data.isTextMessage
|
||||
? data.text
|
||||
: "📷 "
|
||||
"${translations.image}"
|
||||
: "",
|
||||
lastUsed: chat.lastUsed != null
|
||||
? dateFormatter.format(
|
||||
date: chat.lastUsed!,
|
||||
)
|
||||
: null,
|
||||
),
|
||||
size: 40.0,
|
||||
),
|
||||
title: otherUser.fullname ?? translations.anonymousUser,
|
||||
subTitle: chat.lastMessage != null
|
||||
? chat.lastMessage!.isTextMessage()
|
||||
? chat.lastMessage!.text
|
||||
: "📷 "
|
||||
"${translations.image}"
|
||||
: "",
|
||||
lastUsed: chat.lastUsed != null
|
||||
? dateFormatter.format(
|
||||
date: chat.lastUsed!,
|
||||
)
|
||||
: null,
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -490,7 +566,6 @@ class _ChatRow extends StatelessWidget {
|
|||
this.lastUsed,
|
||||
this.subTitle,
|
||||
this.avatar,
|
||||
super.key,
|
||||
});
|
||||
|
||||
/// The title of the chat.
|
||||
|
@ -535,11 +610,7 @@ class _ChatRow extends StatelessWidget {
|
|||
padding: const EdgeInsets.only(top: 3.0),
|
||||
child: Text(
|
||||
subTitle!,
|
||||
style: unreadMessages > 0
|
||||
? theme.textTheme.bodySmall!.copyWith(
|
||||
fontWeight: FontWeight.w800,
|
||||
)
|
||||
: theme.textTheme.bodySmall,
|
||||
style: theme.textTheme.bodySmall,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
maxLines: 2,
|
||||
),
|
||||
|
|
|
@ -1,11 +1,14 @@
|
|||
import 'package:chat_repository_interface/chat_repository_interface.dart';
|
||||
import 'package:flutter/material.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_icon.dart';
|
||||
import 'package:flutter_chat/src/screens/creation/widgets/user_list.dart';
|
||||
import "package:chat_repository_interface/chat_repository_interface.dart";
|
||||
import "package:flutter/material.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_icon.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 {
|
||||
/// Constructs a [NewChatScreen]
|
||||
const NewChatScreen({
|
||||
required this.userId,
|
||||
required this.chatService,
|
||||
|
@ -15,10 +18,19 @@ class NewChatScreen extends StatefulWidget {
|
|||
super.key,
|
||||
});
|
||||
|
||||
/// The user ID of the person currently looking at the chat
|
||||
final String userId;
|
||||
|
||||
/// The chat service associated with the widget.
|
||||
final ChatService chatService;
|
||||
|
||||
/// The chat options
|
||||
final ChatOptions chatOptions;
|
||||
|
||||
/// Callback function triggered when the create group chat button is pressed
|
||||
final VoidCallback onPressCreateGroupChat;
|
||||
|
||||
/// Callback function triggered when a user is tapped
|
||||
final Function(UserModel) onPressCreateChat;
|
||||
|
||||
@override
|
||||
|
@ -35,6 +47,7 @@ class _NewChatScreenState extends State<NewChatScreen> {
|
|||
var theme = Theme.of(context);
|
||||
|
||||
return widget.chatOptions.builders.newChatScreenScaffoldBuilder?.call(
|
||||
context,
|
||||
_AppBar(
|
||||
chatOptions: widget.chatOptions,
|
||||
isSearching: _isSearching,
|
||||
|
@ -55,7 +68,7 @@ class _NewChatScreenState extends State<NewChatScreen> {
|
|||
}
|
||||
},
|
||||
focusNode: _textFieldFocusNode,
|
||||
) as AppBar,
|
||||
),
|
||||
_Body(
|
||||
chatOptions: widget.chatOptions,
|
||||
chatService: widget.chatService,
|
||||
|
@ -218,7 +231,7 @@ class _Body extends StatelessWidget {
|
|||
);
|
||||
} else {
|
||||
return chatOptions.builders.noUsersPlaceholderBuilder
|
||||
?.call(translations) ??
|
||||
?.call(context, translations) ??
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 20),
|
||||
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:flutter/material.dart';
|
||||
import 'package:flutter_chat/src/config/chat_options.dart';
|
||||
import 'package:flutter_chat/src/screens/creation/widgets/image_picker.dart';
|
||||
import 'package:flutter_profile/flutter_profile.dart';
|
||||
import "package:chat_repository_interface/chat_repository_interface.dart";
|
||||
import "package:flutter/material.dart";
|
||||
import "package:flutter_chat/src/config/chat_options.dart";
|
||||
import "package:flutter_chat/src/screens/creation/widgets/image_picker.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 {
|
||||
/// Constructs a [NewGroupChatOverview]
|
||||
const NewGroupChatOverview({
|
||||
super.key,
|
||||
required this.options,
|
||||
required this.users,
|
||||
required this.onComplete,
|
||||
super.key,
|
||||
});
|
||||
|
||||
/// The chat options
|
||||
final ChatOptions options;
|
||||
|
||||
/// The users to be added to the group chat
|
||||
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
|
||||
Widget build(BuildContext context) {
|
||||
var theme = Theme.of(context);
|
||||
|
||||
return options.builders.newGroupChatOverviewScaffoldBuilder?.call(
|
||||
context,
|
||||
_AppBar(
|
||||
options: options,
|
||||
) as AppBar,
|
||||
),
|
||||
_Body(
|
||||
options: options,
|
||||
users: users,
|
||||
|
@ -80,8 +94,12 @@ class _Body extends StatefulWidget {
|
|||
|
||||
final ChatOptions options;
|
||||
final List<UserModel> users;
|
||||
final Function(List<UserModel> users, String chatName, String description,
|
||||
Uint8List? image) onComplete;
|
||||
final Function(
|
||||
List<UserModel> users,
|
||||
String chatName,
|
||||
String description,
|
||||
Uint8List? image,
|
||||
) onComplete;
|
||||
|
||||
@override
|
||||
State<_Body> createState() => _BodyState();
|
||||
|
@ -92,10 +110,10 @@ class _BodyState extends State<_Body> {
|
|||
final TextEditingController _bioController = TextEditingController();
|
||||
Uint8List? image;
|
||||
|
||||
var formKey = GlobalKey<FormState>();
|
||||
var isPressed = false;
|
||||
GlobalKey<FormState> formKey = GlobalKey<FormState>();
|
||||
bool isPressed = false;
|
||||
|
||||
var users = <UserModel>[];
|
||||
List<UserModel> users = <UserModel>[];
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
|
@ -123,7 +141,7 @@ class _BodyState extends State<_Body> {
|
|||
Center(
|
||||
child: Stack(
|
||||
children: [
|
||||
GestureDetector(
|
||||
InkWell(
|
||||
onTap: () async => onPressSelectImage(
|
||||
context,
|
||||
widget.options,
|
||||
|
@ -162,7 +180,7 @@ class _BodyState extends State<_Body> {
|
|||
borderRadius: BorderRadius.circular(40),
|
||||
),
|
||||
child: Center(
|
||||
child: GestureDetector(
|
||||
child: InkWell(
|
||||
onTap: () {
|
||||
setState(() {
|
||||
image = null;
|
||||
|
@ -198,10 +216,7 @@ class _BodyState extends State<_Body> {
|
|||
fillColor: Colors.white,
|
||||
filled: true,
|
||||
hintText: translations.groupNameHintText,
|
||||
hintStyle: theme.textTheme.bodyMedium!.copyWith(
|
||||
color:
|
||||
theme.textTheme.bodyMedium!.color!.withOpacity(0.5),
|
||||
),
|
||||
hintStyle: theme.textTheme.bodyMedium,
|
||||
enabledBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
borderSide: const BorderSide(
|
||||
|
@ -245,10 +260,7 @@ class _BodyState extends State<_Body> {
|
|||
fillColor: Colors.white,
|
||||
filled: true,
|
||||
hintText: translations.groupBioHintText,
|
||||
hintStyle: theme.textTheme.bodyMedium!.copyWith(
|
||||
color:
|
||||
theme.textTheme.bodyMedium!.color!.withOpacity(0.5),
|
||||
),
|
||||
hintStyle: theme.textTheme.bodyMedium,
|
||||
enabledBorder: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
borderSide: const BorderSide(
|
||||
|
@ -357,39 +369,38 @@ class _SelectedUser extends StatelessWidget {
|
|||
final Function(UserModel) onRemove;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return GestureDetector(
|
||||
onTap: () {
|
||||
onRemove(user);
|
||||
},
|
||||
child: Stack(
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(8),
|
||||
child: options.builders.userAvatarBuilder?.call(
|
||||
user,
|
||||
40,
|
||||
) ??
|
||||
Avatar(
|
||||
boxfit: BoxFit.cover,
|
||||
user: User(
|
||||
firstName: user.firstName,
|
||||
lastName: user.lastName,
|
||||
imageUrl: user.imageUrl != "" ? user.imageUrl : null,
|
||||
Widget build(BuildContext context) => InkWell(
|
||||
onTap: () {
|
||||
onRemove(user);
|
||||
},
|
||||
child: Stack(
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(8),
|
||||
child: options.builders.userAvatarBuilder?.call(
|
||||
context,
|
||||
user,
|
||||
40,
|
||||
) ??
|
||||
Avatar(
|
||||
boxfit: BoxFit.cover,
|
||||
user: User(
|
||||
firstName: user.firstName,
|
||||
lastName: user.lastName,
|
||||
imageUrl: user.imageUrl != "" ? user.imageUrl : null,
|
||||
),
|
||||
size: 40,
|
||||
),
|
||||
size: 40,
|
||||
),
|
||||
),
|
||||
Positioned.directional(
|
||||
textDirection: Directionality.of(context),
|
||||
end: 0,
|
||||
child: const Icon(
|
||||
Icons.cancel,
|
||||
size: 20,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
Positioned.directional(
|
||||
textDirection: Directionality.of(context),
|
||||
end: 0,
|
||||
child: const Icon(
|
||||
Icons.cancel,
|
||||
size: 20,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,11 +1,14 @@
|
|||
import 'package:chat_repository_interface/chat_repository_interface.dart';
|
||||
import 'package:flutter/material.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_icon.dart';
|
||||
import 'package:flutter_chat/src/screens/creation/widgets/user_list.dart';
|
||||
import "package:chat_repository_interface/chat_repository_interface.dart";
|
||||
import "package:flutter/material.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_icon.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 {
|
||||
/// Constructs a [NewGroupChatScreen]
|
||||
const NewGroupChatScreen({
|
||||
required this.userId,
|
||||
required this.chatService,
|
||||
|
@ -14,9 +17,16 @@ class NewGroupChatScreen extends StatefulWidget {
|
|||
super.key,
|
||||
});
|
||||
|
||||
/// The user ID of the person currently looking at the chat
|
||||
final String userId;
|
||||
|
||||
/// The chat service associated with the widget.
|
||||
final ChatService chatService;
|
||||
|
||||
/// The chat options
|
||||
final ChatOptions chatOptions;
|
||||
|
||||
/// Callback function triggered when the continue button is pressed
|
||||
final Function(List<UserModel>) onContinue;
|
||||
|
||||
@override
|
||||
|
@ -35,6 +45,7 @@ class _NewGroupChatScreenState extends State<NewGroupChatScreen> {
|
|||
var theme = Theme.of(context);
|
||||
|
||||
return widget.chatOptions.builders.newGroupChatScreenScaffoldBuilder?.call(
|
||||
context,
|
||||
_AppBar(
|
||||
chatOptions: widget.chatOptions,
|
||||
isSearching: _isSearching,
|
||||
|
@ -55,7 +66,7 @@ class _NewGroupChatScreenState extends State<NewGroupChatScreen> {
|
|||
}
|
||||
},
|
||||
focusNode: _textFieldFocusNode,
|
||||
) as AppBar,
|
||||
),
|
||||
_Body(
|
||||
onSelectedUser: handleUserTap,
|
||||
selectedUsers: selectedUsers,
|
||||
|
@ -219,7 +230,7 @@ class _Body extends StatelessWidget {
|
|||
);
|
||||
} else {
|
||||
return chatOptions.builders.noUsersPlaceholderBuilder
|
||||
?.call(translations) ??
|
||||
?.call(context, translations) ??
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 20),
|
||||
child: Align(
|
||||
|
|
|
@ -1,10 +1,11 @@
|
|||
import 'dart:typed_data';
|
||||
import "dart:typed_data";
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_chat/src/config/chat_options.dart';
|
||||
import 'package:flutter_chat/src/config/chat_translations.dart';
|
||||
import 'package:flutter_image_picker/flutter_image_picker.dart';
|
||||
import "package:flutter/material.dart";
|
||||
import "package:flutter_chat/src/config/chat_options.dart";
|
||||
import "package:flutter_chat/src/config/chat_translations.dart";
|
||||
import "package:flutter_image_picker/flutter_image_picker.dart";
|
||||
|
||||
/// The function to call when the user selects an image
|
||||
Future<void> onPressSelectImage(
|
||||
BuildContext context,
|
||||
ChatOptions options,
|
||||
|
@ -14,9 +15,9 @@ Future<void> onPressSelectImage(
|
|||
context: context,
|
||||
builder: (BuildContext context) =>
|
||||
options.builders.imagePickerContainerBuilder?.call(
|
||||
context,
|
||||
() => Navigator.of(context).pop(),
|
||||
options.translations,
|
||||
context,
|
||||
) ??
|
||||
Container(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
|
@ -38,9 +39,7 @@ Future<void> onPressSelectImage(
|
|||
onPressed: () => Navigator.of(context).pop(),
|
||||
child: Text(
|
||||
options.translations.cancelImagePickerBtn,
|
||||
style: Theme.of(context).textTheme.bodyMedium!.copyWith(
|
||||
decoration: TextDecoration.underline,
|
||||
),
|
||||
style: Theme.of(context).textTheme.bodyMedium,
|
||||
),
|
||||
),
|
||||
),
|
||||
|
|
|
@ -1,20 +1,31 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_chat/src/config/chat_options.dart';
|
||||
import "package:flutter/material.dart";
|
||||
import "package:flutter_chat/src/config/chat_options.dart";
|
||||
|
||||
/// The search field widget
|
||||
class SearchField extends StatelessWidget {
|
||||
/// Constructs a [SearchField]
|
||||
const SearchField({
|
||||
super.key,
|
||||
required this.chatOptions,
|
||||
required this.isSearching,
|
||||
required this.onSearch,
|
||||
required this.focusNode,
|
||||
required this.text,
|
||||
super.key,
|
||||
});
|
||||
|
||||
/// The chat options
|
||||
final ChatOptions chatOptions;
|
||||
|
||||
/// Whether the search field is currently in use
|
||||
final bool isSearching;
|
||||
|
||||
/// Callback function triggered when the search field is used
|
||||
final Function(String query) onSearch;
|
||||
|
||||
/// The focus node of the search field
|
||||
final FocusNode focusNode;
|
||||
|
||||
/// The text to display in the search field
|
||||
final String text;
|
||||
|
||||
@override
|
||||
|
@ -22,25 +33,26 @@ class SearchField extends StatelessWidget {
|
|||
var theme = Theme.of(context);
|
||||
var translations = chatOptions.translations;
|
||||
|
||||
return isSearching
|
||||
? TextField(
|
||||
focusNode: focusNode,
|
||||
onChanged: onSearch,
|
||||
decoration: InputDecoration(
|
||||
hintText: translations.searchPlaceholder,
|
||||
hintStyle:
|
||||
theme.textTheme.bodyMedium!.copyWith(color: Colors.white),
|
||||
focusedBorder: UnderlineInputBorder(
|
||||
borderSide: BorderSide(
|
||||
color: theme.colorScheme.primary,
|
||||
),
|
||||
),
|
||||
if (isSearching) {
|
||||
return TextField(
|
||||
focusNode: focusNode,
|
||||
onChanged: onSearch,
|
||||
decoration: InputDecoration(
|
||||
hintText: translations.searchPlaceholder,
|
||||
hintStyle: theme.textTheme.bodyMedium,
|
||||
focusedBorder: UnderlineInputBorder(
|
||||
borderSide: BorderSide(
|
||||
color: theme.colorScheme.primary,
|
||||
),
|
||||
style: theme.textTheme.bodySmall!.copyWith(color: Colors.white),
|
||||
cursorColor: theme.textSelectionTheme.cursorColor ?? Colors.white,
|
||||
)
|
||||
: Text(
|
||||
text,
|
||||
);
|
||||
),
|
||||
),
|
||||
style: theme.textTheme.bodySmall,
|
||||
cursorColor: theme.textSelectionTheme.cursorColor ?? Colors.white,
|
||||
);
|
||||
}
|
||||
|
||||
return 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 {
|
||||
/// Constructs a [SearchIcon].
|
||||
const SearchIcon({
|
||||
super.key,
|
||||
required this.isSearching,
|
||||
required this.onPressed,
|
||||
super.key,
|
||||
});
|
||||
|
||||
/// Whether the search icon is currently in use
|
||||
final bool isSearching;
|
||||
|
||||
/// Callback function triggered when the search icon is pressed
|
||||
final VoidCallback onPressed;
|
||||
|
||||
@override
|
||||
|
|
|
@ -1,11 +1,12 @@
|
|||
import 'package:chat_repository_interface/chat_repository_interface.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_chat/src/config/chat_options.dart';
|
||||
import 'package:flutter_profile/flutter_profile.dart';
|
||||
import "package:chat_repository_interface/chat_repository_interface.dart";
|
||||
import "package:flutter/material.dart";
|
||||
import "package:flutter_chat/src/config/chat_options.dart";
|
||||
import "package:flutter_profile/flutter_profile.dart";
|
||||
|
||||
/// The user list widget
|
||||
class UserList extends StatefulWidget {
|
||||
/// Constructs a [UserList]
|
||||
const UserList({
|
||||
super.key,
|
||||
required this.users,
|
||||
required this.currentUser,
|
||||
required this.query,
|
||||
|
@ -14,15 +15,31 @@ class UserList extends StatefulWidget {
|
|||
this.creatingGroup = false,
|
||||
this.selectedUsers = const [],
|
||||
this.onSelectedUser,
|
||||
super.key,
|
||||
});
|
||||
|
||||
/// The list of users
|
||||
final List<UserModel> users;
|
||||
|
||||
/// The query to search for
|
||||
final String query;
|
||||
|
||||
/// The current user
|
||||
final String currentUser;
|
||||
|
||||
/// The chat options
|
||||
final ChatOptions options;
|
||||
|
||||
/// Whether the user is creating a group
|
||||
final bool creatingGroup;
|
||||
|
||||
/// Callback function triggered when a chat is created
|
||||
final Function(UserModel)? onPressCreateChat;
|
||||
|
||||
/// The selected users
|
||||
final List<UserModel> selectedUsers;
|
||||
|
||||
/// Callback function triggered when a user is selected
|
||||
final Function(UserModel)? onSelectedUser;
|
||||
|
||||
@override
|
||||
|
@ -71,10 +88,11 @@ class _UserListState extends State<UserList> {
|
|||
}
|
||||
},
|
||||
child: widget.options.builders.chatRowContainerBuilder?.call(
|
||||
context,
|
||||
Row(
|
||||
children: [
|
||||
widget.options.builders.userAvatarBuilder
|
||||
?.call(user, 44) ??
|
||||
?.call(context, user, 44) ??
|
||||
Avatar(
|
||||
boxfit: BoxFit.cover,
|
||||
user: User(
|
||||
|
@ -122,7 +140,7 @@ class _UserListState extends State<UserList> {
|
|||
child: Row(
|
||||
children: [
|
||||
widget.options.builders.userAvatarBuilder
|
||||
?.call(user, 44) ??
|
||||
?.call(context, user, 44) ??
|
||||
Avatar(
|
||||
boxfit: BoxFit.cover,
|
||||
user: User(
|
||||
|
@ -162,7 +180,7 @@ class _UserListState extends State<UserList> {
|
|||
);
|
||||
}
|
||||
|
||||
void handlePersonalChatTap(UserModel user) async {
|
||||
Future<void> handlePersonalChatTap(UserModel user) async {
|
||||
if (!isPressed) {
|
||||
setState(() {
|
||||
isPressed = true;
|
||||
|
|
|
@ -5,10 +5,14 @@
|
|||
import "package:flutter_chat/src/config/chat_options.dart";
|
||||
import "package:intl/intl.dart";
|
||||
|
||||
/// The date formatter
|
||||
class DateFormatter {
|
||||
/// Constructs a [DateFormatter]
|
||||
DateFormatter({
|
||||
required this.options,
|
||||
});
|
||||
|
||||
/// The chat options
|
||||
final ChatOptions options;
|
||||
final _now = DateTime.now();
|
||||
|
||||
|
@ -46,6 +50,7 @@ class DateFormatter {
|
|||
|
||||
bool _isThisYear(DateTime date) => date.year == _now.year;
|
||||
|
||||
/// Formats the date
|
||||
String format({
|
||||
required DateTime date,
|
||||
bool showFullDate = false,
|
||||
|
|
|
@ -1,10 +1,11 @@
|
|||
name: flutter_chat
|
||||
description: "A new Flutter package project."
|
||||
version: 0.0.1
|
||||
homepage:
|
||||
homepage: https://www.iconica.app
|
||||
publish_to: https://forgejo.internal.iconica.nl/api/packages/internal/pub
|
||||
|
||||
environment:
|
||||
sdk: '>=3.4.3 <4.0.0'
|
||||
sdk: ">=3.4.3 <4.0.0"
|
||||
flutter: ">=1.17.0"
|
||||
|
||||
dependencies:
|
||||
|
@ -20,7 +21,7 @@ dependencies:
|
|||
ref: 1.0.5
|
||||
flutter_profile:
|
||||
git:
|
||||
ref: 1.5.0
|
||||
ref: 1.6.0
|
||||
url: https://github.com/Iconica-Development/flutter_profile
|
||||
chat_repository_interface:
|
||||
path: ../chat_repository_interface
|
||||
|
@ -28,41 +29,9 @@ dependencies:
|
|||
dev_dependencies:
|
||||
flutter_test:
|
||||
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:
|
||||
|
||||
# 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