mirror of
https://github.com/Iconica-Development/flutter_chat.git
synced 2025-05-18 18:33:49 +02:00
feat: ui update
This commit is contained in:
parent
644615f026
commit
8f13d87a23
28 changed files with 1195 additions and 611 deletions
|
@ -1,5 +1,8 @@
|
||||||
## 3.1.0
|
## 3.1.0
|
||||||
- Fix center the texts for no users found with search and type first message
|
- Fix center the texts for no users found with search and type first message
|
||||||
|
- Fix styling for the whole userstory
|
||||||
|
- Add groupchat profile picture, and bio to the groupchat creation screen
|
||||||
|
- Updated profile of users and groups
|
||||||
|
|
||||||
|
|
||||||
## 3.0.1
|
## 3.0.1
|
||||||
|
|
|
@ -16,15 +16,6 @@ dependencies:
|
||||||
path: ../
|
path: ../
|
||||||
flutter_chat_firebase:
|
flutter_chat_firebase:
|
||||||
path: ../../flutter_chat_firebase
|
path: ../../flutter_chat_firebase
|
||||||
dependency_overrides:
|
|
||||||
flutter_chat:
|
|
||||||
path: ../../flutter_chat
|
|
||||||
flutter_chat_interface:
|
|
||||||
path: ../../flutter_chat_interface
|
|
||||||
flutter_chat_local:
|
|
||||||
path: ../../flutter_chat_local
|
|
||||||
flutter_chat_view:
|
|
||||||
path: ../../flutter_chat_view
|
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
|
|
|
@ -104,6 +104,10 @@ Widget _chatDetailScreenRoute(
|
||||||
if (configuration.onPressUserProfile != null) {
|
if (configuration.onPressUserProfile != null) {
|
||||||
return configuration.onPressUserProfile?.call(context, user);
|
return configuration.onPressUserProfile?.call(context, user);
|
||||||
}
|
}
|
||||||
|
var currentUser =
|
||||||
|
await configuration.chatService.chatUserService.getCurrentUser();
|
||||||
|
var currentUserId = currentUser!.id!;
|
||||||
|
if (context.mounted)
|
||||||
return Navigator.of(context).push(
|
return Navigator.of(context).push(
|
||||||
MaterialPageRoute(
|
MaterialPageRoute(
|
||||||
builder: (context) => _chatProfileScreenRoute(
|
builder: (context) => _chatProfileScreenRoute(
|
||||||
|
@ -111,6 +115,7 @@ Widget _chatDetailScreenRoute(
|
||||||
context,
|
context,
|
||||||
chatId,
|
chatId,
|
||||||
user.id,
|
user.id,
|
||||||
|
currentUserId,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
@ -142,7 +147,10 @@ Widget _chatDetailScreenRoute(
|
||||||
if (configuration.onPressChatTitle?.call(context, chat) != null) {
|
if (configuration.onPressChatTitle?.call(context, chat) != null) {
|
||||||
return configuration.onPressChatTitle?.call(context, chat);
|
return configuration.onPressChatTitle?.call(context, chat);
|
||||||
}
|
}
|
||||||
|
var currentUser =
|
||||||
|
await configuration.chatService.chatUserService.getCurrentUser();
|
||||||
|
var currentUserId = currentUser!.id!;
|
||||||
|
if (context.mounted)
|
||||||
return Navigator.of(context).push(
|
return Navigator.of(context).push(
|
||||||
MaterialPageRoute(
|
MaterialPageRoute(
|
||||||
builder: (context) => _chatProfileScreenRoute(
|
builder: (context) => _chatProfileScreenRoute(
|
||||||
|
@ -150,6 +158,7 @@ Widget _chatDetailScreenRoute(
|
||||||
context,
|
context,
|
||||||
chatId,
|
chatId,
|
||||||
null,
|
null,
|
||||||
|
currentUserId,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
@ -168,17 +177,23 @@ Widget _chatProfileScreenRoute(
|
||||||
BuildContext context,
|
BuildContext context,
|
||||||
String chatId,
|
String chatId,
|
||||||
String? userId,
|
String? userId,
|
||||||
|
String currentUserId,
|
||||||
) =>
|
) =>
|
||||||
ChatProfileScreen(
|
ChatProfileScreen(
|
||||||
|
options: configuration.chatOptionsBuilder(context),
|
||||||
translations: configuration.translations,
|
translations: configuration.translations,
|
||||||
chatService: configuration.chatService,
|
chatService: configuration.chatService,
|
||||||
chatId: chatId,
|
chatId: chatId,
|
||||||
userId: userId,
|
userId: userId,
|
||||||
|
currentUserId: currentUserId,
|
||||||
onTapUser: (user) async {
|
onTapUser: (user) async {
|
||||||
if (configuration.onPressUserProfile != null) {
|
if (configuration.onPressUserProfile != null) {
|
||||||
return configuration.onPressUserProfile!.call(context, user);
|
return configuration.onPressUserProfile!.call(context, user);
|
||||||
}
|
}
|
||||||
|
var currentUser =
|
||||||
|
await configuration.chatService.chatUserService.getCurrentUser();
|
||||||
|
var currentUserId = currentUser!.id!;
|
||||||
|
if (context.mounted)
|
||||||
return Navigator.of(context).push(
|
return Navigator.of(context).push(
|
||||||
MaterialPageRoute(
|
MaterialPageRoute(
|
||||||
builder: (context) => _chatProfileScreenRoute(
|
builder: (context) => _chatProfileScreenRoute(
|
||||||
|
@ -186,10 +201,40 @@ Widget _chatProfileScreenRoute(
|
||||||
context,
|
context,
|
||||||
chatId,
|
chatId,
|
||||||
user.id,
|
user.id,
|
||||||
|
currentUserId,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
onPressStartChat: (user) async {
|
||||||
|
configuration.onPressCreateChat?.call(user);
|
||||||
|
if (configuration.onPressCreateChat != null) return;
|
||||||
|
var chat = await configuration.chatService.chatOverviewService
|
||||||
|
.getChatByUser(user);
|
||||||
|
if (chat.id == null) {
|
||||||
|
chat = await configuration.chatService.chatOverviewService
|
||||||
|
.storeChatIfNot(
|
||||||
|
PersonalChatModel(
|
||||||
|
user: user,
|
||||||
|
),
|
||||||
|
null,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (context.mounted) {
|
||||||
|
await Navigator.of(context).push(
|
||||||
|
MaterialPageRoute(
|
||||||
|
builder: (context) => PopScope(
|
||||||
|
canPop: false,
|
||||||
|
child: _chatDetailScreenRoute(
|
||||||
|
configuration,
|
||||||
|
context,
|
||||||
|
chat.id!,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
/// Constructs the new chat screen route widget.
|
/// Constructs the new chat screen route widget.
|
||||||
|
@ -207,6 +252,8 @@ Widget _newChatScreenRoute(
|
||||||
showGroupChatButton: configuration.enableGroupChatCreation,
|
showGroupChatButton: configuration.enableGroupChatCreation,
|
||||||
onPressCreateGroupChat: () async {
|
onPressCreateGroupChat: () async {
|
||||||
configuration.onPressCreateGroupChat?.call();
|
configuration.onPressCreateGroupChat?.call();
|
||||||
|
configuration.chatService.chatOverviewService
|
||||||
|
.clearCurrentlySelectedUsers();
|
||||||
if (context.mounted) {
|
if (context.mounted) {
|
||||||
await Navigator.of(context).push(
|
await Navigator.of(context).push(
|
||||||
MaterialPageRoute(
|
MaterialPageRoute(
|
||||||
|
@ -223,13 +270,13 @@ Widget _newChatScreenRoute(
|
||||||
if (configuration.onPressCreateChat != null) return;
|
if (configuration.onPressCreateChat != null) return;
|
||||||
var chat = await configuration.chatService.chatOverviewService
|
var chat = await configuration.chatService.chatOverviewService
|
||||||
.getChatByUser(user);
|
.getChatByUser(user);
|
||||||
debugPrint("Chat is ${chat.id}");
|
|
||||||
if (chat.id == null) {
|
if (chat.id == null) {
|
||||||
chat = await configuration.chatService.chatOverviewService
|
chat = await configuration.chatService.chatOverviewService
|
||||||
.storeChatIfNot(
|
.storeChatIfNot(
|
||||||
PersonalChatModel(
|
PersonalChatModel(
|
||||||
user: user,
|
user: user,
|
||||||
),
|
),
|
||||||
|
null,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (context.mounted) {
|
if (context.mounted) {
|
||||||
|
@ -277,19 +324,20 @@ Widget _newGroupChatOverviewScreenRoute(
|
||||||
options: configuration.chatOptionsBuilder(context),
|
options: configuration.chatOptionsBuilder(context),
|
||||||
translations: configuration.translations,
|
translations: configuration.translations,
|
||||||
service: configuration.chatService,
|
service: configuration.chatService,
|
||||||
users: users,
|
onPressCompleteGroupChatCreation:
|
||||||
onPressCompleteGroupChatCreation: (users, groupChatName) async {
|
(users, groupChatName, groupBio, image) async {
|
||||||
configuration.onPressCompleteGroupChatCreation
|
configuration.onPressCompleteGroupChatCreation
|
||||||
?.call(users, groupChatName);
|
?.call(users, groupChatName, image);
|
||||||
if (configuration.onPressCreateGroupChat != null) return;
|
if (configuration.onPressCreateGroupChat != null) return;
|
||||||
var chat =
|
var chat =
|
||||||
await configuration.chatService.chatOverviewService.storeChatIfNot(
|
await configuration.chatService.chatOverviewService.storeChatIfNot(
|
||||||
GroupChatModel(
|
GroupChatModel(
|
||||||
canBeDeleted: true,
|
canBeDeleted: true,
|
||||||
title: groupChatName,
|
title: groupChatName,
|
||||||
imageUrl: "https://picsum.photos/200/300",
|
|
||||||
users: users,
|
users: users,
|
||||||
|
bio: groupBio,
|
||||||
),
|
),
|
||||||
|
image,
|
||||||
);
|
);
|
||||||
if (context.mounted) {
|
if (context.mounted) {
|
||||||
await Navigator.of(context).pushReplacement(
|
await Navigator.of(context).pushReplacement(
|
||||||
|
|
|
@ -152,6 +152,7 @@ List<GoRoute> getChatStoryRoutes(
|
||||||
PersonalChatModel(
|
PersonalChatModel(
|
||||||
user: user,
|
user: user,
|
||||||
),
|
),
|
||||||
|
null,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (context.mounted) {
|
if (context.mounted) {
|
||||||
|
@ -160,9 +161,13 @@ List<GoRoute> getChatStoryRoutes(
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onPressCreateGroupChat: () async => context.push(
|
onPressCreateGroupChat: () async {
|
||||||
|
configuration.chatService.chatOverviewService
|
||||||
|
.clearCurrentlySelectedUsers();
|
||||||
|
return context.push(
|
||||||
ChatUserStoryRoutes.newGroupChatScreen,
|
ChatUserStoryRoutes.newGroupChatScreen,
|
||||||
),
|
);
|
||||||
|
},
|
||||||
);
|
);
|
||||||
return buildScreenWithoutTransition(
|
return buildScreenWithoutTransition(
|
||||||
context: context,
|
context: context,
|
||||||
|
@ -211,25 +216,25 @@ List<GoRoute> getChatStoryRoutes(
|
||||||
pageBuilder: (context, state) {
|
pageBuilder: (context, state) {
|
||||||
var service = configuration.chatServiceBuilder?.call(context) ??
|
var service = configuration.chatServiceBuilder?.call(context) ??
|
||||||
configuration.chatService;
|
configuration.chatService;
|
||||||
var users = state.extra! as List<ChatUserModel>;
|
|
||||||
|
|
||||||
var newGroupChatOverviewScreen = NewGroupChatOverviewScreen(
|
var newGroupChatOverviewScreen = NewGroupChatOverviewScreen(
|
||||||
options: configuration.chatOptionsBuilder(context),
|
options: configuration.chatOptionsBuilder(context),
|
||||||
translations: configuration.translationsBuilder?.call(context) ??
|
translations: configuration.translationsBuilder?.call(context) ??
|
||||||
configuration.translations,
|
configuration.translations,
|
||||||
service: service,
|
service: service,
|
||||||
users: users,
|
onPressCompleteGroupChatCreation:
|
||||||
onPressCompleteGroupChatCreation: (users, groupChatName) async {
|
(users, groupChatName, groupBio, image) async {
|
||||||
configuration.onPressCompleteGroupChatCreation
|
configuration.onPressCompleteGroupChatCreation
|
||||||
?.call(users, groupChatName);
|
?.call(users, groupChatName, image);
|
||||||
var chat = await configuration.chatService.chatOverviewService
|
var chat = await configuration.chatService.chatOverviewService
|
||||||
.storeChatIfNot(
|
.storeChatIfNot(
|
||||||
GroupChatModel(
|
GroupChatModel(
|
||||||
canBeDeleted: true,
|
canBeDeleted: true,
|
||||||
title: groupChatName,
|
title: groupChatName,
|
||||||
imageUrl: "https://picsum.photos/200/300",
|
|
||||||
users: users,
|
users: users,
|
||||||
|
bio: groupBio,
|
||||||
),
|
),
|
||||||
|
image,
|
||||||
);
|
);
|
||||||
if (context.mounted) {
|
if (context.mounted) {
|
||||||
context.go(
|
context.go(
|
||||||
|
@ -259,13 +264,21 @@ List<GoRoute> getChatStoryRoutes(
|
||||||
var id = userId == "null" ? null : userId;
|
var id = userId == "null" ? null : userId;
|
||||||
var service = configuration.chatServiceBuilder?.call(context) ??
|
var service = configuration.chatServiceBuilder?.call(context) ??
|
||||||
configuration.chatService;
|
configuration.chatService;
|
||||||
|
ChatUserModel? currentUser;
|
||||||
|
String? currentUserId;
|
||||||
|
Future.delayed(Duration.zero, () async {
|
||||||
|
currentUser = await service.chatUserService.getCurrentUser();
|
||||||
|
currentUserId = currentUser!.id;
|
||||||
|
});
|
||||||
|
|
||||||
var profileScreen = ChatProfileScreen(
|
var profileScreen = ChatProfileScreen(
|
||||||
|
options: configuration.chatOptionsBuilder(context),
|
||||||
translations: configuration.translationsBuilder?.call(context) ??
|
translations: configuration.translationsBuilder?.call(context) ??
|
||||||
configuration.translations,
|
configuration.translations,
|
||||||
chatService: service,
|
chatService: service,
|
||||||
chatId: chatId!,
|
chatId: chatId!,
|
||||||
userId: id,
|
userId: id,
|
||||||
|
currentUserId: currentUserId!,
|
||||||
onTapUser: (user) async {
|
onTapUser: (user) async {
|
||||||
if (configuration.onPressUserProfile != null) {
|
if (configuration.onPressUserProfile != null) {
|
||||||
return configuration.onPressUserProfile!.call(context, user);
|
return configuration.onPressUserProfile!.call(context, user);
|
||||||
|
@ -275,6 +288,26 @@ List<GoRoute> getChatStoryRoutes(
|
||||||
ChatUserStoryRoutes.chatProfileScreenPath(chatId, user.id),
|
ChatUserStoryRoutes.chatProfileScreenPath(chatId, user.id),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
onPressStartChat: (user) async {
|
||||||
|
configuration.onPressCreateChat?.call(user);
|
||||||
|
if (configuration.onPressCreateChat != null) return;
|
||||||
|
var chat = await configuration.chatService.chatOverviewService
|
||||||
|
.getChatByUser(user);
|
||||||
|
if (chat.id == null) {
|
||||||
|
chat = await configuration.chatService.chatOverviewService
|
||||||
|
.storeChatIfNot(
|
||||||
|
PersonalChatModel(
|
||||||
|
user: user,
|
||||||
|
),
|
||||||
|
null,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (context.mounted) {
|
||||||
|
await context.push(
|
||||||
|
ChatUserStoryRoutes.chatDetailViewPath(chat.id ?? ""),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
);
|
);
|
||||||
return buildScreenWithoutTransition(
|
return buildScreenWithoutTransition(
|
||||||
context: context,
|
context: context,
|
||||||
|
|
|
@ -83,7 +83,11 @@ class ChatUserStoryConfiguration {
|
||||||
final Function(ChatUserModel)? onPressCreateChat;
|
final Function(ChatUserModel)? onPressCreateChat;
|
||||||
|
|
||||||
/// Builder for chat options based on context.
|
/// Builder for chat options based on context.
|
||||||
final Function(List<ChatUserModel>, String)? onPressCompleteGroupChatCreation;
|
final Function(
|
||||||
|
List<ChatUserModel> users,
|
||||||
|
String groupchatName,
|
||||||
|
Uint8List? image,
|
||||||
|
)? onPressCompleteGroupChatCreation;
|
||||||
|
|
||||||
final Function()? onPressCreateGroupChat;
|
final Function()? onPressCreateGroupChat;
|
||||||
|
|
||||||
|
|
|
@ -19,6 +19,7 @@ class FirebaseChatDocument {
|
||||||
this.title,
|
this.title,
|
||||||
this.imageUrl,
|
this.imageUrl,
|
||||||
this.lastMessage,
|
this.lastMessage,
|
||||||
|
this.bio,
|
||||||
});
|
});
|
||||||
|
|
||||||
/// Constructs a FirebaseChatDocument from JSON.
|
/// Constructs a FirebaseChatDocument from JSON.
|
||||||
|
@ -34,7 +35,8 @@ class FirebaseChatDocument {
|
||||||
: FirebaseMessageDocument.fromJson(
|
: FirebaseMessageDocument.fromJson(
|
||||||
json["last_message"],
|
json["last_message"],
|
||||||
null,
|
null,
|
||||||
);
|
),
|
||||||
|
bio = json["bio"];
|
||||||
|
|
||||||
/// The unique identifier of the chat document.
|
/// The unique identifier of the chat document.
|
||||||
final String? id;
|
final String? id;
|
||||||
|
@ -60,6 +62,8 @@ class FirebaseChatDocument {
|
||||||
/// The last message in the chat.
|
/// The last message in the chat.
|
||||||
final FirebaseMessageDocument? lastMessage;
|
final FirebaseMessageDocument? lastMessage;
|
||||||
|
|
||||||
|
final String? bio;
|
||||||
|
|
||||||
/// Converts the FirebaseChatDocument to JSON format.
|
/// Converts the FirebaseChatDocument to JSON format.
|
||||||
Map<String, dynamic> toJson() => {
|
Map<String, dynamic> toJson() => {
|
||||||
"title": title,
|
"title": title,
|
||||||
|
@ -68,5 +72,6 @@ class FirebaseChatDocument {
|
||||||
"last_used": lastUsed,
|
"last_used": lastUsed,
|
||||||
"can_be_deleted": canBeDeleted,
|
"can_be_deleted": canBeDeleted,
|
||||||
"users": users,
|
"users": users,
|
||||||
|
"bio": bio,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -241,7 +241,6 @@ class FirebaseChatDetailService
|
||||||
_cumulativeMessages = [];
|
_cumulativeMessages = [];
|
||||||
lastChat = chatId;
|
lastChat = chatId;
|
||||||
lastMessage = null;
|
lastMessage = null;
|
||||||
debugPrint("Canceling messages stream");
|
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -4,16 +4,20 @@
|
||||||
// SPDX-License-Identifier: BSD-3-Clause
|
// SPDX-License-Identifier: BSD-3-Clause
|
||||||
|
|
||||||
import "dart:async";
|
import "dart:async";
|
||||||
|
import "dart:typed_data";
|
||||||
|
|
||||||
import "package:cloud_firestore/cloud_firestore.dart";
|
import "package:cloud_firestore/cloud_firestore.dart";
|
||||||
import "package:firebase_core/firebase_core.dart";
|
import "package:firebase_core/firebase_core.dart";
|
||||||
import "package:firebase_storage/firebase_storage.dart";
|
import "package:firebase_storage/firebase_storage.dart";
|
||||||
|
import "package:flutter/material.dart";
|
||||||
import "package:flutter_chat_firebase/config/firebase_chat_options.dart";
|
import "package:flutter_chat_firebase/config/firebase_chat_options.dart";
|
||||||
import "package:flutter_chat_firebase/dto/firebase_chat_document.dart";
|
import "package:flutter_chat_firebase/dto/firebase_chat_document.dart";
|
||||||
import "package:flutter_chat_interface/flutter_chat_interface.dart";
|
import "package:flutter_chat_interface/flutter_chat_interface.dart";
|
||||||
|
|
||||||
/// Service class for managing chat overviews using Firebase.
|
/// Service class for managing chat overviews using Firebase.
|
||||||
class FirebaseChatOverviewService implements ChatOverviewService {
|
class FirebaseChatOverviewService
|
||||||
|
with ChangeNotifier
|
||||||
|
implements ChatOverviewService {
|
||||||
late FirebaseFirestore _db;
|
late FirebaseFirestore _db;
|
||||||
late FirebaseStorage _storage;
|
late FirebaseStorage _storage;
|
||||||
late ChatUserService _userService;
|
late ChatUserService _userService;
|
||||||
|
@ -38,6 +42,8 @@ class FirebaseChatOverviewService implements ChatOverviewService {
|
||||||
_options = options ?? const FirebaseChatOptions();
|
_options = options ?? const FirebaseChatOptions();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final List<ChatUserModel> _currentlySelectedUsers = [];
|
||||||
|
|
||||||
Future<int?> _addUnreadChatSubscription(
|
Future<int?> _addUnreadChatSubscription(
|
||||||
String chatId,
|
String chatId,
|
||||||
String userId,
|
String userId,
|
||||||
|
@ -285,6 +291,7 @@ class FirebaseChatOverviewService implements ChatOverviewService {
|
||||||
imageUrl: chat?.imageUrl ?? "",
|
imageUrl: chat?.imageUrl ?? "",
|
||||||
users: users,
|
users: users,
|
||||||
canBeDeleted: chat?.canBeDeleted ?? true,
|
canBeDeleted: chat?.canBeDeleted ?? true,
|
||||||
|
bio: chat?.bio,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -338,7 +345,7 @@ class FirebaseChatOverviewService implements ChatOverviewService {
|
||||||
///
|
///
|
||||||
/// [chat]: The chat to be stored.
|
/// [chat]: The chat to be stored.
|
||||||
@override
|
@override
|
||||||
Future<ChatModel> storeChatIfNot(ChatModel chat) async {
|
Future<ChatModel> storeChatIfNot(ChatModel chat, Uint8List? image) async {
|
||||||
if (chat.id == null) {
|
if (chat.id == null) {
|
||||||
var currentUser = await _userService.getCurrentUser();
|
var currentUser = await _userService.getCurrentUser();
|
||||||
if (chat is PersonalChatModel) {
|
if (chat is PersonalChatModel) {
|
||||||
|
@ -398,22 +405,41 @@ class FirebaseChatOverviewService implements ChatOverviewService {
|
||||||
FirebaseChatDocument(
|
FirebaseChatDocument(
|
||||||
personal: false,
|
personal: false,
|
||||||
title: chat.title,
|
title: chat.title,
|
||||||
imageUrl: chat.imageUrl,
|
|
||||||
canBeDeleted: chat.canBeDeleted,
|
canBeDeleted: chat.canBeDeleted,
|
||||||
users: userIds,
|
users: userIds,
|
||||||
lastUsed: Timestamp.now(),
|
lastUsed: Timestamp.now(),
|
||||||
|
bio: chat.bio,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (image != null) {
|
||||||
|
var imageUrl = await uploadGroupChatImage(image, reference.id);
|
||||||
|
chat.copyWith(imageUrl: imageUrl);
|
||||||
|
await _db
|
||||||
|
.collection(_options.chatsMetaDataCollectionName)
|
||||||
|
.doc(reference.id)
|
||||||
|
.set({"image_url": imageUrl}, SetOptions(merge: true));
|
||||||
|
}
|
||||||
|
var currentChat = await _db
|
||||||
|
.collection(_options.chatsMetaDataCollectionName)
|
||||||
|
.doc(reference.id)
|
||||||
|
.withConverter(
|
||||||
|
fromFirestore: (snapshot, _) =>
|
||||||
|
FirebaseChatDocument.fromJson(snapshot.data()!, snapshot.id),
|
||||||
|
toFirestore: (chat, _) => chat.toJson(),
|
||||||
|
)
|
||||||
|
.get();
|
||||||
|
|
||||||
for (var userId in userIds) {
|
for (var userId in userIds) {
|
||||||
await _db
|
await _db
|
||||||
.collection(_options.usersCollectionName)
|
.collection(_options.usersCollectionName)
|
||||||
.doc(userId)
|
.doc(userId)
|
||||||
.collection(_options.groupChatsCollectionName)
|
.collection(_options.groupChatsCollectionName)
|
||||||
.doc(reference.id)
|
.doc(currentChat.id)
|
||||||
.set({"users": userIds}, SetOptions(merge: true));
|
.set({"users": userIds}, SetOptions(merge: true));
|
||||||
}
|
}
|
||||||
chat.id = reference.id;
|
chat.id = reference.id;
|
||||||
|
currentlySelectedUsers.clear();
|
||||||
} else {
|
} else {
|
||||||
throw Exception("Chat type not supported for firebase");
|
throw Exception("Chat type not supported for firebase");
|
||||||
}
|
}
|
||||||
|
@ -478,4 +504,34 @@ class FirebaseChatOverviewService implements ChatOverviewService {
|
||||||
.doc(chat.id)
|
.doc(chat.id)
|
||||||
.set({"amount_unread_messages": 0}, SetOptions(merge: true));
|
.set({"amount_unread_messages": 0}, SetOptions(merge: true));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<ChatUserModel> get currentlySelectedUsers => _currentlySelectedUsers;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void addCurrentlySelectedUser(ChatUserModel user) {
|
||||||
|
_currentlySelectedUsers.add(user);
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void removeCurrentlySelectedUser(ChatUserModel user) {
|
||||||
|
_currentlySelectedUsers.remove(user);
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<String> uploadGroupChatImage(Uint8List image, String chatId) async {
|
||||||
|
await _storage.ref("groupchatImages/$chatId").putData(image);
|
||||||
|
var imageUrl =
|
||||||
|
await _storage.ref("groupchatImages/$chatId").getDownloadURL();
|
||||||
|
|
||||||
|
return imageUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void clearCurrentlySelectedUsers() {
|
||||||
|
_currentlySelectedUsers.clear();
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
|
|
||||||
name: flutter_chat_firebase
|
name: flutter_chat_firebase
|
||||||
description: A new Flutter package project.
|
description: A new Flutter package project.
|
||||||
version: 3.0.1
|
version: 3.1.0
|
||||||
|
|
||||||
publish_to: https://forgejo.internal.iconica.nl/api/packages/internal/pub
|
publish_to: https://forgejo.internal.iconica.nl/api/packages/internal/pub
|
||||||
|
|
||||||
|
|
|
@ -16,8 +16,9 @@ abstract class GroupChatModelInterface extends ChatModel {
|
||||||
});
|
});
|
||||||
|
|
||||||
String get title;
|
String get title;
|
||||||
String get imageUrl;
|
String? get imageUrl;
|
||||||
List<ChatUserModel> get users;
|
List<ChatUserModel> get users;
|
||||||
|
String? get bio;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
GroupChatModelInterface copyWith({
|
GroupChatModelInterface copyWith({
|
||||||
|
@ -30,6 +31,7 @@ abstract class GroupChatModelInterface extends ChatModel {
|
||||||
String? imageUrl,
|
String? imageUrl,
|
||||||
List<ChatUserModel>? users,
|
List<ChatUserModel>? users,
|
||||||
bool? canBeDeleted,
|
bool? canBeDeleted,
|
||||||
|
String? bio,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -56,13 +58,14 @@ class GroupChatModel implements GroupChatModelInterface {
|
||||||
GroupChatModel({
|
GroupChatModel({
|
||||||
required this.canBeDeleted,
|
required this.canBeDeleted,
|
||||||
required this.title,
|
required this.title,
|
||||||
required this.imageUrl,
|
|
||||||
required this.users,
|
required this.users,
|
||||||
|
this.imageUrl,
|
||||||
this.id,
|
this.id,
|
||||||
this.messages,
|
this.messages,
|
||||||
this.unreadMessages,
|
this.unreadMessages,
|
||||||
this.lastUsed,
|
this.lastUsed,
|
||||||
this.lastMessage,
|
this.lastMessage,
|
||||||
|
this.bio,
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -80,9 +83,11 @@ class GroupChatModel implements GroupChatModelInterface {
|
||||||
@override
|
@override
|
||||||
final String title;
|
final String title;
|
||||||
@override
|
@override
|
||||||
final String imageUrl;
|
final String? imageUrl;
|
||||||
@override
|
@override
|
||||||
final List<ChatUserModel> users;
|
final List<ChatUserModel> users;
|
||||||
|
@override
|
||||||
|
final String? bio;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
GroupChatModel copyWith({
|
GroupChatModel copyWith({
|
||||||
|
@ -95,6 +100,7 @@ class GroupChatModel implements GroupChatModelInterface {
|
||||||
String? title,
|
String? title,
|
||||||
String? imageUrl,
|
String? imageUrl,
|
||||||
List<ChatUserModel>? users,
|
List<ChatUserModel>? users,
|
||||||
|
String? bio,
|
||||||
}) =>
|
}) =>
|
||||||
GroupChatModel(
|
GroupChatModel(
|
||||||
id: id ?? this.id,
|
id: id ?? this.id,
|
||||||
|
@ -106,5 +112,6 @@ class GroupChatModel implements GroupChatModelInterface {
|
||||||
title: title ?? this.title,
|
title: title ?? this.title,
|
||||||
imageUrl: imageUrl ?? this.imageUrl,
|
imageUrl: imageUrl ?? this.imageUrl,
|
||||||
users: users ?? this.users,
|
users: users ?? this.users,
|
||||||
|
bio: bio ?? this.bio,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
|
import "dart:typed_data";
|
||||||
|
|
||||||
|
import "package:flutter/material.dart";
|
||||||
import "package:flutter_chat_interface/flutter_chat_interface.dart";
|
import "package:flutter_chat_interface/flutter_chat_interface.dart";
|
||||||
|
|
||||||
abstract class ChatOverviewService {
|
abstract class ChatOverviewService extends ChangeNotifier {
|
||||||
/// Retrieves a stream of chats.
|
/// Retrieves a stream of chats.
|
||||||
/// This stream is updated whenever a new chat is created.
|
/// This stream is updated whenever a new chat is created.
|
||||||
Stream<List<ChatModel>> getChatsStream();
|
Stream<List<ChatModel>> getChatsStream();
|
||||||
|
@ -18,8 +21,22 @@ abstract class ChatOverviewService {
|
||||||
Future<void> readChat(ChatModel chat);
|
Future<void> readChat(ChatModel chat);
|
||||||
|
|
||||||
/// Creates the chat if it does not exist.
|
/// Creates the chat if it does not exist.
|
||||||
Future<ChatModel> storeChatIfNot(ChatModel chat);
|
Future<ChatModel> storeChatIfNot(ChatModel chat, Uint8List? image);
|
||||||
|
|
||||||
/// Retrieves the number of unread chats.
|
/// Retrieves the number of unread chats.
|
||||||
Stream<int> getUnreadChatsCountStream();
|
Stream<int> getUnreadChatsCountStream();
|
||||||
|
|
||||||
|
/// Retrieves the currently selected users to be added to a new groupchat.
|
||||||
|
List<ChatUserModel> get currentlySelectedUsers;
|
||||||
|
|
||||||
|
/// Adds a user to the currently selected users.
|
||||||
|
void addCurrentlySelectedUser(ChatUserModel user);
|
||||||
|
|
||||||
|
/// Deletes a user from the currently selected users.
|
||||||
|
void removeCurrentlySelectedUser(ChatUserModel user);
|
||||||
|
|
||||||
|
void clearCurrentlySelectedUsers();
|
||||||
|
|
||||||
|
/// uploads an image for a group chat.
|
||||||
|
Future<String> uploadGroupChatImage(Uint8List image, String chatId);
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
|
|
||||||
name: flutter_chat_interface
|
name: flutter_chat_interface
|
||||||
description: A new Flutter package project.
|
description: A new Flutter package project.
|
||||||
version: 3.0.1
|
version: 3.1.0
|
||||||
publish_to: https://forgejo.internal.iconica.nl/api/packages/internal/pub
|
publish_to: https://forgejo.internal.iconica.nl/api/packages/internal/pub
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
|
|
|
@ -7,6 +7,9 @@ import "package:flutter_chat_interface/flutter_chat_interface.dart";
|
||||||
class LocalChatOverviewService
|
class LocalChatOverviewService
|
||||||
with ChangeNotifier
|
with ChangeNotifier
|
||||||
implements ChatOverviewService {
|
implements ChatOverviewService {
|
||||||
|
/// The list of currently selected users.
|
||||||
|
final List<ChatUserModel> _currentlySelectedUsers = [];
|
||||||
|
|
||||||
/// The list of personal chat models.
|
/// The list of personal chat models.
|
||||||
final List<ChatModel> _chats = [];
|
final List<ChatModel> _chats = [];
|
||||||
|
|
||||||
|
@ -22,7 +25,6 @@ class LocalChatOverviewService
|
||||||
_chats[index] = chat;
|
_chats[index] = chat;
|
||||||
_chatsController.addStream(Stream.value(_chats));
|
_chatsController.addStream(Stream.value(_chats));
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
debugPrint("Chat updated: $chat");
|
|
||||||
return Future.value();
|
return Future.value();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -31,15 +33,12 @@ class LocalChatOverviewService
|
||||||
_chats.removeWhere((element) => element.id == chat.id);
|
_chats.removeWhere((element) => element.id == chat.id);
|
||||||
_chatsController.add(_chats);
|
_chatsController.add(_chats);
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
debugPrint("Chat deleted: $chat");
|
|
||||||
return Future.value();
|
return Future.value();
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<ChatModel> getChatById(String id) {
|
Future<ChatModel> getChatById(String id) {
|
||||||
var chat = _chats.firstWhere((element) => element.id == id);
|
var chat = _chats.firstWhere((element) => element.id == id);
|
||||||
debugPrint("Retrieved chat by ID: $chat");
|
|
||||||
debugPrint("Messages are: ${chat.messages?.length}");
|
|
||||||
return Future.value(chat);
|
return Future.value(chat);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -59,7 +58,6 @@ class LocalChatOverviewService
|
||||||
);
|
);
|
||||||
chat.id = chat.hashCode.toString();
|
chat.id = chat.hashCode.toString();
|
||||||
_chats.add(chat);
|
_chats.add(chat);
|
||||||
debugPrint("New chat created: $chat");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_chatsController.add([..._chats]);
|
_chatsController.add([..._chats]);
|
||||||
|
@ -77,19 +75,42 @@ class LocalChatOverviewService
|
||||||
Future<void> readChat(ChatModel chat) async => Future.value();
|
Future<void> readChat(ChatModel chat) async => Future.value();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<ChatModel> storeChatIfNot(ChatModel chat) {
|
Future<ChatModel> storeChatIfNot(ChatModel chat, Uint8List? image) {
|
||||||
var chatExists = _chats.any((element) => element.id == chat.id);
|
var chatExists = _chats.any((element) => element.id == chat.id);
|
||||||
|
|
||||||
if (!chatExists) {
|
if (!chatExists) {
|
||||||
chat.id = chat.hashCode.toString();
|
chat.id = chat.hashCode.toString();
|
||||||
_chats.add(chat);
|
_chats.add(chat);
|
||||||
_chatsController.add([..._chats]);
|
_chatsController.add([..._chats]);
|
||||||
|
currentlySelectedUsers.clear();
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
debugPrint("Chat stored: $chat");
|
|
||||||
} else {
|
|
||||||
debugPrint("Chat already exists: $chat");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return Future.value(chat);
|
return Future.value(chat);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<ChatUserModel> get currentlySelectedUsers => _currentlySelectedUsers;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void addCurrentlySelectedUser(ChatUserModel user) {
|
||||||
|
_currentlySelectedUsers.add(user);
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void removeCurrentlySelectedUser(ChatUserModel user) {
|
||||||
|
_currentlySelectedUsers.remove(user);
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<String> uploadGroupChatImage(Uint8List image, String chatId) =>
|
||||||
|
Future.value("https://picsum.photos/200/300");
|
||||||
|
|
||||||
|
@override
|
||||||
|
void clearCurrentlySelectedUsers() {
|
||||||
|
_currentlySelectedUsers.clear();
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,7 +30,7 @@ class LocalChatUserService implements ChatUserService {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<ChatUserModel?> getCurrentUser() =>
|
Future<ChatUserModel?> getCurrentUser() =>
|
||||||
Future.value(const ChatUserModel());
|
Future.value(users.where((element) => element.id == "3").first);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<ChatUserModel?> getUser(String id) {
|
Future<ChatUserModel?> getUser(String id) {
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
name: flutter_chat_local
|
name: flutter_chat_local
|
||||||
description: "A new Flutter package project."
|
description: "A new Flutter package project."
|
||||||
version: 3.0.1
|
version: 3.1.0
|
||||||
|
|
||||||
publish_to: https://forgejo.internal.iconica.nl/api/packages/internal/pub
|
publish_to: https://forgejo.internal.iconica.nl/api/packages/internal/pub
|
||||||
|
|
||||||
|
|
|
@ -103,11 +103,7 @@ class _ChatDetailRowState extends State<ChatDetailRow> {
|
||||||
Text(
|
Text(
|
||||||
widget.message.sender.fullName ??
|
widget.message.sender.fullName ??
|
||||||
widget.translations.anonymousUser,
|
widget.translations.anonymousUser,
|
||||||
style: TextStyle(
|
style: theme.textTheme.titleMedium,
|
||||||
fontSize: 16,
|
|
||||||
fontWeight: FontWeight.w800,
|
|
||||||
color: theme.textTheme.labelMedium?.color,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Padding(
|
Padding(
|
||||||
|
@ -115,13 +111,9 @@ class _ChatDetailRowState extends State<ChatDetailRow> {
|
||||||
child: Text(
|
child: Text(
|
||||||
_dateFormatter.format(
|
_dateFormatter.format(
|
||||||
date: widget.message.timestamp,
|
date: widget.message.timestamp,
|
||||||
showFullDate: true,
|
showFullDate: false,
|
||||||
),
|
|
||||||
style: const TextStyle(
|
|
||||||
fontSize: 16,
|
|
||||||
fontWeight: FontWeight.w300,
|
|
||||||
color: Color(0xFFBBBBBB),
|
|
||||||
),
|
),
|
||||||
|
style: theme.textTheme.labelSmall,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
@ -137,10 +129,7 @@ class _ChatDetailRowState extends State<ChatDetailRow> {
|
||||||
Flexible(
|
Flexible(
|
||||||
child: Text(
|
child: Text(
|
||||||
(widget.message as ChatTextMessageModel).text,
|
(widget.message as ChatTextMessageModel).text,
|
||||||
style: TextStyle(
|
style: theme.textTheme.bodySmall,
|
||||||
fontSize: 16,
|
|
||||||
color: theme.textTheme.labelMedium?.color,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
if (widget.showTime &&
|
if (widget.showTime &&
|
||||||
|
@ -155,7 +144,7 @@ class _ChatDetailRowState extends State<ChatDetailRow> {
|
||||||
)
|
)
|
||||||
.split(" ")
|
.split(" ")
|
||||||
.last,
|
.last,
|
||||||
style: theme.textTheme.bodySmall,
|
style: theme.textTheme.labelSmall,
|
||||||
textAlign: TextAlign.end,
|
textAlign: TextAlign.end,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|
|
@ -49,9 +49,7 @@ class ChatRow extends StatelessWidget {
|
||||||
title,
|
title,
|
||||||
maxLines: 1,
|
maxLines: 1,
|
||||||
overflow: TextOverflow.ellipsis,
|
overflow: TextOverflow.ellipsis,
|
||||||
style: unreadMessages > 0
|
style: theme.textTheme.titleMedium,
|
||||||
? theme.textTheme.bodyLarge
|
|
||||||
: theme.textTheme.bodyMedium,
|
|
||||||
),
|
),
|
||||||
if (subTitle != null) ...[
|
if (subTitle != null) ...[
|
||||||
Padding(
|
Padding(
|
||||||
|
@ -59,8 +57,10 @@ class ChatRow extends StatelessWidget {
|
||||||
child: Text(
|
child: Text(
|
||||||
subTitle!,
|
subTitle!,
|
||||||
style: unreadMessages > 0
|
style: unreadMessages > 0
|
||||||
? theme.textTheme.bodyLarge
|
? theme.textTheme.bodySmall!.copyWith(
|
||||||
: theme.textTheme.bodyMedium,
|
fontWeight: FontWeight.w800,
|
||||||
|
)
|
||||||
|
: theme.textTheme.bodySmall,
|
||||||
overflow: TextOverflow.ellipsis,
|
overflow: TextOverflow.ellipsis,
|
||||||
maxLines: 2,
|
maxLines: 2,
|
||||||
),
|
),
|
||||||
|
@ -79,10 +79,7 @@ class ChatRow extends StatelessWidget {
|
||||||
padding: const EdgeInsets.only(bottom: 4.0),
|
padding: const EdgeInsets.only(bottom: 4.0),
|
||||||
child: Text(
|
child: Text(
|
||||||
lastUsed!,
|
lastUsed!,
|
||||||
style: const TextStyle(
|
style: theme.textTheme.labelSmall,
|
||||||
color: Color(0xFFBBBBBB),
|
|
||||||
fontSize: 14,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|
|
@ -0,0 +1,33 @@
|
||||||
|
import "dart:typed_data";
|
||||||
|
|
||||||
|
import "package:flutter/material.dart";
|
||||||
|
import "package:flutter_chat_view/flutter_chat_view.dart";
|
||||||
|
import "package:flutter_chat_view/src/components/image_loading_snackbar.dart";
|
||||||
|
|
||||||
|
Future<void> onPressSelectImage(
|
||||||
|
BuildContext context,
|
||||||
|
ChatTranslations translations,
|
||||||
|
ChatOptions options,
|
||||||
|
Function(Uint8List image) onUploadImage,
|
||||||
|
) async =>
|
||||||
|
showModalBottomSheet<Uint8List?>(
|
||||||
|
context: context,
|
||||||
|
builder: (BuildContext context) => options.imagePickerContainerBuilder(
|
||||||
|
() => Navigator.of(context).pop(),
|
||||||
|
translations,
|
||||||
|
context,
|
||||||
|
),
|
||||||
|
).then(
|
||||||
|
(image) async {
|
||||||
|
if (image == null) return;
|
||||||
|
var messenger = ScaffoldMessenger.of(context)
|
||||||
|
..showSnackBar(
|
||||||
|
getImageLoadingSnackbar(translations),
|
||||||
|
)
|
||||||
|
..activate();
|
||||||
|
await onUploadImage(image);
|
||||||
|
Future.delayed(const Duration(seconds: 1), () {
|
||||||
|
messenger.hideCurrentSnackBar();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
);
|
|
@ -4,7 +4,6 @@
|
||||||
|
|
||||||
import "package:flutter/material.dart";
|
import "package:flutter/material.dart";
|
||||||
import "package:flutter_chat_view/flutter_chat_view.dart";
|
import "package:flutter_chat_view/flutter_chat_view.dart";
|
||||||
import "package:flutter_chat_view/src/components/chat_image.dart";
|
|
||||||
import "package:flutter_image_picker/flutter_image_picker.dart";
|
import "package:flutter_image_picker/flutter_image_picker.dart";
|
||||||
import "package:flutter_profile/flutter_profile.dart";
|
import "package:flutter_profile/flutter_profile.dart";
|
||||||
|
|
||||||
|
@ -57,15 +56,16 @@ Widget _createNewChatButton(
|
||||||
BuildContext context,
|
BuildContext context,
|
||||||
VoidCallback onPressed,
|
VoidCallback onPressed,
|
||||||
ChatTranslations translations,
|
ChatTranslations translations,
|
||||||
) =>
|
) {
|
||||||
Padding(
|
var theme = Theme.of(context);
|
||||||
|
return Padding(
|
||||||
padding: const EdgeInsets.symmetric(
|
padding: const EdgeInsets.symmetric(
|
||||||
vertical: 24,
|
vertical: 24,
|
||||||
horizontal: 5,
|
horizontal: 4,
|
||||||
),
|
),
|
||||||
child: ElevatedButton(
|
child: ElevatedButton(
|
||||||
style: ElevatedButton.styleFrom(
|
style: ElevatedButton.styleFrom(
|
||||||
backgroundColor: Theme.of(context).primaryColor,
|
backgroundColor: theme.colorScheme.primary,
|
||||||
fixedSize: const Size(254, 44),
|
fixedSize: const Size(254, 44),
|
||||||
shape: RoundedRectangleBorder(
|
shape: RoundedRectangleBorder(
|
||||||
borderRadius: BorderRadius.circular(56),
|
borderRadius: BorderRadius.circular(56),
|
||||||
|
@ -74,14 +74,11 @@ Widget _createNewChatButton(
|
||||||
onPressed: onPressed,
|
onPressed: onPressed,
|
||||||
child: Text(
|
child: Text(
|
||||||
translations.newChatButton,
|
translations.newChatButton,
|
||||||
style: const TextStyle(
|
style: theme.textTheme.displayLarge,
|
||||||
color: Colors.white,
|
|
||||||
fontWeight: FontWeight.w800,
|
|
||||||
fontSize: 20,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
|
||||||
Widget _createMessageInput(
|
Widget _createMessageInput(
|
||||||
TextEditingController textEditingController,
|
TextEditingController textEditingController,
|
||||||
|
@ -91,6 +88,7 @@ Widget _createMessageInput(
|
||||||
) {
|
) {
|
||||||
var theme = Theme.of(context);
|
var theme = Theme.of(context);
|
||||||
return TextField(
|
return TextField(
|
||||||
|
style: theme.textTheme.bodySmall,
|
||||||
textCapitalization: TextCapitalization.sentences,
|
textCapitalization: TextCapitalization.sentences,
|
||||||
controller: textEditingController,
|
controller: textEditingController,
|
||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
|
@ -111,7 +109,9 @@ Widget _createMessageInput(
|
||||||
horizontal: 30,
|
horizontal: 30,
|
||||||
),
|
),
|
||||||
hintText: translations.messagePlaceholder,
|
hintText: translations.messagePlaceholder,
|
||||||
hintStyle: theme.inputDecorationTheme.hintStyle,
|
hintStyle: theme.textTheme.bodyMedium!.copyWith(
|
||||||
|
color: theme.textTheme.bodyMedium!.color!.withOpacity(0.5),
|
||||||
|
),
|
||||||
fillColor: Colors.white,
|
fillColor: Colors.white,
|
||||||
filled: true,
|
filled: true,
|
||||||
border: const OutlineInputBorder(
|
border: const OutlineInputBorder(
|
||||||
|
@ -127,24 +127,33 @@ Widget _createMessageInput(
|
||||||
|
|
||||||
Widget _createChatRowContainer(
|
Widget _createChatRowContainer(
|
||||||
Widget chatRow,
|
Widget chatRow,
|
||||||
) =>
|
BuildContext context,
|
||||||
Padding(
|
) {
|
||||||
padding: const EdgeInsets.symmetric(
|
var theme = Theme.of(context);
|
||||||
vertical: 12.0,
|
return DecoratedBox(
|
||||||
horizontal: 10.0,
|
decoration: BoxDecoration(
|
||||||
),
|
|
||||||
child: ColoredBox(
|
|
||||||
color: Colors.transparent,
|
color: Colors.transparent,
|
||||||
|
border: Border(
|
||||||
|
bottom: BorderSide(
|
||||||
|
color: theme.dividerColor,
|
||||||
|
width: 0.5,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(12),
|
||||||
child: chatRow,
|
child: chatRow,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
|
||||||
Widget _createImagePickerContainer(
|
Widget _createImagePickerContainer(
|
||||||
VoidCallback onClose,
|
VoidCallback onClose,
|
||||||
ChatTranslations translations,
|
ChatTranslations translations,
|
||||||
BuildContext context,
|
BuildContext context,
|
||||||
) =>
|
) {
|
||||||
Container(
|
var theme = Theme.of(context);
|
||||||
|
return Container(
|
||||||
padding: const EdgeInsets.all(8.0),
|
padding: const EdgeInsets.all(8.0),
|
||||||
color: Colors.white,
|
color: Colors.white,
|
||||||
child: ImagePicker(
|
child: ImagePicker(
|
||||||
|
@ -155,19 +164,23 @@ Widget _createImagePickerContainer(
|
||||||
iconSize: 60.0,
|
iconSize: 60.0,
|
||||||
makePhotoText: translations.takePicture,
|
makePhotoText: translations.takePicture,
|
||||||
selectImageText: translations.uploadFile,
|
selectImageText: translations.uploadFile,
|
||||||
|
selectImageIcon: const Icon(
|
||||||
|
Icons.insert_drive_file_rounded,
|
||||||
|
size: 60,
|
||||||
),
|
),
|
||||||
customButton: ElevatedButton(
|
|
||||||
style: ElevatedButton.styleFrom(
|
|
||||||
backgroundColor: Theme.of(context).primaryColor,
|
|
||||||
),
|
),
|
||||||
|
customButton: TextButton(
|
||||||
onPressed: onClose,
|
onPressed: onClose,
|
||||||
child: Text(
|
child: Text(
|
||||||
translations.cancelImagePickerBtn,
|
translations.cancelImagePickerBtn,
|
||||||
style: const TextStyle(color: Colors.white),
|
style: theme.textTheme.bodyMedium!.copyWith(
|
||||||
|
decoration: TextDecoration.underline,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
|
||||||
Scaffold _createScaffold(
|
Scaffold _createScaffold(
|
||||||
AppBar appbar,
|
AppBar appbar,
|
||||||
|
@ -185,20 +198,27 @@ Widget _createUserAvatar(
|
||||||
double size,
|
double size,
|
||||||
) =>
|
) =>
|
||||||
Avatar(
|
Avatar(
|
||||||
|
boxfit: BoxFit.cover,
|
||||||
user: User(
|
user: User(
|
||||||
firstName: user.firstName,
|
firstName: user.firstName,
|
||||||
lastName: user.lastName,
|
lastName: user.lastName,
|
||||||
imageUrl: user.imageUrl,
|
imageUrl: user.imageUrl != "" ? user.imageUrl : null,
|
||||||
),
|
),
|
||||||
size: size,
|
size: size,
|
||||||
);
|
);
|
||||||
|
|
||||||
Widget _createGroupAvatar(
|
Widget _createGroupAvatar(
|
||||||
String groupName,
|
String groupName,
|
||||||
String imageUrl,
|
String? imageUrl,
|
||||||
double size,
|
double size,
|
||||||
) =>
|
) =>
|
||||||
ChatImage(
|
Avatar(
|
||||||
image: imageUrl,
|
boxfit: BoxFit.cover,
|
||||||
|
user: User(
|
||||||
|
firstName: groupName,
|
||||||
|
lastName: null,
|
||||||
|
imageUrl: imageUrl != "" ? imageUrl : null,
|
||||||
|
),
|
||||||
size: size,
|
size: size,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -249,6 +269,7 @@ typedef TextInputBuilder = Widget Function(
|
||||||
|
|
||||||
typedef ContainerBuilder = Widget Function(
|
typedef ContainerBuilder = Widget Function(
|
||||||
Widget child,
|
Widget child,
|
||||||
|
BuildContext context,
|
||||||
);
|
);
|
||||||
|
|
||||||
typedef ImagePickerContainerBuilder = Widget Function(
|
typedef ImagePickerContainerBuilder = Widget Function(
|
||||||
|
@ -270,7 +291,7 @@ typedef UserAvatarBuilder = Widget Function(
|
||||||
|
|
||||||
typedef GroupAvatarBuilder = Widget Function(
|
typedef GroupAvatarBuilder = Widget Function(
|
||||||
String groupName,
|
String groupName,
|
||||||
String imageUrl,
|
String? imageUrl,
|
||||||
double size,
|
double size,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -15,7 +15,6 @@ class ChatTranslations {
|
||||||
required this.newChatButton,
|
required this.newChatButton,
|
||||||
required this.newGroupChatButton,
|
required this.newGroupChatButton,
|
||||||
required this.newChatTitle,
|
required this.newChatTitle,
|
||||||
required this.deleteChatButton,
|
|
||||||
required this.image,
|
required this.image,
|
||||||
required this.searchPlaceholder,
|
required this.searchPlaceholder,
|
||||||
required this.startTyping,
|
required this.startTyping,
|
||||||
|
@ -40,6 +39,13 @@ class ChatTranslations {
|
||||||
required this.groupNameValidatorTooLong,
|
required this.groupNameValidatorTooLong,
|
||||||
required this.groupNameHintText,
|
required this.groupNameHintText,
|
||||||
required this.newGroupChatTitle,
|
required this.newGroupChatTitle,
|
||||||
|
required this.groupBioHintText,
|
||||||
|
required this.groupProfileBioHeader,
|
||||||
|
required this.groupBioValidatorEmpty,
|
||||||
|
required this.groupChatNameFieldHeader,
|
||||||
|
required this.groupBioFieldHeader,
|
||||||
|
required this.selectedMembersHeader,
|
||||||
|
required this.createGroupChatButton,
|
||||||
});
|
});
|
||||||
|
|
||||||
/// Default translations for the chat component view
|
/// Default translations for the chat component view
|
||||||
|
@ -47,7 +53,7 @@ class ChatTranslations {
|
||||||
this.chatsTitle = "Chats",
|
this.chatsTitle = "Chats",
|
||||||
this.chatsUnread = "unread",
|
this.chatsUnread = "unread",
|
||||||
this.newChatButton = "Start chat",
|
this.newChatButton = "Start chat",
|
||||||
this.newGroupChatButton = "Create a group chat",
|
this.newGroupChatButton = "Create a groupchat",
|
||||||
this.newChatTitle = "Start a chat",
|
this.newChatTitle = "Start a chat",
|
||||||
this.image = "Image",
|
this.image = "Image",
|
||||||
this.searchPlaceholder = "Search...",
|
this.searchPlaceholder = "Search...",
|
||||||
|
@ -58,25 +64,31 @@ class ChatTranslations {
|
||||||
this.writeFirstMessageInGroupChat =
|
this.writeFirstMessageInGroupChat =
|
||||||
"Write the first message in this group chat",
|
"Write the first message in this group chat",
|
||||||
this.imageUploading = "Image is uploading...",
|
this.imageUploading = "Image is uploading...",
|
||||||
this.deleteChatButton = "Delete",
|
|
||||||
this.deleteChatModalTitle = "Delete chat",
|
this.deleteChatModalTitle = "Delete chat",
|
||||||
this.deleteChatModalDescription =
|
this.deleteChatModalDescription =
|
||||||
"Are you sure you want to delete this chat?",
|
"Are you sure you want to delete this chat?",
|
||||||
this.deleteChatModalCancel = "Cancel",
|
this.deleteChatModalCancel = "Cancel",
|
||||||
this.deleteChatModalConfirm = "Delete",
|
this.deleteChatModalConfirm = "Confirm",
|
||||||
this.noUsersFound = "No users were found to start a chat with",
|
this.noUsersFound = "No users were found to start a chat with",
|
||||||
this.noChatsFound = "Click on 'Start a chat' to create a new chat",
|
this.noChatsFound = "Click on 'Start a chat' to create a new chat",
|
||||||
this.anonymousUser = "Anonymous user",
|
this.anonymousUser = "Anonymous user",
|
||||||
this.chatCantBeDeleted = "This chat can't be deleted",
|
this.chatCantBeDeleted = "This chat can't be deleted",
|
||||||
this.chatProfileUsers = "Users:",
|
this.chatProfileUsers = "Members:",
|
||||||
this.imagePickerTitle = "Do you want to upload a file or take a picture?",
|
this.imagePickerTitle = "Do you want to upload a file or take a picture?",
|
||||||
this.uploadFile = "UPLOAD FILE",
|
this.uploadFile = "UPLOAD FILE",
|
||||||
this.takePicture = "TAKE PICTURE",
|
this.takePicture = "TAKE PICTURE",
|
||||||
this.groupNameHintText = "Group chat name",
|
this.groupNameHintText = "Groupchat name",
|
||||||
this.groupNameValidatorEmpty = "Please enter a group chat name",
|
this.groupNameValidatorEmpty = "Please enter a group chat name",
|
||||||
this.groupNameValidatorTooLong =
|
this.groupNameValidatorTooLong =
|
||||||
"Group name is too long, max 15 characters",
|
"Group name is too long, max 15 characters",
|
||||||
this.newGroupChatTitle = "New Group Chat",
|
this.newGroupChatTitle = "start a groupchat",
|
||||||
|
this.groupBioHintText = "Bio",
|
||||||
|
this.groupProfileBioHeader = "Bio",
|
||||||
|
this.groupBioValidatorEmpty = "Please enter a bio",
|
||||||
|
this.groupChatNameFieldHeader = "Chat name",
|
||||||
|
this.groupBioFieldHeader = "Additional information for members",
|
||||||
|
this.selectedMembersHeader = "Members: ",
|
||||||
|
this.createGroupChatButton = "Create groupchat",
|
||||||
});
|
});
|
||||||
|
|
||||||
final String chatsTitle;
|
final String chatsTitle;
|
||||||
|
@ -84,7 +96,6 @@ class ChatTranslations {
|
||||||
final String newChatButton;
|
final String newChatButton;
|
||||||
final String newGroupChatButton;
|
final String newGroupChatButton;
|
||||||
final String newChatTitle;
|
final String newChatTitle;
|
||||||
final String deleteChatButton;
|
|
||||||
final String image;
|
final String image;
|
||||||
final String searchPlaceholder;
|
final String searchPlaceholder;
|
||||||
final String startTyping;
|
final String startTyping;
|
||||||
|
@ -104,6 +115,10 @@ class ChatTranslations {
|
||||||
final String imagePickerTitle;
|
final String imagePickerTitle;
|
||||||
final String uploadFile;
|
final String uploadFile;
|
||||||
final String takePicture;
|
final String takePicture;
|
||||||
|
final String groupChatNameFieldHeader;
|
||||||
|
final String groupBioFieldHeader;
|
||||||
|
final String selectedMembersHeader;
|
||||||
|
final String createGroupChatButton;
|
||||||
|
|
||||||
/// Shown when the user has no name
|
/// Shown when the user has no name
|
||||||
final String anonymousUser;
|
final String anonymousUser;
|
||||||
|
@ -111,6 +126,9 @@ class ChatTranslations {
|
||||||
final String groupNameValidatorTooLong;
|
final String groupNameValidatorTooLong;
|
||||||
final String groupNameHintText;
|
final String groupNameHintText;
|
||||||
final String newGroupChatTitle;
|
final String newGroupChatTitle;
|
||||||
|
final String groupBioHintText;
|
||||||
|
final String groupProfileBioHeader;
|
||||||
|
final String groupBioValidatorEmpty;
|
||||||
|
|
||||||
// copyWith method to override the default values
|
// copyWith method to override the default values
|
||||||
ChatTranslations copyWith({
|
ChatTranslations copyWith({
|
||||||
|
@ -119,7 +137,6 @@ class ChatTranslations {
|
||||||
String? newChatButton,
|
String? newChatButton,
|
||||||
String? newGroupChatButton,
|
String? newGroupChatButton,
|
||||||
String? newChatTitle,
|
String? newChatTitle,
|
||||||
String? deleteChatButton,
|
|
||||||
String? image,
|
String? image,
|
||||||
String? searchPlaceholder,
|
String? searchPlaceholder,
|
||||||
String? startTyping,
|
String? startTyping,
|
||||||
|
@ -144,6 +161,13 @@ class ChatTranslations {
|
||||||
String? groupNameValidatorTooLong,
|
String? groupNameValidatorTooLong,
|
||||||
String? groupNameHintText,
|
String? groupNameHintText,
|
||||||
String? newGroupChatTitle,
|
String? newGroupChatTitle,
|
||||||
|
String? groupBioHintText,
|
||||||
|
String? groupProfileBioHeader,
|
||||||
|
String? groupBioValidatorEmpty,
|
||||||
|
String? groupChatNameFieldHeader,
|
||||||
|
String? groupBioFieldHeader,
|
||||||
|
String? selectedMembersHeader,
|
||||||
|
String? createGroupChatButton,
|
||||||
}) =>
|
}) =>
|
||||||
ChatTranslations(
|
ChatTranslations(
|
||||||
chatsTitle: chatsTitle ?? this.chatsTitle,
|
chatsTitle: chatsTitle ?? this.chatsTitle,
|
||||||
|
@ -151,7 +175,6 @@ class ChatTranslations {
|
||||||
newChatButton: newChatButton ?? this.newChatButton,
|
newChatButton: newChatButton ?? this.newChatButton,
|
||||||
newGroupChatButton: newGroupChatButton ?? this.newGroupChatButton,
|
newGroupChatButton: newGroupChatButton ?? this.newGroupChatButton,
|
||||||
newChatTitle: newChatTitle ?? this.newChatTitle,
|
newChatTitle: newChatTitle ?? this.newChatTitle,
|
||||||
deleteChatButton: deleteChatButton ?? this.deleteChatButton,
|
|
||||||
image: image ?? this.image,
|
image: image ?? this.image,
|
||||||
searchPlaceholder: searchPlaceholder ?? this.searchPlaceholder,
|
searchPlaceholder: searchPlaceholder ?? this.searchPlaceholder,
|
||||||
startTyping: startTyping ?? this.startTyping,
|
startTyping: startTyping ?? this.startTyping,
|
||||||
|
@ -177,9 +200,23 @@ class ChatTranslations {
|
||||||
uploadFile: uploadFile ?? this.uploadFile,
|
uploadFile: uploadFile ?? this.uploadFile,
|
||||||
takePicture: takePicture ?? this.takePicture,
|
takePicture: takePicture ?? this.takePicture,
|
||||||
anonymousUser: anonymousUser ?? this.anonymousUser,
|
anonymousUser: anonymousUser ?? this.anonymousUser,
|
||||||
groupNameValidatorEmpty: this.groupNameValidatorEmpty,
|
groupNameValidatorEmpty:
|
||||||
groupNameValidatorTooLong: this.groupNameValidatorTooLong,
|
groupNameValidatorEmpty ?? this.groupNameValidatorEmpty,
|
||||||
groupNameHintText: this.groupNameHintText,
|
groupNameValidatorTooLong:
|
||||||
newGroupChatTitle: this.newGroupChatTitle,
|
groupNameValidatorTooLong ?? this.groupNameValidatorTooLong,
|
||||||
|
groupNameHintText: groupNameHintText ?? this.groupNameHintText,
|
||||||
|
newGroupChatTitle: newGroupChatTitle ?? this.newGroupChatTitle,
|
||||||
|
groupBioHintText: groupBioHintText ?? this.groupBioHintText,
|
||||||
|
groupProfileBioHeader:
|
||||||
|
groupProfileBioHeader ?? this.groupProfileBioHeader,
|
||||||
|
groupBioValidatorEmpty:
|
||||||
|
groupBioValidatorEmpty ?? this.groupBioValidatorEmpty,
|
||||||
|
groupChatNameFieldHeader:
|
||||||
|
groupChatNameFieldHeader ?? this.groupChatNameFieldHeader,
|
||||||
|
groupBioFieldHeader: groupBioFieldHeader ?? this.groupBioFieldHeader,
|
||||||
|
selectedMembersHeader:
|
||||||
|
selectedMembersHeader ?? this.selectedMembersHeader,
|
||||||
|
createGroupChatButton:
|
||||||
|
createGroupChatButton ?? this.createGroupChatButton,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,7 +9,7 @@ import "package:flutter/material.dart";
|
||||||
import "package:flutter_chat_view/flutter_chat_view.dart";
|
import "package:flutter_chat_view/flutter_chat_view.dart";
|
||||||
import "package:flutter_chat_view/src/components/chat_bottom.dart";
|
import "package:flutter_chat_view/src/components/chat_bottom.dart";
|
||||||
import "package:flutter_chat_view/src/components/chat_detail_row.dart";
|
import "package:flutter_chat_view/src/components/chat_detail_row.dart";
|
||||||
import "package:flutter_chat_view/src/components/image_loading_snackbar.dart";
|
import "package:flutter_chat_view/src/components/image_picker_popup.dart";
|
||||||
|
|
||||||
class ChatDetailScreen extends StatefulWidget {
|
class ChatDetailScreen extends StatefulWidget {
|
||||||
const ChatDetailScreen({
|
const ChatDetailScreen({
|
||||||
|
@ -139,29 +139,6 @@ class _ChatDetailScreenState extends State<ChatDetailScreen> {
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
var theme = Theme.of(context);
|
var theme = Theme.of(context);
|
||||||
|
|
||||||
Future<void> onPressSelectImage() async => showModalBottomSheet<Uint8List?>(
|
|
||||||
context: context,
|
|
||||||
builder: (BuildContext context) =>
|
|
||||||
widget.options.imagePickerContainerBuilder(
|
|
||||||
() => Navigator.of(context).pop(),
|
|
||||||
widget.translations,
|
|
||||||
context,
|
|
||||||
),
|
|
||||||
).then(
|
|
||||||
(image) async {
|
|
||||||
if (image == null) return;
|
|
||||||
var messenger = ScaffoldMessenger.of(context)
|
|
||||||
..showSnackBar(
|
|
||||||
getImageLoadingSnackbar(widget.translations),
|
|
||||||
)
|
|
||||||
..activate();
|
|
||||||
await widget.onUploadImage(image);
|
|
||||||
Future.delayed(const Duration(seconds: 1), () {
|
|
||||||
messenger.hideCurrentSnackBar();
|
|
||||||
});
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
return FutureBuilder<ChatModel>(
|
return FutureBuilder<ChatModel>(
|
||||||
// ignore: discarded_futures
|
// ignore: discarded_futures
|
||||||
future: widget.service.chatOverviewService.getChatById(widget.chatId),
|
future: widget.service.chatOverviewService.getChatById(widget.chatId),
|
||||||
|
@ -175,7 +152,6 @@ class _ChatDetailScreenState extends State<ChatDetailScreen> {
|
||||||
|
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
backgroundColor: theme.appBarTheme.backgroundColor,
|
|
||||||
iconTheme: theme.appBarTheme.iconTheme ??
|
iconTheme: theme.appBarTheme.iconTheme ??
|
||||||
const IconThemeData(color: Colors.white),
|
const IconThemeData(color: Colors.white),
|
||||||
centerTitle: true,
|
centerTitle: true,
|
||||||
|
@ -192,7 +168,7 @@ class _ChatDetailScreenState extends State<ChatDetailScreen> {
|
||||||
child: widget.chatTitleBuilder?.call(chatTitle) ??
|
child: widget.chatTitleBuilder?.call(chatTitle) ??
|
||||||
Text(
|
Text(
|
||||||
chatTitle,
|
chatTitle,
|
||||||
style: theme.appBarTheme.titleTextStyle,
|
style: theme.textTheme.headlineLarge,
|
||||||
overflow: TextOverflow.ellipsis,
|
overflow: TextOverflow.ellipsis,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -253,7 +229,12 @@ class _ChatDetailScreenState extends State<ChatDetailScreen> {
|
||||||
ChatBottom(
|
ChatBottom(
|
||||||
chat: chatModel,
|
chat: chatModel,
|
||||||
messageInputBuilder: widget.options.messageInputBuilder,
|
messageInputBuilder: widget.options.messageInputBuilder,
|
||||||
onPressSelectImage: onPressSelectImage,
|
onPressSelectImage: () async => onPressSelectImage.call(
|
||||||
|
context,
|
||||||
|
widget.translations,
|
||||||
|
widget.options,
|
||||||
|
widget.onUploadImage,
|
||||||
|
),
|
||||||
onMessageSubmit: widget.onMessageSubmit,
|
onMessageSubmit: widget.onMessageSubmit,
|
||||||
translations: widget.translations,
|
translations: widget.translations,
|
||||||
iconColor: widget.iconColor,
|
iconColor: widget.iconColor,
|
||||||
|
|
|
@ -8,6 +8,9 @@ class ChatProfileScreen extends StatefulWidget {
|
||||||
required this.chatId,
|
required this.chatId,
|
||||||
required this.translations,
|
required this.translations,
|
||||||
required this.onTapUser,
|
required this.onTapUser,
|
||||||
|
required this.options,
|
||||||
|
required this.onPressStartChat,
|
||||||
|
required this.currentUserId,
|
||||||
this.userId,
|
this.userId,
|
||||||
super.key,
|
super.key,
|
||||||
});
|
});
|
||||||
|
@ -27,6 +30,15 @@ class ChatProfileScreen extends StatefulWidget {
|
||||||
/// Callback function for tapping on a user.
|
/// Callback function for tapping on a user.
|
||||||
final Function(ChatUserModel user) onTapUser;
|
final Function(ChatUserModel user) onTapUser;
|
||||||
|
|
||||||
|
/// Chat options.
|
||||||
|
final ChatOptions options;
|
||||||
|
|
||||||
|
/// Callback function for starting a chat.
|
||||||
|
final Function(ChatUserModel user) onPressStartChat;
|
||||||
|
|
||||||
|
/// The current user.
|
||||||
|
final String currentUserId;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<ChatProfileScreen> createState() => _ProfileScreenState();
|
State<ChatProfileScreen> createState() => _ProfileScreenState();
|
||||||
}
|
}
|
||||||
|
@ -65,9 +77,9 @@ class _ProfileScreenState extends State<ChatProfileScreen> {
|
||||||
imageUrl: data.imageUrl,
|
imageUrl: data.imageUrl,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
backgroundColor: theme.appBarTheme.backgroundColor,
|
|
||||||
iconTheme: theme.appBarTheme.iconTheme ??
|
iconTheme: theme.appBarTheme.iconTheme ??
|
||||||
const IconThemeData(color: Colors.white),
|
const IconThemeData(color: Colors.white),
|
||||||
title: Text(
|
title: Text(
|
||||||
|
@ -78,16 +90,27 @@ class _ProfileScreenState extends State<ChatProfileScreen> {
|
||||||
: (data is GroupChatModel)
|
: (data is GroupChatModel)
|
||||||
? data.title
|
? data.title
|
||||||
: "",
|
: "",
|
||||||
style: theme.appBarTheme.titleTextStyle,
|
style: theme.textTheme.headlineLarge,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
body: snapshot.hasData
|
body: snapshot.hasData
|
||||||
? ListView(
|
? Stack(
|
||||||
|
children: [
|
||||||
|
ListView(
|
||||||
children: [
|
children: [
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.symmetric(vertical: 20),
|
padding: const EdgeInsets.symmetric(vertical: 20),
|
||||||
child: Avatar(
|
child: Column(
|
||||||
user: user,
|
children: [
|
||||||
|
widget.options.userAvatarBuilder(
|
||||||
|
ChatUserModel(
|
||||||
|
firstName: user!.firstName,
|
||||||
|
lastName: user.lastName,
|
||||||
|
imageUrl: user.imageUrl,
|
||||||
|
),
|
||||||
|
60,
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const Divider(
|
const Divider(
|
||||||
|
@ -97,44 +120,93 @@ class _ProfileScreenState extends State<ChatProfileScreen> {
|
||||||
if (data is GroupChatModel) ...[
|
if (data is GroupChatModel) ...[
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.symmetric(
|
padding: const EdgeInsets.symmetric(
|
||||||
horizontal: 100,
|
vertical: 24,
|
||||||
vertical: 20,
|
horizontal: 20,
|
||||||
),
|
),
|
||||||
child: Text(
|
|
||||||
widget.translations.chatProfileUsers,
|
|
||||||
style: const TextStyle(
|
|
||||||
fontSize: 18,
|
|
||||||
fontWeight: FontWeight.bold,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
...data.users.map((e) {
|
|
||||||
var user = User(
|
|
||||||
firstName: e.firstName ?? "",
|
|
||||||
lastName: e.lastName ?? "",
|
|
||||||
imageUrl: e.imageUrl,
|
|
||||||
);
|
|
||||||
return Padding(
|
|
||||||
padding: const EdgeInsets.symmetric(vertical: 4.0),
|
|
||||||
child: GestureDetector(
|
|
||||||
onTap: () {
|
|
||||||
widget.onTapUser.call(e);
|
|
||||||
},
|
|
||||||
child: Column(
|
child: Column(
|
||||||
mainAxisAlignment: MainAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
children: [
|
children: [
|
||||||
Avatar(
|
Text(
|
||||||
user: user,
|
widget.translations.groupProfileBioHeader,
|
||||||
|
style: theme.textTheme.titleMedium,
|
||||||
|
),
|
||||||
|
const SizedBox(
|
||||||
|
height: 12,
|
||||||
),
|
),
|
||||||
Text(
|
Text(
|
||||||
user.firstName!,
|
data.bio ?? "",
|
||||||
|
style: theme.textTheme.bodyMedium!
|
||||||
|
.copyWith(color: Colors.black),
|
||||||
|
),
|
||||||
|
const SizedBox(
|
||||||
|
height: 12,
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
widget.translations.chatProfileUsers,
|
||||||
|
style: theme.textTheme.titleMedium,
|
||||||
|
),
|
||||||
|
const SizedBox(
|
||||||
|
height: 12,
|
||||||
|
),
|
||||||
|
Wrap(
|
||||||
|
children: [
|
||||||
|
...data.users.map(
|
||||||
|
(user) => Padding(
|
||||||
|
padding: const EdgeInsets.only(
|
||||||
|
bottom: 8,
|
||||||
|
right: 8,
|
||||||
|
),
|
||||||
|
child: GestureDetector(
|
||||||
|
onTap: () {
|
||||||
|
widget.onTapUser.call(user);
|
||||||
|
},
|
||||||
|
child: Column(
|
||||||
|
mainAxisAlignment:
|
||||||
|
MainAxisAlignment.start,
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
widget.options.userAvatarBuilder(
|
||||||
|
user,
|
||||||
|
44,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
),
|
||||||
}),
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
],
|
||||||
|
),
|
||||||
|
if (data is ChatUserModel &&
|
||||||
|
widget.currentUserId != data.id) ...[
|
||||||
|
Align(
|
||||||
|
alignment: Alignment.bottomCenter,
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(
|
||||||
|
vertical: 24,
|
||||||
|
horizontal: 80,
|
||||||
|
),
|
||||||
|
child: FilledButton(
|
||||||
|
onPressed: () {
|
||||||
|
widget.onPressStartChat(data);
|
||||||
|
},
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
widget.translations.newChatButton,
|
||||||
|
style: theme.textTheme.displayLarge,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|
|
@ -77,10 +77,9 @@ class _ChatScreenState extends State<ChatScreen> {
|
||||||
var theme = Theme.of(context);
|
var theme = Theme.of(context);
|
||||||
return widget.options.scaffoldBuilder(
|
return widget.options.scaffoldBuilder(
|
||||||
AppBar(
|
AppBar(
|
||||||
backgroundColor: theme.appBarTheme.backgroundColor,
|
|
||||||
title: Text(
|
title: Text(
|
||||||
translations.chatsTitle,
|
translations.chatsTitle,
|
||||||
style: theme.appBarTheme.titleTextStyle,
|
style: theme.textTheme.headlineLarge,
|
||||||
),
|
),
|
||||||
centerTitle: true,
|
centerTitle: true,
|
||||||
actions: [
|
actions: [
|
||||||
|
@ -96,9 +95,8 @@ class _ChatScreenState extends State<ChatScreen> {
|
||||||
child: Text(
|
child: Text(
|
||||||
"${snapshot.data ?? 0} ${translations.chatsUnread}",
|
"${snapshot.data ?? 0} ${translations.chatsUnread}",
|
||||||
style: widget.unreadMessageTextStyle ??
|
style: widget.unreadMessageTextStyle ??
|
||||||
const TextStyle(
|
theme.textTheme.bodySmall!.copyWith(
|
||||||
color: Colors.white,
|
color: Colors.white,
|
||||||
fontSize: 14,
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -147,119 +145,23 @@ class _ChatScreenState extends State<ChatScreen> {
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
border: Border(
|
border: Border(
|
||||||
bottom: BorderSide(
|
bottom: BorderSide(
|
||||||
color: theme.colorScheme.secondary
|
color: theme.dividerColor,
|
||||||
.withOpacity(0.3),
|
|
||||||
width: 0.5,
|
width: 0.5,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
child: Builder(
|
child: Builder(
|
||||||
builder: (context) => !(widget
|
builder: (context) =>
|
||||||
.disableDismissForPermanentChats &&
|
!(widget.disableDismissForPermanentChats &&
|
||||||
!chat.canBeDeleted)
|
!chat.canBeDeleted)
|
||||||
? Dismissible(
|
? Dismissible(
|
||||||
confirmDismiss: (_) async =>
|
confirmDismiss: (_) async =>
|
||||||
widget.deleteChatDialog
|
widget.deleteChatDialog
|
||||||
?.call(context, chat) ??
|
?.call(context, chat) ??
|
||||||
showModalBottomSheet(
|
_deleteDialog(
|
||||||
context: context,
|
chat,
|
||||||
builder: (BuildContext context) =>
|
translations,
|
||||||
Container(
|
|
||||||
padding:
|
|
||||||
const EdgeInsets.all(16.0),
|
|
||||||
child: Column(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
crossAxisAlignment:
|
|
||||||
CrossAxisAlignment.stretch,
|
|
||||||
children: [
|
|
||||||
Text(
|
|
||||||
chat.canBeDeleted
|
|
||||||
? translations
|
|
||||||
.deleteChatModalTitle
|
|
||||||
: translations
|
|
||||||
.chatCantBeDeleted,
|
|
||||||
textAlign: TextAlign.center,
|
|
||||||
style: const TextStyle(
|
|
||||||
fontSize: 24,
|
|
||||||
fontWeight:
|
|
||||||
FontWeight.bold,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 24),
|
|
||||||
if (chat.canBeDeleted)
|
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets
|
|
||||||
.symmetric(
|
|
||||||
horizontal: 16,
|
|
||||||
),
|
|
||||||
child: Text(
|
|
||||||
translations
|
|
||||||
.deleteChatModalDescription,
|
|
||||||
textAlign:
|
|
||||||
TextAlign.center,
|
|
||||||
style: const TextStyle(
|
|
||||||
fontSize: 18,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(
|
|
||||||
height: 24,
|
|
||||||
),
|
|
||||||
Row(
|
|
||||||
mainAxisAlignment:
|
|
||||||
MainAxisAlignment
|
|
||||||
.center,
|
|
||||||
children: [
|
|
||||||
ElevatedButton(
|
|
||||||
onPressed: () =>
|
|
||||||
Navigator.of(
|
|
||||||
context,
|
context,
|
||||||
).pop(false),
|
|
||||||
child: Text(
|
|
||||||
translations
|
|
||||||
.deleteChatModalCancel,
|
|
||||||
style:
|
|
||||||
const TextStyle(
|
|
||||||
color: Colors.black,
|
|
||||||
fontSize: 18,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
if (chat.canBeDeleted)
|
|
||||||
const SizedBox(
|
|
||||||
width: 16,
|
|
||||||
),
|
|
||||||
if (chat.canBeDeleted)
|
|
||||||
ElevatedButton(
|
|
||||||
style: ElevatedButton
|
|
||||||
.styleFrom(
|
|
||||||
backgroundColor:
|
|
||||||
Theme.of(
|
|
||||||
context,
|
|
||||||
).primaryColor,
|
|
||||||
),
|
|
||||||
onPressed: () =>
|
|
||||||
Navigator.of(
|
|
||||||
context,
|
|
||||||
).pop(
|
|
||||||
true,
|
|
||||||
),
|
|
||||||
child: Text(
|
|
||||||
translations
|
|
||||||
.deleteChatModalConfirm,
|
|
||||||
style:
|
|
||||||
const TextStyle(
|
|
||||||
color:
|
|
||||||
Colors.white,
|
|
||||||
fontSize: 18,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
onDismissed: (_) {
|
onDismissed: (_) {
|
||||||
setState(() {
|
setState(() {
|
||||||
|
@ -267,14 +169,28 @@ class _ChatScreenState extends State<ChatScreen> {
|
||||||
});
|
});
|
||||||
widget.onDeleteChat(chat);
|
widget.onDeleteChat(chat);
|
||||||
},
|
},
|
||||||
background: ColoredBox(
|
secondaryBackground: const ColoredBox(
|
||||||
color: Colors.red,
|
color: Colors.red,
|
||||||
child: Align(
|
child: Align(
|
||||||
alignment: Alignment.centerRight,
|
alignment: Alignment.centerRight,
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.all(8.0),
|
padding: EdgeInsets.all(8.0),
|
||||||
child: Text(
|
child: Icon(
|
||||||
translations.deleteChatButton,
|
Icons.delete,
|
||||||
|
color: Colors.white,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
background: const ColoredBox(
|
||||||
|
color: Colors.red,
|
||||||
|
child: Align(
|
||||||
|
alignment: Alignment.centerLeft,
|
||||||
|
child: Padding(
|
||||||
|
padding: EdgeInsets.all(8.0),
|
||||||
|
child: Icon(
|
||||||
|
Icons.delete,
|
||||||
|
color: Colors.white,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -318,6 +234,79 @@ class _ChatScreenState extends State<ChatScreen> {
|
||||||
theme.colorScheme.surface,
|
theme.colorScheme.surface,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<bool?> _deleteDialog(
|
||||||
|
ChatModel chat,
|
||||||
|
ChatTranslations translations,
|
||||||
|
BuildContext context,
|
||||||
|
) async {
|
||||||
|
var theme = Theme.of(context);
|
||||||
|
var title = chat.canBeDeleted
|
||||||
|
? translations.deleteChatModalTitle
|
||||||
|
: translations.chatCantBeDeleted;
|
||||||
|
return showModalBottomSheet<bool>(
|
||||||
|
context: context,
|
||||||
|
builder: (BuildContext context) => Container(
|
||||||
|
padding: const EdgeInsets.all(20),
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
title,
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
style: theme.textTheme.titleMedium,
|
||||||
|
),
|
||||||
|
const SizedBox(
|
||||||
|
height: 20,
|
||||||
|
),
|
||||||
|
if (chat.canBeDeleted) ...[
|
||||||
|
Text(
|
||||||
|
translations.deleteChatModalDescription,
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
style: theme.textTheme.bodyMedium,
|
||||||
|
),
|
||||||
|
const SizedBox(
|
||||||
|
height: 20,
|
||||||
|
),
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 60),
|
||||||
|
child: FilledButton(
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.of(
|
||||||
|
context,
|
||||||
|
).pop(true);
|
||||||
|
},
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
widget.translations.deleteChatModalConfirm,
|
||||||
|
style: theme.textTheme.displayLarge,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
TextButton(
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.of(
|
||||||
|
context,
|
||||||
|
).pop(false);
|
||||||
|
},
|
||||||
|
child: Text(
|
||||||
|
widget.translations.deleteChatModalCancel,
|
||||||
|
style: theme.textTheme.bodyMedium!.copyWith(
|
||||||
|
color: theme.textTheme.bodyMedium!.color?.withOpacity(0.5),
|
||||||
|
decoration: TextDecoration.underline,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class ChatListItem extends StatelessWidget {
|
class ChatListItem extends StatelessWidget {
|
||||||
|
@ -381,6 +370,7 @@ class ChatListItem extends StatelessWidget {
|
||||||
)
|
)
|
||||||
: null,
|
: null,
|
||||||
),
|
),
|
||||||
|
context,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -50,7 +50,6 @@ class _NewChatScreenState extends State<NewChatScreen> {
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
iconTheme: theme.appBarTheme.iconTheme ??
|
iconTheme: theme.appBarTheme.iconTheme ??
|
||||||
const IconThemeData(color: Colors.white),
|
const IconThemeData(color: Colors.white),
|
||||||
backgroundColor: theme.appBarTheme.backgroundColor,
|
|
||||||
title: _buildSearchField(),
|
title: _buildSearchField(),
|
||||||
actions: [
|
actions: [
|
||||||
_buildSearchIcon(),
|
_buildSearchIcon(),
|
||||||
|
@ -58,47 +57,32 @@ class _NewChatScreenState extends State<NewChatScreen> {
|
||||||
),
|
),
|
||||||
body: Column(
|
body: Column(
|
||||||
children: [
|
children: [
|
||||||
if (widget.showGroupChatButton) ...[
|
if (widget.showGroupChatButton && !_isSearching) ...[
|
||||||
GestureDetector(
|
|
||||||
onTap: () async {
|
|
||||||
await widget.onPressCreateGroupChat();
|
|
||||||
},
|
|
||||||
child: Container(
|
|
||||||
color: Colors.grey[900],
|
|
||||||
child: SizedBox(
|
|
||||||
height: 60.0,
|
|
||||||
child: Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
||||||
children: [
|
|
||||||
Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.only(
|
padding: const EdgeInsets.only(
|
||||||
left: 16.0,
|
left: 32,
|
||||||
|
right: 32,
|
||||||
|
top: 20,
|
||||||
),
|
),
|
||||||
child: IconButton(
|
child: FilledButton(
|
||||||
icon: const Icon(
|
onPressed: () async {
|
||||||
Icons.group,
|
await widget.onPressCreateGroupChat();
|
||||||
color: Colors.white,
|
|
||||||
),
|
|
||||||
onPressed: () {
|
|
||||||
// Handle group chat creation
|
|
||||||
},
|
},
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
const Icon(
|
||||||
|
Icons.groups,
|
||||||
),
|
),
|
||||||
|
const SizedBox(
|
||||||
|
width: 4,
|
||||||
),
|
),
|
||||||
Text(
|
Text(
|
||||||
widget.translations.newGroupChatButton,
|
widget.translations.newGroupChatButton,
|
||||||
style: const TextStyle(
|
style: theme.textTheme.displayLarge,
|
||||||
color: Colors.white,
|
|
||||||
fontSize: 16.0,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
@ -138,19 +122,20 @@ class _NewChatScreenState extends State<NewChatScreen> {
|
||||||
},
|
},
|
||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
hintText: widget.translations.searchPlaceholder,
|
hintText: widget.translations.searchPlaceholder,
|
||||||
hintStyle: theme.inputDecorationTheme.hintStyle,
|
hintStyle:
|
||||||
|
theme.textTheme.bodyMedium!.copyWith(color: Colors.white),
|
||||||
focusedBorder: UnderlineInputBorder(
|
focusedBorder: UnderlineInputBorder(
|
||||||
borderSide: BorderSide(
|
borderSide: BorderSide(
|
||||||
color: theme.colorScheme.primary,
|
color: theme.colorScheme.primary,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
style: theme.inputDecorationTheme.hintStyle,
|
style: theme.textTheme.bodySmall!.copyWith(color: Colors.white),
|
||||||
cursorColor: theme.textSelectionTheme.cursorColor ?? Colors.white,
|
cursorColor: theme.textSelectionTheme.cursorColor ?? Colors.white,
|
||||||
)
|
)
|
||||||
: Text(
|
: Text(
|
||||||
widget.translations.newChatTitle,
|
widget.translations.newChatTitle,
|
||||||
style: theme.appBarTheme.titleTextStyle,
|
style: theme.textTheme.headlineLarge,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -205,51 +190,14 @@ class _NewChatScreenState extends State<NewChatScreen> {
|
||||||
.noUsersPlaceholderBuilder(widget.translations, context);
|
.noUsersPlaceholderBuilder(widget.translations, context);
|
||||||
}
|
}
|
||||||
var isPressed = false;
|
var isPressed = false;
|
||||||
return ListView.builder(
|
return Padding(
|
||||||
|
padding: widget.options.paddingAroundChatList ??
|
||||||
|
const EdgeInsets.symmetric(vertical: 8, horizontal: 12),
|
||||||
|
child: ListView.builder(
|
||||||
itemCount: filteredUsers.length,
|
itemCount: filteredUsers.length,
|
||||||
itemBuilder: (context, index) {
|
itemBuilder: (context, index) {
|
||||||
var user = filteredUsers[index];
|
var user = filteredUsers[index];
|
||||||
return DecoratedBox(
|
return InkWell(
|
||||||
decoration: BoxDecoration(
|
|
||||||
border: Border(
|
|
||||||
bottom: BorderSide(
|
|
||||||
color: theme.colorScheme.secondary.withOpacity(0.3),
|
|
||||||
width: 0.5,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
child: GestureDetector(
|
|
||||||
child: widget.options.chatRowContainerBuilder(
|
|
||||||
Padding(
|
|
||||||
padding: widget.options.paddingAroundChatList ??
|
|
||||||
const EdgeInsets.symmetric(vertical: 8, horizontal: 28),
|
|
||||||
child: ColoredBox(
|
|
||||||
color: Colors.transparent,
|
|
||||||
child: Padding(
|
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 20.0),
|
|
||||||
child: Row(
|
|
||||||
children: [
|
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 12),
|
|
||||||
child: widget.options.userAvatarBuilder(user, 40.0),
|
|
||||||
),
|
|
||||||
Expanded(
|
|
||||||
child: Container(
|
|
||||||
height: 40.0,
|
|
||||||
alignment: Alignment.centerLeft,
|
|
||||||
child: Text(
|
|
||||||
user.fullName ??
|
|
||||||
widget.translations.anonymousUser,
|
|
||||||
style: theme.textTheme.bodyLarge,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
onTap: () async {
|
onTap: () async {
|
||||||
if (!isPressed) {
|
if (!isPressed) {
|
||||||
isPressed = true;
|
isPressed = true;
|
||||||
|
@ -257,9 +205,24 @@ class _NewChatScreenState extends State<NewChatScreen> {
|
||||||
isPressed = false;
|
isPressed = false;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
child: widget.options.chatRowContainerBuilder(
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
widget.options.userAvatarBuilder(user, 44),
|
||||||
|
const SizedBox(
|
||||||
|
width: 12,
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
user.fullName ?? widget.translations.anonymousUser,
|
||||||
|
style: theme.textTheme.titleMedium,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
context,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,15 +2,17 @@
|
||||||
//
|
//
|
||||||
// SPDX-License-Identifier: BSD-3-Clause
|
// SPDX-License-Identifier: BSD-3-Clause
|
||||||
|
|
||||||
|
import "dart:typed_data";
|
||||||
|
|
||||||
import "package:flutter/material.dart";
|
import "package:flutter/material.dart";
|
||||||
import "package:flutter_chat_view/flutter_chat_view.dart";
|
import "package:flutter_chat_view/flutter_chat_view.dart";
|
||||||
|
import "package:flutter_chat_view/src/components/image_picker_popup.dart";
|
||||||
|
|
||||||
class NewGroupChatOverviewScreen extends StatefulWidget {
|
class NewGroupChatOverviewScreen extends StatefulWidget {
|
||||||
const NewGroupChatOverviewScreen({
|
const NewGroupChatOverviewScreen({
|
||||||
required this.options,
|
required this.options,
|
||||||
required this.onPressCompleteGroupChatCreation,
|
required this.onPressCompleteGroupChatCreation,
|
||||||
required this.service,
|
required this.service,
|
||||||
required this.users,
|
|
||||||
this.translations = const ChatTranslations.empty(),
|
this.translations = const ChatTranslations.empty(),
|
||||||
super.key,
|
super.key,
|
||||||
});
|
});
|
||||||
|
@ -18,8 +20,12 @@ class NewGroupChatOverviewScreen extends StatefulWidget {
|
||||||
final ChatOptions options;
|
final ChatOptions options;
|
||||||
final ChatTranslations translations;
|
final ChatTranslations translations;
|
||||||
final ChatService service;
|
final ChatService service;
|
||||||
final List<ChatUserModel> users;
|
final Function(
|
||||||
final Function(List<ChatUserModel>, String) onPressCompleteGroupChatCreation;
|
List<ChatUserModel> users,
|
||||||
|
String groupchatName,
|
||||||
|
String? groupchatBio,
|
||||||
|
Uint8List? imageBytes,
|
||||||
|
) onPressCompleteGroupChatCreation;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<NewGroupChatOverviewScreen> createState() =>
|
State<NewGroupChatOverviewScreen> createState() =>
|
||||||
|
@ -28,15 +34,24 @@ class NewGroupChatOverviewScreen extends StatefulWidget {
|
||||||
|
|
||||||
class _NewGroupChatOverviewScreenState
|
class _NewGroupChatOverviewScreenState
|
||||||
extends State<NewGroupChatOverviewScreen> {
|
extends State<NewGroupChatOverviewScreen> {
|
||||||
final TextEditingController _textEditingController = TextEditingController();
|
final TextEditingController _chatNameController = TextEditingController();
|
||||||
|
final TextEditingController _bioController = TextEditingController();
|
||||||
|
Uint8List? image;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
var theme = Theme.of(context);
|
var theme = Theme.of(context);
|
||||||
var formKey = GlobalKey<FormState>();
|
var formKey = GlobalKey<FormState>();
|
||||||
var isPressed = false;
|
var isPressed = false;
|
||||||
|
var users = widget.service.chatOverviewService.currentlySelectedUsers;
|
||||||
|
|
||||||
|
void onUploadImage(groupImage) {
|
||||||
|
setState(() {
|
||||||
|
image = groupImage;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
backgroundColor: theme.colorScheme.surface,
|
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
iconTheme: theme.appBarTheme.iconTheme ??
|
iconTheme: theme.appBarTheme.iconTheme ??
|
||||||
const IconThemeData(color: Colors.white),
|
const IconThemeData(color: Colors.white),
|
||||||
|
@ -46,15 +61,113 @@ class _NewGroupChatOverviewScreenState
|
||||||
style: theme.appBarTheme.titleTextStyle,
|
style: theme.appBarTheme.titleTextStyle,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
body: Padding(
|
body: Stack(
|
||||||
padding: const EdgeInsets.all(16.0),
|
children: [
|
||||||
|
SingleChildScrollView(
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 32),
|
||||||
child: Form(
|
child: Form(
|
||||||
key: formKey,
|
key: formKey,
|
||||||
child: TextFormField(
|
child: Column(
|
||||||
controller: _textEditingController,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
const SizedBox(
|
||||||
|
height: 40,
|
||||||
|
),
|
||||||
|
Center(
|
||||||
|
child: Stack(
|
||||||
|
children: [
|
||||||
|
GestureDetector(
|
||||||
|
onTap: () async {
|
||||||
|
await onPressSelectImage(
|
||||||
|
context,
|
||||||
|
widget.translations,
|
||||||
|
widget.options,
|
||||||
|
onUploadImage,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
child: Container(
|
||||||
|
width: 80,
|
||||||
|
height: 80,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: const Color(0xFFD9D9D9),
|
||||||
|
borderRadius: BorderRadius.circular(40),
|
||||||
|
image: image != null
|
||||||
|
? DecorationImage(
|
||||||
|
image: MemoryImage(image!),
|
||||||
|
fit: BoxFit.cover,
|
||||||
|
)
|
||||||
|
: null,
|
||||||
|
),
|
||||||
|
child: image == null
|
||||||
|
? const Icon(Icons.image)
|
||||||
|
: null,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
if (image != null)
|
||||||
|
Positioned.directional(
|
||||||
|
textDirection: Directionality.of(context),
|
||||||
|
end: 0,
|
||||||
|
child: Container(
|
||||||
|
width: 20,
|
||||||
|
height: 20,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: const Color(0xFFBCBCBC),
|
||||||
|
borderRadius: BorderRadius.circular(40),
|
||||||
|
),
|
||||||
|
child: Center(
|
||||||
|
child: GestureDetector(
|
||||||
|
onTap: () {
|
||||||
|
setState(() {
|
||||||
|
image = null;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
child: const Icon(
|
||||||
|
Icons.close,
|
||||||
|
size: 12,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
else
|
||||||
|
const SizedBox.shrink(),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(
|
||||||
|
height: 40,
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
widget.translations.groupChatNameFieldHeader,
|
||||||
|
style: theme.textTheme.titleMedium,
|
||||||
|
),
|
||||||
|
const SizedBox(
|
||||||
|
height: 12,
|
||||||
|
),
|
||||||
|
TextFormField(
|
||||||
|
style: theme.textTheme.bodySmall,
|
||||||
|
controller: _chatNameController,
|
||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
|
fillColor: Colors.white,
|
||||||
|
filled: true,
|
||||||
hintText: widget.translations.groupNameHintText,
|
hintText: widget.translations.groupNameHintText,
|
||||||
hintStyle: theme.inputDecorationTheme.hintStyle,
|
hintStyle: theme.textTheme.bodyMedium!.copyWith(
|
||||||
|
color: theme.textTheme.bodyMedium!.color!
|
||||||
|
.withOpacity(0.5),
|
||||||
|
),
|
||||||
|
enabledBorder: OutlineInputBorder(
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
borderSide: const BorderSide(
|
||||||
|
color: Colors.transparent,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
focusedBorder: OutlineInputBorder(
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
borderSide: const BorderSide(
|
||||||
|
color: Colors.transparent,
|
||||||
|
),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
validator: (value) {
|
validator: (value) {
|
||||||
if (value == null || value.isEmpty) {
|
if (value == null || value.isEmpty) {
|
||||||
|
@ -65,27 +178,143 @@ class _NewGroupChatOverviewScreenState
|
||||||
return null;
|
return null;
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
const SizedBox(
|
||||||
|
height: 16,
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
widget.translations.groupBioFieldHeader,
|
||||||
|
style: theme.textTheme.titleMedium,
|
||||||
|
),
|
||||||
|
const SizedBox(
|
||||||
|
height: 12,
|
||||||
|
),
|
||||||
|
TextFormField(
|
||||||
|
style: theme.textTheme.bodySmall,
|
||||||
|
controller: _bioController,
|
||||||
|
minLines: null,
|
||||||
|
maxLines: 5,
|
||||||
|
decoration: InputDecoration(
|
||||||
|
fillColor: Colors.white,
|
||||||
|
filled: true,
|
||||||
|
hintText: widget.translations.groupBioHintText,
|
||||||
|
hintStyle: theme.textTheme.bodyMedium!.copyWith(
|
||||||
|
color: theme.textTheme.bodyMedium!.color!
|
||||||
|
.withOpacity(0.5),
|
||||||
|
),
|
||||||
|
enabledBorder: OutlineInputBorder(
|
||||||
|
borderRadius: BorderRadius.circular(12),
|
||||||
|
borderSide: const BorderSide(
|
||||||
|
color: Colors.transparent,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
floatingActionButton: FloatingActionButton(
|
focusedBorder: OutlineInputBorder(
|
||||||
backgroundColor: Theme.of(context).primaryColor,
|
borderRadius: BorderRadius.circular(12),
|
||||||
onPressed: () async {
|
borderSide: const BorderSide(
|
||||||
|
color: Colors.transparent,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
validator: (value) {
|
||||||
|
if (value == null || value.isEmpty) {
|
||||||
|
return widget.translations.groupBioValidatorEmpty;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
),
|
||||||
|
const SizedBox(
|
||||||
|
height: 16,
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
"${widget.translations.selectedMembersHeader}"
|
||||||
|
"${users.length}",
|
||||||
|
style: theme.textTheme.titleMedium,
|
||||||
|
),
|
||||||
|
const SizedBox(
|
||||||
|
height: 12,
|
||||||
|
),
|
||||||
|
Wrap(
|
||||||
|
children: [
|
||||||
|
...users.map(
|
||||||
|
_selectedUser,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const SizedBox(
|
||||||
|
height: 80,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Align(
|
||||||
|
alignment: Alignment.bottomCenter,
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(
|
||||||
|
vertical: 24,
|
||||||
|
horizontal: 80,
|
||||||
|
),
|
||||||
|
child: FilledButton(
|
||||||
|
onPressed: users.isNotEmpty
|
||||||
|
? () async {
|
||||||
if (!isPressed) {
|
if (!isPressed) {
|
||||||
isPressed = true;
|
isPressed = true;
|
||||||
if (formKey.currentState!.validate()) {
|
if (formKey.currentState!.validate()) {
|
||||||
await widget.onPressCompleteGroupChatCreation(
|
await widget.onPressCompleteGroupChatCreation(
|
||||||
widget.users,
|
users,
|
||||||
_textEditingController.text,
|
_chatNameController.text,
|
||||||
|
_bioController.text,
|
||||||
|
image,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
isPressed = false;
|
isPressed = false;
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
child: const Icon(
|
: null,
|
||||||
Icons.check_circle,
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
widget.translations.createGroupChatButton,
|
||||||
|
style: theme.textTheme.displayLarge,
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
floatingActionButtonLocation: FloatingActionButtonLocation.endFloat,
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
// floatingActionButton: FloatingActionButton(
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Widget _selectedUser(ChatUserModel user) => GestureDetector(
|
||||||
|
onTap: () {
|
||||||
|
setState(() {
|
||||||
|
widget.service.chatOverviewService
|
||||||
|
.removeCurrentlySelectedUser(user);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
child: Stack(
|
||||||
|
children: [
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.all(8),
|
||||||
|
child: widget.options.userAvatarBuilder(
|
||||||
|
user,
|
||||||
|
40,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Positioned.directional(
|
||||||
|
textDirection: Directionality.of(context),
|
||||||
|
end: 0,
|
||||||
|
child: const Icon(
|
||||||
|
Icons.cancel,
|
||||||
|
size: 20,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,7 +23,6 @@ class NewGroupChatScreen extends StatefulWidget {
|
||||||
|
|
||||||
class _NewGroupChatScreenState extends State<NewGroupChatScreen> {
|
class _NewGroupChatScreenState extends State<NewGroupChatScreen> {
|
||||||
final FocusNode _textFieldFocusNode = FocusNode();
|
final FocusNode _textFieldFocusNode = FocusNode();
|
||||||
List<ChatUserModel> selectedUserList = [];
|
|
||||||
|
|
||||||
bool _isSearching = false;
|
bool _isSearching = false;
|
||||||
String query = "";
|
String query = "";
|
||||||
|
@ -51,19 +50,19 @@ class _NewGroupChatScreenState extends State<NewGroupChatScreen> {
|
||||||
} else if (snapshot.hasError) {
|
} else if (snapshot.hasError) {
|
||||||
return Text("Error: ${snapshot.error}");
|
return Text("Error: ${snapshot.error}");
|
||||||
} else if (snapshot.hasData) {
|
} else if (snapshot.hasData) {
|
||||||
return _buildUserList(snapshot.data!);
|
return Stack(
|
||||||
|
children: [
|
||||||
|
_buildUserList(snapshot.data!),
|
||||||
|
NextButton(
|
||||||
|
service: widget.service,
|
||||||
|
onPressGroupChatOverview: widget.onPressGroupChatOverview,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
}
|
}
|
||||||
return const SizedBox.shrink();
|
return const SizedBox.shrink();
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
floatingActionButton: FloatingActionButton(
|
|
||||||
backgroundColor: Theme.of(context).primaryColor,
|
|
||||||
onPressed: () async {
|
|
||||||
await widget.onPressGroupChatOverview(selectedUserList);
|
|
||||||
},
|
|
||||||
child: const Icon(Icons.arrow_forward_ios),
|
|
||||||
),
|
|
||||||
floatingActionButtonLocation: FloatingActionButtonLocation.endFloat,
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -80,14 +79,15 @@ class _NewGroupChatScreenState extends State<NewGroupChatScreen> {
|
||||||
},
|
},
|
||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
hintText: widget.translations.searchPlaceholder,
|
hintText: widget.translations.searchPlaceholder,
|
||||||
hintStyle: theme.inputDecorationTheme.hintStyle,
|
hintStyle:
|
||||||
|
theme.textTheme.bodyMedium!.copyWith(color: Colors.white),
|
||||||
focusedBorder: UnderlineInputBorder(
|
focusedBorder: UnderlineInputBorder(
|
||||||
borderSide: BorderSide(
|
borderSide: BorderSide(
|
||||||
color: theme.colorScheme.primary,
|
color: theme.colorScheme.primary,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
style: theme.inputDecorationTheme.hintStyle,
|
style: theme.textTheme.bodySmall!.copyWith(color: Colors.white),
|
||||||
cursorColor: theme.textSelectionTheme.cursorColor ?? Colors.white,
|
cursorColor: theme.textSelectionTheme.cursorColor ?? Colors.white,
|
||||||
)
|
)
|
||||||
: Text(
|
: Text(
|
||||||
|
@ -140,9 +140,74 @@ class _NewGroupChatScreenState extends State<NewGroupChatScreen> {
|
||||||
|
|
||||||
return UserList(
|
return UserList(
|
||||||
filteredUsers: filteredUsers,
|
filteredUsers: filteredUsers,
|
||||||
selectedUserList: selectedUserList,
|
|
||||||
options: widget.options,
|
options: widget.options,
|
||||||
translations: widget.translations,
|
translations: widget.translations,
|
||||||
|
service: widget.service,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class NextButton extends StatefulWidget {
|
||||||
|
const NextButton({
|
||||||
|
required this.service,
|
||||||
|
required this.onPressGroupChatOverview,
|
||||||
|
super.key,
|
||||||
|
});
|
||||||
|
|
||||||
|
final ChatService service;
|
||||||
|
final Function(List<ChatUserModel>) onPressGroupChatOverview;
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<NextButton> createState() => _NextButtonState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _NextButtonState extends State<NextButton> {
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
widget.service.chatOverviewService.addListener(_listen);
|
||||||
|
super.initState();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
widget.service.chatOverviewService.removeListener(_listen);
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
void _listen() {
|
||||||
|
setState(() {});
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
var theme = Theme.of(context);
|
||||||
|
return Align(
|
||||||
|
alignment: Alignment.bottomCenter,
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(
|
||||||
|
vertical: 24,
|
||||||
|
horizontal: 80,
|
||||||
|
),
|
||||||
|
child: FilledButton(
|
||||||
|
onPressed: widget
|
||||||
|
.service.chatOverviewService.currentlySelectedUsers.isNotEmpty
|
||||||
|
? () async {
|
||||||
|
await widget.onPressGroupChatOverview(
|
||||||
|
widget.service.chatOverviewService.currentlySelectedUsers,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
: null,
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
"Next",
|
||||||
|
style: theme.textTheme.displayLarge,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -150,16 +215,16 @@ class _NewGroupChatScreenState extends State<NewGroupChatScreen> {
|
||||||
class UserList extends StatefulWidget {
|
class UserList extends StatefulWidget {
|
||||||
const UserList({
|
const UserList({
|
||||||
required this.filteredUsers,
|
required this.filteredUsers,
|
||||||
required this.selectedUserList,
|
|
||||||
required this.options,
|
required this.options,
|
||||||
required this.translations,
|
required this.translations,
|
||||||
|
required this.service,
|
||||||
super.key,
|
super.key,
|
||||||
});
|
});
|
||||||
|
|
||||||
final List<ChatUserModel> filteredUsers;
|
final List<ChatUserModel> filteredUsers;
|
||||||
final List<ChatUserModel> selectedUserList;
|
|
||||||
final ChatOptions options;
|
final ChatOptions options;
|
||||||
final ChatTranslations translations;
|
final ChatTranslations translations;
|
||||||
|
final ChatService service;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<UserList> createState() => _UserListState();
|
State<UserList> createState() => _UserListState();
|
||||||
|
@ -167,75 +232,70 @@ class UserList extends StatefulWidget {
|
||||||
|
|
||||||
class _UserListState extends State<UserList> {
|
class _UserListState extends State<UserList> {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) => ListView.builder(
|
void initState() {
|
||||||
|
widget.service.chatOverviewService.addListener(_listen);
|
||||||
|
super.initState();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
widget.service.chatOverviewService.removeListener(_listen);
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
void _listen() {
|
||||||
|
setState(() {});
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) => Padding(
|
||||||
|
padding: widget.options.paddingAroundChatList ??
|
||||||
|
const EdgeInsets.symmetric(vertical: 8, horizontal: 12),
|
||||||
|
child: ListView.builder(
|
||||||
itemCount: widget.filteredUsers.length,
|
itemCount: widget.filteredUsers.length,
|
||||||
itemBuilder: (context, index) {
|
itemBuilder: (context, index) {
|
||||||
var user = widget.filteredUsers[index];
|
var user = widget.filteredUsers[index];
|
||||||
var isSelected = widget.selectedUserList
|
var isSelected = widget
|
||||||
|
.service.chatOverviewService.currentlySelectedUsers
|
||||||
.any((selectedUser) => selectedUser == user);
|
.any((selectedUser) => selectedUser == user);
|
||||||
var theme = Theme.of(context);
|
var theme = Theme.of(context);
|
||||||
return DecoratedBox(
|
return widget.options.chatRowContainerBuilder(
|
||||||
decoration: BoxDecoration(
|
Row(
|
||||||
border: Border(
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
bottom: BorderSide(
|
children: [
|
||||||
color: theme.colorScheme.secondary.withOpacity(0.3),
|
Row(
|
||||||
width: 0.5,
|
children: [
|
||||||
|
widget.options.userAvatarBuilder(user, 44),
|
||||||
|
const SizedBox(
|
||||||
|
width: 12,
|
||||||
),
|
),
|
||||||
|
Text(
|
||||||
|
user.fullName ?? widget.translations.anonymousUser,
|
||||||
|
style: theme.textTheme.titleMedium,
|
||||||
),
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
child: InkWell(
|
Checkbox(
|
||||||
onTap: () {
|
value: isSelected,
|
||||||
|
onChanged: (value) {
|
||||||
setState(() {
|
setState(() {
|
||||||
if (widget.selectedUserList.contains(user)) {
|
if (widget
|
||||||
widget.selectedUserList.remove(user);
|
.service.chatOverviewService.currentlySelectedUsers
|
||||||
|
.contains(user)) {
|
||||||
|
widget.service.chatOverviewService
|
||||||
|
.removeCurrentlySelectedUser(user);
|
||||||
} else {
|
} else {
|
||||||
widget.selectedUserList.add(user);
|
widget.service.chatOverviewService
|
||||||
|
.addCurrentlySelectedUser(user);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
child: Padding(
|
|
||||||
padding: widget.options.paddingAroundChatList ??
|
|
||||||
const EdgeInsets.fromLTRB(28, 8, 28, 8),
|
|
||||||
child: ColoredBox(
|
|
||||||
color: Colors.transparent,
|
|
||||||
child: Padding(
|
|
||||||
padding: const EdgeInsets.symmetric(
|
|
||||||
vertical: 12.0,
|
|
||||||
horizontal: 30,
|
|
||||||
),
|
|
||||||
child: Row(
|
|
||||||
children: [
|
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 12),
|
|
||||||
child: widget.options.userAvatarBuilder(user, 40.0),
|
|
||||||
),
|
|
||||||
Expanded(
|
|
||||||
child: Container(
|
|
||||||
height: 40,
|
|
||||||
alignment: Alignment.centerLeft,
|
|
||||||
child: Text(
|
|
||||||
user.fullName ??
|
|
||||||
widget.translations.anonymousUser,
|
|
||||||
style: theme.textTheme.bodyLarge,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
if (isSelected) ...[
|
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.only(right: 16.0),
|
|
||||||
child: Icon(
|
|
||||||
Icons.check_circle,
|
|
||||||
color: theme.colorScheme.primary,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
|
context,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,11 +23,39 @@ class DateFormatter {
|
||||||
.inDays ==
|
.inDays ==
|
||||||
0;
|
0;
|
||||||
|
|
||||||
|
bool _isYesterday(DateTime date) =>
|
||||||
|
DateTime(
|
||||||
|
date.year,
|
||||||
|
date.month,
|
||||||
|
date.day,
|
||||||
|
)
|
||||||
|
.difference(
|
||||||
|
DateTime(
|
||||||
|
_now.year,
|
||||||
|
_now.month,
|
||||||
|
_now.day,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
.inDays ==
|
||||||
|
-1;
|
||||||
|
|
||||||
|
bool _isThisYear(DateTime date) => date.year == _now.year;
|
||||||
|
|
||||||
String format({
|
String format({
|
||||||
required DateTime date,
|
required DateTime date,
|
||||||
bool showFullDate = false,
|
bool showFullDate = false,
|
||||||
}) =>
|
}) {
|
||||||
DateFormat(
|
if(showFullDate) {
|
||||||
_isToday(date) ? "HH:mm" : 'dd-MM-yyyy${showFullDate ? ' HH:mm' : ''}',
|
return DateFormat("dd - MM - yyyy HH:mm").format(date);
|
||||||
).format(date);
|
}
|
||||||
|
if (_isToday(date)) {
|
||||||
|
return DateFormat("HH:mm").format(date);
|
||||||
|
} else if (_isYesterday(date)) {
|
||||||
|
return "yesterday";
|
||||||
|
} else if (_isThisYear(date)) {
|
||||||
|
return DateFormat("dd MMMM").format(date);
|
||||||
|
} else {
|
||||||
|
return DateFormat("dd - MM - yyyy").format(date);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
|
|
||||||
name: flutter_chat_view
|
name: flutter_chat_view
|
||||||
description: A standard flutter package.
|
description: A standard flutter package.
|
||||||
version: 3.0.1
|
version: 3.1.0
|
||||||
|
|
||||||
publish_to: https://forgejo.internal.iconica.nl/api/packages/internal/pub
|
publish_to: https://forgejo.internal.iconica.nl/api/packages/internal/pub
|
||||||
|
|
||||||
|
@ -25,7 +25,7 @@ dependencies:
|
||||||
version: ^1.0.5
|
version: ^1.0.5
|
||||||
flutter_profile:
|
flutter_profile:
|
||||||
hosted: https://forgejo.internal.iconica.nl/api/packages/internal/pub
|
hosted: https://forgejo.internal.iconica.nl/api/packages/internal/pub
|
||||||
version: ^1.3.0
|
version: ^1.5.0
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
|
|
Loading…
Reference in a new issue