From 6acaf964661712330753368bf1383f05a0ed6ae9 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Tue, 22 Jun 2021 12:52:11 +0200 Subject: [PATCH] refactor home page --- .../lib/advanced_options_page.dart | 9 +- packages/stream_chat_v1/lib/channel_list.dart | 199 +++++++++ .../stream_chat_v1/lib/channel_list_page.dart | 416 +++++++++++------- .../lib/channel_media_display_screen.dart | 16 +- .../stream_chat_v1/lib/choose_user_page.dart | 10 +- packages/stream_chat_v1/lib/home_page.dart | 236 +--------- packages/stream_chat_v1/lib/main.dart | 240 ++-------- .../lib/pinned_messages_screen.dart | 16 +- .../stream_chat_v1/lib/routes/app_routes.dart | 12 +- .../stream_chat_v1/lib/routes/routes.dart | 1 + .../stream_chat_v1/lib/splash_screen.dart | 122 +++++ packages/stream_chat_v1/pubspec.yaml | 20 +- 12 files changed, 647 insertions(+), 650 deletions(-) create mode 100644 packages/stream_chat_v1/lib/channel_list.dart create mode 100644 packages/stream_chat_v1/lib/splash_screen.dart diff --git a/packages/stream_chat_v1/lib/advanced_options_page.dart b/packages/stream_chat_v1/lib/advanced_options_page.dart index 3882580..4a97c68 100644 --- a/packages/stream_chat_v1/lib/advanced_options_page.dart +++ b/packages/stream_chat_v1/lib/advanced_options_page.dart @@ -1,3 +1,4 @@ +import 'package:example/home_page.dart'; import 'package:example/routes/routes.dart'; import 'package:example/stream_version.dart'; import 'package:flutter/material.dart'; @@ -306,7 +307,6 @@ class _AdvancedOptionsPageState extends State { key: kStreamToken, value: userToken, ); - client.closeConnection(); } catch (e) { var errorText = 'Error connecting, retry'; if (e is Map) { @@ -317,15 +317,14 @@ class _AdvancedOptionsPageState extends State { _apiKeyError = errorText.toUpperCase(); }); loading = false; - client.closeConnection(); return; } loading = false; await Navigator.pushNamedAndRemoveUntil( context, - Routes.APP, - ModalRoute.withName(Routes.APP), - arguments: client, + Routes.HOME, + ModalRoute.withName(Routes.HOME), + arguments: HomePageArgs(client), ); } }, diff --git a/packages/stream_chat_v1/lib/channel_list.dart b/packages/stream_chat_v1/lib/channel_list.dart new file mode 100644 index 0000000..e88c9a1 --- /dev/null +++ b/packages/stream_chat_v1/lib/channel_list.dart @@ -0,0 +1,199 @@ +import 'dart:async'; + +import 'package:example/routes/routes.dart'; +import 'package:example/search_text_field.dart'; +import 'package:flutter/material.dart'; +import 'package:stream_chat_flutter/stream_chat_flutter.dart'; + +import 'channel_page.dart'; +import 'chat_info_screen.dart'; +import 'group_info_screen.dart'; + +class ChannelList extends StatefulWidget { + @override + _ChannelList createState() => _ChannelList(); +} + +class _ChannelList extends State { + TextEditingController? _controller; + + String _channelQuery = ''; + + bool _isSearchActive = false; + + Timer? _debounce; + + void _channelQueryListener() { + if (_debounce?.isActive ?? false) _debounce!.cancel(); + _debounce = Timer(const Duration(milliseconds: 350), () { + if (mounted) { + setState(() { + _channelQuery = _controller!.text; + _isSearchActive = _channelQuery.isNotEmpty; + }); + } + }); + } + + @override + void initState() { + super.initState(); + _controller = TextEditingController()..addListener(_channelQueryListener); + } + + @override + void dispose() { + _controller?.removeListener(_channelQueryListener); + _controller?.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + final user = StreamChat.of(context).user; + return WillPopScope( + onWillPop: () async { + if (_isSearchActive) { + _controller!.clear(); + setState(() => _isSearchActive = false); + return false; + } + return true; + }, + child: ChannelsBloc( + child: MessageSearchBloc( + child: NestedScrollView( + floatHeaderSlivers: true, + headerSliverBuilder: (_, __) => [ + SliverToBoxAdapter( + child: SearchTextField( + controller: _controller, + showCloseButton: _isSearchActive, + ), + ), + ], + body: AnimatedSwitcher( + duration: const Duration(milliseconds: 350), + child: GestureDetector( + behavior: HitTestBehavior.opaque, + onPanDown: (_) => FocusScope.of(context).unfocus(), + child: _isSearchActive + ? MessageSearchListView( + showErrorTile: true, + messageQuery: _channelQuery, + filters: Filter.in_('members', [user!.id]), + sortOptions: [ + SortOption( + 'created_at', + direction: SortOption.ASC, + ), + ], + pullToRefresh: false, + paginationParams: PaginationParams(limit: 20), + emptyBuilder: (_) { + return LayoutBuilder( + builder: (context, viewportConstraints) { + return SingleChildScrollView( + physics: 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: Colors.grey, + ), + ), + Text( + 'No results...', + ), + ], + ), + ), + ), + ); + }, + ); + }, + onItemTap: (messageResponse) async { + FocusScope.of(context).requestFocus(FocusNode()); + final client = StreamChat.of(context).client; + final message = messageResponse.message; + final channel = client.channel( + messageResponse.channel!.type, + id: messageResponse.channel!.id, + ); + if (channel.state == null) { + await channel.watch(); + } + Navigator.pushNamed( + context, + Routes.CHANNEL_PAGE, + arguments: ChannelPageArgs( + channel: channel, + initialMessage: message, + ), + ); + }, + ) + : ChannelListView( + onStartChatPressed: () { + Navigator.pushNamed(context, Routes.NEW_CHAT); + }, + swipeToAction: true, + filter: Filter.in_('members', [user!.id]), + presence: true, + pagination: PaginationParams( + limit: 20, + ), + channelWidget: ChannelPage(), + onViewInfoTap: (channel) { + Navigator.pop(context); + if (channel.memberCount == 2 && channel.isDistinct) { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => StreamChannel( + channel: channel, + child: ChatInfoScreen( + messageTheme: StreamChatTheme.of(context) + .ownMessageTheme, + user: channel.state!.members + .where((m) => + m.userId != + channel.client.state.user!.id) + .first + .user, + ), + ), + ), + ); + } else { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => StreamChannel( + channel: channel, + child: GroupInfoScreen( + messageTheme: StreamChatTheme.of(context) + .ownMessageTheme, + ), + ), + ), + ); + } + }, + ), + ), + ), + ), + ), + ), + ); + } +} diff --git a/packages/stream_chat_v1/lib/channel_list_page.dart b/packages/stream_chat_v1/lib/channel_list_page.dart index c6529d5..baf6eca 100644 --- a/packages/stream_chat_v1/lib/channel_list_page.dart +++ b/packages/stream_chat_v1/lib/channel_list_page.dart @@ -1,195 +1,277 @@ import 'dart:async'; import 'package:example/routes/routes.dart'; -import 'package:example/search_text_field.dart'; +import 'package:example/user_mentions_page.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:stream_chat_flutter/stream_chat_flutter.dart'; +import 'package:streaming_shared_preferences/streaming_shared_preferences.dart'; -import 'channel_page.dart'; -import 'chat_info_screen.dart'; -import 'group_info_screen.dart'; +import 'channel_list.dart'; class ChannelListPage extends StatefulWidget { + const ChannelListPage({ + Key? key, + }) : super(key: key); + @override _ChannelListPageState createState() => _ChannelListPageState(); } class _ChannelListPageState extends State { - TextEditingController? _controller; + int _currentIndex = 0; - String _channelQuery = ''; + bool _isSelected(int index) => _currentIndex == index; - bool _isSearchActive = false; - - Timer? _debounce; - - void _channelQueryListener() { - if (_debounce?.isActive ?? false) _debounce!.cancel(); - _debounce = Timer(const Duration(milliseconds: 350), () { - if (mounted) { - setState(() { - _channelQuery = _controller!.text; - _isSearchActive = _channelQuery.isNotEmpty; - }); - } - }); - } - - @override - void initState() { - super.initState(); - _controller = TextEditingController()..addListener(_channelQueryListener); - } - - @override - void dispose() { - _controller?.removeListener(_channelQueryListener); - _controller?.dispose(); - super.dispose(); + List get _navBarItems { + return [ + BottomNavigationBarItem( + icon: Stack( + clipBehavior: Clip.none, + children: [ + StreamSvgIcon.message( + color: _isSelected(0) + ? StreamChatTheme.of(context).colorTheme.black + : Colors.grey, + ), + Positioned( + top: -3, + right: -16, + child: UnreadIndicator(), + ), + ], + ), + label: 'Chats', + ), + BottomNavigationBarItem( + icon: Stack( + clipBehavior: Clip.none, + children: [ + StreamSvgIcon.mentions( + color: _isSelected(1) + ? StreamChatTheme.of(context).colorTheme.black + : Colors.grey, + ), + ], + ), + label: 'Mentions', + ), + ]; } @override Widget build(BuildContext context) { final user = StreamChat.of(context).user; - return WillPopScope( - onWillPop: () async { - if (_isSearchActive) { - _controller!.clear(); - setState(() => _isSearchActive = false); - return false; + if (user == null) { + return Offstage(); + } + return Scaffold( + backgroundColor: StreamChatTheme.of(context).colorTheme.whiteSnow, + appBar: ChannelListHeader( + onNewChatButtonTap: () { + Navigator.pushNamed(context, Routes.NEW_CHAT); + }, + preNavigationCallback: () { + FocusScope.of(context).requestFocus(FocusNode()); + }, + ), + drawer: LeftDrawer( + user: user, + ), + drawerEdgeDragWidth: 50, + bottomNavigationBar: BottomNavigationBar( + backgroundColor: StreamChatTheme.of(context).colorTheme.white, + currentIndex: _currentIndex, + items: _navBarItems, + selectedLabelStyle: StreamChatTheme.of(context).textTheme.footnoteBold, + unselectedLabelStyle: + StreamChatTheme.of(context).textTheme.footnoteBold, + type: BottomNavigationBarType.fixed, + selectedItemColor: StreamChatTheme.of(context).colorTheme.black, + unselectedItemColor: Colors.grey, + onTap: (index) { + setState(() => _currentIndex = index); + }, + ), + body: IndexedStack( + index: _currentIndex, + children: [ + ChannelList(), + UserMentionsPage(), + ], + ), + ); + } + + StreamSubscription? badgeListener; + + @override + void initState() { + if (!kIsWeb) { + badgeListener = StreamChat.of(context) + .client + .state + .totalUnreadCountStream + .listen((count) { + if (count > 0) { + FlutterAppBadger.updateBadgeCount(count); + } else { + FlutterAppBadger.removeBadge(); } - return true; - }, - child: ChannelsBloc( - child: MessageSearchBloc( - child: NestedScrollView( - floatHeaderSlivers: true, - headerSliverBuilder: (_, __) => [ - SliverToBoxAdapter( - child: SearchTextField( - controller: _controller, - showCloseButton: _isSearchActive, - ), - ), - ], - body: AnimatedSwitcher( - duration: const Duration(milliseconds: 350), - child: GestureDetector( - behavior: HitTestBehavior.opaque, - onPanDown: (_) => FocusScope.of(context).unfocus(), - child: _isSearchActive - ? MessageSearchListView( - showErrorTile: true, - messageQuery: _channelQuery, - filters: Filter.in_('members', [user!.id]), - sortOptions: [ - SortOption( - 'created_at', - direction: SortOption.ASC, + }); + } + super.initState(); + } + + @override + void dispose() { + badgeListener?.cancel(); + super.dispose(); + } +} + +class LeftDrawer extends StatelessWidget { + const LeftDrawer({ + Key? key, + required this.user, + }) : super(key: key); + + final User user; + + @override + Widget build(BuildContext context) { + return Drawer( + child: Container( + color: StreamChatTheme.of(context).colorTheme.white, + child: SafeArea( + child: Padding( + padding: EdgeInsets.only( + top: MediaQuery.of(context).viewPadding.top + 8, + ), + child: Column( + children: [ + Padding( + padding: const EdgeInsets.only( + bottom: 20.0, + left: 8, + ), + child: Row( + children: [ + UserAvatar( + user: user, + showOnlineStatus: false, + constraints: BoxConstraints.tight(Size.fromRadius(20)), + ), + Padding( + padding: const EdgeInsets.only(left: 16.0), + child: Text( + user.name, + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, ), - ], - pullToRefresh: false, - paginationParams: PaginationParams(limit: 20), - emptyBuilder: (_) { - return LayoutBuilder( - builder: (context, viewportConstraints) { - return SingleChildScrollView( - physics: 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: Colors.grey, - ), - ), - Text( - 'No results...', - ), - ], - ), - ), - ), - ); - }, - ); - }, - onItemTap: (messageResponse) async { - FocusScope.of(context).requestFocus(FocusNode()); - final client = StreamChat.of(context).client; - final message = messageResponse.message; - final channel = client.channel( - messageResponse.channel!.type, - id: messageResponse.channel!.id, - ); - if (channel.state == null) { - await channel.watch(); - } - Navigator.pushNamed( - context, - Routes.CHANNEL_PAGE, - arguments: ChannelPageArgs( - channel: channel, - initialMessage: message, - ), - ); - }, - ) - : ChannelListView( - onStartChatPressed: () { - Navigator.pushNamed(context, Routes.NEW_CHAT); - }, - swipeToAction: true, - filter: Filter.in_('members', [user!.id]), - presence: true, - pagination: PaginationParams( - limit: 20, ), - channelWidget: ChannelPage(), - onViewInfoTap: (channel) { - Navigator.pop(context); - if (channel.memberCount == 2 && channel.isDistinct) { - Navigator.push( - context, - MaterialPageRoute( - builder: (context) => StreamChannel( - channel: channel, - child: ChatInfoScreen( - messageTheme: StreamChatTheme.of(context) - .ownMessageTheme, - user: channel.state!.members - .where((m) => - m.userId != - channel.client.state.user!.id) - .first - .user, - ), - ), - ), - ); - } else { - Navigator.push( - context, - MaterialPageRoute( - builder: (context) => StreamChannel( - channel: channel, - child: GroupInfoScreen( - messageTheme: StreamChatTheme.of(context) - .ownMessageTheme, - ), - ), - ), - ); - } + ), + ], + ), + ), + ListTile( + leading: StreamSvgIcon.penWrite( + color: StreamChatTheme.of(context) + .colorTheme + .black + .withOpacity(.5), + ), + onTap: () { + Navigator.popAndPushNamed( + context, + Routes.NEW_CHAT, + ); + }, + title: Text( + 'New direct message', + style: TextStyle( + fontSize: 14.5, + ), + ), + ), + ListTile( + leading: StreamSvgIcon.contacts( + color: StreamChatTheme.of(context) + .colorTheme + .black + .withOpacity(.5), + ), + onTap: () { + Navigator.popAndPushNamed( + context, + Routes.NEW_GROUP_CHAT, + ); + }, + title: Text( + 'New group', + style: TextStyle( + fontSize: 14.5, + ), + ), + ), + Expanded( + child: Container( + alignment: Alignment.bottomCenter, + child: ListTile( + onTap: () async { + Navigator.pop(context); + + if (!kIsWeb) { + final secureStorage = FlutterSecureStorage(); + await secureStorage.deleteAll(); + } + + final client = StreamChat.of(context).client; + client.disconnectUser(); + await client.dispose(); + + await Navigator.of( + context, + rootNavigator: true, + ).pushNamedAndRemoveUntil( + Routes.CHOOSE_USER, + ModalRoute.withName(Routes.CHOOSE_USER), + ); + }, + leading: StreamSvgIcon.user( + color: StreamChatTheme.of(context) + .colorTheme + .black + .withOpacity(.5), + ), + title: Text( + 'Sign out', + style: TextStyle( + fontSize: 14.5, + ), + ), + trailing: IconButton( + icon: StreamSvgIcon.iconMoon( + size: 24, + ), + color: StreamChatTheme.of(context).colorTheme.grey, + onPressed: () async { + final sp = await StreamingSharedPreferences.instance; + sp.setInt( + 'theme', + Theme.of(context).brightness == Brightness.dark + ? 1 + : -1, + ); }, ), - ), + ), + ), + ), + ], ), ), ), diff --git a/packages/stream_chat_v1/lib/channel_media_display_screen.dart b/packages/stream_chat_v1/lib/channel_media_display_screen.dart index 5c33622..15f8fd6 100644 --- a/packages/stream_chat_v1/lib/channel_media_display_screen.dart +++ b/packages/stream_chat_v1/lib/channel_media_display_screen.dart @@ -71,21 +71,7 @@ class _ChannelMediaDisplayScreenState extends State { fontSize: 16.0, ), ), - leading: Center( - child: InkWell( - onTap: () { - Navigator.of(context).pop(); - }, - child: Container( - width: 24.0, - height: 24.0, - child: StreamSvgIcon.left( - color: StreamChatTheme.of(context).colorTheme.black, - size: 24.0, - ), - ), - ), - ), + leading: StreamBackButton(), backgroundColor: StreamChatTheme.of(context).colorTheme.white, ), body: _buildMediaGrid(), diff --git a/packages/stream_chat_v1/lib/choose_user_page.dart b/packages/stream_chat_v1/lib/choose_user_page.dart index 7c882e3..e7be235 100644 --- a/packages/stream_chat_v1/lib/choose_user_page.dart +++ b/packages/stream_chat_v1/lib/choose_user_page.dart @@ -1,4 +1,5 @@ import 'package:example/app_config.dart'; +import 'package:example/home_page.dart'; import 'package:example/stream_version.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; @@ -6,6 +7,7 @@ import 'package:flutter_secure_storage/flutter_secure_storage.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; +import 'main.dart'; import 'routes/routes.dart'; const kStreamApiKey = 'STREAM_API_KEY'; @@ -89,8 +91,11 @@ class ChooseUserPage extends StatelessWidget { ), ); - final client = StreamChat.of(context).client; - // client.apiKey = kDefaultStreamApiKey; + final client = StreamChatClient( + kDefaultStreamApiKey, + logLevel: Level.INFO, + )..chatPersistenceClient = chatPersistentClient; + await client.connectUser( user, token, @@ -115,6 +120,7 @@ class ChooseUserPage extends StatelessWidget { context, Routes.HOME, ModalRoute.withName(Routes.HOME), + arguments: HomePageArgs(client), ); }, leading: UserAvatar( diff --git a/packages/stream_chat_v1/lib/home_page.dart b/packages/stream_chat_v1/lib/home_page.dart index af5f68d..7746729 100644 --- a/packages/stream_chat_v1/lib/home_page.dart +++ b/packages/stream_chat_v1/lib/home_page.dart @@ -1,240 +1,30 @@ +import 'package:example/routes/app_routes.dart'; import 'package:example/routes/routes.dart'; -import 'package:example/user_mentions_page.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; -import 'package:flutter_secure_storage/flutter_secure_storage.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; -import 'package:streaming_shared_preferences/streaming_shared_preferences.dart'; -import 'channel_list_page.dart'; +class HomePageArgs { + final StreamChatClient chatClient; -class HomePage extends StatefulWidget { - @override - _HomePageState createState() => _HomePageState(); + HomePageArgs(this.chatClient); } -class _HomePageState extends State { - int _currentIndex = 0; - - bool _isSelected(int index) => _currentIndex == index; - - List get _navBarItems { - return [ - BottomNavigationBarItem( - icon: Stack( - clipBehavior: Clip.none, - children: [ - StreamSvgIcon.message( - color: _isSelected(0) - ? StreamChatTheme.of(context).colorTheme.black - : Colors.grey, - ), - Positioned( - top: -3, - right: -16, - child: UnreadIndicator(), - ), - ], - ), - label: 'Chats', - ), - BottomNavigationBarItem( - icon: Stack( - clipBehavior: Clip.none, - children: [ - StreamSvgIcon.mentions( - color: _isSelected(1) - ? StreamChatTheme.of(context).colorTheme.black - : Colors.grey, - ), - ], - ), - label: 'Mentions', - ), - ]; - } - - @override - Widget build(BuildContext context) { - final user = StreamChat.of(context).user!; - return Scaffold( - backgroundColor: StreamChatTheme.of(context).colorTheme.whiteSnow, - appBar: ChannelListHeader( - onNewChatButtonTap: () { - Navigator.pushNamed(context, Routes.NEW_CHAT); - }, - preNavigationCallback: () { - FocusScope.of(context).requestFocus(FocusNode()); - }, - ), - drawer: LeftDrawer( - user: user, - ), - drawerEdgeDragWidth: 50, - bottomNavigationBar: BottomNavigationBar( - backgroundColor: StreamChatTheme.of(context).colorTheme.white, - currentIndex: _currentIndex, - items: _navBarItems, - selectedLabelStyle: StreamChatTheme.of(context).textTheme.footnoteBold, - unselectedLabelStyle: - StreamChatTheme.of(context).textTheme.footnoteBold, - type: BottomNavigationBarType.fixed, - selectedItemColor: StreamChatTheme.of(context).colorTheme.black, - unselectedItemColor: Colors.grey, - onTap: (index) { - setState(() => _currentIndex = index); - }, - ), - body: IndexedStack( - index: _currentIndex, - children: [ - ChannelListPage(), - UserMentionsPage(), - ], - ), - ); - } -} - -class LeftDrawer extends StatelessWidget { - const LeftDrawer({ +class HomePage extends StatelessWidget { + HomePage({ Key? key, - required this.user, + required this.chatClient, }) : super(key: key); - final User user; + final StreamChatClient chatClient; @override Widget build(BuildContext context) { - return Drawer( - child: Container( - color: StreamChatTheme.of(context).colorTheme.white, - child: SafeArea( - child: Padding( - padding: EdgeInsets.only( - top: MediaQuery.of(context).viewPadding.top + 8, - ), - child: Column( - children: [ - Padding( - padding: const EdgeInsets.only( - bottom: 20.0, - left: 8, - ), - child: Row( - children: [ - UserAvatar( - user: user, - showOnlineStatus: false, - constraints: BoxConstraints.tight(Size.fromRadius(20)), - ), - Padding( - padding: const EdgeInsets.only(left: 16.0), - child: Text( - user.name, - style: TextStyle( - fontSize: 16, - fontWeight: FontWeight.bold, - ), - ), - ), - ], - ), - ), - ListTile( - leading: StreamSvgIcon.penWrite( - color: StreamChatTheme.of(context) - .colorTheme - .black - .withOpacity(.5), - ), - onTap: () { - Navigator.popAndPushNamed( - context, - Routes.NEW_CHAT, - ); - }, - title: Text( - 'New direct message', - style: TextStyle( - fontSize: 14.5, - ), - ), - ), - ListTile( - leading: StreamSvgIcon.contacts( - color: StreamChatTheme.of(context) - .colorTheme - .black - .withOpacity(.5), - ), - onTap: () { - Navigator.popAndPushNamed( - context, - Routes.NEW_GROUP_CHAT, - ); - }, - title: Text( - 'New group', - style: TextStyle( - fontSize: 14.5, - ), - ), - ), - Expanded( - child: Container( - alignment: Alignment.bottomCenter, - child: ListTile( - onTap: () async { - Navigator.pop(context); - - if (!kIsWeb) { - final secureStorage = FlutterSecureStorage(); - await secureStorage.deleteAll(); - } - - StreamChat.of(context).client.disconnectUser(); - - await Navigator.pushNamedAndRemoveUntil( - context, - Routes.APP, - ModalRoute.withName(Routes.APP), - ); - }, - leading: StreamSvgIcon.user( - color: StreamChatTheme.of(context) - .colorTheme - .black - .withOpacity(.5), - ), - title: Text( - 'Sign out', - style: TextStyle( - fontSize: 14.5, - ), - ), - trailing: IconButton( - icon: StreamSvgIcon.iconMoon( - size: 24, - ), - color: StreamChatTheme.of(context).colorTheme.grey, - onPressed: () async { - final sp = await StreamingSharedPreferences.instance; - sp.setInt( - 'theme', - Theme.of(context).brightness == Brightness.dark - ? 1 - : -1, - ); - }, - ), - ), - ), - ), - ], - ), - ), - ), + return StreamChat( + client: chatClient, + child: Navigator( + onGenerateRoute: AppRoutes.generateRoute, + initialRoute: Routes.CHANNEL_LIST_PAGE, ), ); } diff --git a/packages/stream_chat_v1/lib/main.dart b/packages/stream_chat_v1/lib/main.dart index 05af49c..e53e8c0 100644 --- a/packages/stream_chat_v1/lib/main.dart +++ b/packages/stream_chat_v1/lib/main.dart @@ -1,20 +1,18 @@ import 'dart:async'; import 'package:example/choose_user_page.dart'; +import 'package:example/home_page.dart'; +import 'package:example/splash_screen.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter/scheduler.dart'; -import 'package:flutter/services.dart'; -import 'package:flutter_app_badger/flutter_app_badger.dart'; import 'package:flutter_secure_storage/flutter_secure_storage.dart'; -import 'package:lottie/lottie.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; import 'package:stream_chat_persistence/stream_chat_persistence.dart'; import 'package:streaming_shared_preferences/streaming_shared_preferences.dart'; import 'app_config.dart'; -import 'notifications_service.dart'; import 'routes/app_routes.dart'; import 'routes/routes.dart'; @@ -24,8 +22,6 @@ final chatPersistentClient = StreamChatPersistenceClient( ); void main() async { - WidgetsFlutterBinding.ensureInitialized(); - runApp(MyApp()); } @@ -34,13 +30,9 @@ class MyApp extends StatefulWidget { _MyAppState createState() => _MyAppState(); } -class _MyAppState extends State with TickerProviderStateMixin { +class _MyAppState extends State + with SplashScreenStateMixin, TickerProviderStateMixin { InitData? _initData; - bool _animCompleted = false; - Animation? _animation, _scaleAnimation; - AnimationController? _animationController, _scaleAnimationController; - Animation? _colorAnimation; - late int timeOfStartMs; Future _initConnection() async { String? apiKey, userId, token; @@ -69,56 +61,9 @@ class _MyAppState extends State with TickerProviderStateMixin { return InitData(client, prefs); } - void _createAnimations() { - _scaleAnimationController = AnimationController( - vsync: this, - value: 0, - duration: Duration( - milliseconds: 500, - ), - ); - _scaleAnimation = Tween( - begin: 1.0, - end: 1.5, - ).animate(CurvedAnimation( - parent: _scaleAnimationController!, - curve: Curves.easeInOutBack, - )); - - _animationController = AnimationController( - vsync: this, - duration: Duration( - milliseconds: 1000, - ), - ); - _animation = Tween( - begin: 0.0, - end: 1000.0, - ).animate(CurvedAnimation( - parent: _animationController!, - curve: Curves.easeInOut, - )); - _colorAnimation = ColorTween( - begin: Color(0xff005FFF), - end: Color(0xff005FFF), - ).animate(CurvedAnimation( - parent: _animationController!, - curve: Curves.easeInOut, - )); - _colorAnimation = ColorTween( - begin: Color(0xff005FFF), - end: Colors.transparent, - ).animate(CurvedAnimation( - parent: _animationController!, - curve: Curves.easeInOut, - )); - } - @override void initState() { - timeOfStartMs = DateTime.now().millisecondsSinceEpoch; - - _createAnimations(); + final timeOfStartMs = DateTime.now().millisecondsSinceEpoch; _initConnection().then( (initData) { @@ -126,97 +71,23 @@ class _MyAppState extends State with TickerProviderStateMixin { _initData = initData; }); - var now = DateTime.now().millisecondsSinceEpoch; + final now = DateTime.now().millisecondsSinceEpoch; if (now - timeOfStartMs > 1500) { SchedulerBinding.instance!.addPostFrameCallback((timeStamp) { - _scaleAnimationController?.forward().whenComplete(() { - _animationController?.forward(); - }); + forwardAnimations(); }); } else { Future.delayed(Duration(milliseconds: 1500)).then((value) { - _scaleAnimationController?.forward().whenComplete(() { - _animationController?.forward(); - }); - }); - } - - if (!kIsWeb) { - _initData!.client.state.totalUnreadCountStream.listen((count) { - if (count > 0) { - FlutterAppBadger.updateBadgeCount(count); - } else { - FlutterAppBadger.removeBadge(); - } + forwardAnimations(); }); } }, ); - _animationController?.addStatusListener((status) { - if (status == AnimationStatus.completed) { - setState(() { - _animCompleted = true; - }); - } - }); super.initState(); } - Widget _buildAnimation() { - return MaterialApp( - home: Stack( - clipBehavior: Clip.none, - alignment: Alignment.center, - children: [ - AnimatedBuilder( - animation: _scaleAnimation!, - builder: (context, _) { - return Transform.scale( - scale: _scaleAnimation!.value, - child: AnimatedBuilder( - animation: _colorAnimation!, - builder: (context, snapshot) { - return Container( - alignment: Alignment.center, - constraints: BoxConstraints.expand(), - color: _colorAnimation == null - ? Color(0xff005FFF) - : _colorAnimation!.value, - child: !_animationController!.isAnimating - ? Lottie.asset( - 'assets/floating_boat.json', - alignment: Alignment.center, - ) - : SizedBox(), - ); - }), - ); - }, - ), - AnimatedBuilder( - animation: _animation!, - builder: (context, snapshot) { - return Transform.scale( - scale: _animation!.value, - child: Container( - width: 1.0, - height: 1.0, - decoration: BoxDecoration( - color: Colors.white - .withOpacity(1 - _animationController!.value), - shape: BoxShape.circle, - ), - ), - ); - }, - ), - ], - ), - ); - } - @override Widget build(BuildContext context) { return Stack( @@ -229,27 +100,6 @@ class _MyAppState extends State with TickerProviderStateMixin { defaultValue: 0, ), builder: (context, snapshot) => MaterialApp( - builder: (context, child) { - return StreamChat( - backgroundKeepAlive: Duration(seconds: 5), - client: _initData!.client, - onBackgroundEventReceived: (e) => showLocalNotification( - e, _initData!.client.state.user!.id), - child: Builder( - builder: (context) => AnnotatedRegion( - child: child!, - value: SystemUiOverlayStyle( - systemNavigationBarColor: - StreamChatTheme.of(context).colorTheme.white, - systemNavigationBarIconBrightness: - Theme.of(context).brightness == Brightness.dark - ? Brightness.light - : Brightness.dark, - ), - ), - ), - ); - }, theme: ThemeData.light(), darkTheme: ThemeData.dark(), themeMode: { @@ -257,13 +107,38 @@ class _MyAppState extends State with TickerProviderStateMixin { 0: ThemeMode.system, 1: ThemeMode.light, }[snapshot], + builder: (context, child) => StreamChatTheme( + data: StreamChatThemeData( + brightness: Theme.of(context).brightness, + ), + 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( + RouteSettings( + name: Routes.CHOOSE_USER, + ), + )! + ]; + }, initialRoute: _initData!.client.state.user == null ? Routes.CHOOSE_USER : Routes.HOME, ), ), - if (!_animCompleted) _buildAnimation(), + if (!animationCompleted) buildAnimation(), ], ); } @@ -275,50 +150,3 @@ class InitData { InitData(this.client, this.preferences); } - -class HolePainter extends CustomPainter { - HolePainter({ - required this.color, - required this.holeSize, - }); - - Color color; - double holeSize; - - @override - void paint(Canvas canvas, Size size) { - double radius = holeSize / 2; - Rect rect = Rect.fromLTWH(0, 0, size.width, size.height); - Rect outerCircleRect = Rect.fromCircle( - center: Offset(size.width / 2, size.height / 2), radius: radius); - Rect innerCircleRect = Rect.fromCircle( - center: Offset(size.width / 2, size.height / 2), radius: radius / 2); - - Path transparentHole = Path.combine( - PathOperation.difference, - Path()..addRect(rect), - Path() - ..addOval(outerCircleRect) - ..close(), - ); - - Path halfTransparentRing = Path.combine( - PathOperation.difference, - Path() - ..addOval(outerCircleRect) - ..close(), - Path() - ..addOval(innerCircleRect) - ..close(), - ); - - canvas.drawPath(transparentHole, Paint()..color = color); - canvas.drawPath( - halfTransparentRing, Paint()..color = color.withOpacity(0.5)); - } - - @override - bool shouldRepaint(CustomPainter oldDelegate) { - return true; - } -} diff --git a/packages/stream_chat_v1/lib/pinned_messages_screen.dart b/packages/stream_chat_v1/lib/pinned_messages_screen.dart index 82dd532..d8f756b 100644 --- a/packages/stream_chat_v1/lib/pinned_messages_screen.dart +++ b/packages/stream_chat_v1/lib/pinned_messages_screen.dart @@ -70,21 +70,7 @@ class _PinnedMessagesScreenState extends State { fontSize: 16.0, ), ), - leading: Center( - child: InkWell( - onTap: () { - Navigator.of(context).pop(); - }, - child: Container( - width: 24.0, - height: 24.0, - child: StreamSvgIcon.left( - color: StreamChatTheme.of(context).colorTheme.black, - size: 24.0, - ), - ), - ), - ), + leading: StreamBackButton(), backgroundColor: StreamChatTheme.of(context).colorTheme.white, ), body: _buildMediaGrid(), diff --git a/packages/stream_chat_v1/lib/routes/app_routes.dart b/packages/stream_chat_v1/lib/routes/app_routes.dart index 4bc157e..145fa8e 100644 --- a/packages/stream_chat_v1/lib/routes/app_routes.dart +++ b/packages/stream_chat_v1/lib/routes/app_routes.dart @@ -1,3 +1,4 @@ +import 'package:example/channel_list_page.dart'; import 'package:flutter/material.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; @@ -28,7 +29,10 @@ class AppRoutes { return MaterialPageRoute( settings: const RouteSettings(name: Routes.HOME), builder: (_) { - return HomePage(); + final homePageArgs = args as HomePageArgs; + return HomePage( + chatClient: homePageArgs.chatClient, + ); }); case Routes.CHOOSE_USER: return MaterialPageRoute( @@ -92,6 +96,12 @@ class AppRoutes { messageTheme: StreamChatTheme.of(context).ownMessageTheme, ); }); + case Routes.CHANNEL_LIST_PAGE: + return MaterialPageRoute( + settings: const RouteSettings(name: Routes.CHANNEL_LIST_PAGE), + builder: (context) { + return ChannelListPage(); + }); // Default case, should not reach here. default: return null; diff --git a/packages/stream_chat_v1/lib/routes/routes.dart b/packages/stream_chat_v1/lib/routes/routes.dart index 2280ca3..239bd28 100644 --- a/packages/stream_chat_v1/lib/routes/routes.dart +++ b/packages/stream_chat_v1/lib/routes/routes.dart @@ -10,4 +10,5 @@ class Routes { 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'; } diff --git a/packages/stream_chat_v1/lib/splash_screen.dart b/packages/stream_chat_v1/lib/splash_screen.dart new file mode 100644 index 0000000..2c89228 --- /dev/null +++ b/packages/stream_chat_v1/lib/splash_screen.dart @@ -0,0 +1,122 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/scheduler.dart'; +import 'package:lottie/lottie.dart'; + +mixin SplashScreenStateMixin on State + implements TickerProvider { + late Animation animation, scaleAnimation; + late AnimationController _animationController, _scaleAnimationController; + late Animation colorAnimation; + bool animationCompleted = false; + + void _createAnimations() { + _scaleAnimationController = AnimationController( + vsync: this, + value: 0, + duration: Duration( + milliseconds: 500, + ), + ); + scaleAnimation = Tween( + begin: 1.0, + end: 1.5, + ).animate(CurvedAnimation( + parent: _scaleAnimationController, + curve: Curves.easeInOutBack, + )); + + _animationController = AnimationController( + vsync: this, + duration: Duration( + milliseconds: 1000, + ), + ); + animation = Tween( + begin: 0.0, + end: 1000.0, + ).animate(CurvedAnimation( + parent: _animationController, + curve: Curves.easeInOut, + )); + colorAnimation = ColorTween( + begin: Color(0xff005FFF), + end: Color(0xff005FFF), + ).animate(CurvedAnimation( + parent: _animationController, + curve: Curves.easeInOut, + )); + colorAnimation = ColorTween( + begin: Color(0xff005FFF), + end: Colors.transparent, + ).animate(CurvedAnimation( + parent: _animationController, + curve: Curves.easeInOut, + )); + } + + void forwardAnimations() { + _scaleAnimationController.forward().whenComplete(() { + _animationController.forward(); + }); + } + + Widget buildAnimation() => Stack( + clipBehavior: Clip.none, + alignment: Alignment.center, + children: [ + AnimatedBuilder( + animation: scaleAnimation, + builder: (context, _) { + return Transform.scale( + scale: scaleAnimation.value, + child: AnimatedBuilder( + animation: colorAnimation, + builder: (context, snapshot) { + return Container( + alignment: Alignment.center, + constraints: BoxConstraints.expand(), + color: colorAnimation.value, + child: !_animationController.isAnimating + ? Lottie.asset( + 'assets/floating_boat.json', + alignment: Alignment.center, + ) + : SizedBox(), + ); + }), + ); + }, + ), + AnimatedBuilder( + animation: animation, + builder: (context, snapshot) { + return Transform.scale( + scale: animation.value, + child: Container( + width: 1.0, + height: 1.0, + decoration: BoxDecoration( + color: Colors.white + .withOpacity(1 - _animationController.value), + shape: BoxShape.circle, + ), + ), + ); + }, + ), + ], + ); + + @override + void initState() { + _createAnimations(); + _animationController.addStatusListener((status) { + if (status == AnimationStatus.completed) { + setState(() { + animationCompleted = true; + }); + } + }); + super.initState(); + } +} diff --git a/packages/stream_chat_v1/pubspec.yaml b/packages/stream_chat_v1/pubspec.yaml index 9c82d91..8c0528b 100644 --- a/packages/stream_chat_v1/pubspec.yaml +++ b/packages/stream_chat_v1/pubspec.yaml @@ -11,15 +11,9 @@ dependencies: flutter: sdk: flutter stream_chat_flutter: - git: - url: https://github.com/GetStream/stream-chat-flutter.git - ref: ref/segregate-api-layer - path: packages/stream_chat_flutter + path: ../../../stream-chat-flutter/packages/stream_chat_flutter stream_chat_persistence: - git: - url: https://github.com/GetStream/stream-chat-flutter.git - ref: ref/segregate-api-layer - path: packages/stream_chat_persistence + path: ../../../stream-chat-flutter/packages/stream_chat_persistence flutter_local_notifications: ^5.0.0+4 flutter_svg: ^0.22.0 flutter_secure_storage: ^4.2.0 @@ -31,15 +25,9 @@ dependencies: dependency_overrides: stream_chat: - git: - url: https://github.com/GetStream/stream-chat-flutter.git - ref: ref/segregate-api-layer - path: packages/stream_chat + path: ../../../stream-chat-flutter/packages/stream_chat stream_chat_flutter_core: - git: - url: https://github.com/GetStream/stream-chat-flutter.git - ref: ref/segregate-api-layer - path: packages/stream_chat_flutter_core + path: ../../../stream-chat-flutter/packages/stream_chat_flutter_core dev_dependencies: flutter_launcher_icons: ^0.9.0