diff --git a/packages/stream_chat_v1/.gitignore b/packages/stream_chat_v1/.gitignore index 114843e..564aecd 100644 --- a/packages/stream_chat_v1/.gitignore +++ b/packages/stream_chat_v1/.gitignore @@ -32,7 +32,6 @@ /build/ # Web related -lib/generated_plugin_registrant.dart # Symbolication related app.*.symbols diff --git a/packages/stream_chat_v1/ios/Runner.xcodeproj/project.pbxproj b/packages/stream_chat_v1/ios/Runner.xcodeproj/project.pbxproj index daeb9c3..b6f9d7c 100644 --- a/packages/stream_chat_v1/ios/Runner.xcodeproj/project.pbxproj +++ b/packages/stream_chat_v1/ios/Runner.xcodeproj/project.pbxproj @@ -251,6 +251,7 @@ "${BUILT_PRODUCTS_DIR}/SwiftyGif/SwiftyGif.framework", "${BUILT_PRODUCTS_DIR}/connectivity_plus/connectivity_plus.framework", "${BUILT_PRODUCTS_DIR}/file_picker/file_picker.framework", + "${BUILT_PRODUCTS_DIR}/file_selector_ios/file_selector_ios.framework", "${BUILT_PRODUCTS_DIR}/flutter_app_badger/flutter_app_badger.framework", "${BUILT_PRODUCTS_DIR}/flutter_local_notifications/flutter_local_notifications.framework", "${BUILT_PRODUCTS_DIR}/flutter_secure_storage/flutter_secure_storage.framework", @@ -282,6 +283,7 @@ "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/SwiftyGif.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/connectivity_plus.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/file_picker.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/file_selector_ios.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/flutter_app_badger.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/flutter_local_notifications.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/flutter_secure_storage.framework", diff --git a/packages/stream_chat_v1/lib/app.dart b/packages/stream_chat_v1/lib/app.dart index f5f64e3..f58f7a2 100644 --- a/packages/stream_chat_v1/lib/app.dart +++ b/packages/stream_chat_v1/lib/app.dart @@ -1,28 +1,31 @@ import 'dart:async'; +import 'package:example/state/init_data.dart'; import 'package:example/pages/choose_user_page.dart'; -import 'package:example/pages/home_page.dart'; -import 'package:example/utils/localizations.dart'; import 'package:example/pages/splash_screen.dart'; +import 'package:example/routes/app_routes.dart'; +import 'package:example/routes/routes.dart'; +import 'package:example/utils/app_config.dart'; +import 'package:example/utils/local_notification_observer.dart'; +import 'package:example/utils/localizations.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter/scheduler.dart'; import 'package:flutter_secure_storage/flutter_secure_storage.dart'; +import 'package:go_router/go_router.dart'; +import 'package:provider/provider.dart'; import 'package:sentry_flutter/sentry_flutter.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; import 'package:stream_chat_localizations/stream_chat_localizations.dart'; import 'package:stream_chat_persistence/stream_chat_persistence.dart'; import 'package:streaming_shared_preferences/streaming_shared_preferences.dart'; -import 'routes/app_routes.dart'; -import 'routes/routes.dart'; - final chatPersistentClient = StreamChatPersistenceClient( logLevel: Level.SEVERE, connectionMode: ConnectionMode.regular, ); -void sampleAppLogHandler(LogRecord record) async { +void _sampleAppLogHandler(LogRecord record) async { if (kDebugMode) StreamChatClient.defaultLogHandler(record); // report errors to setnry.io @@ -34,14 +37,17 @@ void sampleAppLogHandler(LogRecord record) async { } } -StreamChatClient buildStreamChatClient( - String apiKey, { - Level logLevel = Level.INFO, -}) { +StreamChatClient buildStreamChatClient(String apiKey) { + late Level logLevel; + if (kDebugMode) { + logLevel = Level.INFO; + } else { + logLevel = Level.SEVERE; + } return StreamChatClient( apiKey, logLevel: logLevel, - logHandlerFunction: sampleAppLogHandler, + logHandlerFunction: _sampleAppLogHandler, )..chatPersistenceClient = chatPersistentClient; } @@ -54,7 +60,7 @@ class StreamChatSampleApp extends StatefulWidget { class _StreamChatSampleAppState extends State with SplashScreenStateMixin, TickerProviderStateMixin { - InitData? _initData; + final InitNotifier _initNotifier = InitNotifier(); Future _initConnection() async { String? apiKey, userId, token; @@ -66,7 +72,7 @@ class _StreamChatSampleAppState extends State token = await secureStorage.read(key: kStreamToken); } - final client = buildStreamChatClient(apiKey ?? kStreamApiKey); + final client = buildStreamChatClient(apiKey ?? kDefaultStreamApiKey); if (userId != null && token != null) { await client.connectUser( @@ -87,7 +93,7 @@ class _StreamChatSampleAppState extends State _initConnection().then( (initData) { setState(() { - _initData = initData; + _initNotifier.initData = initData; }); final now = DateTime.now().millisecondsSinceEpoch; @@ -107,67 +113,87 @@ class _StreamChatSampleAppState extends State super.initState(); } + final GlobalKey _navigatorKey = GlobalKey(); + LocalNotificationObserver? localNotificationObserver; + + /// Conditionally sets up the router and adding an observer for the + /// current chat client. + GoRouter _setupRouter() { + if (localNotificationObserver != null) { + localNotificationObserver!.dispose(); + } + localNotificationObserver = LocalNotificationObserver( + _initNotifier.initData!.client, _navigatorKey); + + return GoRouter( + refreshListenable: _initNotifier, + initialLocation: Routes.CHANNEL_LIST_PAGE.path, + navigatorKey: _navigatorKey, + observers: [localNotificationObserver!], + redirect: (context, state) { + final loggedIn = + _initNotifier.initData?.client.state.currentUser != null; + final loggingIn = state.subloc == Routes.CHOOSE_USER.path || + state.subloc == Routes.ADVANCED_OPTIONS.path; + + if (!loggedIn) { + return loggingIn ? null : Routes.CHOOSE_USER.path; + } + + // if the user is logged in but still on the login page, send them to + // the home page + if (loggedIn && state.subloc == Routes.CHOOSE_USER.path) { + return Routes.CHANNEL_LIST_PAGE.path; + } + + return null; + }, + routes: appRoutes, + ); + } + @override Widget build(BuildContext context) { return Stack( alignment: Alignment.center, children: [ - if (_initData != null) - PreferenceBuilder( - preference: _initData!.preferences.getInt( - 'theme', - defaultValue: 0, - ), - builder: (context, snapshot) => MaterialApp( - theme: ThemeData.light(), - darkTheme: ThemeData.dark(), - themeMode: { - -1: ThemeMode.dark, - 0: ThemeMode.system, - 1: ThemeMode.light, - }[snapshot], - supportedLocales: const [ - Locale('en'), - Locale('it'), - ], - localizationsDelegates: const [ - AppLocalizationsDelegate(), - GlobalStreamChatLocalizations.delegate, - GlobalMaterialLocalizations.delegate, - GlobalWidgetsLocalizations.delegate, - ], - builder: (context, child) => StreamChatConfiguration( - data: StreamChatConfigurationData(), - child: StreamChatTheme( - data: StreamChatThemeData( - brightness: Theme.of(context).brightness, + if (_initNotifier.initData != null) + ChangeNotifierProvider.value( + value: _initNotifier, + builder: (context, child) => Builder( + builder: (context) { + context.watch(); // rebuild on change + return PreferenceBuilder( + preference: _initNotifier.initData!.preferences.getInt( + 'theme', + defaultValue: 0, ), - child: child!, - ), - ), - onGenerateRoute: AppRoutes.generateRoute, - onGenerateInitialRoutes: (initialRouteName) { - if (initialRouteName == Routes.HOME) { - return [ - AppRoutes.generateRoute( - RouteSettings( - name: Routes.HOME, - arguments: HomePageArgs(_initData!.client), - ), - )! - ]; - } - return [ - AppRoutes.generateRoute( - const RouteSettings( - name: Routes.CHOOSE_USER, + builder: (context, snapshot) => MaterialApp.router( + theme: ThemeData.light(), + darkTheme: ThemeData.dark(), + themeMode: const { + -1: ThemeMode.dark, + 0: ThemeMode.system, + 1: ThemeMode.light, + }[snapshot], + supportedLocales: const [ + Locale('en'), + Locale('it'), + ], + localizationsDelegates: const [ + AppLocalizationsDelegate(), + GlobalStreamChatLocalizations.delegate, + GlobalMaterialLocalizations.delegate, + GlobalWidgetsLocalizations.delegate, + ], + builder: (context, child) => StreamChat( + client: _initNotifier.initData!.client, + child: child, ), - )! - ]; + routerConfig: _setupRouter(), + ), + ); }, - initialRoute: _initData!.client.state.currentUser == null - ? Routes.CHOOSE_USER - : Routes.HOME, ), ), if (!animationCompleted) buildAnimation(), @@ -175,10 +201,3 @@ class _StreamChatSampleAppState extends State ); } } - -class InitData { - final StreamChatClient client; - final StreamingSharedPreferences preferences; - - InitData(this.client, this.preferences); -} diff --git a/packages/stream_chat_v1/lib/pages/advanced_options_page.dart b/packages/stream_chat_v1/lib/pages/advanced_options_page.dart index 4cd5e09..ab50871 100644 --- a/packages/stream_chat_v1/lib/pages/advanced_options_page.dart +++ b/packages/stream_chat_v1/lib/pages/advanced_options_page.dart @@ -1,13 +1,15 @@ import 'package:example/app.dart'; -import 'package:example/pages/home_page.dart'; +import 'package:example/state/init_data.dart'; import 'package:example/utils/localizations.dart'; import 'package:example/routes/routes.dart'; import 'package:example/widgets/stream_version.dart'; import 'package:flutter/material.dart'; import 'package:flutter_secure_storage/flutter_secure_storage.dart'; +import 'package:go_router/go_router.dart'; +import 'package:provider/provider.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; -import 'choose_user_page.dart'; +import 'package:example/pages/choose_user_page.dart'; class AdvancedOptionsPage extends StatefulWidget { const AdvancedOptionsPage({super.key}); @@ -41,6 +43,86 @@ class _AdvancedOptionsPageState extends State { super.dispose(); } + Future _login() async { + if (loading) { + return; + } + if (_formKey.currentState!.validate()) { + final apiKey = _apiKeyController.text; + final userId = _userIdController.text; + final userToken = _userTokenController.text; + final username = _usernameController.text; + + loading = true; + showDialog( + barrierDismissible: false, + context: context, + barrierColor: StreamChatTheme.of(context).colorTheme.overlay, + builder: (context) => Center( + child: Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(16), + color: StreamChatTheme.of(context).colorTheme.barsBg, + ), + height: 100, + width: 100, + child: const Center( + child: CircularProgressIndicator(), + ), + ), + ), + ); + + final client = buildStreamChatClient(apiKey); + final router = GoRouter.of(context); + final initNotifier = context.read(); + + try { + await client.connectUser( + User( + id: userId, + extraData: { + 'name': username, + }, + ), + userToken, + ); + + const secureStorage = FlutterSecureStorage(); + await Future.wait([ + secureStorage.write( + key: kStreamApiKey, + value: apiKey, + ), + secureStorage.write( + key: kStreamUserId, + value: userId, + ), + secureStorage.write( + key: kStreamToken, + value: userToken, + ), + ]); + } catch (e) { + debugPrint(e.toString()); + var errorText = AppLocalizations.of(context).errorConnecting; + if (e is Map) { + errorText = e['message'] ?? errorText; + } + Navigator.of(context).pop(); + setState(() { + _apiKeyError = errorText.toUpperCase(); + }); + loading = false; + return; + } + loading = false; + initNotifier.initData = initNotifier.initData!.copyWith(client: client); + + router.goNamed(Routes.CHOOSE_USER.name); + } + } + @override Widget build(BuildContext context) { return Scaffold( @@ -260,6 +342,7 @@ class _AdvancedOptionsPageState extends State { ), ), ), + onPressed: _login, child: Text( AppLocalizations.of(context).login, style: TextStyle( @@ -271,84 +354,6 @@ class _AdvancedOptionsPageState extends State { : Colors.white, ), ), - onPressed: () async { - if (loading) { - return; - } - if (_formKey.currentState!.validate()) { - final apiKey = _apiKeyController.text; - final userId = _userIdController.text; - final userToken = _userTokenController.text; - final username = _usernameController.text; - - loading = true; - showDialog( - barrierDismissible: false, - context: context, - barrierColor: - StreamChatTheme.of(context).colorTheme.overlay, - builder: (context) => Center( - child: Container( - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(16), - color: StreamChatTheme.of(context) - .colorTheme - .barsBg, - ), - height: 100, - width: 100, - child: const Center( - child: CircularProgressIndicator(), - ), - ), - ), - ); - - final client = buildStreamChatClient(apiKey); - final navigator = Navigator.of(context); - - try { - await client.connectUser( - User(id: userId, extraData: { - 'name': username, - }), - userToken, - ); - - const secureStorage = FlutterSecureStorage(); - secureStorage.write( - key: kStreamApiKey, - value: apiKey, - ); - secureStorage.write( - key: kStreamUserId, - value: userId, - ); - secureStorage.write( - key: kStreamToken, - value: userToken, - ); - } catch (e) { - var errorText = - AppLocalizations.of(context).errorConnecting; - if (e is Map) { - errorText = e['message'] ?? errorText; - } - Navigator.pop(context); - setState(() { - _apiKeyError = errorText.toUpperCase(); - }); - loading = false; - return; - } - loading = false; - await navigator.pushNamedAndRemoveUntil( - Routes.HOME, - ModalRoute.withName(Routes.HOME), - arguments: HomePageArgs(client), - ); - } - }, ), const StreamVersion(), ], diff --git a/packages/stream_chat_v1/lib/pages/channel_list_page.dart b/packages/stream_chat_v1/lib/pages/channel_list_page.dart index 7812d7b..7ee34bd 100644 --- a/packages/stream_chat_v1/lib/pages/channel_list_page.dart +++ b/packages/stream_chat_v1/lib/pages/channel_list_page.dart @@ -1,17 +1,21 @@ import 'dart:async'; -import 'package:example/utils/localizations.dart'; -import 'package:example/routes/routes.dart'; +import 'package:example/app.dart'; +import 'package:example/state/init_data.dart'; import 'package:example/pages/user_mentions_page.dart'; +import 'package:example/routes/routes.dart'; +import 'package:example/utils/app_config.dart'; +import 'package:example/utils/localizations.dart'; +import 'package:example/widgets/channel_list.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_app_badger/flutter_app_badger.dart'; import 'package:flutter_secure_storage/flutter_secure_storage.dart'; +import 'package:go_router/go_router.dart'; +import 'package:provider/provider.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; import 'package:streaming_shared_preferences/streaming_shared_preferences.dart'; -import 'package:example/widgets/channel_list.dart'; - class ChannelListPage extends StatefulWidget { const ChannelListPage({ Key? key, @@ -71,12 +75,10 @@ class _ChannelListPageState extends State { return Scaffold( backgroundColor: StreamChatTheme.of(context).colorTheme.appBg, appBar: StreamChannelListHeader( - onNewChatButtonTap: () { - Navigator.pushNamed(context, Routes.NEW_CHAT); - }, - preNavigationCallback: () { - FocusScope.of(context).requestFocus(FocusNode()); - }, + onNewChatButtonTap: () => + GoRouter.of(context).pushNamed(Routes.NEW_CHAT.name), + preNavigationCallback: () => + FocusScope.of(context).requestFocus(FocusNode()), ), drawer: LeftDrawer( user: user, @@ -188,10 +190,8 @@ class LeftDrawer extends StatelessWidget { .withOpacity(.5), ), onTap: () { - Navigator.popAndPushNamed( - context, - Routes.NEW_CHAT, - ); + Navigator.of(context).pop(); + GoRouter.of(context).pushNamed(Routes.NEW_CHAT.name); }, title: Text( AppLocalizations.of(context).newDirectMessage, @@ -208,10 +208,8 @@ class LeftDrawer extends StatelessWidget { .withOpacity(.5), ), onTap: () { - Navigator.popAndPushNamed( - context, - Routes.NEW_GROUP_CHAT, - ); + Navigator.of(context).pop(); + GoRouter.of(context).pushNamed(Routes.NEW_GROUP_CHAT.name); }, title: Text( AppLocalizations.of(context).newGroup, @@ -226,22 +224,21 @@ class LeftDrawer extends StatelessWidget { child: ListTile( onTap: () async { final client = StreamChat.of(context).client; - final navigator = - Navigator.of(context, rootNavigator: true); - Navigator.pop(context); + final router = GoRouter.of(context); + final initNotifier = context.read(); if (!kIsWeb) { const secureStorage = FlutterSecureStorage(); await secureStorage.deleteAll(); } - client.disconnectUser(); + await client.disconnectUser(flushChatPersistence: true); await client.dispose(); + initNotifier.initData = initNotifier.initData!.copyWith( + client: + buildStreamChatClient(kDefaultStreamApiKey)); - await navigator.pushNamedAndRemoveUntil( - Routes.CHOOSE_USER, - ModalRoute.withName(Routes.CHOOSE_USER), - ); + router.goNamed(Routes.CHOOSE_USER.name); }, leading: StreamSvgIcon.user( color: StreamChatTheme.of(context) diff --git a/packages/stream_chat_v1/lib/pages/channel_media_display_screen.dart b/packages/stream_chat_v1/lib/pages/channel_media_display_screen.dart index e16cb93..18bd7a5 100644 --- a/packages/stream_chat_v1/lib/pages/channel_media_display_screen.dart +++ b/packages/stream_chat_v1/lib/pages/channel_media_display_screen.dart @@ -1,10 +1,10 @@ import 'package:example/utils/localizations.dart'; -import 'package:example/routes/routes.dart'; import 'package:flutter/material.dart'; +import 'package:go_router/go_router.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; import 'package:video_player/video_player.dart'; -import 'channel_page.dart'; +import '../routes/routes.dart'; class ChannelMediaDisplayScreen extends StatefulWidget { final StreamMessageThemeData messageTheme; @@ -156,23 +156,16 @@ class _ChannelMediaDisplayScreenState extends State { startIndex: position, userName: media[position].message.user!.name, onShowMessage: (m, c) async { - final client = - StreamChat.of(context).client; - final navigator = Navigator.of(context); - final message = m; - final channel = client.channel( - c.type, - id: c.id, - ); + final router = GoRouter.of(context); if (channel.state == null) { await channel.watch(); } - navigator.pushNamed( - Routes.CHANNEL_PAGE, - arguments: ChannelPageArgs( - channel: channel, - initialMessage: message, - ), + router.pushNamed( + Routes.CHANNEL_PAGE.name, + params: + Routes.CHANNEL_PAGE.params(channel), + queryParams: + Routes.CHANNEL_PAGE.queryParams(m), ); }, ), diff --git a/packages/stream_chat_v1/lib/pages/channel_page.dart b/packages/stream_chat_v1/lib/pages/channel_page.dart index 23e57d7..e2714ee 100644 --- a/packages/stream_chat_v1/lib/pages/channel_page.dart +++ b/packages/stream_chat_v1/lib/pages/channel_page.dart @@ -2,21 +2,9 @@ import 'package:collection/collection.dart'; import 'package:example/routes/routes.dart'; import 'package:example/pages/thread_page.dart'; import 'package:flutter/material.dart'; +import 'package:go_router/go_router.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; -import 'chat_info_screen.dart'; -import 'group_info_screen.dart'; - -class ChannelPageArgs { - final Channel? channel; - final Message? initialMessage; - - const ChannelPageArgs({ - this.channel, - this.initialMessage, - }); -} - class ChannelPage extends StatefulWidget { final int? initialScrollIndex; final double? initialAlignment; @@ -63,9 +51,10 @@ class _ChannelPageState extends State { backgroundColor: StreamChatTheme.of(context).colorTheme.appBg, appBar: StreamChannelHeader( showTypingIndicator: false, + onBackPressed: () => GoRouter.of(context).pop(), onImageTap: () async { final channel = StreamChannel.of(context).channel; - final navigator = Navigator.of(context); + final router = GoRouter.of(context); if (channel.memberCount == 2 && channel.isDistinct) { final currentUser = StreamChat.of(context).currentUser; @@ -73,34 +62,16 @@ class _ChannelPageState extends State { (element) => element.user!.id != currentUser!.id, ); if (otherUser != null) { - final pop = await Navigator.push( - context, - MaterialPageRoute( - builder: (context) => StreamChannel( - channel: channel, - child: ChatInfoScreen( - messageTheme: StreamChatTheme.of(context).ownMessageTheme, - user: otherUser.user, - ), - ), - ), + router.pushNamed( + Routes.CHAT_INFO_SCREEN.name, + params: Routes.CHAT_INFO_SCREEN.params(channel), + extra: otherUser.user, ); - - if (pop == true) { - navigator.pop(); - } } } else { - await Navigator.push( - context, - MaterialPageRoute( - builder: (context) => StreamChannel( - channel: channel, - child: GroupInfoScreen( - messageTheme: StreamChatTheme.of(context).ownMessageTheme, - ), - ), - ), + GoRouter.of(context).pushNamed( + Routes.GROUP_INFO_SCREEN.name, + params: Routes.GROUP_INFO_SCREEN.params(channel), ); } }, @@ -117,7 +88,7 @@ class _ChannelPageState extends State { onMessageSwiped: _reply, messageFilter: defaultFilter, messageBuilder: (context, details, messages, defaultMessage) { - final navigator = Navigator.of(context); + final router = GoRouter.of(context); return defaultMessage.copyWith( onReplyTap: _reply, onShowMessage: (m, c) async { @@ -130,12 +101,10 @@ class _ChannelPageState extends State { if (channel.state == null) { await channel.watch(); } - navigator.pushReplacementNamed( - Routes.CHANNEL_PAGE, - arguments: ChannelPageArgs( - channel: channel, - initialMessage: message, - ), + router.goNamed( + Routes.CHANNEL_PAGE.name, + params: Routes.CHANNEL_PAGE.params(channel), + queryParams: Routes.CHANNEL_PAGE.queryParams(message), ); }, deletedBottomRowBuilder: (context, message) { diff --git a/packages/stream_chat_v1/lib/pages/choose_user_page.dart b/packages/stream_chat_v1/lib/pages/choose_user_page.dart index 37969c6..d5be33a 100644 --- a/packages/stream_chat_v1/lib/pages/choose_user_page.dart +++ b/packages/stream_chat_v1/lib/pages/choose_user_page.dart @@ -1,12 +1,13 @@ -import 'package:example/app.dart'; +import 'package:example/state/init_data.dart'; import 'package:example/utils/app_config.dart'; -import 'package:example/pages/home_page.dart'; import 'package:example/utils/localizations.dart'; import 'package:example/widgets/stream_version.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_secure_storage/flutter_secure_storage.dart'; import 'package:flutter_svg/flutter_svg.dart'; +import 'package:go_router/go_router.dart'; +import 'package:provider/provider.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; import '../routes/routes.dart'; @@ -94,12 +95,10 @@ class ChooseUserPage extends StatelessWidget { ), ); - final client = StreamChatClient( - kDefaultStreamApiKey, - logLevel: Level.INFO, - )..chatPersistenceClient = chatPersistentClient; + final client = + context.read().initData!.client; - final navigator = Navigator.of(context); + final router = GoRouter.of(context); await client.connectUser( user, @@ -121,11 +120,7 @@ class ChooseUserPage extends StatelessWidget { value: token, ); } - navigator.pushNamedAndRemoveUntil( - Routes.HOME, - ModalRoute.withName(Routes.HOME), - arguments: HomePageArgs(client), - ); + router.replaceNamed(Routes.CHANNEL_LIST_PAGE.name); }, leading: StreamUserAvatar( user: user, @@ -157,9 +152,8 @@ class ChooseUserPage extends StatelessWidget { ); }), ListTile( - onTap: () { - Navigator.pushNamed(context, Routes.ADVANCED_OPTIONS); - }, + onTap: () => GoRouter.of(context) + .pushNamed(Routes.ADVANCED_OPTIONS.name), leading: CircleAvatar( backgroundColor: StreamChatTheme.of(context).colorTheme.borders, diff --git a/packages/stream_chat_v1/lib/pages/group_chat_details_screen.dart b/packages/stream_chat_v1/lib/pages/group_chat_details_screen.dart index 8b813ec..488b3d5 100644 --- a/packages/stream_chat_v1/lib/pages/group_chat_details_screen.dart +++ b/packages/stream_chat_v1/lib/pages/group_chat_details_screen.dart @@ -1,16 +1,17 @@ +import 'package:example/state/new_group_chat_state.dart'; import 'package:example/utils/localizations.dart'; import 'package:flutter/material.dart'; +import 'package:go_router/go_router.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; -import 'channel_page.dart'; import '../routes/routes.dart'; class GroupChatDetailsScreen extends StatefulWidget { - final List? selectedUsers; + final NewGroupChatState groupChatState; const GroupChatDetailsScreen({ Key? key, - required this.selectedUsers, + required this.groupChatState, }) : super(key: key); @override @@ -18,14 +19,12 @@ class GroupChatDetailsScreen extends StatefulWidget { } class _GroupChatDetailsScreenState extends State { - late final _selectedUsers = [...?widget.selectedUsers]; - late final TextEditingController _groupNameController = TextEditingController()..addListener(_groupNameListener); bool _isGroupNameEmpty = true; - int get _totalUsers => _selectedUsers.length; + int get _totalUsers => widget.groupChatState.users.length; void _groupNameListener() { final name = _groupNameController.text; @@ -48,7 +47,7 @@ class _GroupChatDetailsScreenState extends State { Widget build(BuildContext context) { return WillPopScope( onWillPop: () async { - Navigator.pop(context, _selectedUsers); + GoRouter.of(context).pop(); return false; }, child: Scaffold( @@ -123,21 +122,21 @@ class _GroupChatDetailsScreenState extends State { try { final groupName = _groupNameController.text; final client = StreamChat.of(context).client; - final navigator = Navigator.of(context); + final router = GoRouter.of(context); final channel = client.channel('messaging', id: const Uuid().v4(), extraData: { 'members': [ client.state.currentUser!.id, - ..._selectedUsers.map((e) => e.id), + ...widget.groupChatState.users + .map((e) => e.id), ], 'name': groupName, }); await channel.watch(); - navigator.pushNamedAndRemoveUntil( - Routes.CHANNEL_PAGE, - ModalRoute.withName(Routes.CHANNEL_LIST_PAGE), - arguments: ChannelPageArgs(channel: channel), + router.goNamed( + Routes.CHANNEL_PAGE.name, + params: Routes.CHANNEL_PAGE.params(channel), ); } catch (err) { _showErrorAlert(); @@ -192,67 +191,73 @@ class _GroupChatDetailsScreenState extends State { ), ), ), - Expanded( - child: GestureDetector( - behavior: HitTestBehavior.opaque, - onPanDown: (_) => FocusScope.of(context).unfocus(), - child: ListView.separated( - itemCount: _selectedUsers.length + 1, - separatorBuilder: (_, __) => Container( - height: 1, - color: StreamChatTheme.of(context).colorTheme.borders, - ), - itemBuilder: (_, index) { - if (index == _selectedUsers.length) { - return Container( - height: 1, - color: StreamChatTheme.of(context) - .colorTheme - .borders, - ); - } - final user = _selectedUsers[index]; - return ListTile( - key: ObjectKey(user), - leading: StreamUserAvatar( - user: user, - constraints: const BoxConstraints.tightFor( - width: 40, - height: 40, - ), - ), - title: Text( - user.name, - style: - const TextStyle(fontWeight: FontWeight.bold), - ), - contentPadding: const EdgeInsets.symmetric( - horizontal: 12, - vertical: 8, - ), - trailing: IconButton( - icon: Icon( - Icons.clear_rounded, + AnimatedBuilder( + animation: widget.groupChatState, + builder: (context, child) { + return Expanded( + child: GestureDetector( + behavior: HitTestBehavior.opaque, + onPanDown: (_) => FocusScope.of(context).unfocus(), + child: ListView.separated( + itemCount: widget.groupChatState.users.length + 1, + separatorBuilder: (_, __) => Container( + height: 1, color: StreamChatTheme.of(context) .colorTheme - .textHighEmphasis, + .borders, ), - padding: const EdgeInsets.all(0), - splashRadius: 24, - onPressed: () { - setState(() { - _selectedUsers.remove(user); - }); - if (_selectedUsers.isEmpty) { - Navigator.pop(context, _selectedUsers); + itemBuilder: (_, index) { + if (index == + widget.groupChatState.users.length) { + return Container( + height: 1, + color: StreamChatTheme.of(context) + .colorTheme + .borders, + ); } + final user = widget.groupChatState.users + .elementAt(index); + return ListTile( + key: ObjectKey(user), + leading: StreamUserAvatar( + user: user, + constraints: const BoxConstraints.tightFor( + width: 40, + height: 40, + ), + ), + title: Text( + user.name, + style: const TextStyle( + fontWeight: FontWeight.bold), + ), + contentPadding: const EdgeInsets.symmetric( + horizontal: 12, + vertical: 8, + ), + trailing: IconButton( + icon: Icon( + Icons.clear_rounded, + color: StreamChatTheme.of(context) + .colorTheme + .textHighEmphasis, + ), + padding: const EdgeInsets.all(0), + splashRadius: 24, + onPressed: () { + widget.groupChatState.removeUser(user); + if (widget.groupChatState.users.isEmpty) { + GoRouter.of(context).pop(); + } + }, + ), + ); }, ), - ); - }, - ), - ), - ), + ), + ); + }), ], ), ); @@ -308,6 +313,7 @@ class _GroupChatDetailsScreenState extends State { mainAxisAlignment: MainAxisAlignment.center, children: [ TextButton( + onPressed: GoRouter.of(context).pop, child: Text( AppLocalizations.of(context).ok, style: StreamChatTheme.of(context) @@ -318,9 +324,6 @@ class _GroupChatDetailsScreenState extends State { .colorTheme .accentPrimary), ), - onPressed: () { - Navigator.of(context).pop(); - }, ), ], ), diff --git a/packages/stream_chat_v1/lib/pages/group_info_screen.dart b/packages/stream_chat_v1/lib/pages/group_info_screen.dart index 95c0680..303858e 100644 --- a/packages/stream_chat_v1/lib/pages/group_info_screen.dart +++ b/packages/stream_chat_v1/lib/pages/group_info_screen.dart @@ -2,14 +2,14 @@ import 'dart:async'; import 'package:collection/collection.dart' show IterableExtension; import 'package:example/pages/channel_file_display_screen.dart'; +import 'package:example/routes/routes.dart'; import 'package:example/utils/localizations.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; +import 'package:go_router/go_router.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; import 'channel_media_display_screen.dart'; -import 'channel_page.dart'; -import 'chat_info_screen.dart'; import 'pinned_messages_screen.dart'; class GroupInfoScreen extends StatefulWidget { @@ -647,7 +647,7 @@ class _GroupInfoScreenState extends State { onTap: () async { final streamChannel = StreamChannel.of(context); final streamChat = StreamChat.of(context); - final navigator = Navigator.of(context); + final router = GoRouter.of(context); final res = await showConfirmationBottomSheet( context, title: AppLocalizations.of(context).leaveConversation, @@ -662,7 +662,7 @@ class _GroupInfoScreenState extends State { if (res == true) { final channel = streamChannel.channel; await channel.removeMembers([streamChat.currentUser!.id]); - navigator.pop(); + router.pop(); } }, ), @@ -865,7 +865,7 @@ class _GroupInfoScreenState extends State { AppLocalizations.of(context).viewInfo, () async { final client = StreamChat.of(context).client; - final navigator = Navigator.of(context); + final router = GoRouter.of(context); final c = client.channel('messaging', extraData: { 'members': [ @@ -876,16 +876,10 @@ class _GroupInfoScreenState extends State { await c.watch(); - await navigator.push( - MaterialPageRoute( - builder: (context) => StreamChannel( - channel: c, - child: ChatInfoScreen( - messageTheme: widget.messageTheme, - user: user, - ), - ), - ), + router.pushNamed( + Routes.CHAT_INFO_SCREEN.name, + params: Routes.CHAT_INFO_SCREEN.params(c), + extra: user, ); }, ), @@ -901,7 +895,7 @@ class _GroupInfoScreenState extends State { AppLocalizations.of(context).message, () async { final client = StreamChat.of(context).client; - final navigator = Navigator.of(context); + final router = GoRouter.of(context); final c = client.channel('messaging', extraData: { 'members': [ @@ -912,13 +906,9 @@ class _GroupInfoScreenState extends State { await c.watch(); - await navigator.push( - MaterialPageRoute( - builder: (context) => StreamChannel( - channel: c, - child: const ChannelPage(), - ), - ), + router.pushNamed( + Routes.CHANNEL_PAGE.name, + params: Routes.CHANNEL_PAGE.params(c), ); }, ), @@ -934,7 +924,7 @@ class _GroupInfoScreenState extends State { size: 24.0, ), AppLocalizations.of(context).removeFromGroup, () async { - final navigator = Navigator.of(context); + final router = GoRouter.of(context); final res = await showConfirmationBottomSheet( context, title: AppLocalizations.of(context).removeMember, @@ -949,21 +939,23 @@ class _GroupInfoScreenState extends State { if (res == true) { await channel.removeMembers([user.id]); } - navigator.pop(); + router.pop(); }, color: StreamChatTheme.of(context).colorTheme.accentError), _buildModalListTile( - context, - StreamSvgIcon.closeSmall( - color: StreamChatTheme.of(context) - .colorTheme - .textLowEmphasis, - size: 24.0, - ), - AppLocalizations.of(context).cancel, () { - Navigator.pop(context); - }), + context, + StreamSvgIcon.closeSmall( + color: StreamChatTheme.of(context) + .colorTheme + .textLowEmphasis, + size: 24.0, + ), + AppLocalizations.of(context).cancel, + () { + Navigator.pop(context); + }, + ), ], ), ), diff --git a/packages/stream_chat_v1/lib/pages/home_page.dart b/packages/stream_chat_v1/lib/pages/home_page.dart deleted file mode 100644 index 623844a..0000000 --- a/packages/stream_chat_v1/lib/pages/home_page.dart +++ /dev/null @@ -1,115 +0,0 @@ -import 'dart:async'; - -import 'package:example/pages/channel_page.dart'; -import 'package:example/utils/notifications_service.dart'; -import 'package:example/routes/app_routes.dart'; -import 'package:example/routes/routes.dart'; -import 'package:flutter/material.dart'; -import 'package:stream_chat_flutter/stream_chat_flutter.dart'; - -class MyObserver extends NavigatorObserver { - Route? currentRoute; - late final StreamSubscription _subscription; - - MyObserver( - StreamChatClient client, - GlobalKey navigatorKey, - ) { - _subscription = client - .on( - EventType.messageNew, - EventType.notificationMessageNew, - ) - .listen((event) { - if (event.message?.user?.id == client.state.currentUser?.id) { - return; - } - final channelId = event.channelId; - if (currentRoute?.settings.name == Routes.CHANNEL_PAGE) { - final args = currentRoute?.settings.arguments as ChannelPageArgs; - if (args.channel?.id == channelId) { - return; - } - } - - showLocalNotification( - event, - client.state.currentUser!.id, - navigatorKey.currentState!.context, - ); - }); - } - - @override - void didPop(Route route, Route? previousRoute) { - currentRoute = route; - } - - @override - void didPush(Route route, Route? previousRoute) { - currentRoute = route; - } - - @override - void didRemove(Route route, Route? previousRoute) { - currentRoute = route; - } - - @override - void didReplace({Route? newRoute, Route? oldRoute}) { - currentRoute = newRoute; - } - - void dispose() { - _subscription.cancel(); - } -} - -class HomePageArgs { - final StreamChatClient chatClient; - - HomePageArgs(this.chatClient); -} - -class HomePage extends StatefulWidget { - const HomePage({ - Key? key, - required this.chatClient, - }) : super(key: key); - - final StreamChatClient chatClient; - - @override - State createState() => _HomePageState(); -} - -class _HomePageState extends State { - final GlobalKey _navigatorKey = GlobalKey(); - MyObserver? _observer; - - @override - Widget build(BuildContext context) { - return StreamChat( - client: widget.chatClient, - child: WillPopScope( - onWillPop: () async { - final canPop = await _navigatorKey.currentState?.maybePop() ?? false; - return !canPop; - }, - child: Navigator( - key: _navigatorKey, - onGenerateRoute: AppRoutes.generateRoute, - initialRoute: Routes.CHANNEL_LIST_PAGE, - observers: [_observer!], - ), - ), - ); - } - - @override - void didChangeDependencies() { - _observer?.dispose(); - _observer = MyObserver(widget.chatClient, _navigatorKey); - super.didChangeDependencies(); - } -} diff --git a/packages/stream_chat_v1/lib/pages/new_chat_screen.dart b/packages/stream_chat_v1/lib/pages/new_chat_screen.dart index 5b06df5..45c3528 100644 --- a/packages/stream_chat_v1/lib/pages/new_chat_screen.dart +++ b/packages/stream_chat_v1/lib/pages/new_chat_screen.dart @@ -2,9 +2,9 @@ import 'dart:async'; import 'package:example/utils/localizations.dart'; import 'package:flutter/material.dart'; +import 'package:go_router/go_router.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; -import 'channel_page.dart'; import '../widgets/chips_input_text_field.dart'; import '../routes/routes.dart'; @@ -61,13 +61,14 @@ class _NewChatScreenState extends State { _userNameQuery = _controller.text; _isSearchActive = _userNameQuery.isNotEmpty; }); + + userListController.filter = Filter.and([ + if (_userNameQuery.isNotEmpty) + Filter.autoComplete('name', _userNameQuery), + Filter.notEqual('id', StreamChat.of(context).currentUser!.id), + ]); + userListController.doInitialLoad(); } - userListController.filter = Filter.and([ - if (_userNameQuery.isNotEmpty) - Filter.autoComplete('name', _userNameQuery), - Filter.notEqual('id', StreamChat.of(context).currentUser!.id), - ]); - userListController.doInitialLoad(); }); } @@ -248,9 +249,8 @@ class _NewChatScreenState extends State { if (!_isSearchActive && !_selectedUsers.isNotEmpty) InkWell( onTap: () { - Navigator.pushNamed( - context, - Routes.NEW_GROUP_CHAT, + GoRouter.of(context).pushNamed( + Routes.NEW_GROUP_CHAT.name, ); }, child: Padding( @@ -311,8 +311,6 @@ class _NewChatScreenState extends State { onPanDown: (_) => FocusScope.of(context).unfocus(), child: StreamUserListView( controller: userListController, - // groupAlphabetically: - // _isSearchActive ? false : true, onUserTap: (user) { _controller.clear(); if (!_selectedUsers.contains(user)) { @@ -409,11 +407,9 @@ class _NewChatScreenState extends State { return message; }, onMessageSent: (m) { - Navigator.pushNamedAndRemoveUntil( - context, - Routes.CHANNEL_PAGE, - ModalRoute.withName(Routes.CHANNEL_LIST_PAGE), - arguments: ChannelPageArgs(channel: channel), + GoRouter.of(context).goNamed( + Routes.CHANNEL_PAGE.name, + params: Routes.CHANNEL_PAGE.params(channel!), ); }, ), diff --git a/packages/stream_chat_v1/lib/pages/new_group_chat_screen.dart b/packages/stream_chat_v1/lib/pages/new_group_chat_screen.dart index 8446e6a..fbe2a71 100644 --- a/packages/stream_chat_v1/lib/pages/new_group_chat_screen.dart +++ b/packages/stream_chat_v1/lib/pages/new_group_chat_screen.dart @@ -1,7 +1,9 @@ import 'dart:async'; +import 'package:example/state/new_group_chat_state.dart'; import 'package:example/utils/localizations.dart'; import 'package:flutter/material.dart'; +import 'package:go_router/go_router.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; import '../routes/routes.dart'; @@ -20,7 +22,7 @@ class _NewGroupChatScreenState extends State { String _userNameQuery = ''; - final _selectedUsers = {}; + final groupChatState = NewGroupChatState(); bool _isSearchActive = false; @@ -69,250 +71,240 @@ class _NewGroupChatScreenState extends State { @override Widget build(BuildContext context) { - return Scaffold( - backgroundColor: StreamChatTheme.of(context).colorTheme.appBg, - appBar: AppBar( - elevation: 1, - backgroundColor: StreamChatTheme.of(context).colorTheme.barsBg, - leading: const StreamBackButton(), - title: Text( - AppLocalizations.of(context).addGroupMembers, - style: TextStyle( - color: StreamChatTheme.of(context).colorTheme.textHighEmphasis, - fontSize: 16, - ), - ), - centerTitle: true, - actions: [ - if (_selectedUsers.isNotEmpty) - IconButton( - icon: StreamSvgIcon.arrowRight( - color: StreamChatTheme.of(context).colorTheme.accentPrimary, + return AnimatedBuilder( + animation: groupChatState, + builder: (context, child) { + final state = groupChatState; + return Scaffold( + backgroundColor: StreamChatTheme.of(context).colorTheme.appBg, + appBar: AppBar( + elevation: 1, + backgroundColor: StreamChatTheme.of(context).colorTheme.barsBg, + leading: const StreamBackButton(), + title: Text( + AppLocalizations.of(context).addGroupMembers, + style: TextStyle( + color: StreamChatTheme.of(context).colorTheme.textHighEmphasis, + fontSize: 16, ), - onPressed: () async { - final updatedList = await Navigator.pushNamed( - context, - Routes.NEW_GROUP_CHAT_DETAILS, - arguments: _selectedUsers.toList(growable: false), - ); - if (updatedList != null) { - setState(() { - _selectedUsers - ..clear() - ..addAll(updatedList as Iterable); - }); - } - }, - ) - ], - ), - body: StreamConnectionStatusBuilder( - statusBuilder: (context, status) { - String statusString = ''; - bool showStatus = true; - - switch (status) { - case ConnectionStatus.connected: - statusString = AppLocalizations.of(context).connected; - showStatus = false; - break; - case ConnectionStatus.connecting: - statusString = AppLocalizations.of(context).reconnecting; - break; - case ConnectionStatus.disconnected: - statusString = AppLocalizations.of(context).disconnected; - break; - } - return StreamInfoTile( - showMessage: showStatus, - tileAnchor: Alignment.topCenter, - childAnchor: Alignment.topCenter, - message: statusString, - child: NestedScrollView( - floatHeaderSlivers: true, - headerSliverBuilder: - (BuildContext context, bool innerBoxIsScrolled) { - return [ - SliverToBoxAdapter( - child: SearchTextField( - controller: _controller, - hintText: AppLocalizations.of(context).search, - ), + ), + centerTitle: true, + actions: [ + if (state.users.isNotEmpty) + IconButton( + icon: StreamSvgIcon.arrowRight( + color: StreamChatTheme.of(context).colorTheme.accentPrimary, ), - if (_selectedUsers.isNotEmpty) - SliverToBoxAdapter( - child: SizedBox( - height: 104, - child: ListView.separated( - scrollDirection: Axis.horizontal, - itemCount: _selectedUsers.length, - padding: const EdgeInsets.all(8), - separatorBuilder: (_, __) => - const SizedBox(width: 16), - itemBuilder: (_, index) { - final user = _selectedUsers.elementAt(index); - return Column( - children: [ - Stack( - children: [ - StreamUserAvatar( - onlineIndicatorAlignment: - const Alignment(0.9, 0.9), - user: user, - showOnlineStatus: true, - borderRadius: BorderRadius.circular(32), - constraints: - const BoxConstraints.tightFor( - height: 64, - width: 64, - ), - ), - Positioned( - top: -4, - right: -4, - child: GestureDetector( - onTap: () { - if (_selectedUsers.contains(user)) { - setState(() => - _selectedUsers.remove(user)); - } - }, - child: Container( - decoration: BoxDecoration( - color: StreamChatTheme.of(context) - .colorTheme - .appBg, - shape: BoxShape.circle, - border: Border.all( - color: StreamChatTheme.of(context) - .colorTheme - .appBg, - ), - ), - child: StreamSvgIcon.close( - color: StreamChatTheme.of(context) - .colorTheme - .textHighEmphasis, - size: 24, - ), - ), - ), - ) - ], - ), - const SizedBox(height: 4), - Text( - user.name.split(' ')[0], - style: const TextStyle( - fontWeight: FontWeight.bold, - fontSize: 12, - ), - ), - ], - ); - }, - ), - ), - ), - SliverPersistentHeader( - pinned: true, - delegate: _HeaderDelegate( - height: 32, - child: Container( - width: double.maxFinite, - decoration: BoxDecoration( - gradient: - StreamChatTheme.of(context).colorTheme.bgGradient, - ), - child: Padding( - padding: const EdgeInsets.symmetric( - vertical: 8, - horizontal: 8, - ), - child: Text( - _isSearchActive - ? '${AppLocalizations.of(context).matchesFor} "$_userNameQuery"' - : AppLocalizations.of(context).onThePlatorm, - style: TextStyle( - color: StreamChatTheme.of(context) - .colorTheme - .textLowEmphasis, - ), - ), - ), - ), - ), - ), - ]; - }, - body: GestureDetector( - behavior: HitTestBehavior.opaque, - onPanDown: (_) => FocusScope.of(context).unfocus(), - child: StreamUserListView( - controller: userListController, - itemBuilder: (context, items, index, defaultWidget) { - return defaultWidget.copyWith( - selected: _selectedUsers.contains(items[index]), + onPressed: () async { + GoRouter.of(context).pushNamed( + Routes.NEW_GROUP_CHAT_DETAILS.name, + extra: state, ); }, - onUserTap: (user) { - if (!_selectedUsers.contains(user)) { - setState(() { - _selectedUsers.add(user); - }); - } else { - setState(() { - _selectedUsers.remove(user); - }); - } - }, - emptyBuilder: (_) { - return LayoutBuilder( - builder: (context, viewportConstraints) { - return SingleChildScrollView( - physics: const AlwaysScrollableScrollPhysics(), - child: ConstrainedBox( - constraints: BoxConstraints( - minHeight: viewportConstraints.maxHeight, - ), - child: Center( - child: Column( - children: [ - Padding( - padding: const EdgeInsets.all(24), - child: StreamSvgIcon.search( - size: 96, - color: StreamChatTheme.of(context) - .colorTheme - .textLowEmphasis, + ) + ], + ), + body: StreamConnectionStatusBuilder( + statusBuilder: (context, status) { + String statusString = ''; + bool showStatus = true; + + switch (status) { + case ConnectionStatus.connected: + statusString = AppLocalizations.of(context).connected; + showStatus = false; + break; + case ConnectionStatus.connecting: + statusString = AppLocalizations.of(context).reconnecting; + break; + case ConnectionStatus.disconnected: + statusString = AppLocalizations.of(context).disconnected; + break; + } + return StreamInfoTile( + showMessage: showStatus, + tileAnchor: Alignment.topCenter, + childAnchor: Alignment.topCenter, + message: statusString, + child: NestedScrollView( + floatHeaderSlivers: true, + headerSliverBuilder: + (BuildContext context, bool innerBoxIsScrolled) { + return [ + SliverToBoxAdapter( + child: SearchTextField( + controller: _controller, + hintText: AppLocalizations.of(context).search, + ), + ), + if (state.users.isNotEmpty) + SliverToBoxAdapter( + child: SizedBox( + height: 104, + child: ListView.separated( + scrollDirection: Axis.horizontal, + itemCount: state.users.length, + padding: const EdgeInsets.all(8), + separatorBuilder: (_, __) => + const SizedBox(width: 16), + itemBuilder: (_, index) { + final user = state.users.elementAt(index); + return Column( + children: [ + Stack( + children: [ + StreamUserAvatar( + onlineIndicatorAlignment: + const Alignment(0.9, 0.9), + user: user, + showOnlineStatus: true, + borderRadius: + BorderRadius.circular(32), + constraints: + const BoxConstraints.tightFor( + height: 64, + width: 64, + ), + ), + Positioned( + top: -4, + right: -4, + child: GestureDetector( + onTap: () { + groupChatState.removeUser(user); + }, + child: Container( + decoration: BoxDecoration( + color: + StreamChatTheme.of(context) + .colorTheme + .appBg, + shape: BoxShape.circle, + border: Border.all( + color: StreamChatTheme.of( + context) + .colorTheme + .appBg, + ), + ), + child: StreamSvgIcon.close( + color: + StreamChatTheme.of(context) + .colorTheme + .textHighEmphasis, + size: 24, + ), + ), + ), + ) + ], ), - ), - Text( - AppLocalizations.of(context) - .noUserMatchesTheseKeywords, - style: StreamChatTheme.of(context) - .textTheme - .footnote - .copyWith( + const SizedBox(height: 4), + Text( + user.name.split(' ')[0], + style: const TextStyle( + fontWeight: FontWeight.bold, + fontSize: 12, + ), + ), + ], + ); + }, + ), + ), + ), + SliverPersistentHeader( + pinned: true, + delegate: _HeaderDelegate( + height: 32, + child: Container( + width: double.maxFinite, + decoration: BoxDecoration( + gradient: StreamChatTheme.of(context) + .colorTheme + .bgGradient, + ), + child: Padding( + padding: const EdgeInsets.symmetric( + vertical: 8, + horizontal: 8, + ), + child: Text( + _isSearchActive + ? '${AppLocalizations.of(context).matchesFor} "$_userNameQuery"' + : AppLocalizations.of(context).onThePlatorm, + style: TextStyle( + color: StreamChatTheme.of(context) + .colorTheme + .textLowEmphasis, + ), + ), + ), + ), + ), + ), + ]; + }, + body: GestureDetector( + behavior: HitTestBehavior.opaque, + onPanDown: (_) => FocusScope.of(context).unfocus(), + child: StreamUserListView( + controller: userListController, + itemBuilder: (context, items, index, defaultWidget) { + return defaultWidget.copyWith( + selected: state.users.contains(items[index]), + ); + }, + onUserTap: groupChatState.addOrRemoveUser, + emptyBuilder: (_) { + return LayoutBuilder( + builder: (context, viewportConstraints) { + return SingleChildScrollView( + physics: const AlwaysScrollableScrollPhysics(), + child: ConstrainedBox( + constraints: BoxConstraints( + minHeight: viewportConstraints.maxHeight, + ), + child: Center( + child: Column( + children: [ + Padding( + padding: const EdgeInsets.all(24), + child: StreamSvgIcon.search( + size: 96, color: StreamChatTheme.of(context) .colorTheme .textLowEmphasis, ), + ), + Text( + AppLocalizations.of(context) + .noUserMatchesTheseKeywords, + style: StreamChatTheme.of(context) + .textTheme + .footnote + .copyWith( + color: StreamChatTheme.of(context) + .colorTheme + .textLowEmphasis, + ), + ), + ], ), - ], + ), ), - ), - ), + ); + }, ); }, - ); - }, + ), + ), ), - ), - ), - ); - }, - ), + ); + }, + ), + ); + }, ); } } diff --git a/packages/stream_chat_v1/lib/pages/pinned_messages_screen.dart b/packages/stream_chat_v1/lib/pages/pinned_messages_screen.dart index 407eb63..a38aa55 100644 --- a/packages/stream_chat_v1/lib/pages/pinned_messages_screen.dart +++ b/packages/stream_chat_v1/lib/pages/pinned_messages_screen.dart @@ -1,10 +1,9 @@ import 'package:example/utils/localizations.dart'; import 'package:example/routes/routes.dart'; import 'package:flutter/material.dart'; +import 'package:go_router/go_router.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; -import 'channel_page.dart'; - class PinnedMessagesScreen extends StatefulWidget { const PinnedMessagesScreen({super.key}); @@ -103,7 +102,7 @@ class _PinnedMessagesScreenState extends State { }, onMessageTap: (messageResponse) async { final client = StreamChat.of(context).client; - final navigator = Navigator.of(context); + final router = GoRouter.of(context); final message = messageResponse.message; final channel = client.channel( messageResponse.channel!.type, @@ -112,12 +111,10 @@ class _PinnedMessagesScreenState extends State { if (channel.state == null) { await channel.watch(); } - navigator.pushNamed( - Routes.CHANNEL_PAGE, - arguments: ChannelPageArgs( - channel: channel, - initialMessage: message, - ), + router.pushNamed( + Routes.CHANNEL_PAGE.name, + params: Routes.CHANNEL_PAGE.params(channel), + queryParams: Routes.CHANNEL_PAGE.queryParams(message), ); }, ), diff --git a/packages/stream_chat_v1/lib/pages/user_mentions_page.dart b/packages/stream_chat_v1/lib/pages/user_mentions_page.dart index 5254378..c95329c 100644 --- a/packages/stream_chat_v1/lib/pages/user_mentions_page.dart +++ b/packages/stream_chat_v1/lib/pages/user_mentions_page.dart @@ -1,10 +1,9 @@ import 'package:example/utils/localizations.dart'; import 'package:example/routes/routes.dart'; import 'package:flutter/material.dart'; +import 'package:go_router/go_router.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; -import 'channel_page.dart'; - class UserMentionsPage extends StatefulWidget { const UserMentionsPage({super.key}); @@ -72,7 +71,7 @@ class _UserMentionsPageState extends State { }, onMessageTap: (messageResponse) async { final client = StreamChat.of(context).client; - final navigator = Navigator.of(context); + final router = GoRouter.of(context); final message = messageResponse.message; final channel = client.channel( messageResponse.channel!.type, @@ -81,12 +80,10 @@ class _UserMentionsPageState extends State { if (channel.state == null) { await channel.watch(); } - navigator.pushNamed( - Routes.CHANNEL_PAGE, - arguments: ChannelPageArgs( - channel: channel, - initialMessage: message, - ), + router.pushNamed( + Routes.CHANNEL_PAGE.name, + params: Routes.CHANNEL_PAGE.params(channel), + queryParams: Routes.CHANNEL_PAGE.queryParams(message), ); }, ); diff --git a/packages/stream_chat_v1/lib/routes/app_routes.dart b/packages/stream_chat_v1/lib/routes/app_routes.dart index cee0a57..9842d0c 100644 --- a/packages/stream_chat_v1/lib/routes/app_routes.dart +++ b/packages/stream_chat_v1/lib/routes/app_routes.dart @@ -1,135 +1,127 @@ import 'package:collection/collection.dart'; +import 'package:example/pages/advanced_options_page.dart'; import 'package:example/pages/channel_list_page.dart'; +import 'package:example/pages/channel_page.dart'; +import 'package:example/pages/chat_info_screen.dart'; +import 'package:example/pages/group_chat_details_screen.dart'; +import 'package:example/pages/group_info_screen.dart'; +import 'package:example/pages/new_chat_screen.dart'; +import 'package:example/pages/new_group_chat_screen.dart'; +import 'package:example/pages/thread_page.dart'; +import 'package:example/routes/routes.dart'; +import 'package:example/state/new_group_chat_state.dart'; import 'package:flutter/material.dart'; +import 'package:go_router/go_router.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; -import '../app.dart'; -import '../pages/advanced_options_page.dart'; -import '../pages/channel_page.dart'; -import '../pages/chat_info_screen.dart'; import '../pages/choose_user_page.dart'; -import '../pages/group_chat_details_screen.dart'; -import '../pages/group_info_screen.dart'; -import '../pages/home_page.dart'; -import '../pages/new_chat_screen.dart'; -import '../pages/new_group_chat_screen.dart'; -import '../pages/thread_page.dart'; -import 'routes.dart'; -class AppRoutes { - /// Add entry for new route here - static Route? generateRoute(RouteSettings settings) { - final args = settings.arguments; - switch (settings.name) { - case Routes.APP: - return MaterialPageRoute( - settings: RouteSettings(arguments: args, name: Routes.APP), - builder: (_) { - return const StreamChatSampleApp(); - }); - case Routes.HOME: - return MaterialPageRoute( - settings: RouteSettings(arguments: args, name: Routes.HOME), - builder: (_) { - final homePageArgs = args as HomePageArgs; - return HomePage( - chatClient: homePageArgs.chatClient, - ); - }); - case Routes.CHOOSE_USER: - return MaterialPageRoute( - settings: RouteSettings(arguments: args, name: Routes.CHOOSE_USER), - builder: (_) { - return const ChooseUserPage(); - }); - case Routes.ADVANCED_OPTIONS: - return MaterialPageRoute( - settings: - RouteSettings(arguments: args, name: Routes.ADVANCED_OPTIONS), - builder: (_) => const AdvancedOptionsPage(), - ); - case Routes.CHANNEL_PAGE: - return MaterialPageRoute( - settings: RouteSettings(arguments: args, name: Routes.CHANNEL_PAGE), - builder: (context) { - final channelPageArgs = args as ChannelPageArgs; - final initialMessage = channelPageArgs.initialMessage; +final appRoutes = [ + GoRoute( + name: Routes.CHANNEL_LIST_PAGE.name, + path: Routes.CHANNEL_LIST_PAGE.path, + builder: (BuildContext context, GoRouterState state) => + const ChannelListPage(), + routes: [ + GoRoute( + name: Routes.CHANNEL_PAGE.name, + path: Routes.CHANNEL_PAGE.path, + builder: (context, state) { + final channel = + StreamChat.of(context).client.state.channels[state.params['cid']]; + final messageId = state.queryParams['mid']; + final parentId = state.queryParams['pid']; - return StreamChannel( - channel: channelPageArgs.channel!, - initialMessageId: initialMessage?.id, - child: Builder( - builder: (context) { - final parentId = initialMessage?.parentId; - Message? parentMessage; - if (parentId != null) { - final channel = StreamChannel.of(context).channel; - parentMessage = channel.state!.messages - .firstWhereOrNull((it) => it.id == parentId); - } - if (parentMessage != null) { - return ThreadPage(parent: parentMessage); - } - return ChannelPage( - highlightInitialMessage: - channelPageArgs.initialMessage != null, - ); - }, - ), - ); - }, - ); - case Routes.NEW_CHAT: - return MaterialPageRoute( - settings: RouteSettings(arguments: args, name: Routes.NEW_CHAT), - builder: (_) { - return const NewChatScreen(); - }); - case Routes.NEW_GROUP_CHAT: - return MaterialPageRoute( - settings: - RouteSettings(arguments: args, name: Routes.NEW_GROUP_CHAT), - builder: (_) { - return const NewGroupChatScreen(); - }); - case Routes.NEW_GROUP_CHAT_DETAILS: - return MaterialPageRoute( - settings: RouteSettings( - arguments: args, name: Routes.NEW_GROUP_CHAT_DETAILS), - builder: (_) { - return GroupChatDetailsScreen( - selectedUsers: args as List?, + Message? parentMessage; + if (parentId != null) { + parentMessage = channel?.state!.messages + .firstWhereOrNull((it) => it.id == parentId); + } + + return StreamChannel( + channel: channel!, + initialMessageId: messageId, + child: Builder( + builder: (context) { + return (parentMessage != null) + ? ThreadPage(parent: parentMessage) + : ChannelPage( + highlightInitialMessage: messageId != null, + ); + }, + ), + ); + }, + routes: [ + GoRoute( + name: Routes.CHAT_INFO_SCREEN.name, + path: Routes.CHAT_INFO_SCREEN.path, + builder: (BuildContext context, GoRouterState state) { + final channel = StreamChat.of(context) + .client + .state + .channels[state.params['cid']]; + return StreamChannel( + channel: channel!, + child: ChatInfoScreen( + user: state.extra as User?, + messageTheme: StreamChatTheme.of(context).ownMessageTheme, + ), ); - }); - case Routes.CHAT_INFO_SCREEN: - return MaterialPageRoute( - settings: - RouteSettings(arguments: args, name: Routes.CHAT_INFO_SCREEN), - builder: (context) { - return ChatInfoScreen( - user: args as User?, - messageTheme: StreamChatTheme.of(context).ownMessageTheme, + }, + ), + GoRoute( + name: Routes.GROUP_INFO_SCREEN.name, + path: Routes.GROUP_INFO_SCREEN.path, + builder: (BuildContext context, GoRouterState state) { + final channel = StreamChat.of(context) + .client + .state + .channels[state.params['cid']]; + return StreamChannel( + channel: channel!, + child: GroupInfoScreen( + messageTheme: StreamChatTheme.of(context).ownMessageTheme, + ), ); - }); - case Routes.GROUP_INFO_SCREEN: - return MaterialPageRoute( - settings: - RouteSettings(arguments: args, name: Routes.GROUP_INFO_SCREEN), - builder: (context) { - return GroupInfoScreen( - messageTheme: StreamChatTheme.of(context).ownMessageTheme, - ); - }); - case Routes.CHANNEL_LIST_PAGE: - return MaterialPageRoute( - settings: - RouteSettings(arguments: args, name: Routes.CHANNEL_LIST_PAGE), - builder: (context) { - return const ChannelListPage(); - }); - // Default case, should not reach here. - default: - return null; - } - } -} + }, + ), + ], + ), + ], + ), + GoRoute( + name: Routes.NEW_CHAT.name, + path: Routes.NEW_CHAT.path, + builder: (BuildContext context, GoRouterState state) { + return const NewChatScreen(); + }, + ), + GoRoute( + name: Routes.NEW_GROUP_CHAT.name, + path: Routes.NEW_GROUP_CHAT.path, + builder: (BuildContext context, GoRouterState state) { + return const NewGroupChatScreen(); + }, + ), + GoRoute( + name: Routes.NEW_GROUP_CHAT_DETAILS.name, + path: Routes.NEW_GROUP_CHAT_DETAILS.path, + builder: (BuildContext context, GoRouterState state) { + final groupChatState = state.extra as NewGroupChatState; + return GroupChatDetailsScreen(groupChatState: groupChatState); + }, + ), + GoRoute( + name: Routes.CHOOSE_USER.name, + path: Routes.CHOOSE_USER.path, + builder: (BuildContext context, GoRouterState state) => + const ChooseUserPage(), + ), + GoRoute( + name: Routes.ADVANCED_OPTIONS.name, + path: Routes.ADVANCED_OPTIONS.path, + builder: (BuildContext context, GoRouterState state) => + const AdvancedOptionsPage(), + ), +]; diff --git a/packages/stream_chat_v1/lib/routes/routes.dart b/packages/stream_chat_v1/lib/routes/routes.dart index 844d7b9..5c9749a 100644 --- a/packages/stream_chat_v1/lib/routes/routes.dart +++ b/packages/stream_chat_v1/lib/routes/routes.dart @@ -1,16 +1,43 @@ // ignore_for_file: constant_identifier_names +import 'package:stream_chat_flutter/stream_chat_flutter.dart'; + /// Application routes abstract class Routes { - static const String APP = '/app'; - static const String HOME = '/home'; - static const String CHOOSE_USER = '/choose_user'; - static const String ADVANCED_OPTIONS = '/advance_options'; - static const String CHANNEL_PAGE = '/channel_page'; - static const String NEW_CHAT = '/new_chat'; - static const String NEW_GROUP_CHAT = '/new_group_chat'; - static const String NEW_GROUP_CHAT_DETAILS = '/new_group_chat_details'; - static const String CHAT_INFO_SCREEN = '/chat_info_screen'; - static const String GROUP_INFO_SCREEN = '/group_info_screen'; - static const String CHANNEL_LIST_PAGE = '/channel_list_page'; + static const RouteConfig CHOOSE_USER = + RouteConfig(name: 'choose_user', path: '/users'); + static const RouteConfig ADVANCED_OPTIONS = + RouteConfig(name: 'advanced_options', path: '/options'); + static const ChannelRouteConfig CHANNEL_PAGE = + ChannelRouteConfig(name: 'channel_page', path: 'channel/:cid'); + static const RouteConfig NEW_CHAT = + RouteConfig(name: 'new_chat', path: '/new_chat'); + static const RouteConfig NEW_GROUP_CHAT = + RouteConfig(name: 'new_group_chat', path: '/new_group_chat'); + static const RouteConfig NEW_GROUP_CHAT_DETAILS = RouteConfig( + name: 'new_group_chat_details', path: '/new_group_chat_details'); + static const ChannelRouteConfig CHAT_INFO_SCREEN = + ChannelRouteConfig(name: 'chat_info_screen', path: 'chat_info_screen'); + static const ChannelRouteConfig GROUP_INFO_SCREEN = + ChannelRouteConfig(name: 'group_info_screen', path: 'group_info_screen'); + static const RouteConfig CHANNEL_LIST_PAGE = + RouteConfig(name: 'channel_list_page', path: '/channels'); +} + +class RouteConfig { + final String name; + final String path; + + const RouteConfig({required this.name, required this.path}); +} + +class ChannelRouteConfig extends RouteConfig { + const ChannelRouteConfig({required super.name, required super.path}); + + Map params(Channel channel) => {'cid': channel.cid!}; + + Map queryParams(Message message) => { + 'mid': message.id, + if (message.parentId != null) 'pid': message.parentId! + }; } diff --git a/packages/stream_chat_v1/lib/state/init_data.dart b/packages/stream_chat_v1/lib/state/init_data.dart new file mode 100644 index 0000000..7f23896 --- /dev/null +++ b/packages/stream_chat_v1/lib/state/init_data.dart @@ -0,0 +1,36 @@ +import 'package:flutter/widgets.dart'; +import 'package:stream_chat_flutter/stream_chat_flutter.dart'; +import 'package:streaming_shared_preferences/streaming_shared_preferences.dart'; + +/// {@template init_notifier} +/// [ChangeNotifier] to store [InitData] and notify listeners on change. +/// {@endtemplate} +class InitNotifier extends ChangeNotifier { + /// {@macro init_notifier} + InitNotifier(); + + InitData? _initData; + + set initData(InitData? data) { + _initData = data; + notifyListeners(); + } + + InitData? get initData => _initData; +} + +/// {@template init_data} +/// Manages the initialization data for the sample application. +/// +/// Stores a reference to the current [StreamChatClient]. +/// {@endtemplate} +class InitData { + /// {@macro init_data} + InitData(this.client, this.preferences); + + final StreamChatClient client; + final StreamingSharedPreferences preferences; + + InitData copyWith({required StreamChatClient client}) => + InitData(client, preferences); +} diff --git a/packages/stream_chat_v1/lib/state/new_group_chat_state.dart b/packages/stream_chat_v1/lib/state/new_group_chat_state.dart new file mode 100644 index 0000000..2b6f646 --- /dev/null +++ b/packages/stream_chat_v1/lib/state/new_group_chat_state.dart @@ -0,0 +1,29 @@ +import 'package:flutter/widgets.dart'; +import 'package:stream_chat_flutter/stream_chat_flutter.dart'; + +class NewGroupChatState extends ChangeNotifier { + final users = {}; + + void addUser(User user) { + if (!users.contains(user)) { + users.add(user); + notifyListeners(); + } + } + + void removeUser(User user) { + if (users.contains(user)) { + users.remove(user); + notifyListeners(); + } + } + + void addOrRemoveUser(User user) { + if (users.contains(user)) { + users.remove(user); + } else { + users.add(user); + } + notifyListeners(); + } +} diff --git a/packages/stream_chat_v1/lib/utils/local_notification_observer.dart b/packages/stream_chat_v1/lib/utils/local_notification_observer.dart new file mode 100644 index 0000000..8befd49 --- /dev/null +++ b/packages/stream_chat_v1/lib/utils/local_notification_observer.dart @@ -0,0 +1,69 @@ +import 'dart:async'; + +import 'package:example/routes/routes.dart'; +import 'package:example/utils/notifications_service.dart'; +import 'package:flutter/material.dart'; +import 'package:stream_chat_flutter/stream_chat_flutter.dart'; + +class LocalNotificationObserver extends NavigatorObserver { + Route? currentRoute; + late final StreamSubscription _subscription; + + LocalNotificationObserver( + StreamChatClient client, + GlobalKey navigatorKey, + ) { + _subscription = client + .on( + EventType.messageNew, + EventType.notificationMessageNew, + ) + .listen((event) { + _handleEvent(event, client, navigatorKey); + }); + } + + void _handleEvent(Event event, StreamChatClient client, + GlobalKey navigatorKey) { + if (event.message?.user?.id == client.state.currentUser?.id) { + return; + } + final channelId = event.cid; + if (currentRoute?.settings.name == Routes.CHANNEL_PAGE.name) { + final args = currentRoute?.settings.arguments as Map; + if (args['cid'] == channelId) { + return; + } + } + + showLocalNotification( + event, + client.state.currentUser!.id, + navigatorKey.currentState!.context, + ); + } + + @override + void didPop(Route route, Route? previousRoute) { + currentRoute = route; + } + + @override + void didPush(Route route, Route? previousRoute) { + currentRoute = route; + } + + @override + void didRemove(Route route, Route? previousRoute) { + currentRoute = route; + } + + @override + void didReplace({Route? newRoute, Route? oldRoute}) { + currentRoute = newRoute; + } + + void dispose() { + _subscription.cancel(); + } +} diff --git a/packages/stream_chat_v1/lib/utils/notifications_service.dart b/packages/stream_chat_v1/lib/utils/notifications_service.dart index b56fc99..a02d2ee 100644 --- a/packages/stream_chat_v1/lib/utils/notifications_service.dart +++ b/packages/stream_chat_v1/lib/utils/notifications_service.dart @@ -1,9 +1,9 @@ -import 'package:example/pages/channel_page.dart'; import 'package:example/utils/localizations.dart'; import 'package:example/routes/routes.dart'; import 'package:flutter/material.dart'; import 'package:flutter_local_notifications/flutter_local_notifications.dart' hide Message; +import 'package:go_router/go_router.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; void showLocalNotification( @@ -35,7 +35,7 @@ void showLocalNotification( onSelectNotification: (channelCid) async { if (channelCid != null) { final client = StreamChat.of(context).client; - final navigator = Navigator.of(context); + final router = GoRouter.of(context); var channel = client.state.channels[channelCid]; @@ -50,11 +50,9 @@ void showLocalNotification( await channel.watch(); } - navigator.pushNamed( - Routes.CHANNEL_PAGE, - arguments: ChannelPageArgs( - channel: channel, - ), + router.pushNamed( + Routes.CHANNEL_PAGE.name, + params: Routes.CHANNEL_PAGE.params(channel), ); } }, diff --git a/packages/stream_chat_v1/lib/widgets/channel_list.dart b/packages/stream_chat_v1/lib/widgets/channel_list.dart index 44cb7ba..f63edbe 100644 --- a/packages/stream_chat_v1/lib/widgets/channel_list.dart +++ b/packages/stream_chat_v1/lib/widgets/channel_list.dart @@ -5,10 +5,10 @@ import 'package:example/routes/routes.dart'; import 'package:example/widgets/search_text_field.dart'; import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; +import 'package:go_router/go_router.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; import 'package:flutter_slidable/flutter_slidable.dart'; -import '../pages/channel_page.dart'; import '../pages/chat_info_screen.dart'; import '../pages/group_info_screen.dart'; @@ -150,7 +150,7 @@ class _ChannelList extends State { final messageResponse = messageResponses[index]; FocusScope.of(context).requestFocus(FocusNode()); final client = StreamChat.of(context).client; - final navigator = Navigator.of(context); + final router = GoRouter.of(context); final message = messageResponse.message; final channel = client.channel( messageResponse.channel!.type, @@ -159,12 +159,10 @@ class _ChannelList extends State { if (channel.state == null) { await channel.watch(); } - navigator.pushNamed( - Routes.CHANNEL_PAGE, - arguments: ChannelPageArgs( - channel: channel, - initialMessage: message, - ), + router.pushNamed( + Routes.CHANNEL_PAGE.name, + params: Routes.CHANNEL_PAGE.params(channel), + queryParams: Routes.CHANNEL_PAGE.queryParams(message), ); }, ); @@ -265,12 +263,9 @@ class _ChannelList extends State { ); }, onChannelTap: (channel) { - Navigator.pushNamed( - context, - Routes.CHANNEL_PAGE, - arguments: ChannelPageArgs( - channel: channel, - ), + GoRouter.of(context).pushNamed( + Routes.CHANNEL_PAGE.name, + params: Routes.CHANNEL_PAGE.params(channel), ); }, emptyBuilder: (_) { @@ -286,10 +281,8 @@ class _ChannelList extends State { ), emptyTitle: TextButton( onPressed: () { - Navigator.pushNamed( - context, - Routes.NEW_CHAT, - ); + GoRouter.of(context) + .pushNamed(Routes.NEW_CHAT.name); }, child: Text( 'Start a chat', diff --git a/packages/stream_chat_v1/macos/Runner.xcodeproj/project.pbxproj b/packages/stream_chat_v1/macos/Runner.xcodeproj/project.pbxproj index c973575..8972854 100644 --- a/packages/stream_chat_v1/macos/Runner.xcodeproj/project.pbxproj +++ b/packages/stream_chat_v1/macos/Runner.xcodeproj/project.pbxproj @@ -72,6 +72,7 @@ 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Release.xcconfig; sourceTree = ""; }; 929AC42CE2CDA27FE9235234 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = ""; }; + CDF4BDDD28F9A6920059DB87 /* RunnerDebug.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = RunnerDebug.entitlements; sourceTree = ""; }; D4F4A1B883B395E86A7B745A /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; }; /* End PBXFileReference section */ @@ -142,6 +143,7 @@ 33FAB671232836740065AC1E /* Runner */ = { isa = PBXGroup; children = ( + CDF4BDDD28F9A6920059DB87 /* RunnerDebug.entitlements */, 33CC10F02044A3C60003C045 /* AppDelegate.swift */, 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */, 33E51913231747F40026EE4D /* DebugProfile.entitlements */, @@ -159,7 +161,6 @@ 929AC42CE2CDA27FE9235234 /* Pods-Runner.release.xcconfig */, D4F4A1B883B395E86A7B745A /* Pods-Runner.profile.xcconfig */, ); - name = Pods; path = Pods; sourceTree = ""; }; @@ -209,7 +210,6 @@ 33CC10EC2044A3C60003C045 = { CreatedOnToolsVersion = 9.2; LastSwiftMigration = 1100; - ProvisioningStyle = Automatic; SystemCapabilities = { com.apple.Sandbox = { enabled = 1; @@ -419,6 +419,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements; + CODE_SIGN_IDENTITY = "-"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; INFOPLIST_FILE = Runner/Info.plist; @@ -544,9 +545,11 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; - CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements; + CODE_SIGN_ENTITLEMENTS = Runner/RunnerDebug.entitlements; + CODE_SIGN_IDENTITY = "-"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; + DEVELOPMENT_TEAM = ""; INFOPLIST_FILE = Runner/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", @@ -565,8 +568,10 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = Runner/Release.entitlements; + CODE_SIGN_IDENTITY = "-"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; + DEVELOPMENT_TEAM = ""; INFOPLIST_FILE = Runner/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", diff --git a/packages/stream_chat_v1/macos/Runner/DebugProfile.entitlements b/packages/stream_chat_v1/macos/Runner/DebugProfile.entitlements index 681e73f..0eaccf1 100644 --- a/packages/stream_chat_v1/macos/Runner/DebugProfile.entitlements +++ b/packages/stream_chat_v1/macos/Runner/DebugProfile.entitlements @@ -6,11 +6,11 @@ com.apple.security.cs.allow-jit - com.apple.security.network.server + com.apple.security.files.user-selected.read-write com.apple.security.network.client - com.apple.security.files.user-selected.read-write - + com.apple.security.network.server + diff --git a/packages/stream_chat_v1/macos/Runner/Release.entitlements b/packages/stream_chat_v1/macos/Runner/Release.entitlements index 43a8b8c..a046386 100644 --- a/packages/stream_chat_v1/macos/Runner/Release.entitlements +++ b/packages/stream_chat_v1/macos/Runner/Release.entitlements @@ -4,9 +4,9 @@ com.apple.security.app-sandbox + com.apple.security.files.user-selected.read-write + com.apple.security.network.client - com.apple.security.files.user-selected.read-write - diff --git a/packages/stream_chat_v1/macos/Runner/RunnerDebug.entitlements b/packages/stream_chat_v1/macos/Runner/RunnerDebug.entitlements new file mode 100644 index 0000000..0eaccf1 --- /dev/null +++ b/packages/stream_chat_v1/macos/Runner/RunnerDebug.entitlements @@ -0,0 +1,16 @@ + + + + + com.apple.security.app-sandbox + + com.apple.security.cs.allow-jit + + com.apple.security.files.user-selected.read-write + + com.apple.security.network.client + + com.apple.security.network.server + + + diff --git a/packages/stream_chat_v1/pubspec.yaml b/packages/stream_chat_v1/pubspec.yaml index 1c7a956..8b6e901 100644 --- a/packages/stream_chat_v1/pubspec.yaml +++ b/packages/stream_chat_v1/pubspec.yaml @@ -27,7 +27,7 @@ dependencies: path: packages/stream_chat_localizations flutter_local_notifications: ^9.0.0 flutter_svg: ^1.0.3 - flutter_secure_storage: ^5.0.2 + flutter_secure_storage: ^6.0.0 yaml: ^3.1.0 uuid: ^3.0.5 streaming_shared_preferences: ^2.0.0 @@ -35,6 +35,8 @@ dependencies: collection: ^1.15.0 sentry_flutter: ^6.5.0 flutter_slidable: ^2.0.0 + go_router: ^5.0.5 + provider: ^6.0.3 dev_dependencies: flutter_launcher_icons: ^0.9.2