// ignore_for_file: public_member_api_docs import 'dart:async'; import 'dart:math' as math; import 'dart:ui'; import 'package:flutter/material.dart'; import 'package:responsive_builder/responsive_builder.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; import 'package:stream_chat_flutter_example/debug/channel_page.dart'; import 'package:stream_chat_localizations/stream_chat_localizations.dart'; Future main() async { WidgetsFlutterBinding.ensureInitialized(); /// Create a new instance of [StreamChatClient] passing the apikey obtained /// from your project dashboard. final client = StreamChatClient( 's2dxdhpxd94g', logLevel: Level.OFF, ); /// Set the current user and connect the websocket. In a production /// scenario, this should be done using a backend to generate a user token /// using our server SDK. /// /// Please see the following for more information: /// https://getstream.io/chat/docs/ios_user_setup_and_tokens/ await client.connectUser( User(id: 'super-band-9'), '''eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoic3VwZXItYmFuZC05In0.0L6lGoeLwkz0aZRUcpZKsvaXtNEDHBcezVTZ0oPq40A''', ); final channel = client.channel('messaging', id: 'godevs'); await channel.watch(); runApp( MyApp( client: client, channel: channel, ), ); } /// Example application using Stream Chat Flutter widgets. /// /// Stream Chat Flutter is a set of Flutter widgets which provide full chat /// functionalities for building Flutter applications using Stream. If you'd /// prefer using minimal wrapper widgets for your app, please see our other /// package, `stream_chat_flutter_core`. class MyApp extends StatelessWidget { /// Example using Stream's Flutter package. /// /// If you'd prefer using minimal wrapper widgets for your app, please see /// our other package, `stream_chat_flutter_core`. const MyApp({ super.key, required this.client, required this.channel, }); /// Instance of Stream Client. /// /// Stream's [StreamChatClient] can be used to connect to our servers and /// set the default user for the application. Performing these actions /// trigger a websocket connection allowing for real-time updates. final StreamChatClient client; /// Instance of the Channel final Channel channel; @override Widget build(BuildContext context) { return MaterialApp( debugShowCheckedModeBanner: false, theme: ThemeData.light(), darkTheme: ThemeData.dark(), // themeMode: ThemeMode.dark, supportedLocales: const [ Locale('en'), Locale('hi'), Locale('fr'), Locale('it'), Locale('es'), ], localizationsDelegates: GlobalStreamChatLocalizations.delegates, builder: (context, widget) => StreamChat( client: client, child: widget, ), home: StreamChannel( channel: channel, child: const ResponsiveChat(), ), ); } } class ResponsiveChat extends StatelessWidget { const ResponsiveChat({ super.key, }); @override Widget build(BuildContext context) { return ResponsiveBuilder( builder: (context, sizingInformation) { if (sizingInformation.isMobile) { return ChannelListPage( onTap: (c) { Navigator.push( context, MaterialPageRoute( builder: (context) { return StreamChannel( channel: c, child: ChannelPage( onBackPressed: (context) { Navigator.of( context, rootNavigator: true, ).pop(); }, ), ); }, ), ); }, ); } return const SplitView(); }, breakpoints: const ScreenBreakpoints( desktop: 550, tablet: 550, watch: 300, ), ); } } class SplitView extends StatefulWidget { const SplitView({ super.key, }); @override _SplitViewState createState() => _SplitViewState(); } class _SplitViewState extends State { Channel? selectedChannel; @override Widget build(BuildContext context) { return Flex( direction: Axis.horizontal, children: [ Flexible( child: ChannelListPage( onTap: (channel) { setState(() { selectedChannel = channel; }); }, selectedChannel: selectedChannel, ), ), Flexible( flex: 2, child: ClipPath( child: Scaffold( body: selectedChannel != null ? StreamChannel( key: ValueKey(selectedChannel!.cid), channel: selectedChannel!, child: const ChannelPage(showBackButton: false), ) : Center( child: Text( 'Pick a channel to show the messages 💬', style: Theme.of(context).textTheme.headlineSmall, ), ), ), ), ), ], ); } } class ChannelListPage extends StatefulWidget { const ChannelListPage({ super.key, this.onTap, this.selectedChannel, }); final void Function(Channel)? onTap; final Channel? selectedChannel; @override State createState() => _ChannelListPageState(); } class _ChannelListPageState extends State { late final _listController = StreamChannelListController( client: StreamChat.of(context).client, filter: Filter.in_( 'members', [StreamChat.of(context).currentUser!.id], ), channelStateSort: const [SortOption('last_message_at')], limit: 20, ); @override Widget build(BuildContext context) => Scaffold( body: StreamChannelListView( onChannelTap: widget.onTap, controller: _listController, itemBuilder: (context, channels, index, defaultWidget) { return defaultWidget.copyWith( selected: channels[index] == widget.selectedChannel, ); }, ), ); } class ChannelPage extends StatefulWidget { const ChannelPage({ super.key, this.showBackButton = true, this.onBackPressed, }); final bool showBackButton; final void Function(BuildContext)? onBackPressed; @override State createState() => _ChannelPageState(); } class _ChannelPageState extends State { late final messageInputController = StreamMessageInputController(); final focusNode = FocusNode(); @override Widget build(BuildContext context) => Navigator( onGenerateRoute: (settings) => MaterialPageRoute( builder: (context) => Scaffold( appBar: StreamChannelHeader( onBackPressed: widget.onBackPressed != null ? () { widget.onBackPressed!(context); } : null, onImageTap: () { Navigator.push( context, MaterialPageRoute( builder: (context) { return StreamChannel( channel: StreamChannel.of(context).channel, child: const DebugChannelPage(), ); }, ), ); }, showBackButton: widget.showBackButton, ), body: Column( children: [ Expanded( child: StreamMessageListView( threadBuilder: (context, parent) { return ThreadPage( parent: parent!, ); }, messageBuilder: ( context, messageDetails, messages, defaultWidget, ) { // The threshold after which the message is considered // swiped. const threshold = 0.2; final isMyMessage = messageDetails.isMyMessage; // The direction in which the message can be swiped. final swipeDirection = isMyMessage ? SwipeDirection.endToStart // : SwipeDirection.startToEnd; return Swipeable( key: ValueKey(messageDetails.message.id), direction: swipeDirection, swipeThreshold: threshold, onSwiped: (details) => reply(messageDetails.message), backgroundBuilder: (context, details) { // The alignment of the swipe action. final alignment = isMyMessage ? Alignment.centerRight // : Alignment.centerLeft; // The progress of the swipe action. final progress = math.min(details.progress, threshold) / threshold; // The offset for the reply icon. var offset = Offset.lerp( const Offset(-24, 0), const Offset(12, 0), progress, )!; // If the message is mine, we need to flip the offset. if (isMyMessage) { offset = Offset(-offset.dx, -offset.dy); } final _streamTheme = StreamChatTheme.of(context); return Align( alignment: alignment, child: Transform.translate( offset: offset, child: Opacity( opacity: progress, child: SizedBox.square( dimension: 30, child: CustomPaint( painter: AnimatedCircleBorderPainter( progress: progress, color: _streamTheme.colorTheme.borders, ), child: Center( child: StreamSvgIcon.reply( size: lerpDouble(0, 18, progress), color: _streamTheme .colorTheme.accentPrimary, ), ), ), ), ), ), ); }, child: defaultWidget.copyWith(onReplyTap: reply), ); }, ), ), StreamMessageInput( onQuotedMessageCleared: messageInputController.clearQuotedMessage, focusNode: focusNode, messageInputController: messageInputController, ), ], ), ), ), ); void reply(Message message) { messageInputController.quotedMessage = message; WidgetsBinding.instance.addPostFrameCallback((timeStamp) { focusNode.requestFocus(); }); } @override void dispose() { focusNode.dispose(); messageInputController.dispose(); super.dispose(); } } class ThreadPage extends StatelessWidget { const ThreadPage({ super.key, required this.parent, }); final Message parent; @override Widget build(BuildContext context) { return Scaffold( appBar: StreamThreadHeader( parent: parent, ), body: Column( children: [ Expanded( child: StreamMessageListView( parentMessage: parent, ), ), StreamMessageInput( messageInputController: StreamMessageInputController( message: Message(parentId: parent.id), ), ), ], ), ); } }