stream_chat_flutter 7.3.0 copy "stream_chat_flutter: ^7.3.0" to clipboard
stream_chat_flutter: ^7.3.0 copied to clipboard

Stream Chat official Flutter SDK. Build your own chat experience using Dart and Flutter.

example/lib/main.dart

// 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<void> 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<SplitView> {
  Channel? selectedChannel;

  @override
  Widget build(BuildContext context) {
    return Flex(
      direction: Axis.horizontal,
      children: <Widget>[
        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<ChannelListPage> createState() => _ChannelListPageState();
}

class _ChannelListPageState extends State<ChannelListPage> {
  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<ChannelPage> createState() => _ChannelPageState();
}

class _ChannelPageState extends State<ChannelPage> {
  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: <Widget>[
                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: <Widget>[
          Expanded(
            child: StreamMessageListView(
              parentMessage: parent,
            ),
          ),
          StreamMessageInput(
            messageInputController: StreamMessageInputController(
              message: Message(parentId: parent.id),
            ),
          ),
        ],
      ),
    );
  }
}