From a6e15f9bf76473e18dcac31685c7b5df443bbcc7 Mon Sep 17 00:00:00 2001 From: Freek van de Ven Date: Wed, 12 Feb 2025 11:39:26 +0100 Subject: [PATCH] feat: add onback behavior for each userstory screen so it pops correctly with the android backarrow --- .../flutter_chat_navigator_userstories.dart | 6 +++- packages/flutter_chat/lib/src/routes.dart | 32 ++++++++++++++++--- .../chat_detail/chat_detail_screen.dart | 15 +++++++-- .../lib/src/screens/chat_profile_screen.dart | 15 ++++++++- .../lib/src/screens/chat_screen.dart | 15 ++++++++- .../src/screens/creation/new_chat_screen.dart | 15 ++++++++- .../creation/new_group_chat_overview.dart | 15 ++++++++- .../creation/new_group_chat_screen.dart | 14 +++++++- 8 files changed, 115 insertions(+), 12 deletions(-) diff --git a/packages/flutter_chat/lib/src/flutter_chat_navigator_userstories.dart b/packages/flutter_chat/lib/src/flutter_chat_navigator_userstories.dart index 7fe69ed..c00627d 100644 --- a/packages/flutter_chat/lib/src/flutter_chat_navigator_userstories.dart +++ b/packages/flutter_chat/lib/src/flutter_chat_navigator_userstories.dart @@ -11,7 +11,6 @@ import "package:flutter_chat/src/util/scope.dart"; /// The flutter chat navigator userstory /// [userId] is the id of the user -/// [chatService] is the chat service /// [chatOptions] are the chat options /// This widget is the entry point for the chat UI class FlutterChatNavigatorUserstory extends StatefulWidget { @@ -19,6 +18,7 @@ class FlutterChatNavigatorUserstory extends StatefulWidget { const FlutterChatNavigatorUserstory({ required this.userId, required this.options, + this.onExit, super.key, }); @@ -28,6 +28,9 @@ class FlutterChatNavigatorUserstory extends StatefulWidget { /// The chat options final ChatOptions options; + /// Callback for when the user wants to navigate back to a previous screen + final VoidCallback? onExit; + @override State createState() => _FlutterChatNavigatorUserstoryState(); @@ -62,6 +65,7 @@ class _FlutterChatNavigatorUserstoryState userId: widget.userId, chatService: _service, chatOptions: widget.options, + onExit: widget.onExit, ), ), ), diff --git a/packages/flutter_chat/lib/src/routes.dart b/packages/flutter_chat/lib/src/routes.dart index 398cc6f..71e2fbf 100644 --- a/packages/flutter_chat/lib/src/routes.dart +++ b/packages/flutter_chat/lib/src/routes.dart @@ -17,6 +17,7 @@ class NavigatorWrapper extends StatelessWidget { required this.userId, required this.chatService, required this.chatOptions, + this.onExit, super.key, }); @@ -29,6 +30,9 @@ class NavigatorWrapper extends StatelessWidget { /// The chat userstory configuration final ChatOptions chatOptions; + /// Callback for when the user wants to navigate back + final VoidCallback? onExit; + @override Widget build(BuildContext context) => chatScreen(context); @@ -36,6 +40,7 @@ class NavigatorWrapper extends StatelessWidget { Widget chatScreen(BuildContext context) => ChatScreen( chatService: chatService, chatOptions: chatOptions, + onExit: onExit, onPressChat: (chat) async => _routeToScreen(context, chatDetailScreen(context, chat)), onDeleteChat: (chat) async { @@ -49,6 +54,7 @@ class NavigatorWrapper extends StatelessWidget { Widget chatDetailScreen(BuildContext context, ChatModel chat) => ChatDetailScreen( chat: chat, + onExit: () => Navigator.of(context).pop(), onReadChat: (chat) async => chatService.markAsRead(chatId: chat.id), onPressChatTitle: (chat) async { if (chat.isGroupChat) { @@ -104,6 +110,7 @@ class NavigatorWrapper extends StatelessWidget { userId: userId, userModel: user, chatModel: chat, + onExit: () => Navigator.of(context).pop(), onTapUser: (userId) async { var user = await chatService.getUser(userId: userId).first; @@ -123,13 +130,17 @@ class NavigatorWrapper extends StatelessWidget { userId: userId, chatService: chatService, chatOptions: chatOptions, + onExit: () => Navigator.of(context).pop(), onPressCreateGroupChat: () async => _routeToScreen(context, newGroupChatScreen(context)), onPressCreateChat: (user) async { var chat = await _createChat(user.id); if (!context.mounted) return; - return _routeToScreen(context, chatDetailScreen(context, chat)); + return _replaceCurrentScreen( + context, + chatDetailScreen(context, chat), + ); }, ); @@ -138,8 +149,11 @@ class NavigatorWrapper extends StatelessWidget { userId: userId, chatService: chatService, chatOptions: chatOptions, - onContinue: (users) async => - _routeToScreen(context, newGroupChatOverview(context, users)), + onExit: () => Navigator.of(context).pop(), + onContinue: (users) async => _replaceCurrentScreen( + context, + newGroupChatOverview(context, users), + ), ); /// The new group chat overview screen @@ -147,6 +161,7 @@ class NavigatorWrapper extends StatelessWidget { NewGroupChatOverview( options: chatOptions, users: users, + onExit: () => Navigator.of(context).pop(), onComplete: (users, title, description, image) async { String? path; if (image != null) { @@ -163,7 +178,10 @@ class NavigatorWrapper extends StatelessWidget { ); if (!context.mounted) return; - return _routeToScreen(context, chatDetailScreen(context, chat)); + return _replaceCurrentScreen( + context, + chatDetailScreen(context, chat), + ); }, ); @@ -259,4 +277,10 @@ class NavigatorWrapper extends StatelessWidget { Navigator.of(context).push( MaterialPageRoute(builder: (context) => screen), ); + + /// Replaces the current screen with a new screen for the userstory + Future _replaceCurrentScreen(BuildContext context, Widget screen) async => + Navigator.of(context).pushReplacement( + MaterialPageRoute(builder: (context) => screen), + ); } diff --git a/packages/flutter_chat/lib/src/screens/chat_detail/chat_detail_screen.dart b/packages/flutter_chat/lib/src/screens/chat_detail/chat_detail_screen.dart index a2c0aae..afb57ff 100644 --- a/packages/flutter_chat/lib/src/screens/chat_detail/chat_detail_screen.dart +++ b/packages/flutter_chat/lib/src/screens/chat_detail/chat_detail_screen.dart @@ -8,13 +8,15 @@ import "package:flutter_chat/src/config/screen_types.dart"; import "package:flutter_chat/src/screens/chat_detail/widgets/default_message_builder.dart"; import "package:flutter_chat/src/screens/creation/widgets/image_picker.dart"; import "package:flutter_chat/src/util/scope.dart"; +import "package:flutter_hooks/flutter_hooks.dart"; /// Chat detail screen /// Seen when a user clicks on a chat -class ChatDetailScreen extends StatefulWidget { +class ChatDetailScreen extends StatefulHookWidget { /// Constructs a [ChatDetailScreen]. const ChatDetailScreen({ required this.chat, + required this.onExit, required this.onPressChatTitle, required this.onPressUserProfile, required this.onUploadImage, @@ -45,6 +47,9 @@ class ChatDetailScreen extends StatefulWidget { /// Callback function to get the chat title final String Function(ChatModel chat)? getChatTitle; + /// Callback for when the user wants to navigate back + final VoidCallback onExit; + @override State createState() => _ChatDetailScreenState(); } @@ -85,7 +90,8 @@ class _ChatDetailScreenState extends State { @override Widget build(BuildContext context) { - var chatOptions = ChatScope.of(context).options; + var chatScope = ChatScope.of(context); + var chatOptions = chatScope.options; var appBar = _AppBar( chatTitle: chatTitle, onPressChatTitle: widget.onPressChatTitle, @@ -100,6 +106,11 @@ class _ChatDetailScreenState extends State { onReadChat: widget.onReadChat, ); + useEffect(() { + chatScope.popHandler.add(widget.onExit); + return () => chatScope.popHandler.remove(widget.onExit); + }); + if (chatOptions.builders.baseScreenBuilder == null) { return Scaffold( appBar: appBar, diff --git a/packages/flutter_chat/lib/src/screens/chat_profile_screen.dart b/packages/flutter_chat/lib/src/screens/chat_profile_screen.dart index 5338414..600d418 100644 --- a/packages/flutter_chat/lib/src/screens/chat_profile_screen.dart +++ b/packages/flutter_chat/lib/src/screens/chat_profile_screen.dart @@ -2,15 +2,18 @@ import "package:chat_repository_interface/chat_repository_interface.dart"; import "package:flutter/material.dart"; import "package:flutter_chat/src/config/chat_options.dart"; import "package:flutter_chat/src/config/screen_types.dart"; +import "package:flutter_chat/src/util/scope.dart"; +import "package:flutter_hooks/flutter_hooks.dart"; import "package:flutter_profile/flutter_profile.dart"; /// The chat profile screen /// Seen when a user taps on a chat profile /// Also used for group chats -class ChatProfileScreen extends StatelessWidget { +class ChatProfileScreen extends HookWidget { /// Constructs a [ChatProfileScreen] const ChatProfileScreen({ required this.options, + required this.onExit, required this.userId, required this.userModel, required this.service, @@ -41,8 +44,18 @@ class ChatProfileScreen extends StatelessWidget { /// Callback function triggered when the start chat button is pressed final Function(String)? onPressStartChat; + /// Callback for when the user wants to navigate back + final VoidCallback onExit; + @override Widget build(BuildContext context) { + var chatScope = ChatScope.of(context); + + useEffect(() { + chatScope.popHandler.add(onExit); + return () => chatScope.popHandler.remove(onExit); + }); + if (options.builders.baseScreenBuilder == null) { return Scaffold( appBar: _AppBar( diff --git a/packages/flutter_chat/lib/src/screens/chat_screen.dart b/packages/flutter_chat/lib/src/screens/chat_screen.dart index 672f861..08635f7 100644 --- a/packages/flutter_chat/lib/src/screens/chat_screen.dart +++ b/packages/flutter_chat/lib/src/screens/chat_screen.dart @@ -5,17 +5,19 @@ import "package:flutter_chat/src/config/chat_translations.dart"; import "package:flutter_chat/src/config/screen_types.dart"; import "package:flutter_chat/src/services/date_formatter.dart"; import "package:flutter_chat/src/util/scope.dart"; +import "package:flutter_hooks/flutter_hooks.dart"; import "package:flutter_profile/flutter_profile.dart"; /// The chat screen /// Seen when a user is chatting -class ChatScreen extends StatelessWidget { +class ChatScreen extends HookWidget { /// Constructs a [ChatScreen] const ChatScreen({ required this.chatService, required this.chatOptions, required this.onPressChat, required this.onDeleteChat, + required this.onExit, this.onPressStartChat, super.key, }); @@ -35,8 +37,19 @@ class ChatScreen extends StatelessWidget { /// Callback function for deleting a chat. final void Function(ChatModel chat) onDeleteChat; + /// Callback for when the user wants to navigate back + final VoidCallback? onExit; + @override Widget build(BuildContext context) { + var chatScope = ChatScope.of(context); + + useEffect(() { + if (onExit == null) return null; + chatScope.popHandler.add(onExit!); + return () => chatScope.popHandler.remove(onExit!); + }); + if (chatOptions.builders.baseScreenBuilder == null) { return Scaffold( appBar: _AppBar( diff --git a/packages/flutter_chat/lib/src/screens/creation/new_chat_screen.dart b/packages/flutter_chat/lib/src/screens/creation/new_chat_screen.dart index 80fe780..53f0ec3 100644 --- a/packages/flutter_chat/lib/src/screens/creation/new_chat_screen.dart +++ b/packages/flutter_chat/lib/src/screens/creation/new_chat_screen.dart @@ -5,13 +5,16 @@ import "package:flutter_chat/src/config/screen_types.dart"; import "package:flutter_chat/src/screens/creation/widgets/search_field.dart"; import "package:flutter_chat/src/screens/creation/widgets/search_icon.dart"; import "package:flutter_chat/src/screens/creation/widgets/user_list.dart"; +import "package:flutter_chat/src/util/scope.dart"; +import "package:flutter_hooks/flutter_hooks.dart"; /// New chat screen /// This screen is used to create a new chat -class NewChatScreen extends StatefulWidget { +class NewChatScreen extends StatefulHookWidget { /// Constructs a [NewChatScreen] const NewChatScreen({ required this.userId, + required this.onExit, required this.chatService, required this.chatOptions, required this.onPressCreateGroupChat, @@ -34,6 +37,9 @@ class NewChatScreen extends StatefulWidget { /// Callback function triggered when a user is tapped final Function(UserModel) onPressCreateChat; + /// Callback for when the user wants to navigate back + final VoidCallback onExit; + @override State createState() => _NewChatScreenState(); } @@ -45,6 +51,13 @@ class _NewChatScreenState extends State { @override Widget build(BuildContext context) { + var chatScope = ChatScope.of(context); + + useEffect(() { + chatScope.popHandler.add(widget.onExit); + return () => chatScope.popHandler.remove(widget.onExit); + }); + if (widget.chatOptions.builders.baseScreenBuilder == null) { return Scaffold( appBar: _AppBar( diff --git a/packages/flutter_chat/lib/src/screens/creation/new_group_chat_overview.dart b/packages/flutter_chat/lib/src/screens/creation/new_group_chat_overview.dart index 5bcadff..60a60d1 100644 --- a/packages/flutter_chat/lib/src/screens/creation/new_group_chat_overview.dart +++ b/packages/flutter_chat/lib/src/screens/creation/new_group_chat_overview.dart @@ -5,15 +5,18 @@ import "package:flutter/material.dart"; import "package:flutter_chat/src/config/chat_options.dart"; import "package:flutter_chat/src/config/screen_types.dart"; import "package:flutter_chat/src/screens/creation/widgets/image_picker.dart"; +import "package:flutter_chat/src/util/scope.dart"; +import "package:flutter_hooks/flutter_hooks.dart"; import "package:flutter_profile/flutter_profile.dart"; /// New group chat overview /// Seen after the user has selected the users they /// want to add to the group chat -class NewGroupChatOverview extends StatelessWidget { +class NewGroupChatOverview extends HookWidget { /// Constructs a [NewGroupChatOverview] const NewGroupChatOverview({ required this.options, + required this.onExit, required this.users, required this.onComplete, super.key, @@ -25,6 +28,9 @@ class NewGroupChatOverview extends StatelessWidget { /// The users to be added to the group chat final List users; + /// Callback for when the user wants to navigate back + final VoidCallback onExit; + /// Callback function triggered when the group chat is created final Function( List users, @@ -35,6 +41,13 @@ class NewGroupChatOverview extends StatelessWidget { @override Widget build(BuildContext context) { + var chatScope = ChatScope.of(context); + + useEffect(() { + chatScope.popHandler.add(onExit); + return () => chatScope.popHandler.remove(onExit); + }); + if (options.builders.baseScreenBuilder == null) { return Scaffold( appBar: _AppBar( diff --git a/packages/flutter_chat/lib/src/screens/creation/new_group_chat_screen.dart b/packages/flutter_chat/lib/src/screens/creation/new_group_chat_screen.dart index d96f25f..e757e4b 100644 --- a/packages/flutter_chat/lib/src/screens/creation/new_group_chat_screen.dart +++ b/packages/flutter_chat/lib/src/screens/creation/new_group_chat_screen.dart @@ -5,13 +5,16 @@ import "package:flutter_chat/src/config/screen_types.dart"; import "package:flutter_chat/src/screens/creation/widgets/search_field.dart"; import "package:flutter_chat/src/screens/creation/widgets/search_icon.dart"; import "package:flutter_chat/src/screens/creation/widgets/user_list.dart"; +import "package:flutter_chat/src/util/scope.dart"; +import "package:flutter_hooks/flutter_hooks.dart"; /// New group chat screen /// This screen is used to create a new group chat -class NewGroupChatScreen extends StatefulWidget { +class NewGroupChatScreen extends StatefulHookWidget { /// Constructs a [NewGroupChatScreen] const NewGroupChatScreen({ required this.userId, + required this.onExit, required this.chatService, required this.chatOptions, required this.onContinue, @@ -27,6 +30,9 @@ class NewGroupChatScreen extends StatefulWidget { /// The chat options final ChatOptions chatOptions; + /// Callback for when the user wants to navigate back + final VoidCallback onExit; + /// Callback function triggered when the continue button is pressed final Function(List) onContinue; @@ -43,6 +49,12 @@ class _NewGroupChatScreenState extends State { @override Widget build(BuildContext context) { + var chatScope = ChatScope.of(context); + + useEffect(() { + chatScope.popHandler.add(widget.onExit); + return () => chatScope.popHandler.remove(widget.onExit); + }); if (widget.chatOptions.builders.baseScreenBuilder == null) { return Scaffold( appBar: _AppBar(