mirror of
https://github.com/Iconica-Development/flutter_chat.git
synced 2025-05-18 18:33:49 +02:00
feat: add FlutterChatDetailNavigatorUserstory to start a userstory with only the chat detail and subscreens of that screen
This commit is contained in:
parent
ff28f91524
commit
4ec7da429e
6 changed files with 134 additions and 44 deletions
|
@ -8,6 +8,7 @@
|
|||
- Added ChatScope that can be used to get the ChatService and ChatTranslations from the context. If you use individual components instead of the userstory you need to wrap them with the ChatScope. The options and service will be removed from all the component constructors.
|
||||
- Added getAllUsersForChat to UserRepositoryInterface for fetching all users for a chat
|
||||
- Added flutter_hooks as a dependency for easier state management
|
||||
- Added FlutterChatDetailNavigatorUserstory that can be used to start the userstory from the chat detail screen without having the chat overview screen
|
||||
|
||||
## 4.0.0
|
||||
- Move to the new user story architecture
|
||||
|
|
|
@ -238,4 +238,7 @@ class LocalChatRepository implements ChatRepositoryInterface {
|
|||
required Uint8List image,
|
||||
}) =>
|
||||
Future.value("https://picsum.photos/200/300");
|
||||
|
||||
/// All the chats of the local memory database
|
||||
List<ChatModel> get getLocalChats => chats;
|
||||
}
|
||||
|
|
|
@ -47,4 +47,7 @@ class LocalUserRepository implements UserRepositoryInterface {
|
|||
)
|
||||
.toList(),
|
||||
);
|
||||
|
||||
/// All the users of the local memory database
|
||||
List<UserModel> get getLocalUsers => users;
|
||||
}
|
||||
|
|
|
@ -9,35 +9,31 @@ import "package:flutter_chat/src/routes.dart";
|
|||
import "package:flutter_chat/src/services/pop_handler.dart";
|
||||
import "package:flutter_chat/src/util/scope.dart";
|
||||
|
||||
/// The flutter chat navigator userstory
|
||||
/// [userId] is the id of the user
|
||||
/// [chatOptions] are the chat options
|
||||
/// This widget is the entry point for the chat UI
|
||||
class FlutterChatNavigatorUserstory extends StatefulWidget {
|
||||
/// Constructs a [FlutterChatNavigatorUserstory].
|
||||
const FlutterChatNavigatorUserstory({
|
||||
/// Base class for both chat navigator user stories.
|
||||
abstract class BaseChatNavigatorUserstory extends StatefulWidget {
|
||||
/// Constructs a [BaseChatNavigatorUserstory].
|
||||
const BaseChatNavigatorUserstory({
|
||||
required this.userId,
|
||||
required this.options,
|
||||
this.onExit,
|
||||
super.key,
|
||||
});
|
||||
|
||||
/// The user ID of the person currently looking at the chat
|
||||
/// The user ID of the person starting the chat userstory.
|
||||
final String userId;
|
||||
|
||||
/// The chat options
|
||||
/// The chat userstory configuration.
|
||||
final ChatOptions options;
|
||||
|
||||
/// Callback for when the user wants to navigate back to a previous screen
|
||||
final VoidCallback? onExit;
|
||||
|
||||
@override
|
||||
State<FlutterChatNavigatorUserstory> createState() =>
|
||||
_FlutterChatNavigatorUserstoryState();
|
||||
State<BaseChatNavigatorUserstory> createState();
|
||||
}
|
||||
|
||||
class _FlutterChatNavigatorUserstoryState
|
||||
extends State<FlutterChatNavigatorUserstory> {
|
||||
abstract class _BaseChatNavigatorUserstoryState<
|
||||
T extends BaseChatNavigatorUserstory> extends State<T> {
|
||||
late ChatService _service = ChatService(
|
||||
userId: widget.userId,
|
||||
chatRepository: widget.options.chatRepository,
|
||||
|
@ -45,7 +41,6 @@ class _FlutterChatNavigatorUserstoryState
|
|||
);
|
||||
|
||||
late final PopHandler _popHandler = PopHandler();
|
||||
|
||||
final GlobalKey<NavigatorState> _nestedNavigatorKey =
|
||||
GlobalKey<NavigatorState>();
|
||||
|
||||
|
@ -56,26 +51,19 @@ class _FlutterChatNavigatorUserstoryState
|
|||
service: _service,
|
||||
popHandler: _popHandler,
|
||||
child: NavigatorPopHandler(
|
||||
// ignore: deprecated_member_use
|
||||
onPop: () => _popHandler.handlePop(),
|
||||
child: Navigator(
|
||||
key: _nestedNavigatorKey,
|
||||
onGenerateRoute: (settings) => MaterialPageRoute(
|
||||
builder: (context) => NavigatorWrapper(
|
||||
userId: widget.userId,
|
||||
chatService: _service,
|
||||
chatOptions: widget.options,
|
||||
onExit: widget.onExit,
|
||||
),
|
||||
builder: (context) => buildInitialScreen(),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
@override
|
||||
void didUpdateWidget(covariant FlutterChatNavigatorUserstory oldWidget) {
|
||||
void didUpdateWidget(covariant T oldWidget) {
|
||||
super.didUpdateWidget(oldWidget);
|
||||
|
||||
if (oldWidget.userId != widget.userId ||
|
||||
oldWidget.options != widget.options) {
|
||||
setState(() {
|
||||
|
@ -87,4 +75,68 @@ class _FlutterChatNavigatorUserstoryState
|
|||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// Implemented by subclasses to provide the initial screen of the userstory.
|
||||
Widget buildInitialScreen();
|
||||
}
|
||||
|
||||
/// Default Chat Userstory that starts at the chat list screen.
|
||||
class FlutterChatNavigatorUserstory extends BaseChatNavigatorUserstory {
|
||||
/// Constructs a [FlutterChatNavigatorUserstory].
|
||||
const FlutterChatNavigatorUserstory({
|
||||
required super.userId,
|
||||
required super.options,
|
||||
super.onExit,
|
||||
super.key,
|
||||
});
|
||||
|
||||
@override
|
||||
State<BaseChatNavigatorUserstory> createState() =>
|
||||
_FlutterChatNavigatorUserstoryState();
|
||||
}
|
||||
|
||||
class _FlutterChatNavigatorUserstoryState
|
||||
extends _BaseChatNavigatorUserstoryState<FlutterChatNavigatorUserstory> {
|
||||
@override
|
||||
Widget buildInitialScreen() => NavigatorWrapper(
|
||||
userId: widget.userId,
|
||||
chatService: _service,
|
||||
chatOptions: widget.options,
|
||||
onExit: widget.onExit,
|
||||
);
|
||||
}
|
||||
|
||||
/// Chat Userstory that starts directly in a chat detail screen.
|
||||
class FlutterChatDetailNavigatorUserstory extends BaseChatNavigatorUserstory {
|
||||
/// Constructs a [FlutterChatDetailNavigatorUserstory].
|
||||
const FlutterChatDetailNavigatorUserstory({
|
||||
required super.userId,
|
||||
required super.options,
|
||||
required this.chat,
|
||||
super.onExit,
|
||||
super.key,
|
||||
});
|
||||
|
||||
/// The chat to start in.
|
||||
final ChatModel chat;
|
||||
|
||||
@override
|
||||
State<BaseChatNavigatorUserstory> createState() =>
|
||||
_FlutterChatDetailNavigatorUserstoryState();
|
||||
}
|
||||
|
||||
class _FlutterChatDetailNavigatorUserstoryState
|
||||
extends _BaseChatNavigatorUserstoryState<
|
||||
FlutterChatDetailNavigatorUserstory> {
|
||||
@override
|
||||
Widget buildInitialScreen() => NavigatorWrapper(
|
||||
userId: widget.userId,
|
||||
chatService: _service,
|
||||
chatOptions: widget.options,
|
||||
onExit: widget.onExit,
|
||||
).chatDetailScreen(
|
||||
context,
|
||||
widget.chat,
|
||||
widget.onExit,
|
||||
);
|
||||
}
|
||||
|
|
|
@ -39,8 +39,14 @@ class NavigatorWrapper extends StatelessWidget {
|
|||
/// The chat overview screen
|
||||
Widget chatScreen(BuildContext context) => ChatScreen(
|
||||
onExit: onExit,
|
||||
onPressChat: (chat) async =>
|
||||
_routeToScreen(context, chatDetailScreen(context, chat)),
|
||||
onPressChat: (chat) async => _routeToScreen(
|
||||
context,
|
||||
chatDetailScreen(
|
||||
context,
|
||||
chat,
|
||||
() => Navigator.of(context).pop(),
|
||||
),
|
||||
),
|
||||
onDeleteChat: (chat) async {
|
||||
await chatService.deleteChat(chatId: chat.id);
|
||||
},
|
||||
|
@ -49,10 +55,14 @@ class NavigatorWrapper extends StatelessWidget {
|
|||
);
|
||||
|
||||
/// The chat screen
|
||||
Widget chatDetailScreen(BuildContext context, ChatModel chat) =>
|
||||
Widget chatDetailScreen(
|
||||
BuildContext context,
|
||||
ChatModel chat,
|
||||
VoidCallback? onExit,
|
||||
) =>
|
||||
ChatDetailScreen(
|
||||
chat: chat,
|
||||
onExit: () => Navigator.of(context).pop(),
|
||||
onExit: onExit,
|
||||
onReadChat: (chat) async => chatService.markAsRead(chatId: chat.id),
|
||||
onPressChatTitle: (chat) async {
|
||||
if (chat.isGroupChat) {
|
||||
|
@ -116,7 +126,14 @@ class NavigatorWrapper extends StatelessWidget {
|
|||
var chat = await _createChat(userId);
|
||||
|
||||
if (!context.mounted) return;
|
||||
return _routeToScreen(context, chatDetailScreen(context, chat));
|
||||
return _routeToScreen(
|
||||
context,
|
||||
chatDetailScreen(
|
||||
context,
|
||||
chat,
|
||||
() => Navigator.of(context).pop(),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
|
@ -131,7 +148,11 @@ class NavigatorWrapper extends StatelessWidget {
|
|||
if (!context.mounted) return;
|
||||
return _replaceCurrentScreen(
|
||||
context,
|
||||
chatDetailScreen(context, chat),
|
||||
chatDetailScreen(
|
||||
context,
|
||||
chat,
|
||||
() => Navigator.of(context).pop(),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
|
@ -168,7 +189,11 @@ class NavigatorWrapper extends StatelessWidget {
|
|||
if (!context.mounted) return;
|
||||
return _replaceCurrentScreen(
|
||||
context,
|
||||
chatDetailScreen(context, chat),
|
||||
chatDetailScreen(
|
||||
context,
|
||||
chat,
|
||||
() => Navigator.of(context).pop(),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
|
|
|
@ -48,7 +48,7 @@ class ChatDetailScreen extends StatefulHookWidget {
|
|||
final String Function(ChatModel chat)? getChatTitle;
|
||||
|
||||
/// Callback for when the user wants to navigate back
|
||||
final VoidCallback onExit;
|
||||
final VoidCallback? onExit;
|
||||
|
||||
@override
|
||||
State<ChatDetailScreen> createState() => _ChatDetailScreenState();
|
||||
|
@ -96,6 +96,7 @@ class _ChatDetailScreenState extends State<ChatDetailScreen> {
|
|||
chatTitle: chatTitle,
|
||||
onPressChatTitle: widget.onPressChatTitle,
|
||||
chatModel: widget.chat,
|
||||
onPressBack: widget.onExit,
|
||||
);
|
||||
|
||||
var body = _Body(
|
||||
|
@ -107,8 +108,9 @@ class _ChatDetailScreenState extends State<ChatDetailScreen> {
|
|||
);
|
||||
|
||||
useEffect(() {
|
||||
chatScope.popHandler.add(widget.onExit);
|
||||
return () => chatScope.popHandler.remove(widget.onExit);
|
||||
if (widget.onExit == null) return null;
|
||||
chatScope.popHandler.add(widget.onExit!);
|
||||
return () => chatScope.popHandler.remove(widget.onExit!);
|
||||
});
|
||||
|
||||
if (chatOptions.builders.baseScreenBuilder == null) {
|
||||
|
@ -132,11 +134,13 @@ class _AppBar extends StatelessWidget implements PreferredSizeWidget {
|
|||
required this.chatTitle,
|
||||
required this.onPressChatTitle,
|
||||
required this.chatModel,
|
||||
this.onPressBack,
|
||||
});
|
||||
|
||||
final String? chatTitle;
|
||||
final Function(ChatModel) onPressChatTitle;
|
||||
final ChatModel chatModel;
|
||||
final VoidCallback? onPressBack;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
|
@ -144,18 +148,20 @@ class _AppBar extends StatelessWidget implements PreferredSizeWidget {
|
|||
var theme = Theme.of(context);
|
||||
|
||||
return AppBar(
|
||||
iconTheme: theme.appBarTheme.iconTheme ??
|
||||
const IconThemeData(color: Colors.white),
|
||||
iconTheme: theme.appBarTheme.iconTheme,
|
||||
centerTitle: true,
|
||||
leading: GestureDetector(
|
||||
onTap: () {
|
||||
Navigator.popUntil(context, (route) => route.isFirst);
|
||||
},
|
||||
child: const Icon(
|
||||
Icons.arrow_back_ios,
|
||||
),
|
||||
),
|
||||
title: GestureDetector(
|
||||
leading: onPressBack == null
|
||||
? null
|
||||
: InkWell(
|
||||
onTap: onPressBack,
|
||||
child: const Icon(
|
||||
Icons.arrow_back_ios,
|
||||
),
|
||||
),
|
||||
title: InkWell(
|
||||
splashColor: Colors.transparent,
|
||||
highlightColor: Colors.transparent,
|
||||
hoverColor: Colors.transparent,
|
||||
onTap: () => onPressChatTitle.call(chatModel),
|
||||
child: options.builders.chatTitleBuilder?.call(chatTitle ?? "") ??
|
||||
Text(
|
||||
|
|
Loading…
Reference in a new issue