From 803b42307b7d200a2785c6c2743ef108d3160169 Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Thu, 21 Jan 2021 14:02:00 +0530 Subject: [PATCH 01/39] feat: Added new ui package --- packages/stream_chat_flutter_core/.gitignore | 74 +++++++++++++++++++ packages/stream_chat_flutter_core/.metadata | 10 +++ .../stream_chat_flutter_core/CHANGELOG.md | 3 + packages/stream_chat_flutter_core/LICENSE | 1 + packages/stream_chat_flutter_core/README.md | 14 ++++ .../lib/stream_chat_flutter_core.dart | 7 ++ .../stream_chat_flutter_core/pubspec.yaml | 54 ++++++++++++++ .../test/stream_chat_flutter_core_test.dart | 13 ++++ 8 files changed, 176 insertions(+) create mode 100644 packages/stream_chat_flutter_core/.gitignore create mode 100644 packages/stream_chat_flutter_core/.metadata create mode 100644 packages/stream_chat_flutter_core/CHANGELOG.md create mode 100644 packages/stream_chat_flutter_core/LICENSE create mode 100644 packages/stream_chat_flutter_core/README.md create mode 100644 packages/stream_chat_flutter_core/lib/stream_chat_flutter_core.dart create mode 100644 packages/stream_chat_flutter_core/pubspec.yaml create mode 100644 packages/stream_chat_flutter_core/test/stream_chat_flutter_core_test.dart diff --git a/packages/stream_chat_flutter_core/.gitignore b/packages/stream_chat_flutter_core/.gitignore new file mode 100644 index 00000000..1985397a --- /dev/null +++ b/packages/stream_chat_flutter_core/.gitignore @@ -0,0 +1,74 @@ +# Miscellaneous +*.class +*.log +*.pyc +*.swp +.DS_Store +.atom/ +.buildlog/ +.history +.svn/ + +# IntelliJ related +*.iml +*.ipr +*.iws +.idea/ + +# The .vscode folder contains launch configuration and tasks you configure in +# VS Code which you may wish to be included in version control, so this line +# is commented out by default. +#.vscode/ + +# Flutter/Dart/Pub related +**/doc/api/ +.dart_tool/ +.flutter-plugins +.flutter-plugins-dependencies +.packages +.pub-cache/ +.pub/ +build/ + +# Android related +**/android/**/gradle-wrapper.jar +**/android/.gradle +**/android/captures/ +**/android/gradlew +**/android/gradlew.bat +**/android/local.properties +**/android/**/GeneratedPluginRegistrant.java + +# iOS/XCode related +**/ios/**/*.mode1v3 +**/ios/**/*.mode2v3 +**/ios/**/*.moved-aside +**/ios/**/*.pbxuser +**/ios/**/*.perspectivev3 +**/ios/**/*sync/ +**/ios/**/.sconsign.dblite +**/ios/**/.tags* +**/ios/**/.vagrant/ +**/ios/**/DerivedData/ +**/ios/**/Icon? +**/ios/**/Pods/ +**/ios/**/.symlinks/ +**/ios/**/profile +**/ios/**/xcuserdata +**/ios/.generated/ +**/ios/Flutter/App.framework +**/ios/Flutter/Flutter.framework +**/ios/Flutter/Flutter.podspec +**/ios/Flutter/Generated.xcconfig +**/ios/Flutter/app.flx +**/ios/Flutter/app.zip +**/ios/Flutter/flutter_assets/ +**/ios/Flutter/flutter_export_environment.sh +**/ios/ServiceDefinitions.json +**/ios/Runner/GeneratedPluginRegistrant.* + +# Exceptions to above rules. +!**/ios/**/default.mode1v3 +!**/ios/**/default.mode2v3 +!**/ios/**/default.pbxuser +!**/ios/**/default.perspectivev3 diff --git a/packages/stream_chat_flutter_core/.metadata b/packages/stream_chat_flutter_core/.metadata new file mode 100644 index 00000000..5eb50347 --- /dev/null +++ b/packages/stream_chat_flutter_core/.metadata @@ -0,0 +1,10 @@ +# This file tracks properties of this Flutter project. +# Used by Flutter tool to assess capabilities and perform upgrades etc. +# +# This file should be version controlled and should not be manually edited. + +version: + revision: 78910062997c3a836feee883712c241a5fd22983 + channel: stable + +project_type: package diff --git a/packages/stream_chat_flutter_core/CHANGELOG.md b/packages/stream_chat_flutter_core/CHANGELOG.md new file mode 100644 index 00000000..ac071598 --- /dev/null +++ b/packages/stream_chat_flutter_core/CHANGELOG.md @@ -0,0 +1,3 @@ +## [0.0.1] - TODO: Add release date. + +* TODO: Describe initial release. diff --git a/packages/stream_chat_flutter_core/LICENSE b/packages/stream_chat_flutter_core/LICENSE new file mode 100644 index 00000000..ba75c69f --- /dev/null +++ b/packages/stream_chat_flutter_core/LICENSE @@ -0,0 +1 @@ +TODO: Add your license here. diff --git a/packages/stream_chat_flutter_core/README.md b/packages/stream_chat_flutter_core/README.md new file mode 100644 index 00000000..0acef57c --- /dev/null +++ b/packages/stream_chat_flutter_core/README.md @@ -0,0 +1,14 @@ +# stream_chat_flutter_core + +Stream Chat Official Flutter SDK Core. Build and customize your own chat experience using Dart and Flutter. + +## Getting Started + +This project is a starting point for a Dart +[package](https://flutter.dev/developing-packages/), +a library module containing code that can be shared easily across +multiple Flutter or Dart projects. + +For help getting started with Flutter, view our +[online documentation](https://flutter.dev/docs), which offers tutorials, +samples, guidance on mobile development, and a full API reference. diff --git a/packages/stream_chat_flutter_core/lib/stream_chat_flutter_core.dart b/packages/stream_chat_flutter_core/lib/stream_chat_flutter_core.dart new file mode 100644 index 00000000..1f8e28bb --- /dev/null +++ b/packages/stream_chat_flutter_core/lib/stream_chat_flutter_core.dart @@ -0,0 +1,7 @@ +library stream_chat_flutter_core; + +/// A Calculator. +class Calculator { + /// Returns [value] plus 1. + int addOne(int value) => value + 1; +} diff --git a/packages/stream_chat_flutter_core/pubspec.yaml b/packages/stream_chat_flutter_core/pubspec.yaml new file mode 100644 index 00000000..9590e0c4 --- /dev/null +++ b/packages/stream_chat_flutter_core/pubspec.yaml @@ -0,0 +1,54 @@ +name: stream_chat_flutter_core +description: Stream Chat Official Flutter SDK Core. Build and customize your own chat experience using Dart and Flutter. +version: 0.0.1 +author: +homepage: + +environment: + sdk: ">=2.7.0 <3.0.0" + flutter: ">=1.17.0" + +dependencies: + flutter: + sdk: flutter + +dev_dependencies: + flutter_test: + sdk: flutter + +# For information on the generic Dart part of this file, see the +# following page: https://dart.dev/tools/pub/pubspec + +# The following section is specific to Flutter. +flutter: + + # To add assets to your package, add an assets section, like this: + # assets: + # - images/a_dot_burr.jpeg + # - images/a_dot_ham.jpeg + # + # For details regarding assets in packages, see + # https://flutter.dev/assets-and-images/#from-packages + # + # An image asset can refer to one or more resolution-specific "variants", see + # https://flutter.dev/assets-and-images/#resolution-aware. + + # To add custom fonts to your package, add a fonts section here, + # in this "flutter" section. Each entry in this list should have a + # "family" key with the font family name, and a "fonts" key with a + # list giving the asset and other descriptors for the font. For + # example: + # fonts: + # - family: Schyler + # fonts: + # - asset: fonts/Schyler-Regular.ttf + # - asset: fonts/Schyler-Italic.ttf + # style: italic + # - family: Trajan Pro + # fonts: + # - asset: fonts/TrajanPro.ttf + # - asset: fonts/TrajanPro_Bold.ttf + # weight: 700 + # + # For details regarding fonts in packages, see + # https://flutter.dev/custom-fonts/#from-packages diff --git a/packages/stream_chat_flutter_core/test/stream_chat_flutter_core_test.dart b/packages/stream_chat_flutter_core/test/stream_chat_flutter_core_test.dart new file mode 100644 index 00000000..626831ae --- /dev/null +++ b/packages/stream_chat_flutter_core/test/stream_chat_flutter_core_test.dart @@ -0,0 +1,13 @@ +import 'package:flutter_test/flutter_test.dart'; + +import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; + +void main() { + test('adds one to input values', () { + final calculator = Calculator(); + expect(calculator.addOne(2), 3); + expect(calculator.addOne(-7), -6); + expect(calculator.addOne(0), 1); + expect(() => calculator.addOne(null), throwsNoSuchMethodError); + }); +} From e0474b8ded44706e8a2d78ca04645397bd12faeb Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Thu, 21 Jan 2021 17:55:31 +0530 Subject: [PATCH 02/39] feat: Added first widgets --- .../lib/src/lazy_load_scroll_view.dart | 148 +++ .../lib/src/reaction_icon.dart | 9 + .../lib/src/stream_chat.dart | 177 ++++ .../lib/src/stream_chat_theme.dart | 898 ++++++++++++++++++ .../lib/src/user_list_view.dart | 507 ++++++++++ .../lib/src/users_bloc.dart | 108 +++ .../stream_chat_flutter_core/pubspec.yaml | 1 + 7 files changed, 1848 insertions(+) create mode 100644 packages/stream_chat_flutter_core/lib/src/lazy_load_scroll_view.dart create mode 100644 packages/stream_chat_flutter_core/lib/src/reaction_icon.dart create mode 100644 packages/stream_chat_flutter_core/lib/src/stream_chat.dart create mode 100644 packages/stream_chat_flutter_core/lib/src/stream_chat_theme.dart create mode 100644 packages/stream_chat_flutter_core/lib/src/user_list_view.dart create mode 100644 packages/stream_chat_flutter_core/lib/src/users_bloc.dart diff --git a/packages/stream_chat_flutter_core/lib/src/lazy_load_scroll_view.dart b/packages/stream_chat_flutter_core/lib/src/lazy_load_scroll_view.dart new file mode 100644 index 00000000..bc1f4d1b --- /dev/null +++ b/packages/stream_chat_flutter_core/lib/src/lazy_load_scroll_view.dart @@ -0,0 +1,148 @@ +import 'package:flutter/foundation.dart'; +import 'package:flutter/widgets.dart'; + +enum _LoadingStatus { LOADING, STABLE } + +/// A widget that wraps a [Widget] and will trigger [onEndOfPage]/[onStartOfPage] when it +/// reaches the bottom/start of the list +class LazyLoadScrollView extends StatefulWidget { + /// The [Widget] that this widget watches for changes on + final Widget child; + + /// Called when the [child] reaches the start of the list + final AsyncCallback onStartOfPage; + + /// Called when the [child] reaches the end of the list + final AsyncCallback onEndOfPage; + + /// Called when the list scrolling starts + final VoidCallback onPageScrollStart; + + /// Called when the list scrolling ends + final VoidCallback onPageScrollEnd; + + /// Called every time the [child] is in-between the list + final VoidCallback onInBetweenOfPage; + + /// The offset to take into account when triggering [onEndOfPage]/[onStartOfPage] in pixels + final double scrollOffset; + + /// Used to determine if loading of new data has finished. You should use set this if you aren't using a FutureBuilder or StreamBuilder + final bool isLoading; + + /// Initiates a LazyLoadScrollView widget + const LazyLoadScrollView({ + Key key, + @required this.child, + this.onStartOfPage, + this.onEndOfPage, + this.onPageScrollStart, + this.onPageScrollEnd, + this.onInBetweenOfPage, + this.isLoading = false, + this.scrollOffset = 100, + }) : assert(child != null), + super(key: key); + + @override + State createState() => _LazyLoadScrollViewState(); +} + +class _LazyLoadScrollViewState extends State { + _LoadingStatus _loadMoreStatus = _LoadingStatus.STABLE; + double _scrollPosition = 0.0; + + @override + Widget build(BuildContext context) { + return NotificationListener( + child: widget.child, + onNotification: _onNotification, + ); + } + + bool _onNotification(Notification notification) { + if (notification is ScrollStartNotification) { + if (widget.onPageScrollStart != null) { + widget.onPageScrollStart(); + return true; + } + } + if (notification is ScrollEndNotification) { + if (widget.onPageScrollEnd != null) { + widget.onPageScrollEnd(); + return true; + } + } + if (notification is ScrollUpdateNotification) { + final pixels = notification.metrics.pixels; + final maxScrollExtent = notification.metrics.maxScrollExtent; + final minScrollExtent = notification.metrics.minScrollExtent; + final scrollOffset = widget.scrollOffset; + + if (pixels > (minScrollExtent + scrollOffset) && + pixels < (maxScrollExtent - scrollOffset)) { + if (widget.onInBetweenOfPage != null) { + widget.onInBetweenOfPage(); + return true; + } + } + + final extentBefore = notification.metrics.extentBefore; + final extentAfter = notification.metrics.extentAfter; + final scrollingDown = _scrollPosition < pixels; + + if (scrollOffset == null || scrollOffset == 0) { + if (extentAfter == 0) { + _onEndOfPage(); + } + if (extentBefore == 0) { + _onStartOfPage(); + } + } else { + if (scrollingDown) { + if (extentAfter <= scrollOffset) { + _onEndOfPage(); + } + } else { + if (extentBefore <= scrollOffset) { + _onStartOfPage(); + } + } + } + _scrollPosition = pixels; + return true; + } + if (notification is OverscrollNotification) { + if (notification.overscroll > 0) { + _onEndOfPage(); + } + if (notification.overscroll < 0) { + _onStartOfPage(); + } + return true; + } + return false; + } + + void _onEndOfPage() { + if (_loadMoreStatus != null && _loadMoreStatus == _LoadingStatus.STABLE) { + _loadMoreStatus = _LoadingStatus.LOADING; + if (widget.onEndOfPage != null) { + widget.onEndOfPage().whenComplete(() { + _loadMoreStatus = _LoadingStatus.STABLE; + }); + } + } + } + + void _onStartOfPage() { + if (_loadMoreStatus != null && _loadMoreStatus == _LoadingStatus.STABLE) { + _loadMoreStatus = _LoadingStatus.LOADING; + if (widget.onStartOfPage != null) { + widget.onStartOfPage().whenComplete(() { + _loadMoreStatus = _LoadingStatus.STABLE; + }); + } + } + } +} diff --git a/packages/stream_chat_flutter_core/lib/src/reaction_icon.dart b/packages/stream_chat_flutter_core/lib/src/reaction_icon.dart new file mode 100644 index 00000000..99b93328 --- /dev/null +++ b/packages/stream_chat_flutter_core/lib/src/reaction_icon.dart @@ -0,0 +1,9 @@ +class ReactionIcon { + final String type; + final String assetName; + + ReactionIcon({ + this.type, + this.assetName, + }); +} diff --git a/packages/stream_chat_flutter_core/lib/src/stream_chat.dart b/packages/stream_chat_flutter_core/lib/src/stream_chat.dart new file mode 100644 index 00000000..e6c47c9d --- /dev/null +++ b/packages/stream_chat_flutter_core/lib/src/stream_chat.dart @@ -0,0 +1,177 @@ +import 'dart:async'; + +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:stream_chat/stream_chat.dart'; +import 'package:stream_chat_flutter_core/src/stream_chat_theme.dart'; + +/// Widget used to provide information about the chat to the widget tree +/// +/// class MyApp extends StatelessWidget { +/// final Client client; +/// +/// MyApp(this.client); +/// +/// @override +/// Widget build(BuildContext context) { +/// return MaterialApp( +/// home: Container( +/// child: StreamChat( +/// client: client, +/// child: ChannelListPage(), +/// ), +/// ), +/// ); +/// } +/// } +/// +/// Use [StreamChat.of] to get the current [StreamChatState] instance. +class StreamChat extends StatefulWidget { + final Client client; + final Widget child; + final StreamChatThemeData streamChatThemeData; + + StreamChat({ + Key key, + @required this.client, + @required this.child, + this.streamChatThemeData, + }) : super( + key: key, + ); + + @override + StreamChatState createState() => StreamChatState(); + + /// Use this method to get the current [StreamChatState] instance + static StreamChatState of(BuildContext context) { + StreamChatState streamChatState; + + streamChatState = context.findAncestorStateOfType(); + + if (streamChatState == null) { + throw Exception( + 'You must have a StreamChat widget at the top of your widget tree'); + } + + return streamChatState; + } +} + +/// The current state of the StreamChat widget +class StreamChatState extends State with WidgetsBindingObserver { + Client get client => widget.client; + Timer _disconnectTimer; + + @override + Widget build(BuildContext context) { + final theme = _getTheme(context, widget.streamChatThemeData); + return StreamChatTheme( + data: theme, + child: Builder( + builder: (context) { + final materialTheme = Theme.of(context); + final streamTheme = StreamChatTheme.of(context); + return Theme( + data: materialTheme.copyWith( + primaryIconTheme: streamTheme.primaryIconTheme, + accentColor: streamTheme.colorTheme.accentBlue, + scaffoldBackgroundColor: streamTheme.colorTheme.white, + ), + child: widget.child, + ); + }, + ), + ); + } + + StreamChatThemeData _getTheme( + BuildContext context, + StreamChatThemeData themeData, + ) { + final defaultTheme = StreamChatThemeData.getDefaultTheme(Theme.of(context)); + return defaultTheme.merge(themeData) ?? themeData; + } + + /// The current user + User get user => widget.client.state.user; + + /// The current user as a stream + Stream get userStream => widget.client.state.userStream; + + @override + void initState() { + super.initState(); + WidgetsBinding.instance.addObserver(this); + // client.state?.totalUnreadCountStream?.listen((count) { + // if (count > 0) { + // FlutterAppBadger.updateBadgeCount(count); + // } else { + // FlutterAppBadger.removeBadge(); + // } + // }); + } + + StreamSubscription _newMessageSubscription; + + @override + void didChangeAppLifecycleState(AppLifecycleState state) { + if (client.state?.user != null) { + if (state == AppLifecycleState.paused) { + if (client.showLocalNotification != null) { + _newMessageSubscription = client + .on(EventType.messageNew) + .where((e) => e.user?.id != user.id) + .where((e) => e.message.silent != true) + .where((e) => e.message.shadowed != true) + .listen((event) async { + final channel = client.channel( + event.channelType, + id: event.channelId, + ); + + client.showLocalNotification( + event.message, + ChannelModel( + id: channel.id, + createdAt: channel.createdAt, + extraData: channel.extraData, + type: channel.type, + memberCount: channel.memberCount, + frozen: channel.frozen, + cid: channel.cid, + deletedAt: channel.deletedAt, + config: channel.config, + createdBy: channel.createdBy, + updatedAt: channel.updatedAt, + lastMessageAt: channel.lastMessageAt, + ), + ); + }); + _disconnectTimer = Timer(client.backgroundKeepAlive, () { + client.disconnect(); + }); + } else { + client.disconnect(); + } + } else if (state == AppLifecycleState.resumed) { + _newMessageSubscription?.cancel(); + if (_disconnectTimer?.isActive == true) { + _disconnectTimer.cancel(); + } else { + if (client.wsConnectionStatus.value == + ConnectionStatus.disconnected) { + NotificationService.handleIosMessageQueue(client); + client.connect(); + } + } + } + } + } + + @override + void dispose() { + WidgetsBinding.instance.removeObserver(this); + super.dispose(); + } +} diff --git a/packages/stream_chat_flutter_core/lib/src/stream_chat_theme.dart b/packages/stream_chat_flutter_core/lib/src/stream_chat_theme.dart new file mode 100644 index 00000000..be584492 --- /dev/null +++ b/packages/stream_chat_flutter_core/lib/src/stream_chat_theme.dart @@ -0,0 +1,898 @@ +import 'package:flutter/material.dart'; +import 'package:stream_chat/stream_chat.dart'; +import 'package:stream_chat_flutter_core/src/reaction_icon.dart'; + +/// Inherited widget providing the [StreamChatThemeData] to the widget tree +class StreamChatTheme extends InheritedWidget { + final StreamChatThemeData data; + + StreamChatTheme({ + Key key, + @required this.data, + Widget child, + }) : super( + key: key, + child: child, + ); + + @override + bool updateShouldNotify(StreamChatTheme old) { + return data != old.data; + } + + /// Use this method to get the current [StreamChatThemeData] instance + static StreamChatThemeData of(BuildContext context) { + final streamChatTheme = + context.dependOnInheritedWidgetOfExactType(); + + if (streamChatTheme == null) { + throw Exception( + 'You must have a StreamChatTheme widget at the top of your widget tree', + ); + } + + return streamChatTheme.data; + } +} + +/// Theme data +class StreamChatThemeData { + /// The text themes used in the widgets + final TextTheme textTheme; + + /// The button themes used in the widgets + final ButtonThemeData buttonTheme; + + /// The text themes used in the widgets + final ColorTheme colorTheme; + + /// Theme of the [ChannelPreview] + final ChannelPreviewTheme channelPreviewTheme; + + /// Theme of the chat widgets dedicated to a channel + final ChannelTheme channelTheme; + + /// Theme of the current user messages + final MessageTheme ownMessageTheme; + + /// Theme of other users messages + final MessageTheme otherMessageTheme; + + /// The widget that will be built when the channel image is unavailable + final Widget Function(BuildContext, Channel) defaultChannelImage; + + /// The widget that will be built when the user image is unavailable + final Widget Function(BuildContext, User) defaultUserImage; + + /// Primary icon theme + final IconThemeData primaryIconTheme; + + /// Assets used for rendering reactions + final List reactionIcons; + + /// Create a theme from scratch + const StreamChatThemeData({ + this.textTheme, + this.buttonTheme, + this.colorTheme, + this.channelPreviewTheme, + this.channelTheme, + this.otherMessageTheme, + this.ownMessageTheme, + this.defaultChannelImage, + this.defaultUserImage, + this.primaryIconTheme, + this.reactionIcons, + }); + + /// Create a theme from a Material [Theme] + factory StreamChatThemeData.fromTheme(ThemeData theme) { + final defaultTheme = getDefaultTheme(theme); + final customizedTheme = StreamChatThemeData( + primaryIconTheme: theme.primaryIconTheme, + ownMessageTheme: MessageTheme( + replies: TextStyle(color: theme.accentColor), + messageLinks: TextStyle(color: theme.accentColor), + ), + otherMessageTheme: MessageTheme( + replies: TextStyle(color: theme.accentColor), + messageLinks: TextStyle(color: theme.accentColor), + ), + ); + return defaultTheme.merge(customizedTheme) ?? customizedTheme; + } + + /// Creates a copy of [StreamChatThemeData] with specified attributes overridden. + StreamChatThemeData copyWith({ + TextTheme textTheme, + ButtonThemeData buttonTheme, + ColorTheme colorTheme, + ChannelPreviewTheme channelPreviewTheme, + ChannelTheme channelTheme, + MessageTheme ownMessageTheme, + MessageTheme otherMessageTheme, + Widget Function(BuildContext, Channel) defaultChannelImage, + Widget Function(BuildContext, User) defaultUserImage, + IconThemeData primaryIconTheme, + List reactionIcons, + }) => + StreamChatThemeData( + textTheme: textTheme ?? this.textTheme, + buttonTheme: buttonTheme ?? this.buttonTheme, + colorTheme: colorTheme ?? this.colorTheme, + primaryIconTheme: primaryIconTheme ?? this.primaryIconTheme, + defaultChannelImage: defaultChannelImage ?? this.defaultChannelImage, + defaultUserImage: defaultUserImage ?? this.defaultUserImage, + channelPreviewTheme: channelPreviewTheme ?? this.channelPreviewTheme, + channelTheme: channelTheme ?? this.channelTheme, + ownMessageTheme: ownMessageTheme ?? this.ownMessageTheme, + otherMessageTheme: otherMessageTheme ?? this.otherMessageTheme, + reactionIcons: reactionIcons ?? this.reactionIcons, + ); + + StreamChatThemeData merge(StreamChatThemeData other) { + if (other == null) return this; + return copyWith( + textTheme: textTheme?.merge(other.textTheme) ?? other.textTheme, + buttonTheme: other.buttonTheme, + colorTheme: colorTheme?.merge(other.colorTheme) ?? other.colorTheme, + primaryIconTheme: other.primaryIconTheme, + defaultChannelImage: other.defaultChannelImage, + defaultUserImage: other.defaultUserImage, + channelPreviewTheme: + channelPreviewTheme?.merge(other.channelPreviewTheme) ?? + other.channelPreviewTheme, + channelTheme: + channelTheme?.merge(other.channelTheme) ?? other.channelTheme, + ownMessageTheme: ownMessageTheme?.merge(other.ownMessageTheme) ?? + other.ownMessageTheme, + otherMessageTheme: otherMessageTheme?.merge(other.otherMessageTheme) ?? + other.otherMessageTheme, + reactionIcons: other.reactionIcons, + ); + } + + /// Get the default Stream Chat theme + static StreamChatThemeData getDefaultTheme(ThemeData theme) { + final accentColor = Color(0xff006cff); + final isDark = theme.brightness == Brightness.dark; + final textTheme = isDark ? TextTheme.dark() : TextTheme.light(); + final colorTheme = isDark ? ColorTheme.dark() : ColorTheme.light(); + return StreamChatThemeData( + textTheme: textTheme, + colorTheme: colorTheme, + buttonTheme: ButtonThemeData( + height: 48.0, + buttonColor: isDark ? Color(0xffffffff) : Color(0xff006aff), + textTheme: ButtonTextTheme.accent, + colorScheme: theme.colorScheme.copyWith( + secondary: isDark ? Color(0xff005eff) : Color(0xffffffff), + ), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(26), + ), + ), + primaryIconTheme: IconThemeData(color: colorTheme.black.withOpacity(.5)), + defaultChannelImage: (context, channel) => SizedBox(), + defaultUserImage: (context, user) => Center( + child: Image.network( + getRandomPicUrl(user), + filterQuality: FilterQuality.high, + fit: BoxFit.cover, + ), + ), + channelPreviewTheme: ChannelPreviewTheme( + unreadCounterColor: colorTheme.accentRed, + avatarTheme: AvatarTheme( + borderRadius: BorderRadius.circular(20), + constraints: BoxConstraints.tightFor( + height: 40, + width: 40, + ), + ), + title: textTheme.bodyBold, + subtitle: textTheme.footnote.copyWith( + color: Color(0xff7A7A7A), + ), + lastMessageAt: textTheme.footnote.copyWith( + color: colorTheme.black.withOpacity(.5), + ), + indicatorIconSize: 16.0), + channelTheme: ChannelTheme( + messageInputButtonIconTheme: theme.iconTheme.copyWith( + color: accentColor, + ), + channelHeaderTheme: ChannelHeaderTheme( + avatarTheme: AvatarTheme( + borderRadius: BorderRadius.circular(20), + constraints: BoxConstraints.tightFor( + height: 40, + width: 40, + ), + ), + color: colorTheme.white, + title: TextStyle( + fontSize: 14, + color: colorTheme.black, + ), + lastMessageAt: TextStyle( + fontSize: 11, + color: colorTheme.black.withOpacity(.5), + ), + ), + inputBackground: colorTheme.white.withAlpha(12), + ), + ownMessageTheme: MessageTheme( + messageText: textTheme.body, + createdAt: textTheme.footnote.copyWith(color: colorTheme.grey), + replies: textTheme.footnoteBold.copyWith(color: accentColor), + messageBackgroundColor: colorTheme.greyGainsboro, + reactionsBackgroundColor: colorTheme.white, + reactionsBorderColor: colorTheme.greyWhisper, + reactionsMaskColor: colorTheme.whiteSnow, + messageBorderColor: colorTheme.greyGainsboro, + avatarTheme: AvatarTheme( + borderRadius: BorderRadius.circular(20), + constraints: BoxConstraints.tightFor( + height: 32, + width: 32, + ), + ), + messageLinks: TextStyle( + color: accentColor, + ), + ), + otherMessageTheme: MessageTheme( + reactionsBackgroundColor: colorTheme.greyGainsboro, + reactionsBorderColor: colorTheme.white, + reactionsMaskColor: colorTheme.whiteSnow, + messageText: textTheme.body, + createdAt: textTheme.footnote.copyWith(color: colorTheme.grey), + replies: textTheme.footnoteBold.copyWith(color: accentColor), + messageLinks: TextStyle( + color: accentColor, + ), + messageBackgroundColor: colorTheme.white, + messageBorderColor: colorTheme.greyWhisper, + avatarTheme: AvatarTheme( + borderRadius: BorderRadius.circular(20), + constraints: BoxConstraints.tightFor( + height: 32, + width: 32, + ), + ), + ), + reactionIcons: [ + ReactionIcon( + type: 'love', + assetName: 'Icon_love_reaction.svg', + ), + ReactionIcon( + type: 'like', + assetName: 'Icon_thumbs_up_reaction.svg', + ), + ReactionIcon( + type: 'sad', + assetName: 'Icon_thumbs_down_reaction.svg', + ), + ReactionIcon( + type: 'haha', + assetName: 'Icon_LOL_reaction.svg', + ), + ReactionIcon( + type: 'wow', + assetName: 'Icon_wut_reaction.svg', + ), + ], + ); + } +} + +enum TextThemeType { + light, + dark, +} + +class TextTheme { + final TextStyle title; + final TextStyle headlineBold; + final TextStyle headline; + final TextStyle bodyBold; + final TextStyle body; + final TextStyle footnoteBold; + final TextStyle footnote; + final TextStyle captionBold; + + TextTheme.light({ + this.title = const TextStyle( + fontSize: 22, + fontWeight: FontWeight.bold, + color: Colors.black, + ), + this.headlineBold = const TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + color: Colors.black, + ), + this.headline = const TextStyle( + fontSize: 16, + fontWeight: FontWeight.w500, + color: Colors.black, + ), + this.bodyBold = const TextStyle( + fontSize: 14, + fontWeight: FontWeight.bold, + color: Colors.black, + ), + this.body = const TextStyle( + fontSize: 14, + fontWeight: FontWeight.w500, + color: Colors.black, + ), + this.footnoteBold = const TextStyle( + fontSize: 12, + fontWeight: FontWeight.w500, + color: Colors.black, + ), + this.footnote = const TextStyle( + fontSize: 12, + color: Colors.black, + ), + this.captionBold = const TextStyle( + fontSize: 10, + fontWeight: FontWeight.bold, + color: Colors.black, + ), + }); + + TextTheme.dark({ + this.title = const TextStyle( + fontSize: 22, + fontWeight: FontWeight.bold, + color: Colors.white, + ), + this.headlineBold = const TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + color: Colors.white, + ), + this.headline = const TextStyle( + fontSize: 16, + fontWeight: FontWeight.w500, + color: Colors.white, + ), + this.bodyBold = const TextStyle( + fontSize: 14, + fontWeight: FontWeight.bold, + color: Colors.white, + ), + this.body = const TextStyle( + fontSize: 14, + fontWeight: FontWeight.w500, + color: Colors.white, + ), + this.footnoteBold = const TextStyle( + fontSize: 12, + fontWeight: FontWeight.w500, + color: Colors.white, + ), + this.footnote = const TextStyle( + fontSize: 12, + color: Colors.white, + ), + this.captionBold = const TextStyle( + fontSize: 10, + fontWeight: FontWeight.bold, + color: Colors.white, + ), + }); + + TextTheme copyWith({ + TextThemeType type = TextThemeType.light, + TextStyle body, + TextStyle title, + TextStyle headlineBold, + TextStyle headline, + TextStyle bodyBold, + TextStyle footnoteBold, + TextStyle footnote, + TextStyle captionBold, + }) { + return type == TextThemeType.light + ? TextTheme.light( + body: body ?? this.body, + title: title ?? this.title, + headlineBold: headlineBold ?? this.headlineBold, + headline: headline ?? this.headline, + bodyBold: bodyBold ?? this.bodyBold, + footnoteBold: footnoteBold ?? this.footnoteBold, + footnote: footnote ?? this.footnote, + captionBold: captionBold ?? this.captionBold, + ) + : TextTheme.dark( + body: body ?? this.body, + title: title ?? this.title, + headlineBold: headlineBold ?? this.headlineBold, + headline: headline ?? this.headline, + bodyBold: bodyBold ?? this.bodyBold, + footnoteBold: footnoteBold ?? this.footnoteBold, + footnote: footnote ?? this.footnote, + captionBold: captionBold ?? this.captionBold, + ); + } + + TextTheme merge(TextTheme other) { + if (other == null) return this; + return copyWith( + body: body?.merge(other.body) ?? other.body, + title: title?.merge(other.title) ?? other.title, + headlineBold: + headlineBold?.merge(other.headlineBold) ?? other.headlineBold, + headline: headline?.merge(other.headline) ?? other.headline, + bodyBold: bodyBold?.merge(other.bodyBold) ?? other.bodyBold, + footnoteBold: + footnoteBold?.merge(other.footnoteBold) ?? other.footnoteBold, + footnote: footnote?.merge(other.footnote) ?? other.footnote, + captionBold: captionBold?.merge(other.captionBold) ?? other.captionBold, + ); + } +} + +enum ColorThemeType { + light, + dark, +} + +class ColorTheme { + final Color black; + final Color grey; + final Color greyGainsboro; + final Color greyWhisper; + final Color whiteSmoke; + final Color whiteSnow; + final Color white; + final Color blueAlice; + final Color accentBlue; + final Color accentRed; + final Color accentGreen; + final Effect borderTop; + final Effect borderBottom; + final Effect shadowIconButton; + final Effect modalShadow; + final Color highlight; + final Color overlay; + final Color overlayDark; + final Gradient bgGradient; + + ColorTheme.light({ + this.black = const Color(0xff000000), + this.grey = const Color(0xff7a7a7a), + this.greyGainsboro = const Color(0xffdbdbdb), + this.greyWhisper = const Color(0xffecebeb), + this.whiteSmoke = const Color(0xfff2f2f2), + this.whiteSnow = const Color(0xfffcfcfc), + this.white = const Color(0xffffffff), + this.blueAlice = const Color(0xffe9f2ff), + this.accentBlue = const Color(0xff005FFF), + this.accentRed = const Color(0xffFF3842), + this.accentGreen = const Color(0xff20E070), + this.highlight = const Color(0xfffbf4dd), + this.overlay = const Color.fromRGBO(0, 0, 0, 0.2), + this.overlayDark = const Color.fromRGBO(0, 0, 0, 0.6), + this.bgGradient = const LinearGradient( + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + colors: [const Color(0xfff7f7f7), const Color(0xfffcfcfc)], + stops: [0, 1], + ), + this.borderTop = const Effect( + sigmaX: 0, + sigmaY: -1, + color: Color(0xff000000), + blur: 0.0, + alpha: 0.08), + this.borderBottom = const Effect( + sigmaX: 0, sigmaY: 1, color: Color(0xff000000), blur: 0.0, alpha: 0.08), + this.shadowIconButton = const Effect( + sigmaX: 0, sigmaY: 2, color: Color(0xff000000), alpha: 0.5, blur: 4.0), + this.modalShadow = const Effect( + sigmaX: 0, sigmaY: 0, color: Color(0xff000000), alpha: 1, blur: 8.0), + }); + + ColorTheme.dark({ + this.black = const Color(0xffffffff), + this.grey = const Color(0xff7a7a7a), + this.greyGainsboro = const Color(0xff2d2f2f), + this.greyWhisper = const Color(0xff1c1e22), + this.whiteSmoke = const Color(0xff13151b), + this.whiteSnow = const Color(0xff070A0D), + this.white = const Color(0xff101418), + this.blueAlice = const Color(0xff00193D), + this.accentBlue = const Color(0xff005FFF), + this.accentRed = const Color(0xffFF3742), + this.accentGreen = const Color(0xff20E070), + this.borderTop = const Effect( + sigmaX: 0, sigmaY: -1, color: Color(0xff141924), blur: 0.0), + this.borderBottom = const Effect( + sigmaX: 0, sigmaY: 1, color: Color(0xff141924), blur: 0.0, alpha: 1.0), + this.shadowIconButton = const Effect( + sigmaX: 0, sigmaY: 2, color: Color(0xff000000), alpha: 0.5, blur: 4.0), + this.modalShadow = const Effect( + sigmaX: 0, sigmaY: 0, color: Color(0xff000000), alpha: 1, blur: 8.0), + this.highlight = const Color(0xff302d22), + this.overlay = const Color.fromRGBO(0, 0, 0, 0.4), + this.overlayDark = const Color.fromRGBO(255, 255, 255, 0.6), + this.bgGradient = const LinearGradient( + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + colors: [const Color(0xff101214), const Color(0xff070a0d)], + stops: [0, 1], + ), + }); + + ColorTheme copyWith({ + ColorThemeType type = ColorThemeType.light, + Color black, + Color grey, + Color greyGainsboro, + Color greyWhisper, + Color whiteSmoke, + Color whiteSnow, + Color white, + Color blueAlice, + Color accentBlue, + Color accentRed, + Color accentGreen, + Effect borderTop, + Effect borderBottom, + Effect shadowIconButton, + Effect modalShadow, + Color highlight, + Color overlay, + Color overlayDark, + Gradient bgGradient, + }) { + return type == ColorThemeType.light + ? ColorTheme.light( + black: black ?? this.black, + grey: grey ?? this.grey, + greyGainsboro: greyGainsboro ?? this.greyGainsboro, + greyWhisper: greyWhisper ?? this.greyWhisper, + whiteSmoke: whiteSmoke ?? this.whiteSmoke, + whiteSnow: whiteSnow ?? this.whiteSnow, + white: white ?? this.white, + blueAlice: blueAlice ?? this.blueAlice, + accentBlue: accentBlue ?? this.accentBlue, + accentRed: accentRed ?? this.accentRed, + accentGreen: accentGreen ?? this.accentGreen, + borderTop: borderTop ?? this.borderTop, + borderBottom: borderBottom ?? this.borderBottom, + shadowIconButton: shadowIconButton ?? this.shadowIconButton, + modalShadow: modalShadow ?? this.modalShadow, + highlight: highlight ?? this.highlight, + overlay: overlay ?? this.overlay, + overlayDark: overlayDark ?? this.overlayDark, + bgGradient: bgGradient ?? this.bgGradient, + ) + : ColorTheme.dark( + black: black ?? this.black, + grey: grey ?? this.grey, + greyGainsboro: greyGainsboro ?? this.greyGainsboro, + greyWhisper: greyWhisper ?? this.greyWhisper, + whiteSmoke: whiteSmoke ?? this.whiteSmoke, + whiteSnow: whiteSnow ?? this.whiteSnow, + white: white ?? this.white, + blueAlice: blueAlice ?? this.blueAlice, + accentBlue: accentBlue ?? this.accentBlue, + accentRed: accentRed ?? this.accentRed, + accentGreen: accentGreen ?? this.accentGreen, + borderTop: borderTop ?? this.borderTop, + borderBottom: borderBottom ?? this.borderBottom, + shadowIconButton: shadowIconButton ?? this.shadowIconButton, + modalShadow: modalShadow ?? this.modalShadow, + highlight: highlight ?? this.highlight, + overlay: overlay ?? this.overlay, + overlayDark: overlayDark ?? this.overlayDark, + bgGradient: bgGradient ?? this.bgGradient, + ); + } + + ColorTheme merge(ColorTheme other) { + if (other == null) return this; + return copyWith( + black: other.black, + grey: other.grey, + greyGainsboro: other.greyGainsboro, + greyWhisper: other.greyWhisper, + whiteSmoke: other.whiteSmoke, + whiteSnow: other.whiteSnow, + white: other.white, + blueAlice: other.blueAlice, + accentBlue: other.accentBlue, + accentRed: other.accentRed, + accentGreen: other.accentGreen, + highlight: other.highlight, + overlay: other.overlay, + overlayDark: other.overlayDark, + bgGradient: other.bgGradient, + borderTop: other.borderTop, + borderBottom: other.borderBottom, + shadowIconButton: other.shadowIconButton, + modalShadow: other.modalShadow, + ); + } +} + +/// Channel theme data +class ChannelTheme { + /// Theme of the [ChannelHeader] widget + final ChannelHeaderTheme channelHeaderTheme; + + /// IconTheme of the send button in [MessageInput] + final IconThemeData messageInputButtonIconTheme; + + /// Theme of the send button in [MessageInput] + final ButtonThemeData messageInputButtonTheme; + + /// Background color of [MessageInput] + final Color inputBackground; + + ChannelTheme({ + this.channelHeaderTheme, + this.messageInputButtonIconTheme, + this.messageInputButtonTheme, + this.inputBackground, + }); + + /// Creates a copy of [ChannelTheme] with specified attributes overridden. + ChannelTheme copyWith({ + ChannelHeaderTheme channelHeaderTheme, + IconThemeData messageInputButtonIconTheme, + ButtonThemeData messageInputButtonTheme, + Color inputBackground, + }) => + ChannelTheme( + channelHeaderTheme: channelHeaderTheme ?? this.channelHeaderTheme, + messageInputButtonIconTheme: + messageInputButtonIconTheme ?? this.messageInputButtonIconTheme, + messageInputButtonTheme: + messageInputButtonTheme ?? this.messageInputButtonTheme, + inputBackground: inputBackground ?? this.inputBackground, + ); + + ChannelTheme merge(ChannelTheme other) { + if (other == null) return this; + return copyWith( + channelHeaderTheme: channelHeaderTheme?.merge(other.channelHeaderTheme) ?? + other.channelHeaderTheme, + messageInputButtonIconTheme: messageInputButtonIconTheme + ?.merge(other.messageInputButtonIconTheme) ?? + other.messageInputButtonIconTheme, + messageInputButtonTheme: other.messageInputButtonTheme, + inputBackground: other.inputBackground, + ); + } +} + +class AvatarTheme { + final BoxConstraints constraints; + final BorderRadius borderRadius; + + AvatarTheme({ + this.constraints, + this.borderRadius, + }); + + AvatarTheme copyWith({ + BoxConstraints constraints, + BorderRadius borderRadius, + }) => + AvatarTheme( + constraints: constraints ?? this.constraints, + borderRadius: borderRadius ?? this.borderRadius, + ); + + AvatarTheme merge(AvatarTheme other) { + if (other == null) return this; + return copyWith( + constraints: other.constraints, + borderRadius: other.borderRadius, + ); + } +} + +class MessageTheme { + final TextStyle messageText; + final TextStyle messageAuthor; + final TextStyle messageLinks; + final TextStyle createdAt; + final TextStyle replies; + final Color messageBackgroundColor; + final Color messageBorderColor; + final Color reactionsBackgroundColor; + final Color reactionsBorderColor; + final Color reactionsMaskColor; + final AvatarTheme avatarTheme; + + const MessageTheme({ + this.replies, + this.messageText, + this.messageAuthor, + this.messageLinks, + this.messageBackgroundColor, + this.messageBorderColor, + this.reactionsBackgroundColor, + this.reactionsBorderColor, + this.reactionsMaskColor, + this.avatarTheme, + this.createdAt, + }); + + MessageTheme copyWith({ + TextStyle messageText, + TextStyle messageAuthor, + TextStyle messageLinks, + TextStyle createdAt, + TextStyle replies, + Color messageBackgroundColor, + Color messageBorderColor, + AvatarTheme avatarTheme, + Color reactionsBackgroundColor, + Color reactionsBorderColor, + Color reactionsMaskColor, + }) => + MessageTheme( + messageText: messageText ?? this.messageText, + messageAuthor: messageAuthor ?? this.messageAuthor, + messageLinks: messageLinks ?? this.messageLinks, + createdAt: createdAt ?? this.createdAt, + messageBackgroundColor: + messageBackgroundColor ?? this.messageBackgroundColor, + messageBorderColor: messageBorderColor ?? this.messageBorderColor, + avatarTheme: avatarTheme ?? this.avatarTheme, + replies: replies ?? this.replies, + reactionsBackgroundColor: + reactionsBackgroundColor ?? this.reactionsBackgroundColor, + reactionsBorderColor: reactionsBorderColor ?? this.reactionsBorderColor, + reactionsMaskColor: reactionsMaskColor ?? this.reactionsMaskColor, + ); + + MessageTheme merge(MessageTheme other) { + if (other == null) return this; + return copyWith( + messageText: messageText?.merge(other.messageText) ?? other.messageText, + messageAuthor: + messageAuthor?.merge(other.messageAuthor) ?? other.messageAuthor, + messageLinks: + messageLinks?.merge(other.messageLinks) ?? other.messageLinks, + createdAt: createdAt?.merge(other.createdAt) ?? other.createdAt, + replies: replies?.merge(other.replies) ?? other.replies, + messageBackgroundColor: other.messageBackgroundColor, + messageBorderColor: other.messageBorderColor, + avatarTheme: avatarTheme?.merge(other.avatarTheme) ?? other.avatarTheme, + reactionsBackgroundColor: other.reactionsBackgroundColor, + reactionsBorderColor: other.reactionsBorderColor, + reactionsMaskColor: other.reactionsMaskColor, + ); + } +} + +class ChannelPreviewTheme { + final TextStyle title; + final TextStyle subtitle; + final TextStyle lastMessageAt; + final AvatarTheme avatarTheme; + final Color unreadCounterColor; + final double indicatorIconSize; + + const ChannelPreviewTheme({ + this.title, + this.subtitle, + this.lastMessageAt, + this.avatarTheme, + this.unreadCounterColor, + this.indicatorIconSize, + }); + + ChannelPreviewTheme copyWith({ + TextStyle title, + TextStyle subtitle, + TextStyle lastMessageAt, + AvatarTheme avatarTheme, + Color unreadCounterColor, + double indicatorIconSize, + }) => + ChannelPreviewTheme( + title: title ?? this.title, + subtitle: subtitle ?? this.subtitle, + lastMessageAt: lastMessageAt ?? this.lastMessageAt, + avatarTheme: avatarTheme ?? this.avatarTheme, + unreadCounterColor: unreadCounterColor ?? this.unreadCounterColor, + indicatorIconSize: indicatorIconSize ?? this.indicatorIconSize, + ); + + ChannelPreviewTheme merge(ChannelPreviewTheme other) { + if (other == null) return this; + return copyWith( + title: title?.merge(other.title) ?? other.title, + subtitle: subtitle?.merge(other.subtitle) ?? other.subtitle, + lastMessageAt: + lastMessageAt?.merge(other.lastMessageAt) ?? other.lastMessageAt, + avatarTheme: avatarTheme?.merge(other.avatarTheme) ?? other.avatarTheme, + unreadCounterColor: other.unreadCounterColor, + ); + } +} + +class ChannelHeaderTheme { + final TextStyle title; + final TextStyle lastMessageAt; + final AvatarTheme avatarTheme; + final Color color; + + const ChannelHeaderTheme({ + this.title, + this.lastMessageAt, + this.avatarTheme, + this.color, + }); + + ChannelHeaderTheme copyWith({ + TextStyle title, + TextStyle lastMessageAt, + AvatarTheme avatarTheme, + Color color, + }) => + ChannelHeaderTheme( + title: title ?? this.title, + lastMessageAt: lastMessageAt ?? this.lastMessageAt, + avatarTheme: avatarTheme ?? this.avatarTheme, + color: color ?? this.color, + ); + + ChannelHeaderTheme merge(ChannelHeaderTheme other) { + if (other == null) return this; + return copyWith( + title: title?.merge(other.title) ?? other.title, + lastMessageAt: + lastMessageAt?.merge(other.lastMessageAt) ?? other.lastMessageAt, + avatarTheme: avatarTheme?.merge(other.avatarTheme) ?? other.avatarTheme, + color: other.color, + ); + } +} + +class Effect { + final double sigmaX; + final double sigmaY; + final Color color; + final double alpha; + final double blur; + + const Effect({ + this.sigmaX, + this.sigmaY, + this.color, + this.alpha, + this.blur, + }); + + Effect copyWith({ + double sigmaX, + double sigmaY, + Color color, + double alpha, + double blur, + }) => + Effect( + sigmaX: sigmaX ?? this.sigmaX, + sigmaY: sigmaY ?? this.sigmaY, + color: color ?? this.color, + alpha: color ?? this.alpha, + blur: blur ?? this.blur, + ); +} + +/// Get random png with initials +String getRandomPicUrl(User user) => + 'https://getstream.io/random_png/?id=${user.id}&name=${user.name}'; diff --git a/packages/stream_chat_flutter_core/lib/src/user_list_view.dart b/packages/stream_chat_flutter_core/lib/src/user_list_view.dart new file mode 100644 index 00000000..4681d931 --- /dev/null +++ b/packages/stream_chat_flutter_core/lib/src/user_list_view.dart @@ -0,0 +1,507 @@ +import 'dart:convert'; + +import 'package:flutter/material.dart'; +import 'package:stream_chat/stream_chat.dart'; +import 'package:stream_chat_flutter_core/src/lazy_load_scroll_view.dart'; +import 'package:stream_chat_flutter_core/src/users_bloc.dart'; +import 'package:stream_chat_flutter_core/src/stream_chat_theme.dart'; + +/// Callback called when tapping on a user +typedef UserTapCallback = void Function(User, Widget); + +/// Builder used to create a custom [ListUserItem] from a [User] +typedef UserItemBuilder = Widget Function(BuildContext, User, bool); + +/// +/// It shows the list of current users. +/// +/// ```dart +/// class UsersListPage extends StatelessWidget { +/// @override +/// Widget build(BuildContext context) { +/// return Scaffold( +/// body: UsersListView( +/// filter: { +/// 'members': { +/// '\$in': [StreamChat.of(context).user.id], +/// } +/// }, +/// sort: [SortOption('last_message_at')], +/// pagination: PaginationParams( +/// limit: 20, +/// ), +/// channelWidget: ChannelPage(), +/// ), +/// ); +/// } +/// } +/// ``` +/// +/// +/// Make sure to have a [UsersBloc] ancestor in order to provide the information about the users. +/// The widget uses a [ListView.separated], [GridView.builder] to render the list, grid of channels. +/// +/// The widget components render the ui based on the first ancestor of type [StreamChatTheme]. +/// Modify it to change the widget appearance. +class UserListView extends StatefulWidget { + /// Instantiate a new UserListView + const UserListView({ + Key key, + this.errorBuilder, + this.emptyBuilder, + this.filter, + this.options, + this.sort, + this.pagination, + this.onUserTap, + this.onUserLongPress, + this.userWidget, + this.userItemBuilder, + this.separatorBuilder, + this.onImageTap, + this.selectedUsers, + this.pullToRefresh = true, + this.groupAlphabetically = false, + this.crossAxisCount = 1, + }) : assert( + crossAxisCount == 1 || groupAlphabetically == false, + 'Cannot group alphabetically when crossAxisCount > 1', + ), + super(key: key); + + /// The builder that will be used in case of error + final Widget Function(Error error) errorBuilder; + + /// The builder used when the channel list is empty. + final WidgetBuilder emptyBuilder; + + /// The query filters to use. + /// You can query on any of the custom fields you've defined on the [Channel]. + /// You can also filter other built-in channel fields. + final Map filter; + + /// Query channels options. + /// + /// state: if true returns the Channel state + /// watch: if true listen to changes to this Channel in real time. + final Map options; + + /// The sorting used for the channels matching the filters. + /// Sorting is based on field and direction, multiple sorting options can be provided. + /// You can sort based on last_updated, last_message_at, updated_at, created_at or member_count. + /// Direction can be ascending or descending. + final List sort; + + /// Pagination parameters + /// limit: the number of users to return (max is 30) + /// offset: the offset (max is 1000) + /// message_limit: how many messages should be included to each channel + final PaginationParams pagination; + + /// Function called when tapping on a channel + /// By default it calls [Navigator.push] building a [MaterialPageRoute] + /// with the widget [userWidget] as child. + final UserTapCallback onUserTap; + + /// Function called when long pressing on a channel + final Function(User) onUserLongPress; + + /// Widget used when opening a channel + final Widget userWidget; + + /// Builder used to create a custom user preview + final UserItemBuilder userItemBuilder; + + /// Builder used to create a custom item separator + final Function(BuildContext, int) separatorBuilder; + + /// The function called when the image is tapped + final Function(User) onImageTap; + + /// Set it to false to disable the pull-to-refresh widget + final bool pullToRefresh; + + /// Sets a blue trailing checkMark in [ListUserItem] for all the [selectedUsers] + final Set selectedUsers; + + /// Set it to true to group users by their first character + /// + /// defaults to false + final bool groupAlphabetically; + + /// The number of children in the cross axis. + final int crossAxisCount; + + @override + _UserListViewState createState() => _UserListViewState(); +} + +class _UserListViewState extends State + with WidgetsBindingObserver { + bool get _isListView => widget.crossAxisCount == 1; + + @override + void initState() { + super.initState(); + final usersBloc = UsersBloc.of(context); + usersBloc.queryUsers( + filter: widget.filter, + sort: widget.sort, + pagination: widget.pagination, + options: widget.options, + ); + } + + @override + Widget build(BuildContext context) { + final usersBloc = UsersBloc.of(context); + + if (!widget.pullToRefresh) { + return _buildListView(usersBloc); + } + + return RefreshIndicator( + onRefresh: () async { + return usersBloc.queryUsers( + filter: widget.filter, + sort: widget.sort, + options: widget.options, + pagination: widget.pagination, + ); + }, + child: _buildListView(usersBloc), + ); + } + + bool get isListAlreadySorted => + widget.sort?.any((e) => e.field == 'name' && e.direction == 1) ?? false; + + Stream> _buildUserStream( + UsersBlocState usersBlocState, + ) { + return usersBlocState.usersStream.map( + (users) { + if (widget.groupAlphabetically) { + var temp = users; + if (!isListAlreadySorted) { + temp = users..sort((curr, next) => curr.name.compareTo(next.name)); + } + final groupedUsers = >{}; + for (var e in temp) { + final alphabet = e.name[0]?.toUpperCase(); + groupedUsers[alphabet] = [...groupedUsers[alphabet] ?? [], e]; + } + final items = []; + for (var key in groupedUsers.keys) { + items.add(ListHeaderItem(key)); + items.addAll(groupedUsers[key].map((e) => ListUserItem(e))); + } + return items; + } + return users.map((e) => ListUserItem(e)).toList(); + }, + ); + } + + StreamBuilder> _buildListView( + UsersBlocState usersBlocState, + ) { + return StreamBuilder( + stream: _buildUserStream(usersBlocState), + builder: (context, snapshot) { + if (snapshot.hasError) { + if (snapshot.error is Error) { + print((snapshot.error as Error).stackTrace); + } + + if (widget.errorBuilder != null) { + return widget.errorBuilder(snapshot.error); + } + + var message = snapshot.error.toString(); + if (snapshot.error is DioError) { + final dioError = snapshot.error as DioError; + if (dioError.type == DioErrorType.RESPONSE) { + message = dioError.message; + } else { + message = 'Check your connection and retry'; + } + } + return Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text.rich( + TextSpan( + children: [ + WidgetSpan( + child: Padding( + padding: const EdgeInsets.only( + right: 2.0, + ), + child: Icon(Icons.error_outline), + ), + ), + TextSpan(text: 'Error loading channels'), + ], + ), + style: Theme.of(context).textTheme.headline6, + ), + Padding( + padding: const EdgeInsets.only( + top: 16.0, + ), + child: Text(message), + ), + FlatButton( + onPressed: () { + usersBlocState.queryUsers( + filter: widget.filter, + sort: widget.sort, + pagination: widget.pagination, + options: widget.options, + ); + }, + child: Text('Retry'), + ), + ], + ), + ); + } + + if (!snapshot.hasData) { + return LayoutBuilder( + builder: (context, viewportConstraints) { + return SingleChildScrollView( + physics: AlwaysScrollableScrollPhysics(), + child: ConstrainedBox( + constraints: BoxConstraints( + minHeight: viewportConstraints.maxHeight, + ), + child: Center( + child: CircularProgressIndicator(), + ), + ), + ); + }, + ); + } + + final items = snapshot.data; + + if (items.isEmpty && widget.emptyBuilder != null) { + return widget.emptyBuilder(context); + } + + if (items.isEmpty && widget.emptyBuilder == null) { + return LayoutBuilder( + builder: (context, viewportConstraints) { + return SingleChildScrollView( + physics: AlwaysScrollableScrollPhysics(), + child: ConstrainedBox( + constraints: BoxConstraints( + minHeight: viewportConstraints.maxHeight, + ), + child: Center( + child: Text('There are no users currently'), + ), + ), + ); + }, + ); + } + + final child = _isListView + ? ListView.separated( + physics: AlwaysScrollableScrollPhysics(), + itemCount: items.isNotEmpty ? items.length + 1 : items.length, + separatorBuilder: (_, index) { + if (widget.separatorBuilder != null) { + return widget.separatorBuilder(context, index); + } + return _separatorBuilder(context, index); + }, + itemBuilder: (context, index) { + return _listItemBuilder(context, index, items); + }, + ) + : GridView.builder( + gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: widget.crossAxisCount, + ), + itemCount: items.isNotEmpty ? items.length + 1 : items.length, + physics: AlwaysScrollableScrollPhysics(), + itemBuilder: (context, index) { + return _gridItemBuilder(context, index, items); + }, + ); + + return LazyLoadScrollView( + onEndOfPage: () async { + return _listenUserPagination(usersBlocState); + }, + child: child, + ); + }, + ); + } + + Widget _listItemBuilder(BuildContext context, int i, List items) { + final usersProvider = UsersBloc.of(context); + if (i < items.length) { + final item = items[i]; + return item.when( + headerItem: (header) { + return Container( + key: ValueKey('HEADER-$header'), + color: + StreamChatTheme.of(context).colorTheme.black.withOpacity(0.05), + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 8.0, vertical: 6), + child: Text( + header, + style: TextStyle( + fontWeight: FontWeight.bold, + fontSize: 14.5, + color: StreamChatTheme.of(context).colorTheme.grey, + ), + ), + ), + ); + }, + userItem: (user) { + final selected = widget.selectedUsers?.contains(user) ?? false; + return Container( + key: ValueKey('USER-${user.id}'), + child: widget.userItemBuilder(context, user, selected), + ); + }, + ); + } else { + return _buildQueryProgressIndicator(context, usersProvider); + } + } + + Widget _gridItemBuilder(BuildContext context, int i, List items) { + final usersProvider = UsersBloc.of(context); + if (i < items.length) { + final item = items[i]; + return item.when( + headerItem: (_) => Offstage(), + userItem: (user) { + final selected = widget.selectedUsers?.contains(user) ?? false; + return Container( + key: ValueKey('USER-${user.id}'), + child: widget.userItemBuilder(context, user, selected), + ); + }, + ); + } else { + return _buildQueryProgressIndicator(context, usersProvider); + } + } + + Widget _buildQueryProgressIndicator(context, UsersBlocState usersProvider) { + return StreamBuilder( + stream: usersProvider.queryUsersLoading, + initialData: false, + builder: (context, snapshot) { + if (snapshot.hasError) { + return Container( + color: StreamChatTheme.of(context) + .colorTheme + .accentRed + .withOpacity(.2), + child: Padding( + padding: const EdgeInsets.symmetric(vertical: 16.0), + child: Center( + child: Text('Error loading users'), + ), + ), + ); + } + return Container( + height: 100, + padding: EdgeInsets.all(32), + child: Center( + child: snapshot.data ? CircularProgressIndicator() : Container(), + ), + ); + }); + } + + Widget _separatorBuilder(context, i) { + return Container( + height: 1, + color: StreamChatTheme.of(context).colorTheme.greyWhisper, + ); + } + + void _listenUserPagination(UsersBlocState usersProvider) { + usersProvider.queryUsers( + filter: widget.filter, + sort: widget.sort, + pagination: widget.pagination.copyWith( + offset: usersProvider.users?.length ?? 0, + ), + options: widget.options, + ); + } + + @override + void didUpdateWidget(UserListView oldWidget) { + super.didUpdateWidget(oldWidget); + if (widget.filter?.toString() != oldWidget.filter?.toString() || + jsonEncode(widget.sort) != jsonEncode(oldWidget.sort) || + widget.pagination?.toJson()?.toString() != + oldWidget.pagination?.toJson()?.toString() || + widget.options?.toString() != oldWidget.options?.toString()) { + final usersBloc = UsersBloc.of(context); + usersBloc.queryUsers( + filter: widget.filter, + sort: widget.sort, + pagination: widget.pagination, + options: widget.options, + ); + } + } +} + +abstract class ListItem { + String get key { + if (this is ListHeaderItem) { + final header = (this as ListHeaderItem).heading; + return 'HEADER-$header'; + } + if (this is ListUserItem) { + final user = (this as ListUserItem).user; + return 'USER-${user.id}'; + } + return null; + } + + Widget when({ + @required Widget Function(String heading) headerItem, + @required Widget Function(User user) userItem, + }) { + if (this is ListHeaderItem) { + return headerItem((this as ListHeaderItem).heading); + } + if (this is ListUserItem) { + return userItem((this as ListUserItem).user); + } + return SizedBox(); + } +} + +class ListHeaderItem extends ListItem { + final String heading; + + ListHeaderItem(this.heading); +} + +class ListUserItem extends ListItem { + final User user; + + ListUserItem(this.user); +} diff --git a/packages/stream_chat_flutter_core/lib/src/users_bloc.dart b/packages/stream_chat_flutter_core/lib/src/users_bloc.dart new file mode 100644 index 00000000..91cbaf80 --- /dev/null +++ b/packages/stream_chat_flutter_core/lib/src/users_bloc.dart @@ -0,0 +1,108 @@ +import 'package:flutter/material.dart'; +import 'package:rxdart/rxdart.dart'; +import 'package:stream_chat/stream_chat.dart'; + +import 'stream_chat.dart'; + +/// Widget dedicated to the management of a users list with pagination +class UsersBloc extends StatefulWidget { + /// The widget child + final Widget child; + + /// Instantiate a new UsersBloc + const UsersBloc({ + Key key, + @required this.child, + }) : super(key: key); + + @override + UsersBlocState createState() => UsersBlocState(); + + /// Use this method to get the current [UsersBlocState] instance + static UsersBlocState of(BuildContext context) { + UsersBlocState state; + + state = context.findAncestorStateOfType(); + + if (state == null) { + throw Exception('You must have a UsersBloc widget as ancestor'); + } + + return state; + } +} + +/// The current state of the [UsersBloc] +class UsersBlocState extends State + with AutomaticKeepAliveClientMixin { + /// The current users list + List get users => _usersController.value; + + /// The current users list as a stream + Stream> get usersStream => _usersController.stream; + + final BehaviorSubject> _usersController = BehaviorSubject(); + + final BehaviorSubject _queryUsersLoadingController = + BehaviorSubject.seeded(false); + + /// The stream notifying the state of queryUsers call + Stream get queryUsersLoading => _queryUsersLoadingController.stream; + + /// Calls [Client.queryUsers] updating [queryUsersLoading] stream + Future queryUsers({ + Map filter, + List sort, + Map options, + PaginationParams pagination, + }) async { + final client = StreamChat.of(context).client; + + if (client.state?.user == null || + _queryUsersLoadingController.value == true) { + return; + } + _queryUsersLoadingController.add(true); + try { + final clear = pagination == null || + pagination.offset == null || + pagination.offset == 0; + + final oldUsers = List.from(users ?? []); + + final usersResponse = await client.queryUsers( + filter: filter, + sort: sort, + options: options, + pagination: pagination, + ); + + if (clear) { + _usersController.add(usersResponse.users); + } else { + final temp = oldUsers + usersResponse.users; + _usersController.add(temp); + } + + _queryUsersLoadingController.add(false); + } catch (err, stackTrace) { + _queryUsersLoadingController.addError(err, stackTrace); + } + } + + @override + Widget build(BuildContext context) { + super.build(context); + return widget.child; + } + + @override + void dispose() { + _usersController.close(); + _queryUsersLoadingController.close(); + super.dispose(); + } + + @override + bool get wantKeepAlive => true; +} diff --git a/packages/stream_chat_flutter_core/pubspec.yaml b/packages/stream_chat_flutter_core/pubspec.yaml index 9590e0c4..10e0688e 100644 --- a/packages/stream_chat_flutter_core/pubspec.yaml +++ b/packages/stream_chat_flutter_core/pubspec.yaml @@ -9,6 +9,7 @@ environment: flutter: ">=1.17.0" dependencies: + stream_chat: ^0.2.23+3 flutter: sdk: flutter From cf3e1b8363acd9525f3dc1c9f058112fc415f60e Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Fri, 22 Jan 2021 14:54:45 +0530 Subject: [PATCH 03/39] feat: Added new widgets --- .../lib/src/channel_list_view.dart | 272 +++++++++++++ .../lib/src/channels_bloc.dart | 184 +++++++++ .../lib/src/message_search_bloc.dart | 150 ++++++++ .../lib/src/stream_channel.dart | 356 ++++++++++++++++++ .../stream_chat_flutter_core/pubspec.yaml | 1 + 5 files changed, 963 insertions(+) create mode 100644 packages/stream_chat_flutter_core/lib/src/channel_list_view.dart create mode 100644 packages/stream_chat_flutter_core/lib/src/channels_bloc.dart create mode 100644 packages/stream_chat_flutter_core/lib/src/message_search_bloc.dart create mode 100644 packages/stream_chat_flutter_core/lib/src/stream_channel.dart diff --git a/packages/stream_chat_flutter_core/lib/src/channel_list_view.dart b/packages/stream_chat_flutter_core/lib/src/channel_list_view.dart new file mode 100644 index 00000000..920ee09b --- /dev/null +++ b/packages/stream_chat_flutter_core/lib/src/channel_list_view.dart @@ -0,0 +1,272 @@ +import 'dart:async'; +import 'dart:convert'; + +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:stream_chat/stream_chat.dart'; +import 'package:stream_chat_flutter_core/src/channels_bloc.dart'; + +import 'stream_chat.dart'; + +/// Callback called when tapping on a channel +typedef ChannelTapCallback = void Function(Channel, Widget); + +/// Builder used to create a custom [ChannelPreview] from a [Channel] +typedef ChannelPreviewBuilder = Widget Function(BuildContext, Channel); + +typedef ViewInfoCallback = void Function(Channel); + +/// ![screenshot](https://raw.githubusercontent.com/GetStream/stream-chat-flutter/master/screenshots/channel_list_view.png) +/// ![screenshot](https://raw.githubusercontent.com/GetStream/stream-chat-flutter/master/screenshots/channel_list_view_paint.png) +/// +/// It shows the list of current channels. +/// +/// ```dart +/// class ChannelListPage extends StatelessWidget { +/// @override +/// Widget build(BuildContext context) { +/// return Scaffold( +/// body: ChannelListView( +/// filter: { +/// 'members': { +/// '\$in': [StreamChat.of(context).user.id], +/// } +/// }, +/// sort: [SortOption('last_message_at')], +/// pagination: PaginationParams( +/// limit: 20, +/// ), +/// channelWidget: ChannelPage(), +/// ), +/// ); +/// } +/// } +/// ``` +/// +/// +/// Make sure to have a [StreamChat] ancestor in order to provide the information about the channels. +/// The widget uses a [ListView.custom] to render the list of channels. +/// +/// The widget components render the ui based on the first ancestor of type [StreamChatTheme]. +/// Modify it to change the widget appearance. +class ChannelListView extends StatefulWidget { + /// Instantiate a new ChannelListView + ChannelListView({ + Key key, + this.filter, + this.options, + this.sort, + this.pagination, + this.separatorBuilder, + this.errorBuilder, + this.swipeToAction = false, + this.pullToRefresh = true, + @required this.emptyBuilder, + @required this.loadingBuilder, + @required this.listBuilder, + }) : super(key: key); + + /// The builder that will be used in case of error + final Widget Function(Error error) errorBuilder; + + final WidgetBuilder loadingBuilder; + + final Function(BuildContext, List) listBuilder; + + /// If true a default swipe to action behaviour will be added to this widget + final bool swipeToAction; + + /// The builder used when the channel list is empty. + final WidgetBuilder emptyBuilder; + + /// The query filters to use. + /// You can query on any of the custom fields you've defined on the [Channel]. + /// You can also filter other built-in channel fields. + final Map filter; + + /// Query channels options. + /// + /// state: if true returns the Channel state + /// watch: if true listen to changes to this Channel in real time. + final Map options; + + /// The sorting used for the channels matching the filters. + /// Sorting is based on field and direction, multiple sorting options can be provided. + /// You can sort based on last_updated, last_message_at, updated_at, created_at or member_count. + /// Direction can be ascending or descending. + final List sort; + + /// Pagination parameters + /// limit: the number of channels to return (max is 30) + /// offset: the offset (max is 1000) + /// message_limit: how many messages should be included to each channel + final PaginationParams pagination; + + /// Builder used to create a custom item separator + final Function(BuildContext, int) separatorBuilder; + + /// Set it to false to disable the pull-to-refresh widget + final bool pullToRefresh; + + @override + _ChannelListViewState createState() => _ChannelListViewState(); +} + +class _ChannelListViewState extends State + with WidgetsBindingObserver { + final ScrollController _scrollController = ScrollController(); + + @override + Widget build(BuildContext context) { + final channelsBloc = ChannelsBloc.of(context); + + if (!widget.pullToRefresh) { + return _buildListView(channelsBloc); + } + + return RefreshIndicator( + onRefresh: () async { + return channelsBloc.queryChannels( + filter: widget.filter, + sortOptions: widget.sort, + paginationParams: widget.pagination, + options: widget.options, + ); + }, + child: _buildListView(channelsBloc), + ); + } + + StreamBuilder> _buildListView( + ChannelsBlocState channelsBlocState, + ) { + return StreamBuilder>( + stream: channelsBlocState.channelsStream, + builder: (context, snapshot) { + var child; + if (snapshot.hasError) { + child = _buildErrorWidget( + snapshot, + context, + channelsBlocState, + ); + } else if (!snapshot.hasData) { + child = _buildLoadingWidget(); + } else { + final channels = snapshot.data; + + child = widget.emptyBuilder(context); + + if (channels.isNotEmpty) { + return widget.listBuilder(context, channels); + } + } + + return AnimatedSwitcher( + child: child, + duration: Duration(milliseconds: 500), + ); + }, + ); + } + + Widget _buildLoadingWidget() { + return widget.loadingBuilder(context); + } + + Widget _buildErrorWidget( + AsyncSnapshot> snapshot, + BuildContext context, + ChannelsBlocState channelsBlocState, + ) { + if (snapshot.error is Error) { + print((snapshot.error as Error).stackTrace); + } + + return widget.errorBuilder(snapshot.error); + } + + void _listenChannelPagination(ChannelsBlocState channelsProvider) { + if (_scrollController.position.maxScrollExtent == + _scrollController.offset && + _scrollController.offset != 0) { + channelsProvider.queryChannels( + filter: widget.filter, + sortOptions: widget.sort, + paginationParams: widget.pagination.copyWith( + offset: channelsProvider.channels?.length ?? 0, + ), + options: widget.options, + ); + } + } + + StreamSubscription _subscription; + + @override + void initState() { + super.initState(); + + WidgetsBinding.instance.addObserver(this); + + final channelsBloc = ChannelsBloc.of(context); + channelsBloc.queryChannels( + filter: widget.filter, + sortOptions: widget.sort, + paginationParams: widget.pagination, + options: widget.options, + ); + + _scrollController.addListener(() { + channelsBloc.queryChannelsLoading.first.then((loading) { + if (!loading) { + _listenChannelPagination(channelsBloc); + } + }); + }); + + final client = StreamChat.of(context).client; + + _subscription = client + .on( + EventType.connectionRecovered, + EventType.notificationAddedToChannel, + EventType.notificationMessageNew, + EventType.channelVisible, + ) + .listen((event) { + channelsBloc.queryChannels( + filter: widget.filter, + sortOptions: widget.sort, + paginationParams: widget.pagination, + options: widget.options, + ); + }); + } + + @override + void didUpdateWidget(ChannelListView oldWidget) { + super.didUpdateWidget(oldWidget); + + if (widget.filter?.toString() != oldWidget.filter?.toString() || + jsonEncode(widget.sort) != jsonEncode(oldWidget.sort) || + widget.pagination?.toJson()?.toString() != + oldWidget.pagination?.toJson()?.toString() || + widget.options?.toString() != oldWidget.options?.toString()) { + final channelsBloc = ChannelsBloc.of(context); + channelsBloc.queryChannels( + filter: widget.filter, + sortOptions: widget.sort, + paginationParams: widget.pagination, + options: widget.options, + ); + } + } + + @override + void dispose() { + _subscription.cancel(); + WidgetsBinding.instance.removeObserver(this); + super.dispose(); + } +} diff --git a/packages/stream_chat_flutter_core/lib/src/channels_bloc.dart b/packages/stream_chat_flutter_core/lib/src/channels_bloc.dart new file mode 100644 index 00000000..a9ab1682 --- /dev/null +++ b/packages/stream_chat_flutter_core/lib/src/channels_bloc.dart @@ -0,0 +1,184 @@ +import 'dart:async'; + +import 'package:flutter/material.dart'; +import 'package:rxdart/rxdart.dart'; +import 'package:stream_chat/stream_chat.dart'; +import 'package:stream_chat_flutter_core/src/stream_chat.dart'; + +/// Widget dedicated to the management of a channel list with pagination +class ChannelsBloc extends StatefulWidget { + /// The widget child + final Widget child; + + /// Set this to true to prevent channels to be brought to the top of the list when a new message arrives + final bool lockChannelsOrder; + + /// Comparator used to sort the channels when a message.new event is received + final Comparator channelsComparator; + + /// Function used to evaluate if a channel should be added to the list when a message.new event is received + final bool Function(Event) shouldAddChannel; + + /// Instantiate a new ChannelsBloc + const ChannelsBloc({ + Key key, + this.child, + this.lockChannelsOrder = false, + this.channelsComparator, + this.shouldAddChannel, + }) : super(key: key); + + @override + ChannelsBlocState createState() => ChannelsBlocState(); + + /// Use this method to get the current [ChannelsBlocState] instance + static ChannelsBlocState of(BuildContext context) { + ChannelsBlocState streamChatState; + + streamChatState = context.findAncestorStateOfType(); + + if (streamChatState == null) { + throw Exception('You must have a ChannelsBloc widget as ancestor'); + } + + return streamChatState; + } +} + +/// The current state of the [ChannelsBloc] +class ChannelsBlocState extends State + with AutomaticKeepAliveClientMixin { + @override + Widget build(BuildContext context) { + super.build(context); + return widget.child; + } + + /// The current channel list + List get channels => _channelsController.value; + + /// The current channel list as a stream + Stream> get channelsStream => _channelsController.stream; + + final BehaviorSubject _queryChannelsLoadingController = + BehaviorSubject.seeded(false); + + final BehaviorSubject> _channelsController = BehaviorSubject(); + + /// The stream notifying the state of queryChannel call + Stream get queryChannelsLoading => + _queryChannelsLoadingController.stream; + + final List _hiddenChannels = []; + + /// Calls [client.queryChannels] updating [queryChannelsLoading] stream + Future queryChannels({ + Map filter, + List sortOptions, + PaginationParams paginationParams, + Map options, + bool onlyOffline = false, + }) async { + final client = StreamChat.of(context).client; + + if (client.state?.user == null || + _queryChannelsLoadingController.value == true) { + return; + } + _queryChannelsLoadingController.sink.add(true); + + try { + final clear = paginationParams == null || + paginationParams.offset == null || + paginationParams.offset == 0; + final oldChannels = List.from(channels ?? []); + final _channels = await client.queryChannels( + filter: filter, + sort: sortOptions, + options: options, + paginationParams: paginationParams, + onlyOffline: onlyOffline, + ); + + if (clear) { + _channelsController.add(_channels); + } else { + final l = oldChannels + _channels; + _channelsController.add(l); + } + _queryChannelsLoadingController.sink.add(false); + } catch (err, stackTrace) { + print(err); + print(stackTrace); + _queryChannelsLoadingController.addError(err, stackTrace); + } + } + + final List _subscriptions = []; + + @override + void initState() { + super.initState(); + + final client = StreamChat.of(context).client; + + if (!widget.lockChannelsOrder) { + _subscriptions.add(client.on(EventType.messageNew).listen((e) { + final newChannels = List.from(channels ?? []); + final index = newChannels.indexWhere((c) => c.cid == e.cid); + if (index > -1) { + if (index > 0) { + final channel = newChannels.removeAt(index); + newChannels.insert(0, channel); + } + } else if (widget.shouldAddChannel != null && + widget.shouldAddChannel(e)) { + final hiddenIndex = _hiddenChannels.indexWhere((c) => c.cid == e.cid); + if (hiddenIndex > -1) { + newChannels.insert(0, _hiddenChannels[hiddenIndex]); + _hiddenChannels.removeAt(hiddenIndex); + } else { + if (client.state?.channels != null && + client.state?.channels[e.cid] != null) { + newChannels.insert(0, client.state.channels[e.cid]); + } + } + } + + if (widget.channelsComparator != null) { + newChannels.sort(widget.channelsComparator); + } + _channelsController.add(newChannels); + })); + } + + _subscriptions.add(client.on(EventType.channelHidden).listen((event) async { + final newChannels = List.from(channels ?? []); + final channelIndex = newChannels.indexWhere((c) => c.cid == event.cid); + if (channelIndex > -1) { + final channel = newChannels.removeAt(channelIndex); + _hiddenChannels.add(channel); + _channelsController.add(newChannels); + } + })); + + _subscriptions.add(client + .on(EventType.channelDeleted, EventType.notificationRemovedFromChannel) + .listen((e) { + final channel = e.channel; + _channelsController + .add(List.from(channels..removeWhere((c) => c.cid == channel.cid))); + })); + } + + @override + void dispose() { + _channelsController.close(); + _queryChannelsLoadingController.close(); + _subscriptions.forEach((s) => s.cancel()); + super.dispose(); + } + + @override + bool get wantKeepAlive => true; +} diff --git a/packages/stream_chat_flutter_core/lib/src/message_search_bloc.dart b/packages/stream_chat_flutter_core/lib/src/message_search_bloc.dart new file mode 100644 index 00000000..67b6455d --- /dev/null +++ b/packages/stream_chat_flutter_core/lib/src/message_search_bloc.dart @@ -0,0 +1,150 @@ +import 'package:flutter/material.dart'; +import 'package:rxdart/rxdart.dart'; +import 'package:stream_chat/stream_chat.dart'; + +import 'stream_chat.dart'; + +/// Widget dedicated to the management of a message list with pagination +class MessageSearchBloc extends StatefulWidget { + /// The widget child + final Widget child; + + /// Instantiate a new MessageSearchBloc + const MessageSearchBloc({ + Key key, + @required this.child, + }) : super(key: key); + + @override + MessageSearchBlocState createState() => MessageSearchBlocState(); + + /// Use this method to get the current [MessageSearchBlocState] instance + static MessageSearchBlocState of(BuildContext context) { + MessageSearchBlocState state; + + state = context.findAncestorStateOfType(); + + if (state == null) { + throw Exception('You must have a MessageSearchBloc widget as ancestor'); + } + + return state; + } +} + +/// The current state of the [MessageSearchBloc] +class MessageSearchBlocState extends State + with AutomaticKeepAliveClientMixin { + /// The current messages list + List get messageResponses => _messageResponses.value; + + /// The current messages list as a stream + Stream> get messagesStream => + _messageResponses.stream; + + final BehaviorSubject> _messageResponses = + BehaviorSubject(); + + final BehaviorSubject _queryMessagesLoadingController = + BehaviorSubject.seeded(false); + + /// The stream notifying the state of queryUsers call + Stream get queryMessagesLoading => + _queryMessagesLoadingController.stream; + + /// Calls [Client.search] updating [messageResponses] stream + Future search({ + Map filter, + Map messageFilter, + List sort, + String query, + PaginationParams pagination, + }) async { + _messageResponses.add(null); + try { + final messages = await _search( + filter: filter, + messageFilter: messageFilter, + sort: sort, + query: query, + pagination: pagination, + ); + _messageResponses.add(messages.results); + } catch (err, stk) { + _messageResponses.addError(err, stk); + } + } + + /// Calls [Client.search] updating [queryMessagesLoading] stream + Future loadMore({ + Map filter, + Map messageFilter, + List sort, + String query, + PaginationParams pagination, + }) async { + if (_queryMessagesLoadingController.value == true) { + return; + } + _queryMessagesLoadingController.add(true); + try { + final clear = pagination == null || + pagination.offset == null || + pagination.offset == 0; + + final oldMessages = List.from(messageResponses ?? []); + + final messages = await _search( + filter: filter, + messageFilter: messageFilter, + sort: sort, + query: query, + pagination: pagination, + ); + + if (clear) { + _messageResponses.add(messages.results); + } else { + final temp = oldMessages + messages.results; + _messageResponses.add(temp); + } + + _queryMessagesLoadingController.add(false); + } catch (err, stackTrace) { + _queryMessagesLoadingController.addError(err, stackTrace); + } + } + + Future _search({ + Map filter, + Map messageFilter, + List sort, + String query, + PaginationParams pagination, + }) { + final client = StreamChat.of(context).client; + return client.search( + filter, + sort, + query, + pagination, + messageFilters: messageFilter, + ); + } + + @override + Widget build(BuildContext context) { + super.build(context); + return widget.child; + } + + @override + void dispose() { + _messageResponses.close(); + _queryMessagesLoadingController.close(); + super.dispose(); + } + + @override + bool get wantKeepAlive => true; +} diff --git a/packages/stream_chat_flutter_core/lib/src/stream_channel.dart b/packages/stream_chat_flutter_core/lib/src/stream_channel.dart new file mode 100644 index 00000000..8b4b97ef --- /dev/null +++ b/packages/stream_chat_flutter_core/lib/src/stream_channel.dart @@ -0,0 +1,356 @@ +import 'dart:async'; + +import 'package:flutter/material.dart'; +import 'package:rxdart/rxdart.dart'; +import 'package:stream_chat/stream_chat.dart'; + +enum QueryDirection { top, bottom } + +/// Widget used to provide information about the channel to the widget tree +/// +/// Use [StreamChannel.of] to get the current [StreamChannelState] instance. +class StreamChannel extends StatefulWidget { + const StreamChannel({ + Key key, + @required this.child, + @required this.channel, + this.showLoading = true, + this.initialMessageId, + }) : assert(child != null), + assert(channel != null), + super(key: key); + + final Widget child; + final Channel channel; + final bool showLoading; + + /// If passed the channel will load from this particular message. + final String initialMessageId; + + /// Use this method to get the current [StreamChannelState] instance + static StreamChannelState of(BuildContext context) { + StreamChannelState streamChannelState; + + streamChannelState = context.findAncestorStateOfType(); + + if (streamChannelState == null) { + throw Exception( + 'You must have a StreamChannel widget at the top of your widget tree'); + } + + return streamChannelState; + } + + @override + StreamChannelState createState() => StreamChannelState(); +} + +class StreamChannelState extends State { + /// Current channel + Channel get channel => widget.channel; + + /// InitialMessageId + String get initialMessageId => widget.initialMessageId; + + /// Current channel state stream + Stream get channelStateStream => + widget.channel.state.channelStateStream; + + final _queryTopMessagesController = BehaviorSubject.seeded(false); + final _queryBottomMessagesController = BehaviorSubject.seeded(false); + + /// The stream notifying the state of [_queryTopMessages] call + Stream get queryTopMessages => _queryTopMessagesController.stream; + + /// The stream notifying the state of [_queryBottomMessages] call + Stream get queryBottomMessages => _queryBottomMessagesController.stream; + + bool _topPaginationEnded = false; + bool _bottomPaginationEnded = false; + + Future _queryTopMessages({ + int limit = 20, + bool preferOffline = false, + }) async { + if (_topPaginationEnded || _queryTopMessagesController?.value == true) { + return; + } + _queryTopMessagesController.add(true); + + if (channel.state.messages.isEmpty) { + return _queryTopMessagesController.add(false); + } + + final oldestMessage = channel.state.messages.first; + + try { + final state = await queryBeforeMessage( + oldestMessage.id, + limit: limit, + preferOffline: preferOffline, + ); + if (state.messages.isEmpty || state.messages.length < limit) { + _topPaginationEnded = true; + } + _queryTopMessagesController.add(false); + } catch (e, stk) { + _queryTopMessagesController.addError(e, stk); + } + } + + Future _queryBottomMessages({ + int limit = 20, + bool preferOffline = false, + }) async { + if (_bottomPaginationEnded || + _queryBottomMessagesController?.value == true || + channel?.state?.isUpToDate == true) return; + _queryBottomMessagesController.add(true); + + if (channel.state.messages.isEmpty) { + return _queryBottomMessagesController.add(false); + } + + final recentMessage = channel.state.messages.last; + + try { + final state = await queryAfterMessage( + recentMessage.id, + limit: limit, + preferOffline: preferOffline, + ); + if (state.messages.isEmpty || state.messages.length < limit) { + _bottomPaginationEnded = true; + } + _queryBottomMessagesController.add(false); + } catch (e, stk) { + _queryBottomMessagesController.addError(e, stk); + } + } + + /// Calls [channel.query] updating [queryMessage] stream + Future queryMessages({QueryDirection direction = QueryDirection.top}) { + if (direction == QueryDirection.top) return _queryTopMessages(); + return _queryBottomMessages(); + } + + /// Calls [channel.getReplies] updating [queryMessage] stream + Future getReplies( + String parentId, { + int limit = 50, + bool preferOffline = false, + }) async { + if (_topPaginationEnded || _queryTopMessagesController.value) return; + _queryTopMessagesController.add(true); + + Message message; + if (channel.state.threads.containsKey(parentId)) { + final thread = channel.state.threads[parentId]; + if (thread.isNotEmpty) { + message = thread.first; + } + } + + try { + final response = await channel.getReplies( + parentId, + PaginationParams( + lessThan: message?.id, + limit: limit, + ), + preferOffline: preferOffline, + ); + if (response.messages.isEmpty || response.messages.length < limit) { + _topPaginationEnded = true; + } + _queryTopMessagesController.add(false); + } catch (e, stk) { + _queryTopMessagesController.addError(e, stk); + } + } + + /// Query the channel members and watchers + Future queryMembersAndWatchers() async { + await widget.channel.query( + membersPagination: PaginationParams( + offset: channel.state.members?.length, + limit: 100, + ), + watchersPagination: PaginationParams( + offset: channel.state.watchers?.length, + limit: 100, + ), + ); + } + + /// Loads channel at specific message + Future loadChannelAtMessage( + String messageId, { + int before = 20, + int after = 20, + bool preferOffline = false, + }) { + return queryAtMessage( + messageId: messageId, + before: before, + after: after, + preferOffline: preferOffline, + ); + } + + /// + Future queryAtMessage({ + String messageId, + int before = 20, + int after = 20, + bool preferOffline = false, + }) async { + if (channel.state == null) return; + channel.state.isUpToDate = false; + channel.state.truncate(); + + if (messageId == null) { + await channel.query( + messagesPagination: PaginationParams( + limit: before, + ), + preferOffline: preferOffline, + ); + channel.state.isUpToDate = true; + return; + } + + return Future.wait([ + queryBeforeMessage( + messageId, + limit: before, + preferOffline: preferOffline, + ), + queryAfterMessage( + messageId, + limit: after, + preferOffline: preferOffline, + ), + ]); + } + + /// + Future queryBeforeMessage( + String messageId, { + int limit = 20, + bool preferOffline = false, + }) { + return channel.query( + messagesPagination: PaginationParams( + lessThan: messageId, + limit: limit, + ), + preferOffline: preferOffline, + ); + } + + /// + Future queryAfterMessage( + String messageId, { + int limit = 20, + bool preferOffline = false, + }) async { + final state = await channel.query( + messagesPagination: PaginationParams( + greaterThanOrEqual: messageId, + limit: limit, + ), + preferOffline: preferOffline, + ); + if (state.messages.isEmpty || state.messages.length < limit) { + channel.state.isUpToDate = true; + } + return state; + } + + /// + Future getMessage(String messageId) async { + var message = channel.state.messages.firstWhere( + (it) => it.id == messageId, + orElse: () => null, + ); + if (message == null) { + final response = await channel.getMessagesById([messageId]); + message = response.messages.first; + } + return message; + } + + /// Reloads the channel with latest message + Future reloadChannel() => queryAtMessage(before: 30); + + List> _futures; + + Future get _loadChannelAtMessage async { + try { + await loadChannelAtMessage(initialMessageId); + return true; + } catch (e, stk) { + print('Error: $e\nStack: $stk'); + rethrow; + } + } + + @override + void initState() { + super.initState(); + _futures = [widget.channel.initialized]; + if (initialMessageId != null) { + _futures.add(_loadChannelAtMessage); + } + } + + @override + void dispose() { + _queryTopMessagesController.close(); + _queryBottomMessagesController.close(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + Widget child = FutureBuilder>( + future: Future.wait(_futures), + initialData: [ + channel.state != null, + if (initialMessageId != null) false, + ], + builder: (context, snapshot) { + if (snapshot.hasError) { + if (snapshot.error is Error) { + print((snapshot.error as Error).stackTrace); + } + var message = snapshot.error.toString(); + if (snapshot.error is DioError) { + final dioError = snapshot.error as DioError; + if (dioError.type == DioErrorType.RESPONSE) { + message = dioError.message; + } else { + message = 'Check your connection and retry'; + } + } + return Center( + child: Text(message), + ); + } + final initialized = snapshot.data[0]; + final dataLoaded = initialMessageId == null ? true : snapshot.data[1]; + if (widget.showLoading && (!initialized || !dataLoaded)) { + return Center( + child: CircularProgressIndicator(), + ); + } + return widget.child; + }, + ); + if (initialMessageId != null) { + child = Material(child: child); + } + return child; + } +} diff --git a/packages/stream_chat_flutter_core/pubspec.yaml b/packages/stream_chat_flutter_core/pubspec.yaml index 10e0688e..e318c67d 100644 --- a/packages/stream_chat_flutter_core/pubspec.yaml +++ b/packages/stream_chat_flutter_core/pubspec.yaml @@ -10,6 +10,7 @@ environment: dependencies: stream_chat: ^0.2.23+3 + rxdart: ^0.24.1 flutter: sdk: flutter From c0ebd9efe418e3873c750315eed46d1df6b85e50 Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Fri, 22 Jan 2021 17:14:48 +0530 Subject: [PATCH 04/39] feat: Removed theme and added new widgets --- .../lib/src/channel_list_view.dart | 69 +- .../lib/src/message_list_view.dart | 166 ++++ .../lib/src/message_search_list_view.dart | 194 ++++ .../lib/src/stream_chat.dart | 37 +- .../lib/src/stream_chat_theme.dart | 898 ------------------ .../lib/src/user_list_view.dart | 293 +----- 6 files changed, 419 insertions(+), 1238 deletions(-) create mode 100644 packages/stream_chat_flutter_core/lib/src/message_list_view.dart create mode 100644 packages/stream_chat_flutter_core/lib/src/message_search_list_view.dart delete mode 100644 packages/stream_chat_flutter_core/lib/src/stream_chat_theme.dart diff --git a/packages/stream_chat_flutter_core/lib/src/channel_list_view.dart b/packages/stream_chat_flutter_core/lib/src/channel_list_view.dart index 920ee09b..38417190 100644 --- a/packages/stream_chat_flutter_core/lib/src/channel_list_view.dart +++ b/packages/stream_chat_flutter_core/lib/src/channel_list_view.dart @@ -8,14 +8,6 @@ import 'package:stream_chat_flutter_core/src/channels_bloc.dart'; import 'stream_chat.dart'; -/// Callback called when tapping on a channel -typedef ChannelTapCallback = void Function(Channel, Widget); - -/// Builder used to create a custom [ChannelPreview] from a [Channel] -typedef ChannelPreviewBuilder = Widget Function(BuildContext, Channel); - -typedef ViewInfoCallback = void Function(Channel); - /// ![screenshot](https://raw.githubusercontent.com/GetStream/stream-chat-flutter/master/screenshots/channel_list_view.png) /// ![screenshot](https://raw.githubusercontent.com/GetStream/stream-chat-flutter/master/screenshots/channel_list_view_paint.png) /// @@ -57,15 +49,16 @@ class ChannelListView extends StatefulWidget { this.options, this.sort, this.pagination, - this.separatorBuilder, - this.errorBuilder, - this.swipeToAction = false, this.pullToRefresh = true, + @required this.errorBuilder, @required this.emptyBuilder, @required this.loadingBuilder, @required this.listBuilder, + this.channelListController, }) : super(key: key); + final ChannelListController channelListController; + /// The builder that will be used in case of error final Widget Function(Error error) errorBuilder; @@ -73,9 +66,6 @@ class ChannelListView extends StatefulWidget { final Function(BuildContext, List) listBuilder; - /// If true a default swipe to action behaviour will be added to this widget - final bool swipeToAction; - /// The builder used when the channel list is empty. final WidgetBuilder emptyBuilder; @@ -102,9 +92,6 @@ class ChannelListView extends StatefulWidget { /// message_limit: how many messages should be included to each channel final PaginationParams pagination; - /// Builder used to create a custom item separator - final Function(BuildContext, int) separatorBuilder; - /// Set it to false to disable the pull-to-refresh widget final bool pullToRefresh; @@ -114,8 +101,6 @@ class ChannelListView extends StatefulWidget { class _ChannelListViewState extends State with WidgetsBindingObserver { - final ScrollController _scrollController = ScrollController(); - @override Widget build(BuildContext context) { final channelsBloc = ChannelsBloc.of(context); @@ -162,10 +147,7 @@ class _ChannelListViewState extends State } } - return AnimatedSwitcher( - child: child, - duration: Duration(milliseconds: 500), - ); + return child; }, ); } @@ -186,19 +168,17 @@ class _ChannelListViewState extends State return widget.errorBuilder(snapshot.error); } - void _listenChannelPagination(ChannelsBlocState channelsProvider) { - if (_scrollController.position.maxScrollExtent == - _scrollController.offset && - _scrollController.offset != 0) { - channelsProvider.queryChannels( - filter: widget.filter, - sortOptions: widget.sort, - paginationParams: widget.pagination.copyWith( - offset: channelsProvider.channels?.length ?? 0, - ), - options: widget.options, - ); - } + void paginateData() { + final channelsBloc = ChannelsBloc.of(context); + + channelsBloc.queryChannels( + filter: widget.filter, + sortOptions: widget.sort, + paginationParams: widget.pagination.copyWith( + offset: channelsBloc.channels?.length ?? 0, + ), + options: widget.options, + ); } StreamSubscription _subscription; @@ -217,14 +197,6 @@ class _ChannelListViewState extends State options: widget.options, ); - _scrollController.addListener(() { - channelsBloc.queryChannelsLoading.first.then((loading) { - if (!loading) { - _listenChannelPagination(channelsBloc); - } - }); - }); - final client = StreamChat.of(context).client; _subscription = client @@ -242,6 +214,10 @@ class _ChannelListViewState extends State options: widget.options, ); }); + + if (widget.channelListController != null) { + widget.channelListController.paginateData = paginateData; + } } @override @@ -270,3 +246,8 @@ class _ChannelListViewState extends State super.dispose(); } } + +/// Controller used for paginating data in [ChannelListView] +class ChannelListController { + VoidCallback paginateData; +} diff --git a/packages/stream_chat_flutter_core/lib/src/message_list_view.dart b/packages/stream_chat_flutter_core/lib/src/message_list_view.dart new file mode 100644 index 00000000..d0a59266 --- /dev/null +++ b/packages/stream_chat_flutter_core/lib/src/message_list_view.dart @@ -0,0 +1,166 @@ +import 'dart:async'; + +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:stream_chat/stream_chat.dart'; +import 'stream_channel.dart'; + +/// ![screenshot](https://raw.githubusercontent.com/GetStream/stream-chat-flutter/master/screenshots/message_listview.png) +/// ![screenshot](https://raw.githubusercontent.com/GetStream/stream-chat-flutter/master/screenshots/message_listview_paint.png) +/// +/// It shows the list of messages of the current channel. +/// +/// ```dart +/// class ChannelPage extends StatelessWidget { +/// const ChannelPage({ +/// Key key, +/// }) : super(key: key); +/// +/// @override +/// Widget build(BuildContext context) { +/// return Scaffold( +/// appBar: ChannelHeader(), +/// body: Column( +/// children: [ +/// Expanded( +/// child: MessageListView( +/// threadBuilder: (_, parentMessage) { +/// return ThreadPage( +/// parent: parentMessage, +/// ); +/// }, +/// ), +/// ), +/// MessageInput(), +/// ], +/// ), +/// ); +/// } +/// } +/// ``` +/// +/// +/// Make sure to have a [StreamChannel] ancestor in order to provide the information about the channels. +/// The widget uses a [ListView.custom] to render the list of channels. +/// +/// The widget components render the ui based on the first ancestor of type [StreamChatTheme]. +/// Modify it to change the widget appearance. +class MessageListView extends StatefulWidget { + /// Instantiate a new MessageListView + MessageListView({ + Key key, + this.showScrollToBottom = true, + this.parentMessage, + this.dateDividerBuilder, + @required this.loadingBuilder, + @required this.emptyBuilder, + @required this.messageListBuilder, + this.messageListController, + }) : super(key: key); + + MessageListController messageListController; + + final Widget Function(BuildContext, List) messageListBuilder; + + /// Function used to build a loading widget + final WidgetBuilder loadingBuilder; + + /// Function used to build an empty widget + final WidgetBuilder emptyBuilder; + + /// If true will show a scroll to bottom message when there are new messages and the scroll offset is not zero + final bool showScrollToBottom; + + /// Parent message in case of a thread + final Message parentMessage; + + /// Builder used to render date dividers + final Widget Function(DateTime) dateDividerBuilder; + + @override + _MessageListViewState createState() => _MessageListViewState(); +} + +class _MessageListViewState extends State { + StreamChannelState streamChannel; + + bool get _upToDate => streamChannel.channel.state.isUpToDate; + + bool get _isThreadConversation => widget.parentMessage != null; + + int initialIndex; + double initialAlignment; + + List messages = []; + + bool initialMessageHighlightComplete = false; + + @override + Widget build(BuildContext context) { + final messagesStream = _isThreadConversation + ? streamChannel.channel.state.threadsStream + .where((threads) => threads.containsKey(widget.parentMessage.id)) + .map((threads) => threads[widget.parentMessage.id]) + : streamChannel.channel.state?.messagesStream; + + return StreamBuilder>( + stream: messagesStream?.map((messages) => messages + ?.where((e) => + (!e.isDeleted && e.shadowed != true) || + (e.isDeleted && + e.user.id == streamChannel.channel.client.state.user.id)) + ?.toList()), + builder: (context, snapshot) { + if (!snapshot.hasData) { + return widget.loadingBuilder(context); + } + + final messageList = snapshot.data?.reversed?.toList() ?? []; + if (messageList.isEmpty) { + if (_upToDate) { + return widget.emptyBuilder(context); + } + } else { + messages = messageList; + } + + return widget.messageListBuilder(context, messages); + }); + } + + Future paginateData(QueryDirection direction) { + if (!_isThreadConversation) { + return streamChannel.queryMessages(direction: direction); + } else { + return streamChannel.getReplies(widget.parentMessage.id); + } + } + + @override + void initState() { + streamChannel = StreamChannel.of(context); + + if (_isThreadConversation) { + streamChannel.getReplies(widget.parentMessage.id); + } + + if (widget.messageListController != null) { + widget.messageListController.paginateData = paginateData; + } + + super.initState(); + } + + @override + void dispose() { + if (!_upToDate) { + streamChannel.reloadChannel(); + } + super.dispose(); + } +} + +/// Controller used for paginating data in [ChannelListView] +class MessageListController { + Function(QueryDirection direction) paginateData; +} diff --git a/packages/stream_chat_flutter_core/lib/src/message_search_list_view.dart b/packages/stream_chat_flutter_core/lib/src/message_search_list_view.dart new file mode 100644 index 00000000..f27f7c8e --- /dev/null +++ b/packages/stream_chat_flutter_core/lib/src/message_search_list_view.dart @@ -0,0 +1,194 @@ +import 'dart:convert'; + +import 'package:flutter/material.dart'; +import 'package:stream_chat/stream_chat.dart'; +import 'lazy_load_scroll_view.dart'; +import 'message_search_bloc.dart'; + +/// Callback called when tapping on a user +typedef MessageSearchItemTapCallback = void Function(GetMessageResponse); + +/// Builder used to create a custom [ListUserItem] from a [User] +typedef MessageSearchItemBuilder = Widget Function( + BuildContext, GetMessageResponse); + +/// Builder used when [MessageSearchListView] is empty +typedef EmptyMessageSearchBuilder = Widget Function( + BuildContext context, String searchQuery); + +/// +/// It shows the list of searched messages. +/// +/// ```dart +/// class MessageSearchPage extends StatelessWidget { +/// @override +/// Widget build(BuildContext context) { +/// return Scaffold( +/// body: MessageSearchListView( +/// messageQuery: _channelQuery, +/// filters: { +/// 'members': { +/// r'$in': [user.id] +/// } +/// }, +/// paginationParams: PaginationParams(limit: 20), +/// ), +/// ); +/// } +/// } +/// ``` +/// +/// +/// Make sure to have a [MessageSearchBloc] ancestor in order to provide the information about the messages. +/// The widget uses a [ListView.separated] to render the list of messages. +/// +/// The widget components render the ui based on the first ancestor of type [StreamChatTheme]. +/// Modify it to change the widget appearance. +class MessageSearchListView extends StatefulWidget { + /// Instantiate a new MessageSearchListView + const MessageSearchListView({ + Key key, + this.messageQuery, + this.filters, + this.sortOptions, + this.paginationParams, + this.messageFilters, + @required this.emptyBuilder, + @required this.errorBuilder, + @required this.loadingBuilder, + @required this.childBuilder, + this.messageSearchListController, + }) : super(key: key); + + final MessageSearchListController messageSearchListController; + + /// Message String to search on + final String messageQuery; + + /// The query filters to use. + /// You can query on any of the custom fields you've defined on the [Channel]. + /// You can also filter other built-in channel fields. + final Map filters; + + /// The sorting used for the channels matching the filters. + /// Sorting is based on field and direction, multiple sorting options can be provided. + /// You can sort based on last_updated, last_message_at, updated_at, created_at or member_count. + /// Direction can be ascending or descending. + final List sortOptions; + + /// Pagination parameters + /// limit: the number of users to return (max is 30) + /// offset: the offset (max is 1000) + /// message_limit: how many messages should be included to each channel + final PaginationParams paginationParams; + + /// The message query filters to use. + /// You can query on any of the custom fields you've defined on the [Channel]. + /// You can also filter other built-in channel fields. + final Map messageFilters; + + final Widget Function(List) childBuilder; + + /// The builder used when the channel list is empty. + final EmptyMessageSearchBuilder emptyBuilder; + + /// The builder that will be used in case of error + final Widget Function(Error error) errorBuilder; + + final WidgetBuilder loadingBuilder; + + @override + _MessageSearchListViewState createState() => _MessageSearchListViewState(); +} + +class _MessageSearchListViewState extends State { + @override + void initState() { + super.initState(); + final messageSearchBloc = MessageSearchBloc.of(context); + messageSearchBloc.search( + filter: widget.filters, + sort: widget.sortOptions, + query: widget.messageQuery, + pagination: widget.paginationParams, + messageFilter: widget.messageFilters, + ); + + if (widget.messageSearchListController != null) { + widget.messageSearchListController.paginateData = paginateData; + } + } + + @override + Widget build(BuildContext context) { + final messageSearchBloc = MessageSearchBloc.of(context); + return _buildListView(messageSearchBloc); + } + + Widget _buildListView(MessageSearchBlocState messageSearchBloc) { + return StreamBuilder>( + stream: messageSearchBloc.messagesStream, + builder: (context, snapshot) { + if (snapshot.hasError) { + if (snapshot.error is Error) { + print((snapshot.error as Error).stackTrace); + } + + return widget.errorBuilder(snapshot.error); + } + + if (!snapshot.hasData) { + return widget.loadingBuilder(context); + } + + final items = snapshot.data; + + if (items.isEmpty) { + return widget.emptyBuilder(context, widget.messageQuery); + } + + return widget.childBuilder(snapshot.data); + }, + ); + } + + void paginateData() { + final messageSearchBloc = MessageSearchBloc.of(context); + + messageSearchBloc.loadMore( + filter: widget.filters, + sort: widget.sortOptions, + pagination: widget.paginationParams.copyWith( + offset: messageSearchBloc.messageResponses?.length ?? 0, + ), + query: widget.messageQuery, + messageFilter: widget.messageFilters, + ); + } + + @override + void didUpdateWidget(MessageSearchListView oldWidget) { + super.didUpdateWidget(oldWidget); + if (widget.filters?.toString() != oldWidget.filters?.toString() || + jsonEncode(widget.sortOptions) != jsonEncode(oldWidget.sortOptions) || + widget.paginationParams?.toJson()?.toString() != + oldWidget.paginationParams?.toJson()?.toString() || + widget.messageQuery?.toString() != oldWidget.messageQuery?.toString() || + widget.messageFilters?.toString() != + oldWidget.messageFilters?.toString()) { + final messageSearchBloc = MessageSearchBloc.of(context); + messageSearchBloc.search( + filter: widget.filters, + sort: widget.sortOptions, + query: widget.messageQuery, + pagination: widget.paginationParams, + messageFilter: widget.messageFilters, + ); + } + } +} + +/// Controller used for paginating data in [ChannelListView] +class MessageSearchListController { + VoidCallback paginateData; +} diff --git a/packages/stream_chat_flutter_core/lib/src/stream_chat.dart b/packages/stream_chat_flutter_core/lib/src/stream_chat.dart index e6c47c9d..17ec62b6 100644 --- a/packages/stream_chat_flutter_core/lib/src/stream_chat.dart +++ b/packages/stream_chat_flutter_core/lib/src/stream_chat.dart @@ -3,7 +3,6 @@ import 'dart:async'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:stream_chat/stream_chat.dart'; -import 'package:stream_chat_flutter_core/src/stream_chat_theme.dart'; /// Widget used to provide information about the chat to the widget tree /// @@ -29,13 +28,11 @@ import 'package:stream_chat_flutter_core/src/stream_chat_theme.dart'; class StreamChat extends StatefulWidget { final Client client; final Widget child; - final StreamChatThemeData streamChatThemeData; StreamChat({ Key key, @required this.client, @required this.child, - this.streamChatThemeData, }) : super( key: key, ); @@ -65,32 +62,7 @@ class StreamChatState extends State with WidgetsBindingObserver { @override Widget build(BuildContext context) { - final theme = _getTheme(context, widget.streamChatThemeData); - return StreamChatTheme( - data: theme, - child: Builder( - builder: (context) { - final materialTheme = Theme.of(context); - final streamTheme = StreamChatTheme.of(context); - return Theme( - data: materialTheme.copyWith( - primaryIconTheme: streamTheme.primaryIconTheme, - accentColor: streamTheme.colorTheme.accentBlue, - scaffoldBackgroundColor: streamTheme.colorTheme.white, - ), - child: widget.child, - ); - }, - ), - ); - } - - StreamChatThemeData _getTheme( - BuildContext context, - StreamChatThemeData themeData, - ) { - final defaultTheme = StreamChatThemeData.getDefaultTheme(Theme.of(context)); - return defaultTheme.merge(themeData) ?? themeData; + return widget.child; } /// The current user @@ -103,13 +75,6 @@ class StreamChatState extends State with WidgetsBindingObserver { void initState() { super.initState(); WidgetsBinding.instance.addObserver(this); - // client.state?.totalUnreadCountStream?.listen((count) { - // if (count > 0) { - // FlutterAppBadger.updateBadgeCount(count); - // } else { - // FlutterAppBadger.removeBadge(); - // } - // }); } StreamSubscription _newMessageSubscription; diff --git a/packages/stream_chat_flutter_core/lib/src/stream_chat_theme.dart b/packages/stream_chat_flutter_core/lib/src/stream_chat_theme.dart deleted file mode 100644 index be584492..00000000 --- a/packages/stream_chat_flutter_core/lib/src/stream_chat_theme.dart +++ /dev/null @@ -1,898 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:stream_chat/stream_chat.dart'; -import 'package:stream_chat_flutter_core/src/reaction_icon.dart'; - -/// Inherited widget providing the [StreamChatThemeData] to the widget tree -class StreamChatTheme extends InheritedWidget { - final StreamChatThemeData data; - - StreamChatTheme({ - Key key, - @required this.data, - Widget child, - }) : super( - key: key, - child: child, - ); - - @override - bool updateShouldNotify(StreamChatTheme old) { - return data != old.data; - } - - /// Use this method to get the current [StreamChatThemeData] instance - static StreamChatThemeData of(BuildContext context) { - final streamChatTheme = - context.dependOnInheritedWidgetOfExactType(); - - if (streamChatTheme == null) { - throw Exception( - 'You must have a StreamChatTheme widget at the top of your widget tree', - ); - } - - return streamChatTheme.data; - } -} - -/// Theme data -class StreamChatThemeData { - /// The text themes used in the widgets - final TextTheme textTheme; - - /// The button themes used in the widgets - final ButtonThemeData buttonTheme; - - /// The text themes used in the widgets - final ColorTheme colorTheme; - - /// Theme of the [ChannelPreview] - final ChannelPreviewTheme channelPreviewTheme; - - /// Theme of the chat widgets dedicated to a channel - final ChannelTheme channelTheme; - - /// Theme of the current user messages - final MessageTheme ownMessageTheme; - - /// Theme of other users messages - final MessageTheme otherMessageTheme; - - /// The widget that will be built when the channel image is unavailable - final Widget Function(BuildContext, Channel) defaultChannelImage; - - /// The widget that will be built when the user image is unavailable - final Widget Function(BuildContext, User) defaultUserImage; - - /// Primary icon theme - final IconThemeData primaryIconTheme; - - /// Assets used for rendering reactions - final List reactionIcons; - - /// Create a theme from scratch - const StreamChatThemeData({ - this.textTheme, - this.buttonTheme, - this.colorTheme, - this.channelPreviewTheme, - this.channelTheme, - this.otherMessageTheme, - this.ownMessageTheme, - this.defaultChannelImage, - this.defaultUserImage, - this.primaryIconTheme, - this.reactionIcons, - }); - - /// Create a theme from a Material [Theme] - factory StreamChatThemeData.fromTheme(ThemeData theme) { - final defaultTheme = getDefaultTheme(theme); - final customizedTheme = StreamChatThemeData( - primaryIconTheme: theme.primaryIconTheme, - ownMessageTheme: MessageTheme( - replies: TextStyle(color: theme.accentColor), - messageLinks: TextStyle(color: theme.accentColor), - ), - otherMessageTheme: MessageTheme( - replies: TextStyle(color: theme.accentColor), - messageLinks: TextStyle(color: theme.accentColor), - ), - ); - return defaultTheme.merge(customizedTheme) ?? customizedTheme; - } - - /// Creates a copy of [StreamChatThemeData] with specified attributes overridden. - StreamChatThemeData copyWith({ - TextTheme textTheme, - ButtonThemeData buttonTheme, - ColorTheme colorTheme, - ChannelPreviewTheme channelPreviewTheme, - ChannelTheme channelTheme, - MessageTheme ownMessageTheme, - MessageTheme otherMessageTheme, - Widget Function(BuildContext, Channel) defaultChannelImage, - Widget Function(BuildContext, User) defaultUserImage, - IconThemeData primaryIconTheme, - List reactionIcons, - }) => - StreamChatThemeData( - textTheme: textTheme ?? this.textTheme, - buttonTheme: buttonTheme ?? this.buttonTheme, - colorTheme: colorTheme ?? this.colorTheme, - primaryIconTheme: primaryIconTheme ?? this.primaryIconTheme, - defaultChannelImage: defaultChannelImage ?? this.defaultChannelImage, - defaultUserImage: defaultUserImage ?? this.defaultUserImage, - channelPreviewTheme: channelPreviewTheme ?? this.channelPreviewTheme, - channelTheme: channelTheme ?? this.channelTheme, - ownMessageTheme: ownMessageTheme ?? this.ownMessageTheme, - otherMessageTheme: otherMessageTheme ?? this.otherMessageTheme, - reactionIcons: reactionIcons ?? this.reactionIcons, - ); - - StreamChatThemeData merge(StreamChatThemeData other) { - if (other == null) return this; - return copyWith( - textTheme: textTheme?.merge(other.textTheme) ?? other.textTheme, - buttonTheme: other.buttonTheme, - colorTheme: colorTheme?.merge(other.colorTheme) ?? other.colorTheme, - primaryIconTheme: other.primaryIconTheme, - defaultChannelImage: other.defaultChannelImage, - defaultUserImage: other.defaultUserImage, - channelPreviewTheme: - channelPreviewTheme?.merge(other.channelPreviewTheme) ?? - other.channelPreviewTheme, - channelTheme: - channelTheme?.merge(other.channelTheme) ?? other.channelTheme, - ownMessageTheme: ownMessageTheme?.merge(other.ownMessageTheme) ?? - other.ownMessageTheme, - otherMessageTheme: otherMessageTheme?.merge(other.otherMessageTheme) ?? - other.otherMessageTheme, - reactionIcons: other.reactionIcons, - ); - } - - /// Get the default Stream Chat theme - static StreamChatThemeData getDefaultTheme(ThemeData theme) { - final accentColor = Color(0xff006cff); - final isDark = theme.brightness == Brightness.dark; - final textTheme = isDark ? TextTheme.dark() : TextTheme.light(); - final colorTheme = isDark ? ColorTheme.dark() : ColorTheme.light(); - return StreamChatThemeData( - textTheme: textTheme, - colorTheme: colorTheme, - buttonTheme: ButtonThemeData( - height: 48.0, - buttonColor: isDark ? Color(0xffffffff) : Color(0xff006aff), - textTheme: ButtonTextTheme.accent, - colorScheme: theme.colorScheme.copyWith( - secondary: isDark ? Color(0xff005eff) : Color(0xffffffff), - ), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(26), - ), - ), - primaryIconTheme: IconThemeData(color: colorTheme.black.withOpacity(.5)), - defaultChannelImage: (context, channel) => SizedBox(), - defaultUserImage: (context, user) => Center( - child: Image.network( - getRandomPicUrl(user), - filterQuality: FilterQuality.high, - fit: BoxFit.cover, - ), - ), - channelPreviewTheme: ChannelPreviewTheme( - unreadCounterColor: colorTheme.accentRed, - avatarTheme: AvatarTheme( - borderRadius: BorderRadius.circular(20), - constraints: BoxConstraints.tightFor( - height: 40, - width: 40, - ), - ), - title: textTheme.bodyBold, - subtitle: textTheme.footnote.copyWith( - color: Color(0xff7A7A7A), - ), - lastMessageAt: textTheme.footnote.copyWith( - color: colorTheme.black.withOpacity(.5), - ), - indicatorIconSize: 16.0), - channelTheme: ChannelTheme( - messageInputButtonIconTheme: theme.iconTheme.copyWith( - color: accentColor, - ), - channelHeaderTheme: ChannelHeaderTheme( - avatarTheme: AvatarTheme( - borderRadius: BorderRadius.circular(20), - constraints: BoxConstraints.tightFor( - height: 40, - width: 40, - ), - ), - color: colorTheme.white, - title: TextStyle( - fontSize: 14, - color: colorTheme.black, - ), - lastMessageAt: TextStyle( - fontSize: 11, - color: colorTheme.black.withOpacity(.5), - ), - ), - inputBackground: colorTheme.white.withAlpha(12), - ), - ownMessageTheme: MessageTheme( - messageText: textTheme.body, - createdAt: textTheme.footnote.copyWith(color: colorTheme.grey), - replies: textTheme.footnoteBold.copyWith(color: accentColor), - messageBackgroundColor: colorTheme.greyGainsboro, - reactionsBackgroundColor: colorTheme.white, - reactionsBorderColor: colorTheme.greyWhisper, - reactionsMaskColor: colorTheme.whiteSnow, - messageBorderColor: colorTheme.greyGainsboro, - avatarTheme: AvatarTheme( - borderRadius: BorderRadius.circular(20), - constraints: BoxConstraints.tightFor( - height: 32, - width: 32, - ), - ), - messageLinks: TextStyle( - color: accentColor, - ), - ), - otherMessageTheme: MessageTheme( - reactionsBackgroundColor: colorTheme.greyGainsboro, - reactionsBorderColor: colorTheme.white, - reactionsMaskColor: colorTheme.whiteSnow, - messageText: textTheme.body, - createdAt: textTheme.footnote.copyWith(color: colorTheme.grey), - replies: textTheme.footnoteBold.copyWith(color: accentColor), - messageLinks: TextStyle( - color: accentColor, - ), - messageBackgroundColor: colorTheme.white, - messageBorderColor: colorTheme.greyWhisper, - avatarTheme: AvatarTheme( - borderRadius: BorderRadius.circular(20), - constraints: BoxConstraints.tightFor( - height: 32, - width: 32, - ), - ), - ), - reactionIcons: [ - ReactionIcon( - type: 'love', - assetName: 'Icon_love_reaction.svg', - ), - ReactionIcon( - type: 'like', - assetName: 'Icon_thumbs_up_reaction.svg', - ), - ReactionIcon( - type: 'sad', - assetName: 'Icon_thumbs_down_reaction.svg', - ), - ReactionIcon( - type: 'haha', - assetName: 'Icon_LOL_reaction.svg', - ), - ReactionIcon( - type: 'wow', - assetName: 'Icon_wut_reaction.svg', - ), - ], - ); - } -} - -enum TextThemeType { - light, - dark, -} - -class TextTheme { - final TextStyle title; - final TextStyle headlineBold; - final TextStyle headline; - final TextStyle bodyBold; - final TextStyle body; - final TextStyle footnoteBold; - final TextStyle footnote; - final TextStyle captionBold; - - TextTheme.light({ - this.title = const TextStyle( - fontSize: 22, - fontWeight: FontWeight.bold, - color: Colors.black, - ), - this.headlineBold = const TextStyle( - fontSize: 16, - fontWeight: FontWeight.bold, - color: Colors.black, - ), - this.headline = const TextStyle( - fontSize: 16, - fontWeight: FontWeight.w500, - color: Colors.black, - ), - this.bodyBold = const TextStyle( - fontSize: 14, - fontWeight: FontWeight.bold, - color: Colors.black, - ), - this.body = const TextStyle( - fontSize: 14, - fontWeight: FontWeight.w500, - color: Colors.black, - ), - this.footnoteBold = const TextStyle( - fontSize: 12, - fontWeight: FontWeight.w500, - color: Colors.black, - ), - this.footnote = const TextStyle( - fontSize: 12, - color: Colors.black, - ), - this.captionBold = const TextStyle( - fontSize: 10, - fontWeight: FontWeight.bold, - color: Colors.black, - ), - }); - - TextTheme.dark({ - this.title = const TextStyle( - fontSize: 22, - fontWeight: FontWeight.bold, - color: Colors.white, - ), - this.headlineBold = const TextStyle( - fontSize: 16, - fontWeight: FontWeight.bold, - color: Colors.white, - ), - this.headline = const TextStyle( - fontSize: 16, - fontWeight: FontWeight.w500, - color: Colors.white, - ), - this.bodyBold = const TextStyle( - fontSize: 14, - fontWeight: FontWeight.bold, - color: Colors.white, - ), - this.body = const TextStyle( - fontSize: 14, - fontWeight: FontWeight.w500, - color: Colors.white, - ), - this.footnoteBold = const TextStyle( - fontSize: 12, - fontWeight: FontWeight.w500, - color: Colors.white, - ), - this.footnote = const TextStyle( - fontSize: 12, - color: Colors.white, - ), - this.captionBold = const TextStyle( - fontSize: 10, - fontWeight: FontWeight.bold, - color: Colors.white, - ), - }); - - TextTheme copyWith({ - TextThemeType type = TextThemeType.light, - TextStyle body, - TextStyle title, - TextStyle headlineBold, - TextStyle headline, - TextStyle bodyBold, - TextStyle footnoteBold, - TextStyle footnote, - TextStyle captionBold, - }) { - return type == TextThemeType.light - ? TextTheme.light( - body: body ?? this.body, - title: title ?? this.title, - headlineBold: headlineBold ?? this.headlineBold, - headline: headline ?? this.headline, - bodyBold: bodyBold ?? this.bodyBold, - footnoteBold: footnoteBold ?? this.footnoteBold, - footnote: footnote ?? this.footnote, - captionBold: captionBold ?? this.captionBold, - ) - : TextTheme.dark( - body: body ?? this.body, - title: title ?? this.title, - headlineBold: headlineBold ?? this.headlineBold, - headline: headline ?? this.headline, - bodyBold: bodyBold ?? this.bodyBold, - footnoteBold: footnoteBold ?? this.footnoteBold, - footnote: footnote ?? this.footnote, - captionBold: captionBold ?? this.captionBold, - ); - } - - TextTheme merge(TextTheme other) { - if (other == null) return this; - return copyWith( - body: body?.merge(other.body) ?? other.body, - title: title?.merge(other.title) ?? other.title, - headlineBold: - headlineBold?.merge(other.headlineBold) ?? other.headlineBold, - headline: headline?.merge(other.headline) ?? other.headline, - bodyBold: bodyBold?.merge(other.bodyBold) ?? other.bodyBold, - footnoteBold: - footnoteBold?.merge(other.footnoteBold) ?? other.footnoteBold, - footnote: footnote?.merge(other.footnote) ?? other.footnote, - captionBold: captionBold?.merge(other.captionBold) ?? other.captionBold, - ); - } -} - -enum ColorThemeType { - light, - dark, -} - -class ColorTheme { - final Color black; - final Color grey; - final Color greyGainsboro; - final Color greyWhisper; - final Color whiteSmoke; - final Color whiteSnow; - final Color white; - final Color blueAlice; - final Color accentBlue; - final Color accentRed; - final Color accentGreen; - final Effect borderTop; - final Effect borderBottom; - final Effect shadowIconButton; - final Effect modalShadow; - final Color highlight; - final Color overlay; - final Color overlayDark; - final Gradient bgGradient; - - ColorTheme.light({ - this.black = const Color(0xff000000), - this.grey = const Color(0xff7a7a7a), - this.greyGainsboro = const Color(0xffdbdbdb), - this.greyWhisper = const Color(0xffecebeb), - this.whiteSmoke = const Color(0xfff2f2f2), - this.whiteSnow = const Color(0xfffcfcfc), - this.white = const Color(0xffffffff), - this.blueAlice = const Color(0xffe9f2ff), - this.accentBlue = const Color(0xff005FFF), - this.accentRed = const Color(0xffFF3842), - this.accentGreen = const Color(0xff20E070), - this.highlight = const Color(0xfffbf4dd), - this.overlay = const Color.fromRGBO(0, 0, 0, 0.2), - this.overlayDark = const Color.fromRGBO(0, 0, 0, 0.6), - this.bgGradient = const LinearGradient( - begin: Alignment.topCenter, - end: Alignment.bottomCenter, - colors: [const Color(0xfff7f7f7), const Color(0xfffcfcfc)], - stops: [0, 1], - ), - this.borderTop = const Effect( - sigmaX: 0, - sigmaY: -1, - color: Color(0xff000000), - blur: 0.0, - alpha: 0.08), - this.borderBottom = const Effect( - sigmaX: 0, sigmaY: 1, color: Color(0xff000000), blur: 0.0, alpha: 0.08), - this.shadowIconButton = const Effect( - sigmaX: 0, sigmaY: 2, color: Color(0xff000000), alpha: 0.5, blur: 4.0), - this.modalShadow = const Effect( - sigmaX: 0, sigmaY: 0, color: Color(0xff000000), alpha: 1, blur: 8.0), - }); - - ColorTheme.dark({ - this.black = const Color(0xffffffff), - this.grey = const Color(0xff7a7a7a), - this.greyGainsboro = const Color(0xff2d2f2f), - this.greyWhisper = const Color(0xff1c1e22), - this.whiteSmoke = const Color(0xff13151b), - this.whiteSnow = const Color(0xff070A0D), - this.white = const Color(0xff101418), - this.blueAlice = const Color(0xff00193D), - this.accentBlue = const Color(0xff005FFF), - this.accentRed = const Color(0xffFF3742), - this.accentGreen = const Color(0xff20E070), - this.borderTop = const Effect( - sigmaX: 0, sigmaY: -1, color: Color(0xff141924), blur: 0.0), - this.borderBottom = const Effect( - sigmaX: 0, sigmaY: 1, color: Color(0xff141924), blur: 0.0, alpha: 1.0), - this.shadowIconButton = const Effect( - sigmaX: 0, sigmaY: 2, color: Color(0xff000000), alpha: 0.5, blur: 4.0), - this.modalShadow = const Effect( - sigmaX: 0, sigmaY: 0, color: Color(0xff000000), alpha: 1, blur: 8.0), - this.highlight = const Color(0xff302d22), - this.overlay = const Color.fromRGBO(0, 0, 0, 0.4), - this.overlayDark = const Color.fromRGBO(255, 255, 255, 0.6), - this.bgGradient = const LinearGradient( - begin: Alignment.topCenter, - end: Alignment.bottomCenter, - colors: [const Color(0xff101214), const Color(0xff070a0d)], - stops: [0, 1], - ), - }); - - ColorTheme copyWith({ - ColorThemeType type = ColorThemeType.light, - Color black, - Color grey, - Color greyGainsboro, - Color greyWhisper, - Color whiteSmoke, - Color whiteSnow, - Color white, - Color blueAlice, - Color accentBlue, - Color accentRed, - Color accentGreen, - Effect borderTop, - Effect borderBottom, - Effect shadowIconButton, - Effect modalShadow, - Color highlight, - Color overlay, - Color overlayDark, - Gradient bgGradient, - }) { - return type == ColorThemeType.light - ? ColorTheme.light( - black: black ?? this.black, - grey: grey ?? this.grey, - greyGainsboro: greyGainsboro ?? this.greyGainsboro, - greyWhisper: greyWhisper ?? this.greyWhisper, - whiteSmoke: whiteSmoke ?? this.whiteSmoke, - whiteSnow: whiteSnow ?? this.whiteSnow, - white: white ?? this.white, - blueAlice: blueAlice ?? this.blueAlice, - accentBlue: accentBlue ?? this.accentBlue, - accentRed: accentRed ?? this.accentRed, - accentGreen: accentGreen ?? this.accentGreen, - borderTop: borderTop ?? this.borderTop, - borderBottom: borderBottom ?? this.borderBottom, - shadowIconButton: shadowIconButton ?? this.shadowIconButton, - modalShadow: modalShadow ?? this.modalShadow, - highlight: highlight ?? this.highlight, - overlay: overlay ?? this.overlay, - overlayDark: overlayDark ?? this.overlayDark, - bgGradient: bgGradient ?? this.bgGradient, - ) - : ColorTheme.dark( - black: black ?? this.black, - grey: grey ?? this.grey, - greyGainsboro: greyGainsboro ?? this.greyGainsboro, - greyWhisper: greyWhisper ?? this.greyWhisper, - whiteSmoke: whiteSmoke ?? this.whiteSmoke, - whiteSnow: whiteSnow ?? this.whiteSnow, - white: white ?? this.white, - blueAlice: blueAlice ?? this.blueAlice, - accentBlue: accentBlue ?? this.accentBlue, - accentRed: accentRed ?? this.accentRed, - accentGreen: accentGreen ?? this.accentGreen, - borderTop: borderTop ?? this.borderTop, - borderBottom: borderBottom ?? this.borderBottom, - shadowIconButton: shadowIconButton ?? this.shadowIconButton, - modalShadow: modalShadow ?? this.modalShadow, - highlight: highlight ?? this.highlight, - overlay: overlay ?? this.overlay, - overlayDark: overlayDark ?? this.overlayDark, - bgGradient: bgGradient ?? this.bgGradient, - ); - } - - ColorTheme merge(ColorTheme other) { - if (other == null) return this; - return copyWith( - black: other.black, - grey: other.grey, - greyGainsboro: other.greyGainsboro, - greyWhisper: other.greyWhisper, - whiteSmoke: other.whiteSmoke, - whiteSnow: other.whiteSnow, - white: other.white, - blueAlice: other.blueAlice, - accentBlue: other.accentBlue, - accentRed: other.accentRed, - accentGreen: other.accentGreen, - highlight: other.highlight, - overlay: other.overlay, - overlayDark: other.overlayDark, - bgGradient: other.bgGradient, - borderTop: other.borderTop, - borderBottom: other.borderBottom, - shadowIconButton: other.shadowIconButton, - modalShadow: other.modalShadow, - ); - } -} - -/// Channel theme data -class ChannelTheme { - /// Theme of the [ChannelHeader] widget - final ChannelHeaderTheme channelHeaderTheme; - - /// IconTheme of the send button in [MessageInput] - final IconThemeData messageInputButtonIconTheme; - - /// Theme of the send button in [MessageInput] - final ButtonThemeData messageInputButtonTheme; - - /// Background color of [MessageInput] - final Color inputBackground; - - ChannelTheme({ - this.channelHeaderTheme, - this.messageInputButtonIconTheme, - this.messageInputButtonTheme, - this.inputBackground, - }); - - /// Creates a copy of [ChannelTheme] with specified attributes overridden. - ChannelTheme copyWith({ - ChannelHeaderTheme channelHeaderTheme, - IconThemeData messageInputButtonIconTheme, - ButtonThemeData messageInputButtonTheme, - Color inputBackground, - }) => - ChannelTheme( - channelHeaderTheme: channelHeaderTheme ?? this.channelHeaderTheme, - messageInputButtonIconTheme: - messageInputButtonIconTheme ?? this.messageInputButtonIconTheme, - messageInputButtonTheme: - messageInputButtonTheme ?? this.messageInputButtonTheme, - inputBackground: inputBackground ?? this.inputBackground, - ); - - ChannelTheme merge(ChannelTheme other) { - if (other == null) return this; - return copyWith( - channelHeaderTheme: channelHeaderTheme?.merge(other.channelHeaderTheme) ?? - other.channelHeaderTheme, - messageInputButtonIconTheme: messageInputButtonIconTheme - ?.merge(other.messageInputButtonIconTheme) ?? - other.messageInputButtonIconTheme, - messageInputButtonTheme: other.messageInputButtonTheme, - inputBackground: other.inputBackground, - ); - } -} - -class AvatarTheme { - final BoxConstraints constraints; - final BorderRadius borderRadius; - - AvatarTheme({ - this.constraints, - this.borderRadius, - }); - - AvatarTheme copyWith({ - BoxConstraints constraints, - BorderRadius borderRadius, - }) => - AvatarTheme( - constraints: constraints ?? this.constraints, - borderRadius: borderRadius ?? this.borderRadius, - ); - - AvatarTheme merge(AvatarTheme other) { - if (other == null) return this; - return copyWith( - constraints: other.constraints, - borderRadius: other.borderRadius, - ); - } -} - -class MessageTheme { - final TextStyle messageText; - final TextStyle messageAuthor; - final TextStyle messageLinks; - final TextStyle createdAt; - final TextStyle replies; - final Color messageBackgroundColor; - final Color messageBorderColor; - final Color reactionsBackgroundColor; - final Color reactionsBorderColor; - final Color reactionsMaskColor; - final AvatarTheme avatarTheme; - - const MessageTheme({ - this.replies, - this.messageText, - this.messageAuthor, - this.messageLinks, - this.messageBackgroundColor, - this.messageBorderColor, - this.reactionsBackgroundColor, - this.reactionsBorderColor, - this.reactionsMaskColor, - this.avatarTheme, - this.createdAt, - }); - - MessageTheme copyWith({ - TextStyle messageText, - TextStyle messageAuthor, - TextStyle messageLinks, - TextStyle createdAt, - TextStyle replies, - Color messageBackgroundColor, - Color messageBorderColor, - AvatarTheme avatarTheme, - Color reactionsBackgroundColor, - Color reactionsBorderColor, - Color reactionsMaskColor, - }) => - MessageTheme( - messageText: messageText ?? this.messageText, - messageAuthor: messageAuthor ?? this.messageAuthor, - messageLinks: messageLinks ?? this.messageLinks, - createdAt: createdAt ?? this.createdAt, - messageBackgroundColor: - messageBackgroundColor ?? this.messageBackgroundColor, - messageBorderColor: messageBorderColor ?? this.messageBorderColor, - avatarTheme: avatarTheme ?? this.avatarTheme, - replies: replies ?? this.replies, - reactionsBackgroundColor: - reactionsBackgroundColor ?? this.reactionsBackgroundColor, - reactionsBorderColor: reactionsBorderColor ?? this.reactionsBorderColor, - reactionsMaskColor: reactionsMaskColor ?? this.reactionsMaskColor, - ); - - MessageTheme merge(MessageTheme other) { - if (other == null) return this; - return copyWith( - messageText: messageText?.merge(other.messageText) ?? other.messageText, - messageAuthor: - messageAuthor?.merge(other.messageAuthor) ?? other.messageAuthor, - messageLinks: - messageLinks?.merge(other.messageLinks) ?? other.messageLinks, - createdAt: createdAt?.merge(other.createdAt) ?? other.createdAt, - replies: replies?.merge(other.replies) ?? other.replies, - messageBackgroundColor: other.messageBackgroundColor, - messageBorderColor: other.messageBorderColor, - avatarTheme: avatarTheme?.merge(other.avatarTheme) ?? other.avatarTheme, - reactionsBackgroundColor: other.reactionsBackgroundColor, - reactionsBorderColor: other.reactionsBorderColor, - reactionsMaskColor: other.reactionsMaskColor, - ); - } -} - -class ChannelPreviewTheme { - final TextStyle title; - final TextStyle subtitle; - final TextStyle lastMessageAt; - final AvatarTheme avatarTheme; - final Color unreadCounterColor; - final double indicatorIconSize; - - const ChannelPreviewTheme({ - this.title, - this.subtitle, - this.lastMessageAt, - this.avatarTheme, - this.unreadCounterColor, - this.indicatorIconSize, - }); - - ChannelPreviewTheme copyWith({ - TextStyle title, - TextStyle subtitle, - TextStyle lastMessageAt, - AvatarTheme avatarTheme, - Color unreadCounterColor, - double indicatorIconSize, - }) => - ChannelPreviewTheme( - title: title ?? this.title, - subtitle: subtitle ?? this.subtitle, - lastMessageAt: lastMessageAt ?? this.lastMessageAt, - avatarTheme: avatarTheme ?? this.avatarTheme, - unreadCounterColor: unreadCounterColor ?? this.unreadCounterColor, - indicatorIconSize: indicatorIconSize ?? this.indicatorIconSize, - ); - - ChannelPreviewTheme merge(ChannelPreviewTheme other) { - if (other == null) return this; - return copyWith( - title: title?.merge(other.title) ?? other.title, - subtitle: subtitle?.merge(other.subtitle) ?? other.subtitle, - lastMessageAt: - lastMessageAt?.merge(other.lastMessageAt) ?? other.lastMessageAt, - avatarTheme: avatarTheme?.merge(other.avatarTheme) ?? other.avatarTheme, - unreadCounterColor: other.unreadCounterColor, - ); - } -} - -class ChannelHeaderTheme { - final TextStyle title; - final TextStyle lastMessageAt; - final AvatarTheme avatarTheme; - final Color color; - - const ChannelHeaderTheme({ - this.title, - this.lastMessageAt, - this.avatarTheme, - this.color, - }); - - ChannelHeaderTheme copyWith({ - TextStyle title, - TextStyle lastMessageAt, - AvatarTheme avatarTheme, - Color color, - }) => - ChannelHeaderTheme( - title: title ?? this.title, - lastMessageAt: lastMessageAt ?? this.lastMessageAt, - avatarTheme: avatarTheme ?? this.avatarTheme, - color: color ?? this.color, - ); - - ChannelHeaderTheme merge(ChannelHeaderTheme other) { - if (other == null) return this; - return copyWith( - title: title?.merge(other.title) ?? other.title, - lastMessageAt: - lastMessageAt?.merge(other.lastMessageAt) ?? other.lastMessageAt, - avatarTheme: avatarTheme?.merge(other.avatarTheme) ?? other.avatarTheme, - color: other.color, - ); - } -} - -class Effect { - final double sigmaX; - final double sigmaY; - final Color color; - final double alpha; - final double blur; - - const Effect({ - this.sigmaX, - this.sigmaY, - this.color, - this.alpha, - this.blur, - }); - - Effect copyWith({ - double sigmaX, - double sigmaY, - Color color, - double alpha, - double blur, - }) => - Effect( - sigmaX: sigmaX ?? this.sigmaX, - sigmaY: sigmaY ?? this.sigmaY, - color: color ?? this.color, - alpha: color ?? this.alpha, - blur: blur ?? this.blur, - ); -} - -/// Get random png with initials -String getRandomPicUrl(User user) => - 'https://getstream.io/random_png/?id=${user.id}&name=${user.name}'; diff --git a/packages/stream_chat_flutter_core/lib/src/user_list_view.dart b/packages/stream_chat_flutter_core/lib/src/user_list_view.dart index 4681d931..1b841333 100644 --- a/packages/stream_chat_flutter_core/lib/src/user_list_view.dart +++ b/packages/stream_chat_flutter_core/lib/src/user_list_view.dart @@ -2,15 +2,7 @@ import 'dart:convert'; import 'package:flutter/material.dart'; import 'package:stream_chat/stream_chat.dart'; -import 'package:stream_chat_flutter_core/src/lazy_load_scroll_view.dart'; import 'package:stream_chat_flutter_core/src/users_bloc.dart'; -import 'package:stream_chat_flutter_core/src/stream_chat_theme.dart'; - -/// Callback called when tapping on a user -typedef UserTapCallback = void Function(User, Widget); - -/// Builder used to create a custom [ListUserItem] from a [User] -typedef UserItemBuilder = Widget Function(BuildContext, User, bool); /// /// It shows the list of current users. @@ -47,31 +39,30 @@ class UserListView extends StatefulWidget { /// Instantiate a new UserListView const UserListView({ Key key, - this.errorBuilder, - this.emptyBuilder, this.filter, this.options, this.sort, this.pagination, - this.onUserTap, - this.onUserLongPress, - this.userWidget, - this.userItemBuilder, - this.separatorBuilder, - this.onImageTap, - this.selectedUsers, this.pullToRefresh = true, this.groupAlphabetically = false, - this.crossAxisCount = 1, - }) : assert( - crossAxisCount == 1 || groupAlphabetically == false, - 'Cannot group alphabetically when crossAxisCount > 1', - ), - super(key: key); + @required this.errorBuilder, + @required this.emptyBuilder, + @required this.loadingBuilder, + @required this.listBuilder, + this.userListController, + }) : super(key: key); + + final UserListController userListController; /// The builder that will be used in case of error final Widget Function(Error error) errorBuilder; + /// The builder that will be used to build the list + final Widget Function(BuildContext context, List users) listBuilder; + + /// The builder that will be used for loading + final WidgetBuilder loadingBuilder; + /// The builder used when the channel list is empty. final WidgetBuilder emptyBuilder; @@ -98,48 +89,20 @@ class UserListView extends StatefulWidget { /// message_limit: how many messages should be included to each channel final PaginationParams pagination; - /// Function called when tapping on a channel - /// By default it calls [Navigator.push] building a [MaterialPageRoute] - /// with the widget [userWidget] as child. - final UserTapCallback onUserTap; - - /// Function called when long pressing on a channel - final Function(User) onUserLongPress; - - /// Widget used when opening a channel - final Widget userWidget; - - /// Builder used to create a custom user preview - final UserItemBuilder userItemBuilder; - - /// Builder used to create a custom item separator - final Function(BuildContext, int) separatorBuilder; - - /// The function called when the image is tapped - final Function(User) onImageTap; - /// Set it to false to disable the pull-to-refresh widget final bool pullToRefresh; - /// Sets a blue trailing checkMark in [ListUserItem] for all the [selectedUsers] - final Set selectedUsers; - /// Set it to true to group users by their first character /// /// defaults to false final bool groupAlphabetically; - /// The number of children in the cross axis. - final int crossAxisCount; - @override _UserListViewState createState() => _UserListViewState(); } class _UserListViewState extends State with WidgetsBindingObserver { - bool get _isListView => widget.crossAxisCount == 1; - @override void initState() { super.initState(); @@ -150,6 +113,10 @@ class _UserListViewState extends State pagination: widget.pagination, options: widget.options, ); + + if (widget.userListController != null) { + widget.userListController.paginateData = paginateData; + } } @override @@ -214,235 +181,36 @@ class _UserListViewState extends State print((snapshot.error as Error).stackTrace); } - if (widget.errorBuilder != null) { - return widget.errorBuilder(snapshot.error); - } - - var message = snapshot.error.toString(); - if (snapshot.error is DioError) { - final dioError = snapshot.error as DioError; - if (dioError.type == DioErrorType.RESPONSE) { - message = dioError.message; - } else { - message = 'Check your connection and retry'; - } - } - return Center( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Text.rich( - TextSpan( - children: [ - WidgetSpan( - child: Padding( - padding: const EdgeInsets.only( - right: 2.0, - ), - child: Icon(Icons.error_outline), - ), - ), - TextSpan(text: 'Error loading channels'), - ], - ), - style: Theme.of(context).textTheme.headline6, - ), - Padding( - padding: const EdgeInsets.only( - top: 16.0, - ), - child: Text(message), - ), - FlatButton( - onPressed: () { - usersBlocState.queryUsers( - filter: widget.filter, - sort: widget.sort, - pagination: widget.pagination, - options: widget.options, - ); - }, - child: Text('Retry'), - ), - ], - ), - ); + return widget.errorBuilder(snapshot.error); } if (!snapshot.hasData) { - return LayoutBuilder( - builder: (context, viewportConstraints) { - return SingleChildScrollView( - physics: AlwaysScrollableScrollPhysics(), - child: ConstrainedBox( - constraints: BoxConstraints( - minHeight: viewportConstraints.maxHeight, - ), - child: Center( - child: CircularProgressIndicator(), - ), - ), - ); - }, - ); + return widget.loadingBuilder(context); } final items = snapshot.data; - if (items.isEmpty && widget.emptyBuilder != null) { + if (items.isEmpty) { return widget.emptyBuilder(context); } - if (items.isEmpty && widget.emptyBuilder == null) { - return LayoutBuilder( - builder: (context, viewportConstraints) { - return SingleChildScrollView( - physics: AlwaysScrollableScrollPhysics(), - child: ConstrainedBox( - constraints: BoxConstraints( - minHeight: viewportConstraints.maxHeight, - ), - child: Center( - child: Text('There are no users currently'), - ), - ), - ); - }, - ); + if (items.isEmpty) { + return widget.emptyBuilder(context); } - final child = _isListView - ? ListView.separated( - physics: AlwaysScrollableScrollPhysics(), - itemCount: items.isNotEmpty ? items.length + 1 : items.length, - separatorBuilder: (_, index) { - if (widget.separatorBuilder != null) { - return widget.separatorBuilder(context, index); - } - return _separatorBuilder(context, index); - }, - itemBuilder: (context, index) { - return _listItemBuilder(context, index, items); - }, - ) - : GridView.builder( - gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( - crossAxisCount: widget.crossAxisCount, - ), - itemCount: items.isNotEmpty ? items.length + 1 : items.length, - physics: AlwaysScrollableScrollPhysics(), - itemBuilder: (context, index) { - return _gridItemBuilder(context, index, items); - }, - ); - - return LazyLoadScrollView( - onEndOfPage: () async { - return _listenUserPagination(usersBlocState); - }, - child: child, - ); + return widget.listBuilder(context, items); }, ); } - Widget _listItemBuilder(BuildContext context, int i, List items) { - final usersProvider = UsersBloc.of(context); - if (i < items.length) { - final item = items[i]; - return item.when( - headerItem: (header) { - return Container( - key: ValueKey('HEADER-$header'), - color: - StreamChatTheme.of(context).colorTheme.black.withOpacity(0.05), - child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 8.0, vertical: 6), - child: Text( - header, - style: TextStyle( - fontWeight: FontWeight.bold, - fontSize: 14.5, - color: StreamChatTheme.of(context).colorTheme.grey, - ), - ), - ), - ); - }, - userItem: (user) { - final selected = widget.selectedUsers?.contains(user) ?? false; - return Container( - key: ValueKey('USER-${user.id}'), - child: widget.userItemBuilder(context, user, selected), - ); - }, - ); - } else { - return _buildQueryProgressIndicator(context, usersProvider); - } - } + void paginateData() { + final usersBloc = UsersBloc.of(context); - Widget _gridItemBuilder(BuildContext context, int i, List items) { - final usersProvider = UsersBloc.of(context); - if (i < items.length) { - final item = items[i]; - return item.when( - headerItem: (_) => Offstage(), - userItem: (user) { - final selected = widget.selectedUsers?.contains(user) ?? false; - return Container( - key: ValueKey('USER-${user.id}'), - child: widget.userItemBuilder(context, user, selected), - ); - }, - ); - } else { - return _buildQueryProgressIndicator(context, usersProvider); - } - } - - Widget _buildQueryProgressIndicator(context, UsersBlocState usersProvider) { - return StreamBuilder( - stream: usersProvider.queryUsersLoading, - initialData: false, - builder: (context, snapshot) { - if (snapshot.hasError) { - return Container( - color: StreamChatTheme.of(context) - .colorTheme - .accentRed - .withOpacity(.2), - child: Padding( - padding: const EdgeInsets.symmetric(vertical: 16.0), - child: Center( - child: Text('Error loading users'), - ), - ), - ); - } - return Container( - height: 100, - padding: EdgeInsets.all(32), - child: Center( - child: snapshot.data ? CircularProgressIndicator() : Container(), - ), - ); - }); - } - - Widget _separatorBuilder(context, i) { - return Container( - height: 1, - color: StreamChatTheme.of(context).colorTheme.greyWhisper, - ); - } - - void _listenUserPagination(UsersBlocState usersProvider) { - usersProvider.queryUsers( + usersBloc.queryUsers( filter: widget.filter, sort: widget.sort, pagination: widget.pagination.copyWith( - offset: usersProvider.users?.length ?? 0, + offset: usersBloc.users?.length ?? 0, ), options: widget.options, ); @@ -505,3 +273,8 @@ class ListUserItem extends ListItem { ListUserItem(this.user); } + +/// Controller used for paginating data in [ChannelListView] +class UserListController { + VoidCallback paginateData; +} From 844f3c32776d567a61fa4512446a95e0fcc1cf13 Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Fri, 22 Jan 2021 17:25:03 +0530 Subject: [PATCH 05/39] feat: Added exports --- .../lib/src/reaction_icon.dart | 9 --------- .../lib/stream_chat_flutter_core.dart | 15 ++++++++++----- .../test/stream_chat_flutter_core_test.dart | 10 +--------- 3 files changed, 11 insertions(+), 23 deletions(-) delete mode 100644 packages/stream_chat_flutter_core/lib/src/reaction_icon.dart diff --git a/packages/stream_chat_flutter_core/lib/src/reaction_icon.dart b/packages/stream_chat_flutter_core/lib/src/reaction_icon.dart deleted file mode 100644 index 99b93328..00000000 --- a/packages/stream_chat_flutter_core/lib/src/reaction_icon.dart +++ /dev/null @@ -1,9 +0,0 @@ -class ReactionIcon { - final String type; - final String assetName; - - ReactionIcon({ - this.type, - this.assetName, - }); -} diff --git a/packages/stream_chat_flutter_core/lib/stream_chat_flutter_core.dart b/packages/stream_chat_flutter_core/lib/stream_chat_flutter_core.dart index 1f8e28bb..638f71be 100644 --- a/packages/stream_chat_flutter_core/lib/stream_chat_flutter_core.dart +++ b/packages/stream_chat_flutter_core/lib/stream_chat_flutter_core.dart @@ -1,7 +1,12 @@ library stream_chat_flutter_core; -/// A Calculator. -class Calculator { - /// Returns [value] plus 1. - int addOne(int value) => value + 1; -} +export 'src/channel_list_view.dart'; +export 'src/channels_bloc.dart'; +export 'src/lazy_load_scroll_view.dart'; +export 'src/message_list_view.dart'; +export 'src/message_search_bloc.dart'; +export 'src/message_search_list_view.dart'; +export 'src/stream_channel.dart'; +export 'src/stream_chat.dart'; +export 'src/user_list_view.dart'; +export 'src/users_bloc.dart'; diff --git a/packages/stream_chat_flutter_core/test/stream_chat_flutter_core_test.dart b/packages/stream_chat_flutter_core/test/stream_chat_flutter_core_test.dart index 626831ae..3e26a916 100644 --- a/packages/stream_chat_flutter_core/test/stream_chat_flutter_core_test.dart +++ b/packages/stream_chat_flutter_core/test/stream_chat_flutter_core_test.dart @@ -2,12 +2,4 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; -void main() { - test('adds one to input values', () { - final calculator = Calculator(); - expect(calculator.addOne(2), 3); - expect(calculator.addOne(-7), -6); - expect(calculator.addOne(0), 1); - expect(() => calculator.addOne(null), throwsNoSuchMethodError); - }); -} +void main() {} From b6aa030d81c96446621dc831b6357249479343ab Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Fri, 22 Jan 2021 18:16:05 +0530 Subject: [PATCH 06/39] feat: Created example app --- .../example/.gitignore | 41 ++ .../example/.metadata | 10 + .../example/README.md | 16 + .../example/android/.gitignore | 11 + .../example/android/app/build.gradle | 54 ++ .../android/app/src/debug/AndroidManifest.xml | 7 + .../android/app/src/main/AndroidManifest.xml | 47 ++ .../java/example/example/MainActivity.java | 6 + .../main/res/drawable/launch_background.xml | 12 + .../src/main/res/mipmap-hdpi/ic_launcher.png | Bin 0 -> 544 bytes .../src/main/res/mipmap-mdpi/ic_launcher.png | Bin 0 -> 442 bytes .../src/main/res/mipmap-xhdpi/ic_launcher.png | Bin 0 -> 721 bytes .../main/res/mipmap-xxhdpi/ic_launcher.png | Bin 0 -> 1031 bytes .../main/res/mipmap-xxxhdpi/ic_launcher.png | Bin 0 -> 1443 bytes .../app/src/main/res/values/styles.xml | 18 + .../app/src/profile/AndroidManifest.xml | 7 + .../example/android/build.gradle | 29 + .../example/android/gradle.properties | 3 + .../gradle/wrapper/gradle-wrapper.properties | 6 + .../example/android/settings.gradle | 11 + .../example/ios/.gitignore | 32 ++ .../ios/Flutter/AppFrameworkInfo.plist | 26 + .../example/ios/Flutter/Debug.xcconfig | 1 + .../example/ios/Flutter/Release.xcconfig | 1 + .../ios/Runner.xcodeproj/project.pbxproj | 495 ++++++++++++++++++ .../contents.xcworkspacedata | 7 + .../xcshareddata/IDEWorkspaceChecks.plist | 8 + .../xcshareddata/WorkspaceSettings.xcsettings | 8 + .../xcshareddata/xcschemes/Runner.xcscheme | 91 ++++ .../contents.xcworkspacedata | 7 + .../xcshareddata/IDEWorkspaceChecks.plist | 8 + .../xcshareddata/WorkspaceSettings.xcsettings | 8 + .../example/ios/Runner/AppDelegate.swift | 13 + .../AppIcon.appiconset/Contents.json | 122 +++++ .../Icon-App-1024x1024@1x.png | Bin 0 -> 10932 bytes .../AppIcon.appiconset/Icon-App-20x20@1x.png | Bin 0 -> 564 bytes .../AppIcon.appiconset/Icon-App-20x20@2x.png | Bin 0 -> 1283 bytes .../AppIcon.appiconset/Icon-App-20x20@3x.png | Bin 0 -> 1588 bytes .../AppIcon.appiconset/Icon-App-29x29@1x.png | Bin 0 -> 1025 bytes .../AppIcon.appiconset/Icon-App-29x29@2x.png | Bin 0 -> 1716 bytes .../AppIcon.appiconset/Icon-App-29x29@3x.png | Bin 0 -> 1920 bytes .../AppIcon.appiconset/Icon-App-40x40@1x.png | Bin 0 -> 1283 bytes .../AppIcon.appiconset/Icon-App-40x40@2x.png | Bin 0 -> 1895 bytes .../AppIcon.appiconset/Icon-App-40x40@3x.png | Bin 0 -> 2665 bytes .../AppIcon.appiconset/Icon-App-60x60@2x.png | Bin 0 -> 2665 bytes .../AppIcon.appiconset/Icon-App-60x60@3x.png | Bin 0 -> 3831 bytes .../AppIcon.appiconset/Icon-App-76x76@1x.png | Bin 0 -> 1888 bytes .../AppIcon.appiconset/Icon-App-76x76@2x.png | Bin 0 -> 3294 bytes .../Icon-App-83.5x83.5@2x.png | Bin 0 -> 3612 bytes .../LaunchImage.imageset/Contents.json | 23 + .../LaunchImage.imageset/LaunchImage.png | Bin 0 -> 68 bytes .../LaunchImage.imageset/LaunchImage@2x.png | Bin 0 -> 68 bytes .../LaunchImage.imageset/LaunchImage@3x.png | Bin 0 -> 68 bytes .../LaunchImage.imageset/README.md | 5 + .../Runner/Base.lproj/LaunchScreen.storyboard | 37 ++ .../ios/Runner/Base.lproj/Main.storyboard | 26 + .../example/ios/Runner/Info.plist | 45 ++ .../ios/Runner/Runner-Bridging-Header.h | 1 + .../example/lib/main.dart | 117 +++++ .../example/pubspec.yaml | 77 +++ .../example/test/widget_test.dart | 30 ++ 61 files changed, 1466 insertions(+) create mode 100644 packages/stream_chat_flutter_core/example/.gitignore create mode 100644 packages/stream_chat_flutter_core/example/.metadata create mode 100644 packages/stream_chat_flutter_core/example/README.md create mode 100644 packages/stream_chat_flutter_core/example/android/.gitignore create mode 100644 packages/stream_chat_flutter_core/example/android/app/build.gradle create mode 100644 packages/stream_chat_flutter_core/example/android/app/src/debug/AndroidManifest.xml create mode 100644 packages/stream_chat_flutter_core/example/android/app/src/main/AndroidManifest.xml create mode 100644 packages/stream_chat_flutter_core/example/android/app/src/main/java/example/example/MainActivity.java create mode 100644 packages/stream_chat_flutter_core/example/android/app/src/main/res/drawable/launch_background.xml create mode 100644 packages/stream_chat_flutter_core/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png create mode 100644 packages/stream_chat_flutter_core/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png create mode 100644 packages/stream_chat_flutter_core/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png create mode 100644 packages/stream_chat_flutter_core/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png create mode 100644 packages/stream_chat_flutter_core/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png create mode 100644 packages/stream_chat_flutter_core/example/android/app/src/main/res/values/styles.xml create mode 100644 packages/stream_chat_flutter_core/example/android/app/src/profile/AndroidManifest.xml create mode 100644 packages/stream_chat_flutter_core/example/android/build.gradle create mode 100644 packages/stream_chat_flutter_core/example/android/gradle.properties create mode 100644 packages/stream_chat_flutter_core/example/android/gradle/wrapper/gradle-wrapper.properties create mode 100644 packages/stream_chat_flutter_core/example/android/settings.gradle create mode 100644 packages/stream_chat_flutter_core/example/ios/.gitignore create mode 100644 packages/stream_chat_flutter_core/example/ios/Flutter/AppFrameworkInfo.plist create mode 100644 packages/stream_chat_flutter_core/example/ios/Flutter/Debug.xcconfig create mode 100644 packages/stream_chat_flutter_core/example/ios/Flutter/Release.xcconfig create mode 100644 packages/stream_chat_flutter_core/example/ios/Runner.xcodeproj/project.pbxproj create mode 100644 packages/stream_chat_flutter_core/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata create mode 100644 packages/stream_chat_flutter_core/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist create mode 100644 packages/stream_chat_flutter_core/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings create mode 100644 packages/stream_chat_flutter_core/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme create mode 100644 packages/stream_chat_flutter_core/example/ios/Runner.xcworkspace/contents.xcworkspacedata create mode 100644 packages/stream_chat_flutter_core/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist create mode 100644 packages/stream_chat_flutter_core/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings create mode 100644 packages/stream_chat_flutter_core/example/ios/Runner/AppDelegate.swift create mode 100644 packages/stream_chat_flutter_core/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json create mode 100644 packages/stream_chat_flutter_core/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png create mode 100644 packages/stream_chat_flutter_core/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png create mode 100644 packages/stream_chat_flutter_core/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png create mode 100644 packages/stream_chat_flutter_core/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png create mode 100644 packages/stream_chat_flutter_core/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png create mode 100644 packages/stream_chat_flutter_core/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png create mode 100644 packages/stream_chat_flutter_core/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png create mode 100644 packages/stream_chat_flutter_core/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png create mode 100644 packages/stream_chat_flutter_core/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png create mode 100644 packages/stream_chat_flutter_core/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png create mode 100644 packages/stream_chat_flutter_core/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png create mode 100644 packages/stream_chat_flutter_core/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png create mode 100644 packages/stream_chat_flutter_core/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png create mode 100644 packages/stream_chat_flutter_core/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png create mode 100644 packages/stream_chat_flutter_core/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png create mode 100644 packages/stream_chat_flutter_core/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json create mode 100644 packages/stream_chat_flutter_core/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png create mode 100644 packages/stream_chat_flutter_core/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png create mode 100644 packages/stream_chat_flutter_core/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png create mode 100644 packages/stream_chat_flutter_core/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md create mode 100644 packages/stream_chat_flutter_core/example/ios/Runner/Base.lproj/LaunchScreen.storyboard create mode 100644 packages/stream_chat_flutter_core/example/ios/Runner/Base.lproj/Main.storyboard create mode 100644 packages/stream_chat_flutter_core/example/ios/Runner/Info.plist create mode 100644 packages/stream_chat_flutter_core/example/ios/Runner/Runner-Bridging-Header.h create mode 100644 packages/stream_chat_flutter_core/example/lib/main.dart create mode 100644 packages/stream_chat_flutter_core/example/pubspec.yaml create mode 100644 packages/stream_chat_flutter_core/example/test/widget_test.dart diff --git a/packages/stream_chat_flutter_core/example/.gitignore b/packages/stream_chat_flutter_core/example/.gitignore new file mode 100644 index 00000000..9d532b18 --- /dev/null +++ b/packages/stream_chat_flutter_core/example/.gitignore @@ -0,0 +1,41 @@ +# Miscellaneous +*.class +*.log +*.pyc +*.swp +.DS_Store +.atom/ +.buildlog/ +.history +.svn/ + +# IntelliJ related +*.iml +*.ipr +*.iws +.idea/ + +# The .vscode folder contains launch configuration and tasks you configure in +# VS Code which you may wish to be included in version control, so this line +# is commented out by default. +#.vscode/ + +# Flutter/Dart/Pub related +**/doc/api/ +**/ios/Flutter/.last_build_id +.dart_tool/ +.flutter-plugins +.flutter-plugins-dependencies +.packages +.pub-cache/ +.pub/ +/build/ + +# Web related +lib/generated_plugin_registrant.dart + +# Symbolication related +app.*.symbols + +# Obfuscation related +app.*.map.json diff --git a/packages/stream_chat_flutter_core/example/.metadata b/packages/stream_chat_flutter_core/example/.metadata new file mode 100644 index 00000000..182cccaf --- /dev/null +++ b/packages/stream_chat_flutter_core/example/.metadata @@ -0,0 +1,10 @@ +# This file tracks properties of this Flutter project. +# Used by Flutter tool to assess capabilities and perform upgrades etc. +# +# This file should be version controlled and should not be manually edited. + +version: + revision: 78910062997c3a836feee883712c241a5fd22983 + channel: stable + +project_type: app diff --git a/packages/stream_chat_flutter_core/example/README.md b/packages/stream_chat_flutter_core/example/README.md new file mode 100644 index 00000000..fef640e1 --- /dev/null +++ b/packages/stream_chat_flutter_core/example/README.md @@ -0,0 +1,16 @@ +# example + +Example app for testing stream_chat_flutter_core + +## Getting Started + +This project is a starting point for a Flutter application. + +A few resources to get you started if this is your first Flutter project: + +- [Lab: Write your first Flutter app](https://flutter.dev/docs/get-started/codelab) +- [Cookbook: Useful Flutter samples](https://flutter.dev/docs/cookbook) + +For help getting started with Flutter, view our +[online documentation](https://flutter.dev/docs), which offers tutorials, +samples, guidance on mobile development, and a full API reference. diff --git a/packages/stream_chat_flutter_core/example/android/.gitignore b/packages/stream_chat_flutter_core/example/android/.gitignore new file mode 100644 index 00000000..0a741cb4 --- /dev/null +++ b/packages/stream_chat_flutter_core/example/android/.gitignore @@ -0,0 +1,11 @@ +gradle-wrapper.jar +/.gradle +/captures/ +/gradlew +/gradlew.bat +/local.properties +GeneratedPluginRegistrant.java + +# Remember to never publicly share your keystore. +# See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app +key.properties diff --git a/packages/stream_chat_flutter_core/example/android/app/build.gradle b/packages/stream_chat_flutter_core/example/android/app/build.gradle new file mode 100644 index 00000000..edcec7b9 --- /dev/null +++ b/packages/stream_chat_flutter_core/example/android/app/build.gradle @@ -0,0 +1,54 @@ +def localProperties = new Properties() +def localPropertiesFile = rootProject.file('local.properties') +if (localPropertiesFile.exists()) { + localPropertiesFile.withReader('UTF-8') { reader -> + localProperties.load(reader) + } +} + +def flutterRoot = localProperties.getProperty('flutter.sdk') +if (flutterRoot == null) { + throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") +} + +def flutterVersionCode = localProperties.getProperty('flutter.versionCode') +if (flutterVersionCode == null) { + flutterVersionCode = '1' +} + +def flutterVersionName = localProperties.getProperty('flutter.versionName') +if (flutterVersionName == null) { + flutterVersionName = '1.0' +} + +apply plugin: 'com.android.application' +apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" + +android { + compileSdkVersion 29 + + lintOptions { + disable 'InvalidPackage' + } + + defaultConfig { + // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). + applicationId "example.example" + minSdkVersion 16 + targetSdkVersion 29 + versionCode flutterVersionCode.toInteger() + versionName flutterVersionName + } + + buildTypes { + release { + // TODO: Add your own signing config for the release build. + // Signing with the debug keys for now, so `flutter run --release` works. + signingConfig signingConfigs.debug + } + } +} + +flutter { + source '../..' +} diff --git a/packages/stream_chat_flutter_core/example/android/app/src/debug/AndroidManifest.xml b/packages/stream_chat_flutter_core/example/android/app/src/debug/AndroidManifest.xml new file mode 100644 index 00000000..397a9492 --- /dev/null +++ b/packages/stream_chat_flutter_core/example/android/app/src/debug/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + diff --git a/packages/stream_chat_flutter_core/example/android/app/src/main/AndroidManifest.xml b/packages/stream_chat_flutter_core/example/android/app/src/main/AndroidManifest.xml new file mode 100644 index 00000000..d2eb734a --- /dev/null +++ b/packages/stream_chat_flutter_core/example/android/app/src/main/AndroidManifest.xml @@ -0,0 +1,47 @@ + + + + + + + + + + + + + + + + + diff --git a/packages/stream_chat_flutter_core/example/android/app/src/main/java/example/example/MainActivity.java b/packages/stream_chat_flutter_core/example/android/app/src/main/java/example/example/MainActivity.java new file mode 100644 index 00000000..e30c2bec --- /dev/null +++ b/packages/stream_chat_flutter_core/example/android/app/src/main/java/example/example/MainActivity.java @@ -0,0 +1,6 @@ +package example.example; + +import io.flutter.embedding.android.FlutterActivity; + +public class MainActivity extends FlutterActivity { +} diff --git a/packages/stream_chat_flutter_core/example/android/app/src/main/res/drawable/launch_background.xml b/packages/stream_chat_flutter_core/example/android/app/src/main/res/drawable/launch_background.xml new file mode 100644 index 00000000..304732f8 --- /dev/null +++ b/packages/stream_chat_flutter_core/example/android/app/src/main/res/drawable/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/packages/stream_chat_flutter_core/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/packages/stream_chat_flutter_core/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..db77bb4b7b0906d62b1847e87f15cdcacf6a4f29 GIT binary patch literal 544 zcmeAS@N?(olHy`uVBq!ia0vp^9w5xY3?!3`olAj~WQl7;NpOBzNqJ&XDuZK6ep0G} zXKrG8YEWuoN@d~6R2!h8bpbvhu0Wd6uZuB!w&u2PAxD2eNXD>P5D~Wn-+_Wa#27Xc zC?Zj|6r#X(-D3u$NCt}(Ms06KgJ4FxJVv{GM)!I~&n8Bnc94O7-Hd)cjDZswgC;Qs zO=b+9!WcT8F?0rF7!Uys2bs@gozCP?z~o%U|N3vA*22NaGQG zlg@K`O_XuxvZ&Ks^m&R!`&1=spLvfx7oGDKDwpwW`#iqdw@AL`7MR}m`rwr|mZgU`8P7SBkL78fFf!WnuYWm$5Z0 zNXhDbCv&49sM544K|?c)WrFfiZvCi9h0O)B3Pgg&ebxsLQ05GG~ AQ2+n{ literal 0 HcmV?d00001 diff --git a/packages/stream_chat_flutter_core/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/packages/stream_chat_flutter_core/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..17987b79bb8a35cc66c3c1fd44f5a5526c1b78be GIT binary patch literal 442 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDA3?vioaBc-sk|nMYCBgY=CFO}lsSJ)O`AMk? zp1FzXsX?iUDV2pMQ*D5Xx&nMcT!A!W`0S9QKQy;}1Cl^CgaH=;G9cpY;r$Q>i*pfB zP2drbID<_#qf;rPZx^FqH)F_D#*k@@q03KywUtLX8Ua?`H+NMzkczFPK3lFz@i_kW%1NOn0|D2I9n9wzH8m|-tHjsw|9>@K=iMBhxvkv6m8Y-l zytQ?X=U+MF$@3 zt`~i=@j|6y)RWMK--}M|=T`o&^Ni>IoWKHEbBXz7?A@mgWoL>!*SXo`SZH-*HSdS+ yn*9;$7;m`l>wYBC5bq;=U}IMqLzqbYCidGC!)_gkIk_C@Uy!y&wkt5C($~2D>~)O*cj@FGjOCM)M>_ixfudOh)?xMu#Fs z#}Y=@YDTwOM)x{K_j*Q;dPdJ?Mz0n|pLRx{4n|)f>SXlmV)XB04CrSJn#dS5nK2lM zrZ9#~WelCp7&e13Y$jvaEXHskn$2V!!DN-nWS__6T*l;H&Fopn?A6HZ-6WRLFP=R` zqG+CE#d4|IbyAI+rJJ`&x9*T`+a=p|0O(+s{UBcyZdkhj=yS1>AirP+0R;mf2uMgM zC}@~JfByORAh4SyRgi&!(cja>F(l*O+nd+@4m$|6K6KDn_&uvCpV23&>G9HJp{xgg zoq1^2_p9@|WEo z*X_Uko@K)qYYv~>43eQGMdbiGbo>E~Q& zrYBH{QP^@Sti!`2)uG{irBBq@y*$B zi#&(U-*=fp74j)RyIw49+0MRPMRU)+a2r*PJ$L5roHt2$UjExCTZSbq%V!HeS7J$N zdG@vOZB4v_lF7Plrx+hxo7(fCV&}fHq)$ literal 0 HcmV?d00001 diff --git a/packages/stream_chat_flutter_core/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/packages/stream_chat_flutter_core/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..d5f1c8d34e7a88e3f88bea192c3a370d44689c3c GIT binary patch literal 1031 zcmeAS@N?(olHy`uVBq!ia0vp^6F``Q8Ax83A=Cw=BuiW)N`mv#O3D+9QW+dm@{>{( zJaZG%Q-e|yQz{EjrrIztFa`(sgt!6~Yi|1%a`XoT0ojZ}lNrNjb9xjc(B0U1_% zz5^97Xt*%oq$rQy4?0GKNfJ44uvxI)gC`h-NZ|&0-7(qS@?b!5r36oQ}zyZrNO3 zMO=Or+<~>+A&uN&E!^Sl+>xE!QC-|oJv`ApDhqC^EWD|@=#J`=d#Xzxs4ah}w&Jnc z$|q_opQ^2TrnVZ0o~wh<3t%W&flvYGe#$xqda2bR_R zvPYgMcHgjZ5nSA^lJr%;<&0do;O^tDDh~=pIxA#coaCY>&N%M2^tq^U%3DB@ynvKo}b?yu-bFc-u0JHzced$sg7S3zqI(2 z#Km{dPr7I=pQ5>FuK#)QwK?Y`E`B?nP+}U)I#c1+FM*1kNvWG|a(TpksZQ3B@sD~b zpQ2)*V*TdwjFOtHvV|;OsiDqHi=6%)o4b!)x$)%9pGTsE z-JL={-Ffv+T87W(Xpooq<`r*VzWQcgBN$$`u}f>-ZQI1BB8ykN*=e4rIsJx9>z}*o zo~|9I;xof literal 0 HcmV?d00001 diff --git a/packages/stream_chat_flutter_core/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/packages/stream_chat_flutter_core/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..4d6372eebdb28e45604e46eeda8dd24651419bc0 GIT binary patch literal 1443 zcmb`G{WsKk6vsdJTdFg%tJav9_E4vzrOaqkWF|A724Nly!y+?N9`YV6wZ}5(X(D_N(?!*n3`|_r0Hc?=PQw&*vnU?QTFY zB_MsH|!j$PP;I}?dppoE_gA(4uc!jV&0!l7_;&p2^pxNo>PEcNJv za5_RT$o2Mf!<+r?&EbHH6nMoTsDOa;mN(wv8RNsHpG)`^ymG-S5By8=l9iVXzN_eG%Xg2@Xeq76tTZ*dGh~Lo9vl;Zfs+W#BydUw zCkZ$o1LqWQO$FC9aKlLl*7x9^0q%0}$OMlp@Kk_jHXOjofdePND+j!A{q!8~Jn+s3 z?~~w@4?egS02}8NuulUA=L~QQfm;MzCGd)XhiftT;+zFO&JVyp2mBww?;QByS_1w! zrQlx%{^cMj0|Bo1FjwY@Q8?Hx0cIPF*@-ZRFpPc#bBw{5@tD(5%sClzIfl8WU~V#u zm5Q;_F!wa$BSpqhN>W@2De?TKWR*!ujY;Yylk_X5#~V!L*Gw~;$%4Q8~Mad z@`-kG?yb$a9cHIApZDVZ^U6Xkp<*4rU82O7%}0jjHlK{id@?-wpN*fCHXyXh(bLt* zPc}H-x0e4E&nQ>y%B-(EL=9}RyC%MyX=upHuFhAk&MLbsF0LP-q`XnH78@fT+pKPW zu72MW`|?8ht^tz$iC}ZwLp4tB;Q49K!QCF3@!iB1qOI=?w z7In!}F~ij(18UYUjnbmC!qKhPo%24?8U1x{7o(+?^Zu0Hx81|FuS?bJ0jgBhEMzf< zCgUq7r2OCB(`XkKcN-TL>u5y#dD6D!)5W?`O5)V^>jb)P)GBdy%t$uUMpf$SNV31$ zb||OojAbvMP?T@$h_ZiFLFVHDmbyMhJF|-_)HX3%m=CDI+ID$0^C>kzxprBW)hw(v zr!Gmda);ICoQyhV_oP5+C%?jcG8v+D@9f?Dk*!BxY}dazmrT@64UrP3hlslANK)bq z$67n83eh}OeW&SV@HG95P|bjfqJ7gw$e+`Hxo!4cx`jdK1bJ>YDSpGKLPZ^1cv$ek zIB?0S<#tX?SJCLWdMd{-ME?$hc7A$zBOdIJ)4!KcAwb=VMov)nK;9z>x~rfT1>dS+ zZ6#`2v@`jgbqq)P22H)Tx2CpmM^o1$B+xT6`(v%5xJ(?j#>Q$+rx_R|7TzDZe{J6q zG1*EcU%tE?!kO%^M;3aM6JN*LAKUVb^xz8-Pxo#jR5(-KBeLJvA@-gxNHx0M-ZJLl z;#JwQoh~9V?`UVo#}{6ka@II>++D@%KqGpMdlQ}?9E*wFcf5(#XQnP$Dk5~%iX^>f z%$y;?M0BLp{O3a(-4A?ewryHrrD%cx#Q^%KY1H zNre$ve+vceSLZcNY4U(RBX&)oZn*Py()h)XkE?PL$!bNb{N5FVI2Y%LKEm%yvpyTP z(1P?z~7YxD~Rf<(a@_y` literal 0 HcmV?d00001 diff --git a/packages/stream_chat_flutter_core/example/android/app/src/main/res/values/styles.xml b/packages/stream_chat_flutter_core/example/android/app/src/main/res/values/styles.xml new file mode 100644 index 00000000..1f83a33f --- /dev/null +++ b/packages/stream_chat_flutter_core/example/android/app/src/main/res/values/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/packages/stream_chat_flutter_core/example/android/app/src/profile/AndroidManifest.xml b/packages/stream_chat_flutter_core/example/android/app/src/profile/AndroidManifest.xml new file mode 100644 index 00000000..397a9492 --- /dev/null +++ b/packages/stream_chat_flutter_core/example/android/app/src/profile/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + diff --git a/packages/stream_chat_flutter_core/example/android/build.gradle b/packages/stream_chat_flutter_core/example/android/build.gradle new file mode 100644 index 00000000..e0d7ae2c --- /dev/null +++ b/packages/stream_chat_flutter_core/example/android/build.gradle @@ -0,0 +1,29 @@ +buildscript { + repositories { + google() + jcenter() + } + + dependencies { + classpath 'com.android.tools.build:gradle:3.5.0' + } +} + +allprojects { + repositories { + google() + jcenter() + } +} + +rootProject.buildDir = '../build' +subprojects { + project.buildDir = "${rootProject.buildDir}/${project.name}" +} +subprojects { + project.evaluationDependsOn(':app') +} + +task clean(type: Delete) { + delete rootProject.buildDir +} diff --git a/packages/stream_chat_flutter_core/example/android/gradle.properties b/packages/stream_chat_flutter_core/example/android/gradle.properties new file mode 100644 index 00000000..94adc3a3 --- /dev/null +++ b/packages/stream_chat_flutter_core/example/android/gradle.properties @@ -0,0 +1,3 @@ +org.gradle.jvmargs=-Xmx1536M +android.useAndroidX=true +android.enableJetifier=true diff --git a/packages/stream_chat_flutter_core/example/android/gradle/wrapper/gradle-wrapper.properties b/packages/stream_chat_flutter_core/example/android/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 00000000..296b146b --- /dev/null +++ b/packages/stream_chat_flutter_core/example/android/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Fri Jun 23 08:50:38 CEST 2017 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.2-all.zip diff --git a/packages/stream_chat_flutter_core/example/android/settings.gradle b/packages/stream_chat_flutter_core/example/android/settings.gradle new file mode 100644 index 00000000..44e62bcf --- /dev/null +++ b/packages/stream_chat_flutter_core/example/android/settings.gradle @@ -0,0 +1,11 @@ +include ':app' + +def localPropertiesFile = new File(rootProject.projectDir, "local.properties") +def properties = new Properties() + +assert localPropertiesFile.exists() +localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) } + +def flutterSdkPath = properties.getProperty("flutter.sdk") +assert flutterSdkPath != null, "flutter.sdk not set in local.properties" +apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle" diff --git a/packages/stream_chat_flutter_core/example/ios/.gitignore b/packages/stream_chat_flutter_core/example/ios/.gitignore new file mode 100644 index 00000000..e96ef602 --- /dev/null +++ b/packages/stream_chat_flutter_core/example/ios/.gitignore @@ -0,0 +1,32 @@ +*.mode1v3 +*.mode2v3 +*.moved-aside +*.pbxuser +*.perspectivev3 +**/*sync/ +.sconsign.dblite +.tags* +**/.vagrant/ +**/DerivedData/ +Icon? +**/Pods/ +**/.symlinks/ +profile +xcuserdata +**/.generated/ +Flutter/App.framework +Flutter/Flutter.framework +Flutter/Flutter.podspec +Flutter/Generated.xcconfig +Flutter/app.flx +Flutter/app.zip +Flutter/flutter_assets/ +Flutter/flutter_export_environment.sh +ServiceDefinitions.json +Runner/GeneratedPluginRegistrant.* + +# Exceptions to above rules. +!default.mode1v3 +!default.mode2v3 +!default.pbxuser +!default.perspectivev3 diff --git a/packages/stream_chat_flutter_core/example/ios/Flutter/AppFrameworkInfo.plist b/packages/stream_chat_flutter_core/example/ios/Flutter/AppFrameworkInfo.plist new file mode 100644 index 00000000..6b4c0f78 --- /dev/null +++ b/packages/stream_chat_flutter_core/example/ios/Flutter/AppFrameworkInfo.plist @@ -0,0 +1,26 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + App + CFBundleIdentifier + io.flutter.flutter.app + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + App + CFBundlePackageType + FMWK + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1.0 + MinimumOSVersion + 8.0 + + diff --git a/packages/stream_chat_flutter_core/example/ios/Flutter/Debug.xcconfig b/packages/stream_chat_flutter_core/example/ios/Flutter/Debug.xcconfig new file mode 100644 index 00000000..592ceee8 --- /dev/null +++ b/packages/stream_chat_flutter_core/example/ios/Flutter/Debug.xcconfig @@ -0,0 +1 @@ +#include "Generated.xcconfig" diff --git a/packages/stream_chat_flutter_core/example/ios/Flutter/Release.xcconfig b/packages/stream_chat_flutter_core/example/ios/Flutter/Release.xcconfig new file mode 100644 index 00000000..592ceee8 --- /dev/null +++ b/packages/stream_chat_flutter_core/example/ios/Flutter/Release.xcconfig @@ -0,0 +1 @@ +#include "Generated.xcconfig" diff --git a/packages/stream_chat_flutter_core/example/ios/Runner.xcodeproj/project.pbxproj b/packages/stream_chat_flutter_core/example/ios/Runner.xcodeproj/project.pbxproj new file mode 100644 index 00000000..9943511d --- /dev/null +++ b/packages/stream_chat_flutter_core/example/ios/Runner.xcodeproj/project.pbxproj @@ -0,0 +1,495 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 46; + objects = { + +/* Begin PBXBuildFile section */ + 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; + 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; + 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; + 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; + 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; + 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; +/* End PBXBuildFile section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 9705A1C41CF9048500538489 /* Embed Frameworks */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + ); + name = "Embed Frameworks"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; + 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; + 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; + 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; + 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; + 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; + 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; + 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; + 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; + 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 97C146EB1CF9000F007C117D /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 9740EEB11CF90186004384FC /* Flutter */ = { + isa = PBXGroup; + children = ( + 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, + 9740EEB21CF90195004384FC /* Debug.xcconfig */, + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, + 9740EEB31CF90195004384FC /* Generated.xcconfig */, + ); + name = Flutter; + sourceTree = ""; + }; + 97C146E51CF9000F007C117D = { + isa = PBXGroup; + children = ( + 9740EEB11CF90186004384FC /* Flutter */, + 97C146F01CF9000F007C117D /* Runner */, + 97C146EF1CF9000F007C117D /* Products */, + ); + sourceTree = ""; + }; + 97C146EF1CF9000F007C117D /* Products */ = { + isa = PBXGroup; + children = ( + 97C146EE1CF9000F007C117D /* Runner.app */, + ); + name = Products; + sourceTree = ""; + }; + 97C146F01CF9000F007C117D /* Runner */ = { + isa = PBXGroup; + children = ( + 97C146FA1CF9000F007C117D /* Main.storyboard */, + 97C146FD1CF9000F007C117D /* Assets.xcassets */, + 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, + 97C147021CF9000F007C117D /* Info.plist */, + 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, + 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, + 74858FAE1ED2DC5600515810 /* AppDelegate.swift */, + 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */, + ); + path = Runner; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 97C146ED1CF9000F007C117D /* Runner */ = { + isa = PBXNativeTarget; + buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; + buildPhases = ( + 9740EEB61CF901F6004384FC /* Run Script */, + 97C146EA1CF9000F007C117D /* Sources */, + 97C146EB1CF9000F007C117D /* Frameworks */, + 97C146EC1CF9000F007C117D /* Resources */, + 9705A1C41CF9048500538489 /* Embed Frameworks */, + 3B06AD1E1E4923F5004D2608 /* Thin Binary */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = Runner; + productName = Runner; + productReference = 97C146EE1CF9000F007C117D /* Runner.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 97C146E61CF9000F007C117D /* Project object */ = { + isa = PBXProject; + attributes = { + LastUpgradeCheck = 1020; + ORGANIZATIONNAME = ""; + TargetAttributes = { + 97C146ED1CF9000F007C117D = { + CreatedOnToolsVersion = 7.3.1; + LastSwiftMigration = 1100; + }; + }; + }; + buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; + compatibilityVersion = "Xcode 9.3"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 97C146E51CF9000F007C117D; + productRefGroup = 97C146EF1CF9000F007C117D /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 97C146ED1CF9000F007C117D /* Runner */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 97C146EC1CF9000F007C117D /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, + 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, + 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, + 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Thin Binary"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; + }; + 9740EEB61CF901F6004384FC /* Run Script */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Run Script"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 97C146EA1CF9000F007C117D /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */, + 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXVariantGroup section */ + 97C146FA1CF9000F007C117D /* Main.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 97C146FB1CF9000F007C117D /* Base */, + ); + name = Main.storyboard; + sourceTree = ""; + }; + 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 97C147001CF9000F007C117D /* Base */, + ); + name = LaunchScreen.storyboard; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 249021D3217E4FDB00AE95B9 /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Profile; + }; + 249021D4217E4FDB00AE95B9 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + ENABLE_BITCODE = NO; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/Flutter", + ); + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + LIBRARY_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/Flutter", + ); + PRODUCT_BUNDLE_IDENTIFIER = example.example; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Profile; + }; + 97C147031CF9000F007C117D /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 97C147041CF9000F007C117D /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = iphoneos; + SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 97C147061CF9000F007C117D /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + ENABLE_BITCODE = NO; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/Flutter", + ); + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + LIBRARY_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/Flutter", + ); + PRODUCT_BUNDLE_IDENTIFIER = example.example; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Debug; + }; + 97C147071CF9000F007C117D /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + ENABLE_BITCODE = NO; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/Flutter", + ); + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + LIBRARY_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/Flutter", + ); + PRODUCT_BUNDLE_IDENTIFIER = example.example; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 97C147031CF9000F007C117D /* Debug */, + 97C147041CF9000F007C117D /* Release */, + 249021D3217E4FDB00AE95B9 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 97C147061CF9000F007C117D /* Debug */, + 97C147071CF9000F007C117D /* Release */, + 249021D4217E4FDB00AE95B9 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 97C146E61CF9000F007C117D /* Project object */; +} diff --git a/packages/stream_chat_flutter_core/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/packages/stream_chat_flutter_core/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 00000000..1d526a16 --- /dev/null +++ b/packages/stream_chat_flutter_core/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/packages/stream_chat_flutter_core/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/packages/stream_chat_flutter_core/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 00000000..18d98100 --- /dev/null +++ b/packages/stream_chat_flutter_core/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/packages/stream_chat_flutter_core/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/packages/stream_chat_flutter_core/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 00000000..f9b0d7c5 --- /dev/null +++ b/packages/stream_chat_flutter_core/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/packages/stream_chat_flutter_core/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/packages/stream_chat_flutter_core/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme new file mode 100644 index 00000000..a28140cf --- /dev/null +++ b/packages/stream_chat_flutter_core/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -0,0 +1,91 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/stream_chat_flutter_core/example/ios/Runner.xcworkspace/contents.xcworkspacedata b/packages/stream_chat_flutter_core/example/ios/Runner.xcworkspace/contents.xcworkspacedata new file mode 100644 index 00000000..1d526a16 --- /dev/null +++ b/packages/stream_chat_flutter_core/example/ios/Runner.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/packages/stream_chat_flutter_core/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/packages/stream_chat_flutter_core/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 00000000..18d98100 --- /dev/null +++ b/packages/stream_chat_flutter_core/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/packages/stream_chat_flutter_core/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/packages/stream_chat_flutter_core/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 00000000..f9b0d7c5 --- /dev/null +++ b/packages/stream_chat_flutter_core/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/packages/stream_chat_flutter_core/example/ios/Runner/AppDelegate.swift b/packages/stream_chat_flutter_core/example/ios/Runner/AppDelegate.swift new file mode 100644 index 00000000..70693e4a --- /dev/null +++ b/packages/stream_chat_flutter_core/example/ios/Runner/AppDelegate.swift @@ -0,0 +1,13 @@ +import UIKit +import Flutter + +@UIApplicationMain +@objc class AppDelegate: FlutterAppDelegate { + override func application( + _ application: UIApplication, + didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? + ) -> Bool { + GeneratedPluginRegistrant.register(with: self) + return super.application(application, didFinishLaunchingWithOptions: launchOptions) + } +} diff --git a/packages/stream_chat_flutter_core/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/packages/stream_chat_flutter_core/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 00000000..d36b1fab --- /dev/null +++ b/packages/stream_chat_flutter_core/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,122 @@ +{ + "images" : [ + { + "size" : "20x20", + "idiom" : "iphone", + "filename" : "Icon-App-20x20@2x.png", + "scale" : "2x" + }, + { + "size" : "20x20", + "idiom" : "iphone", + "filename" : "Icon-App-20x20@3x.png", + "scale" : "3x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@1x.png", + "scale" : "1x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@2x.png", + "scale" : "2x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@3x.png", + "scale" : "3x" + }, + { + "size" : "40x40", + "idiom" : "iphone", + "filename" : "Icon-App-40x40@2x.png", + "scale" : "2x" + }, + { + "size" : "40x40", + "idiom" : "iphone", + "filename" : "Icon-App-40x40@3x.png", + "scale" : "3x" + }, + { + "size" : "60x60", + "idiom" : "iphone", + "filename" : "Icon-App-60x60@2x.png", + "scale" : "2x" + }, + { + "size" : "60x60", + "idiom" : "iphone", + "filename" : "Icon-App-60x60@3x.png", + "scale" : "3x" + }, + { + "size" : "20x20", + "idiom" : "ipad", + "filename" : "Icon-App-20x20@1x.png", + "scale" : "1x" + }, + { + "size" : "20x20", + "idiom" : "ipad", + "filename" : "Icon-App-20x20@2x.png", + "scale" : "2x" + }, + { + "size" : "29x29", + "idiom" : "ipad", + "filename" : "Icon-App-29x29@1x.png", + "scale" : "1x" + }, + { + "size" : "29x29", + "idiom" : "ipad", + "filename" : "Icon-App-29x29@2x.png", + "scale" : "2x" + }, + { + "size" : "40x40", + "idiom" : "ipad", + "filename" : "Icon-App-40x40@1x.png", + "scale" : "1x" + }, + { + "size" : "40x40", + "idiom" : "ipad", + "filename" : "Icon-App-40x40@2x.png", + "scale" : "2x" + }, + { + "size" : "76x76", + "idiom" : "ipad", + "filename" : "Icon-App-76x76@1x.png", + "scale" : "1x" + }, + { + "size" : "76x76", + "idiom" : "ipad", + "filename" : "Icon-App-76x76@2x.png", + "scale" : "2x" + }, + { + "size" : "83.5x83.5", + "idiom" : "ipad", + "filename" : "Icon-App-83.5x83.5@2x.png", + "scale" : "2x" + }, + { + "size" : "1024x1024", + "idiom" : "ios-marketing", + "filename" : "Icon-App-1024x1024@1x.png", + "scale" : "1x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/packages/stream_chat_flutter_core/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png b/packages/stream_chat_flutter_core/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png new file mode 100644 index 0000000000000000000000000000000000000000..dc9ada4725e9b0ddb1deab583e5b5102493aa332 GIT binary patch literal 10932 zcmeHN2~<R zh`|8`A_PQ1nSu(UMFx?8j8PC!!VDphaL#`F42fd#7Vlc`zIE4n%Y~eiz4y1j|NDpi z?<@|pSJ-HM`qifhf@m%MamgwK83`XpBA<+azdF#2QsT{X@z0A9Bq>~TVErigKH1~P zRX-!h-f0NJ4Mh++{D}J+K>~~rq}d%o%+4dogzXp7RxX4C>Km5XEI|PAFDmo;DFm6G zzjVoB`@qW98Yl0Kvc-9w09^PrsobmG*Eju^=3f?0o-t$U)TL1B3;sZ^!++3&bGZ!o-*6w?;oOhf z=A+Qb$scV5!RbG+&2S}BQ6YH!FKb0``VVX~T$dzzeSZ$&9=X$3)_7Z{SspSYJ!lGE z7yig_41zpQ)%5dr4ff0rh$@ky3-JLRk&DK)NEIHecf9c*?Z1bUB4%pZjQ7hD!A0r-@NF(^WKdr(LXj|=UE7?gBYGgGQV zidf2`ZT@pzXf7}!NH4q(0IMcxsUGDih(0{kRSez&z?CFA0RVXsVFw3^u=^KMtt95q z43q$b*6#uQDLoiCAF_{RFc{!H^moH_cmll#Fc^KXi{9GDl{>%+3qyfOE5;Zq|6#Hb zp^#1G+z^AXfRKaa9HK;%b3Ux~U@q?xg<2DXP%6k!3E)PA<#4$ui8eDy5|9hA5&{?v z(-;*1%(1~-NTQ`Is1_MGdQ{+i*ccd96ab$R$T3=% zw_KuNF@vI!A>>Y_2pl9L{9h1-C6H8<)J4gKI6{WzGBi<@u3P6hNsXG=bRq5c+z;Gc3VUCe;LIIFDmQAGy+=mRyF++u=drBWV8-^>0yE9N&*05XHZpPlE zxu@?8(ZNy7rm?|<+UNe0Vs6&o?l`Pt>P&WaL~M&#Eh%`rg@Mbb)J&@DA-wheQ>hRV z<(XhigZAT z>=M;URcdCaiO3d^?H<^EiEMDV+7HsTiOhoaMX%P65E<(5xMPJKxf!0u>U~uVqnPN7T!X!o@_gs3Ct1 zlZ_$5QXP4{Aj645wG_SNT&6m|O6~Tsl$q?nK*)(`{J4b=(yb^nOATtF1_aS978$x3 zx>Q@s4i3~IT*+l{@dx~Hst21fR*+5}S1@cf>&8*uLw-0^zK(+OpW?cS-YG1QBZ5q! zgTAgivzoF#`cSz&HL>Ti!!v#?36I1*l^mkrx7Y|K6L#n!-~5=d3;K<;Zqi|gpNUn_ z_^GaQDEQ*jfzh;`j&KXb66fWEk1K7vxQIMQ_#Wu_%3 z4Oeb7FJ`8I>Px;^S?)}2+4D_83gHEq>8qSQY0PVP?o)zAv3K~;R$fnwTmI-=ZLK`= zTm+0h*e+Yfr(IlH3i7gUclNH^!MU>id$Jw>O?2i0Cila#v|twub21@e{S2v}8Z13( zNDrTXZVgris|qYm<0NU(tAPouG!QF4ZNpZPkX~{tVf8xY690JqY1NVdiTtW+NqyRP zZ&;T0ikb8V{wxmFhlLTQ&?OP7 z;(z*<+?J2~z*6asSe7h`$8~Se(@t(#%?BGLVs$p``;CyvcT?7Y!{tIPva$LxCQ&4W z6v#F*);|RXvI%qnoOY&i4S*EL&h%hP3O zLsrFZhv&Hu5tF$Lx!8(hs&?!Kx5&L(fdu}UI5d*wn~A`nPUhG&Rv z2#ixiJdhSF-K2tpVL=)5UkXRuPAFrEW}7mW=uAmtVQ&pGE-&az6@#-(Te^n*lrH^m@X-ftVcwO_#7{WI)5v(?>uC9GG{lcGXYJ~Q8q zbMFl7;t+kV;|;KkBW2!P_o%Czhw&Q(nXlxK9ak&6r5t_KH8#1Mr-*0}2h8R9XNkr zto5-b7P_auqTJb(TJlmJ9xreA=6d=d)CVbYP-r4$hDn5|TIhB>SReMfh&OVLkMk-T zYf%$taLF0OqYF?V{+6Xkn>iX@TuqQ?&cN6UjC9YF&%q{Ut3zv{U2)~$>-3;Dp)*(? zg*$mu8^i=-e#acaj*T$pNowo{xiGEk$%DusaQiS!KjJH96XZ-hXv+jk%ard#fu=@Q z$AM)YWvE^{%tDfK%nD49=PI|wYu}lYVbB#a7wtN^Nml@CE@{Gv7+jo{_V?I*jkdLD zJE|jfdrmVbkfS>rN*+`#l%ZUi5_bMS<>=MBDNlpiSb_tAF|Zy`K7kcp@|d?yaTmB^ zo?(vg;B$vxS|SszusORgDg-*Uitzdi{dUV+glA~R8V(?`3GZIl^egW{a919!j#>f` znL1o_^-b`}xnU0+~KIFLQ)$Q6#ym%)(GYC`^XM*{g zv3AM5$+TtDRs%`2TyR^$(hqE7Y1b&`Jd6dS6B#hDVbJlUXcG3y*439D8MrK!2D~6gn>UD4Imctb z+IvAt0iaW73Iq$K?4}H`7wq6YkTMm`tcktXgK0lKPmh=>h+l}Y+pDtvHnG>uqBA)l zAH6BV4F}v$(o$8Gfo*PB>IuaY1*^*`OTx4|hM8jZ?B6HY;F6p4{`OcZZ(us-RVwDx zUzJrCQlp@mz1ZFiSZ*$yX3c_#h9J;yBE$2g%xjmGF4ca z&yL`nGVs!Zxsh^j6i%$a*I3ZD2SoNT`{D%mU=LKaEwbN(_J5%i-6Va?@*>=3(dQy` zOv%$_9lcy9+(t>qohkuU4r_P=R^6ME+wFu&LA9tw9RA?azGhjrVJKy&8=*qZT5Dr8g--d+S8zAyJ$1HlW3Olryt`yE zFIph~Z6oF&o64rw{>lgZISC6p^CBer9C5G6yq%?8tC+)7*d+ib^?fU!JRFxynRLEZ zj;?PwtS}Ao#9whV@KEmwQgM0TVP{hs>dg(1*DiMUOKHdQGIqa0`yZnHk9mtbPfoLx zo;^V6pKUJ!5#n`w2D&381#5#_t}AlTGEgDz$^;u;-vxDN?^#5!zN9ngytY@oTv!nc zp1Xn8uR$1Z;7vY`-<*?DfPHB;x|GUi_fI9@I9SVRv1)qETbNU_8{5U|(>Du84qP#7 z*l9Y$SgA&wGbj>R1YeT9vYjZuC@|{rajTL0f%N@>3$DFU=`lSPl=Iv;EjuGjBa$Gw zHD-;%YOE@<-!7-Mn`0WuO3oWuL6tB2cpPw~Nvuj|KM@))ixuDK`9;jGMe2d)7gHin zS<>k@!x;!TJEc#HdL#RF(`|4W+H88d4V%zlh(7#{q2d0OQX9*FW^`^_<3r$kabWAB z$9BONo5}*(%kx zOXi-yM_cmB3>inPpI~)duvZykJ@^^aWzQ=eQ&STUa}2uT@lV&WoRzkUoE`rR0)`=l zFT%f|LA9fCw>`enm$p7W^E@U7RNBtsh{_-7vVz3DtB*y#*~(L9+x9*wn8VjWw|Q~q zKFsj1Yl>;}%MG3=PY`$g$_mnyhuV&~O~u~)968$0b2!Jkd;2MtAP#ZDYw9hmK_+M$ zb3pxyYC&|CuAbtiG8HZjj?MZJBFbt`ryf+c1dXFuC z0*ZQhBzNBd*}s6K_G}(|Z_9NDV162#y%WSNe|FTDDhx)K!c(mMJh@h87@8(^YdK$&d*^WQe8Z53 z(|@MRJ$Lk-&ii74MPIs80WsOFZ(NX23oR-?As+*aq6b?~62@fSVmM-_*cb1RzZ)`5$agEiL`-E9s7{GM2?(KNPgK1(+c*|-FKoy}X(D_b#etO|YR z(BGZ)0Ntfv-7R4GHoXp?l5g#*={S1{u-QzxCGng*oWr~@X-5f~RA14b8~B+pLKvr4 zfgL|7I>jlak9>D4=(i(cqYf7#318!OSR=^`xxvI!bBlS??`xxWeg?+|>MxaIdH1U~#1tHu zB{QMR?EGRmQ_l4p6YXJ{o(hh-7Tdm>TAX380TZZZyVkqHNzjUn*_|cb?T? zt;d2s-?B#Mc>T-gvBmQZx(y_cfkXZO~{N zT6rP7SD6g~n9QJ)8F*8uHxTLCAZ{l1Y&?6v)BOJZ)=R-pY=Y=&1}jE7fQ>USS}xP#exo57uND0i*rEk@$;nLvRB@u~s^dwRf?G?_enN@$t* zbL%JO=rV(3Ju8#GqUpeE3l_Wu1lN9Y{D4uaUe`g>zlj$1ER$6S6@{m1!~V|bYkhZA z%CvrDRTkHuajMU8;&RZ&itnC~iYLW4DVkP<$}>#&(`UO>!n)Po;Mt(SY8Yb`AS9lt znbX^i?Oe9r_o=?})IHKHoQGKXsps_SE{hwrg?6dMI|^+$CeC&z@*LuF+P`7LfZ*yr+KN8B4{Nzv<`A(wyR@!|gw{zB6Ha ziwPAYh)oJ(nlqSknu(8g9N&1hu0$vFK$W#mp%>X~AU1ay+EKWcFdif{% z#4!4aoVVJ;ULmkQf!ke2}3hqxLK>eq|-d7Ly7-J9zMpT`?dxo6HdfJA|t)?qPEVBDv z{y_b?4^|YA4%WW0VZd8C(ZgQzRI5(I^)=Ub`Y#MHc@nv0w-DaJAqsbEHDWG8Ia6ju zo-iyr*sq((gEwCC&^TYBWt4_@|81?=B-?#P6NMff(*^re zYqvDuO`K@`mjm_Jd;mW_tP`3$cS?R$jR1ZN09$YO%_iBqh5ftzSpMQQtxKFU=FYmP zeY^jph+g<4>YO;U^O>-NFLn~-RqlHvnZl2yd2A{Yc1G@Ga$d+Q&(f^tnPf+Z7serIU};17+2DU_f4Z z@GaPFut27d?!YiD+QP@)T=77cR9~MK@bd~pY%X(h%L={{OIb8IQmf-!xmZkm8A0Ga zQSWONI17_ru5wpHg3jI@i9D+_Y|pCqVuHJNdHUauTD=R$JcD2K_liQisqG$(sm=k9;L* z!L?*4B~ql7uioSX$zWJ?;q-SWXRFhz2Jt4%fOHA=Bwf|RzhwqdXGr78y$J)LR7&3T zE1WWz*>GPWKZ0%|@%6=fyx)5rzUpI;bCj>3RKzNG_1w$fIFCZ&UR0(7S?g}`&Pg$M zf`SLsz8wK82Vyj7;RyKmY{a8G{2BHG%w!^T|Njr!h9TO2LaP^_f22Q1=l$QiU84ao zHe_#{S6;qrC6w~7{y(hs-?-j?lbOfgH^E=XcSgnwW*eEz{_Z<_Px$?ny*JR5%f>l)FnDQ543{x%ZCiu33$Wg!pQFfT_}?5Q|_VSlIbLC`dpoMXL}9 zHfd9&47Mo(7D231gb+kjFxZHS4-m~7WurTH&doVX2KI5sU4v(sJ1@T9eCIKPjsqSr z)C01LsCxk=72-vXmX}CQD#BD;Cthymh&~=f$Q8nn0J<}ZrusBy4PvRNE}+1ceuj8u z0mW5k8fmgeLnTbWHGwfKA3@PdZxhn|PypR&^p?weGftrtCbjF#+zk_5BJh7;0`#Wr zgDpM_;Ax{jO##IrT`Oz;MvfwGfV$zD#c2xckpcXC6oou4ML~ezCc2EtnsQTB4tWNg z?4bkf;hG7IMfhgNI(FV5Gs4|*GyMTIY0$B=_*mso9Ityq$m^S>15>-?0(zQ<8Qy<_TjHE33(?_M8oaM zyc;NxzRVK@DL6RJnX%U^xW0Gpg(lXp(!uK1v0YgHjs^ZXSQ|m#lV7ip7{`C_J2TxPmfw%h$|%acrYHt)Re^PB%O&&=~a zhS(%I#+V>J-vjIib^<+s%ludY7y^C(P8nmqn9fp!i+?vr`bziDE=bx`%2W#Xyrj|i z!XQ4v1%L`m{7KT7q+LZNB^h8Ha2e=`Wp65^0;J00)_^G=au=8Yo;1b`CV&@#=jIBo zjN^JNVfYSs)+kDdGe7`1&8!?MQYKS?DuHZf3iogk_%#9E|5S zWeHrmAo>P;ejX7mwq#*}W25m^ZI+{(Z8fI?4jM_fffY0nok=+88^|*_DwcW>mR#e+ zX$F_KMdb6sRz!~7KkyN0G(3XQ+;z3X%PZ4gh;n-%62U<*VUKNv(D&Q->Na@Xb&u5Q3`3DGf+a8O5x7c#7+R+EAYl@R5us)CIw z7sT@_y~Ao@uL#&^LIh&QceqiT^+lb0YbFZt_SHOtWA%mgPEKVNvVgCsXy{5+zl*X8 zCJe)Q@y>wH^>l4;h1l^Y*9%-23TSmE>q5nI@?mt%n;Sj4Qq`Z+ib)a*a^cJc%E9^J zB;4s+K@rARbcBLT5P=@r;IVnBMKvT*)ew*R;&8vu%?Z&S>s?8?)3*YawM0P4!q$Kv zMmKh3lgE~&w&v%wVzH3Oe=jeNT=n@Y6J6TdHWTjXfX~-=1A1Bw`EW8rn}MqeI34nh zexFeA?&C3B2(E?0{drE@DA2pu(A#ElY&6el60Rn|Qpn-FkfQ8M93AfWIr)drgDFEU zghdWK)^71EWCP(@(=c4kfH1Y(4iugD4fve6;nSUpLT%!)MUHs1!zJYy4y||C+SwQ! z)KM&$7_tyM`sljP2fz6&Z;jxRn{Wup8IOUx8D4uh&(=O zx-7$a;U><*5L^!%xRlw)vAbh;sdlR||& ze}8_8%)c2Fwy=F&H|LM+p{pZB5DKTx>Y?F1N%BlZkXf!}JeGuMZk~LPi7{cidvUGB zAJ4LVeNV%XO>LTrklB#^-;8nb;}6l;1oW&WS=Mz*Az!4cqqQzbOSFq`$Q%PfD7srM zpKgP-D_0XPTRX*hAqeq0TDkJ;5HB1%$3Np)99#16c{ zJImlNL(npL!W|Gr_kxl1GVmF5&^$^YherS7+~q$p zt}{a=*RiD2Ikv6o=IM1kgc7zqpaZ;OB)P!1zz*i3{U()Dq#jG)egvK}@uFLa`oyWZ zf~=MV)|yJn`M^$N%ul5);JuQvaU1r2wt(}J_Qgyy`qWQI`hEeRX0uC@c1(dQ2}=U$ tNIIaX+dr)NRWXcxoR{>fqI{SF_dm1Ylv~=3YHI)h002ovPDHLkV1g(pWS;;4 literal 0 HcmV?d00001 diff --git a/packages/stream_chat_flutter_core/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png b/packages/stream_chat_flutter_core/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..f091b6b0bca859a3f474b03065bef75ba58a9e4c GIT binary patch literal 1588 zcmV-42Fv-0P)C1SqPt}wig>|5Crh^=oyX$BK<}M8eLU3e2hGT;=G|!_SP)7zNI6fqUMB=)y zRAZ>eDe#*r`yDAVgB_R*LB*MAc)8(b{g{9McCXW!lq7r(btRoB9!8B-#AI6JMb~YFBEvdsV)`mEQO^&#eRKx@b&x- z5lZm*!WfD8oCLzfHGz#u7sT0^VLMI1MqGxF^v+`4YYnVYgk*=kU?HsSz{v({E3lb9 z>+xILjBN)t6`=g~IBOelGQ(O990@BfXf(DRI5I$qN$0Gkz-FSc$3a+2fX$AedL4u{ z4V+5Ong(9LiGcIKW?_352sR;LtDPmPJXI{YtT=O8=76o9;*n%_m|xo!i>7$IrZ-{l z-x3`7M}qzHsPV@$v#>H-TpjDh2UE$9g6sysUREDy_R(a)>=eHw-WAyfIN z*qb!_hW>G)Tu8nSw9yn#3wFMiLcfc4pY0ek1}8(NqkBR@t4{~oC>ryc-h_ByH(Cg5 z>ao-}771+xE3um9lWAY1FeQFxowa1(!J(;Jg*wrg!=6FdRX+t_<%z&d&?|Bn){>zm zZQj(aA_HeBY&OC^jj*)N`8fa^ePOU72VpInJoI1?`ty#lvlNzs(&MZX+R%2xS~5Kh zX*|AU4QE#~SgPzOXe9>tRj>hjU@c1k5Y_mW*Jp3fI;)1&g3j|zDgC+}2Q_v%YfDax z!?umcN^n}KYQ|a$Lr+51Nf9dkkYFSjZZjkma$0KOj+;aQ&721~t7QUKx61J3(P4P1 zstI~7-wOACnWP4=8oGOwz%vNDqD8w&Q`qcNGGrbbf&0s9L0De{4{mRS?o0MU+nR_! zrvshUau0G^DeMhM_v{5BuLjb#Hh@r23lDAk8oF(C+P0rsBpv85EP>4CVMx#04MOfG z;P%vktHcXwTj~+IE(~px)3*MY77e}p#|c>TD?sMatC0Tu4iKKJ0(X8jxQY*gYtxsC z(zYC$g|@+I+kY;dg_dE>scBf&bP1Nc@Hz<3R)V`=AGkc;8CXqdi=B4l2k|g;2%#m& z*jfX^%b!A8#bI!j9-0Fi0bOXl(-c^AB9|nQaE`*)Hw+o&jS9@7&Gov#HbD~#d{twV zXd^Tr^mWLfFh$@Dr$e;PBEz4(-2q1FF0}c;~B5sA}+Q>TOoP+t>wf)V9Iy=5ruQa;z)y zI9C9*oUga6=hxw6QasLPnee@3^Rr*M{CdaL5=R41nLs(AHk_=Y+A9$2&H(B7!_pURs&8aNw7?`&Z&xY_Ye z)~D5Bog^td-^QbUtkTirdyK^mTHAOuptDflut!#^lnKqU md>ggs(5nOWAqO?umG&QVYK#ibz}*4>0000U6E9hRK9^#O7(mu>ETqrXGsduA8$)?`v2seloOCza43C{NQ$$gAOH**MCn0Q?+L7dl7qnbRdqZ8LSVp1ItDxhxD?t@5_yHg6A8yI zC*%Wgg22K|8E#!~cTNYR~@Y9KepMPrrB8cABapAFa=`H+UGhkXUZV1GnwR1*lPyZ;*K(i~2gp|@bzp8}og7e*#% zEnr|^CWdVV!-4*Y_7rFvlww2Ze+>j*!Z!pQ?2l->4q#nqRu9`ELo6RMS5=br47g_X zRw}P9a7RRYQ%2Vsd0Me{_(EggTnuN6j=-?uFS6j^u69elMypu?t>op*wBx<=Wx8?( ztpe^(fwM6jJX7M-l*k3kEpWOl_Vk3@(_w4oc}4YF4|Rt=2V^XU?#Yz`8(e?aZ@#li0n*=g^qOcVpd-Wbok=@b#Yw zqn8u9a)z>l(1kEaPYZ6hwubN6i<8QHgsu0oE) ziJ(p;Wxm>sf!K+cw>R-(^Y2_bahB+&KI9y^);#0qt}t-$C|Bo71lHi{_+lg#f%RFy z0um=e3$K3i6K{U_4K!EX?F&rExl^W|G8Z8;`5z-k}OGNZ0#WVb$WCpQu-_YsiqKP?BB# vzVHS-CTUF4Ozn5G+mq_~Qqto~ahA+K`|lyv3(-e}00000NkvXXu0mjfd`9t{ literal 0 HcmV?d00001 diff --git a/packages/stream_chat_flutter_core/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png b/packages/stream_chat_flutter_core/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..d0ef06e7edb86cdfe0d15b4b0d98334a86163658 GIT binary patch literal 1716 zcmds$`#;kQ7{|XelZftyR5~xW7?MLxS4^|Hw3&P7^y)@A9Fj{Xm1~_CIV^XZ%SLBn zA;!r`GqGHg=7>xrB{?psZQs88ZaedDoagm^KF{a*>G|dJWRSe^I$DNW008I^+;Kjt z>9p3GNR^I;v>5_`+91i(*G;u5|L+Bu6M=(afLjtkya#yZ175|z$pU~>2#^Z_pCZ7o z1c6UNcv2B3?; zX%qdxCXQpdKRz=#b*q0P%b&o)5ZrNZt7$fiETSK_VaY=mb4GK`#~0K#~9^ zcY!`#Af+4h?UMR-gMKOmpuYeN5P*RKF!(tb`)oe0j2BH1l?=>y#S5pMqkx6i{*=V9JF%>N8`ewGhRE(|WohnD59R^$_36{4>S zDFlPC5|k?;SPsDo87!B{6*7eqmMdU|QZ84>6)Kd9wNfh90=y=TFQay-0__>=<4pk& zYDjgIhL-jQ9o>z32K)BgAH+HxamL{ZL~ozu)Qqe@a`FpH=oQRA8=L-m-1dam(Ix2V z?du;LdMO+ooBelr^_y4{|44tmgH^2hSzPFd;U^!1p>6d|o)(-01z{i&Kj@)z-yfWQ)V#3Uo!_U}q3u`(fOs`_f^ueFii1xBNUB z6MecwJN$CqV&vhc+)b(p4NzGGEgwWNs z@*lUV6LaduZH)4_g!cE<2G6#+hJrWd5(|p1Z;YJ7ifVHv+n49btR}dq?HHDjl{m$T z!jLZcGkb&XS2OG~u%&R$(X+Z`CWec%QKt>NGYvd5g20)PU(dOn^7%@6kQb}C(%=vr z{?RP(z~C9DPnL{q^@pVw@|Vx~@3v!9dCaBtbh2EdtoNHm4kGxp>i#ct)7p|$QJs+U z-a3qtcPvhihub?wnJqEt>zC@)2suY?%-96cYCm$Q8R%-8$PZYsx3~QOLMDf(piXMm zB=<63yQk1AdOz#-qsEDX>>c)EES%$owHKue;?B3)8aRd}m~_)>SL3h2(9X;|+2#7X z+#2)NpD%qJvCQ0a-uzZLmz*ms+l*N}w)3LRQ*6>|Ub-fyptY(keUxw+)jfwF5K{L9 z|Cl_w=`!l_o><384d&?)$6Nh(GAm=4p_;{qVn#hI8lqewW7~wUlyBM-4Z|)cZr?Rh z=xZ&Ol>4(CU85ea(CZ^aO@2N18K>ftl8>2MqetAR53_JA>Fal`^)1Y--Am~UDa4th zKfCYpcXky$XSFDWBMIl(q=Mxj$iMBX=|j9P)^fDmF(5(5$|?Cx}DKEJa&XZP%OyE`*GvvYQ4PV&!g2|L^Q z?YG}tx;sY@GzMmsY`7r$P+F_YLz)(e}% zyakqFB<6|x9R#TdoP{R$>o7y(-`$$p0NxJ6?2B8tH)4^yF(WhqGZlM3=9Ibs$%U1w zWzcss*_c0=v_+^bfb`kBFsI`d;ElwiU%frgRB%qBjn@!0U2zZehBn|{%uNIKBA7n= zzE`nnwTP85{g;8AkYxA68>#muXa!G>xH22D1I*SiD~7C?7Za+9y7j1SHiuSkKK*^O zsZ==KO(Ua#?YUpXl{ViynyT#Hzk=}5X$e04O@fsMQjb}EMuPWFO0e&8(2N(29$@Vd zn1h8Yd>6z(*p^E{c(L0Lg=wVdupg!z@WG;E0k|4a%s7Up5C0c)55XVK*|x9RQeZ1J@1v9MX;>n34(i>=YE@Iur`0Vah(inE3VUFZNqf~tSz{1fz3Fsn_x4F>o(Yo;kpqvBe-sbwH(*Y zu$JOl0b83zu$JMvy<#oH^Wl>aWL*?aDwnS0iEAwC?DK@aT)GHRLhnz2WCvf3Ba;o=aY7 z2{Asu5MEjGOY4O#Ggz@@J;q*0`kd2n8I3BeNuMmYZf{}pg=jTdTCrIIYuW~luKecn z+E-pHY%ohj@uS0%^ z&(OxwPFPD$+#~`H?fMvi9geVLci(`K?Kj|w{rZ9JgthFHV+=6vMbK~0)Ea<&WY-NC zy-PnZft_k2tfeQ*SuC=nUj4H%SQ&Y$gbH4#2sT0cU0SdFs=*W*4hKGpuR1{)mV;Qf5pw4? zfiQgy0w3fC*w&Bj#{&=7033qFR*<*61B4f9K%CQvxEn&bsWJ{&winp;FP!KBj=(P6 z4Z_n4L7cS;ao2)ax?Tm|I1pH|uLpDSRVghkA_UtFFuZ0b2#>!8;>-_0ELjQSD-DRd z4im;599VHDZYtnWZGAB25W-e(2VrzEh|etsv2YoP#VbIZ{aFkwPrzJ#JvCvA*mXS& z`}Q^v9(W4GiSs}#s7BaN!WA2bniM$0J(#;MR>uIJ^uvgD3GS^%*ikdW6-!VFUU?JV zZc2)4cMsX@j z5HQ^e3BUzOdm}yC-xA%SY``k$rbfk z;CHqifhU*jfGM@DkYCecD9vl*qr58l6x<8URB=&%{!Cu3RO*MrKZ4VO}V6R0a zZw3Eg^0iKWM1dcTYZ0>N899=r6?+adUiBKPciJw}L$=1f4cs^bio&cr9baLF>6#BM z(F}EXe-`F=f_@`A7+Q&|QaZ??Txp_dB#lg!NH=t3$G8&06MFhwR=Iu*Im0s_b2B@| znW>X}sy~m#EW)&6E&!*0%}8UAS)wjt+A(io#wGI@Z2S+Ms1Cxl%YVE800007ip7{`C_J2TxPmfw%h$|%acrYHt)Re^PB%O&&=~a zhS(%I#+V>J-vjIib^<+s%ludY7y^C(P8nmqn9fp!i+?vr`bziDE=bx`%2W#Xyrj|i z!XQ4v1%L`m{7KT7q+LZNB^h8Ha2e=`Wp65^0;J00)_^G=au=8Yo;1b`CV&@#=jIBo zjN^JNVfYSs)+kDdGe7`1&8!?MQYKS?DuHZf3iogk_%#9E|5S zWeHrmAo>P;ejX7mwq#*}W25m^ZI+{(Z8fI?4jM_fffY0nok=+88^|*_DwcW>mR#e+ zX$F_KMdb6sRz!~7KkyN0G(3XQ+;z3X%PZ4gh;n-%62U<*VUKNv(D&Q->Na@Xb&u5Q3`3DGf+a8O5x7c#7+R+EAYl@R5us)CIw z7sT@_y~Ao@uL#&^LIh&QceqiT^+lb0YbFZt_SHOtWA%mgPEKVNvVgCsXy{5+zl*X8 zCJe)Q@y>wH^>l4;h1l^Y*9%-23TSmE>q5nI@?mt%n;Sj4Qq`Z+ib)a*a^cJc%E9^J zB;4s+K@rARbcBLT5P=@r;IVnBMKvT*)ew*R;&8vu%?Z&S>s?8?)3*YawM0P4!q$Kv zMmKh3lgE~&w&v%wVzH3Oe=jeNT=n@Y6J6TdHWTjXfX~-=1A1Bw`EW8rn}MqeI34nh zexFeA?&C3B2(E?0{drE@DA2pu(A#ElY&6el60Rn|Qpn-FkfQ8M93AfWIr)drgDFEU zghdWK)^71EWCP(@(=c4kfH1Y(4iugD4fve6;nSUpLT%!)MUHs1!zJYy4y||C+SwQ! z)KM&$7_tyM`sljP2fz6&Z;jxRn{Wup8IOUx8D4uh&(=O zx-7$a;U><*5L^!%xRlw)vAbh;sdlR||& ze}8_8%)c2Fwy=F&H|LM+p{pZB5DKTx>Y?F1N%BlZkXf!}JeGuMZk~LPi7{cidvUGB zAJ4LVeNV%XO>LTrklB#^-;8nb;}6l;1oW&WS=Mz*Az!4cqqQzbOSFq`$Q%PfD7srM zpKgP-D_0XPTRX*hAqeq0TDkJ;5HB1%$3Np)99#16c{ zJImlNL(npL!W|Gr_kxl1GVmF5&^$^YherS7+~q$p zt}{a=*RiD2Ikv6o=IM1kgc7zqpaZ;OB)P!1zz*i3{U()Dq#jG)egvK}@uFLa`oyWZ zf~=MV)|yJn`M^$N%ul5);JuQvaU1r2wt(}J_Qgyy`qWQI`hEeRX0uC@c1(dQ2}=U$ tNIIaX+dr)NRWXcxoR{>fqI{SF_dm1Ylv~=3YHI)h002ovPDHLkV1g(pWS;;4 literal 0 HcmV?d00001 diff --git a/packages/stream_chat_flutter_core/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png b/packages/stream_chat_flutter_core/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..c8f9ed8f5cee1c98386d13b17e89f719e83555b2 GIT binary patch literal 1895 zcmV-t2blPYP)FQtfgmafE#=YDCq`qUBt#QpG%*H6QHY765~R=q zZ6iudfM}q!Pz#~9JgOi8QJ|DSu?1-*(kSi1K4#~5?#|rh?sS)(-JQqX*}ciXJ56_H zdw=^s_srbAdqxlvGyrgGet#6T7_|j;95sL%MtM;q86vOxKM$f#puR)Bjv9Zvz9-di zXOTSsZkM83)E9PYBXC<$6(|>lNLVBb&&6y{NByFCp%6+^ALR@NCTse_wqvNmSWI-m z!$%KlHFH2omF!>#%1l3LTZg(s7eof$7*xB)ZQ0h?ejh?Ta9fDv59+u#MokW+1t8Zb zgHv%K(u9G^Lv`lh#f3<6!JVTL3(dCpxHbnbA;kKqQyd1~^Xe0VIaYBSWm6nsr;dFj z4;G-RyL?cYgsN1{L4ZFFNa;8)Rv0fM0C(~Tkit94 zz#~A)59?QjD&pAPSEQ)p8gP|DS{ng)j=2ux)_EzzJ773GmQ_Cic%3JJhC0t2cx>|v zJcVusIB!%F90{+}8hG3QU4KNeKmK%T>mN57NnCZ^56=0?&3@!j>a>B43pi{!u z7JyDj7`6d)qVp^R=%j>UIY6f+3`+qzIc!Y_=+uN^3BYV|o+$vGo-j-Wm<10%A=(Yk^beI{t%ld@yhKjq0iNjqN4XMGgQtbKubPM$JWBz}YA65k%dm*awtC^+f;a-x4+ddbH^7iDWGg&N0n#MW{kA|=8iMUiFYvMoDY@sPC#t$55gn6ykUTPAr`a@!(;np824>2xJthS z*ZdmT`g5-`BuJs`0LVhz+D9NNa3<=6m;cQLaF?tCv8)zcRSh66*Z|vXhG@$I%U~2l z?`Q zykI#*+rQ=z6Jm=Bui-SfpDYLA=|vzGE(dYm=OC8XM&MDo7ux4UF1~0J1+i%aCUpRe zt3L_uNyQ*cE(38Uy03H%I*)*Bh=Lb^Xj3?I^Hnbeq72(EOK^Y93CNp*uAA{5Lc=ky zx=~RKa4{iTm{_>_vSCm?$Ej=i6@=m%@VvAITnigVg{&@!7CDgs908761meDK5azA} z4?=NOH|PdvabgJ&fW2{Mo$Q0CcD8Qc84%{JPYt5EiG{MdLIAeX%T=D7NIP4%Hw}p9 zg)==!2Lbp#j{u_}hMiao9=!VSyx0gHbeCS`;q&vzeq|fs`y&^X-lso(Ls@-706qmA z7u*T5PMo_w3{se1t2`zWeO^hOvTsohG_;>J0wVqVe+n)AbQCx)yh9;w+J6?NF5Lmo zecS@ieAKL8%bVd@+-KT{yI|S}O>pYckUFs;ry9Ow$CD@ztz5K-*D$^{i(_1llhSh^ zEkL$}tsQt5>QA^;QgjgIfBDmcOgi5YDyu?t6vSnbp=1+@6D& z5MJ}B8q;bRlVoxasyhcUF1+)o`&3r0colr}QJ3hcSdLu;9;td>kf@Tcn<@9sIx&=m z;AD;SCh95=&p;$r{Xz3iWCO^MX83AGJ(yH&eTXgv|0=34#-&WAmw{)U7OU9!Wz^!7 zZ%jZFi@JR;>Mhi7S>V7wQ176|FdW2m?&`qa(ScO^CFPR80HucLHOTy%5s*HR0^8)i h0WYBP*#0Ks^FNSabJA*5${_#%002ovPDHLkV1oKhTl@e3 literal 0 HcmV?d00001 diff --git a/packages/stream_chat_flutter_core/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png b/packages/stream_chat_flutter_core/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..a6d6b8609df07bf62e5100a53a01510388bd2b22 GIT binary patch literal 2665 zcmV-v3YPVWP)oFh3q0MFesq&64WThn3$;G69TfjsAv=f2G9}p zgSx99+!YV6qME!>9MD13x)k(+XE7W?_O4LoLb5ND8 zaV{9+P@>42xDfRiYBMSgD$0!vssptcb;&?u9u(LLBKmkZ>RMD=kvD3h`sk6!QYtBa ztlZI#nu$8lJ^q2Z79UTgZe>BU73(Aospiq+?SdMt8lDZ;*?@tyWVZVS_Q7S&*tJaiRlJ z+aSMOmbg3@h5}v;A*c8SbqM3icg-`Cnwl;7Ts%A1RkNIp+Txl-Ckkvg4oxrqGA5ewEgYqwtECD<_3Egu)xGllKt&J8g&+=ac@Jq4-?w6M3b*>w5 z69N3O%=I^6&UL5gZ!}trC7bUj*12xLdkNs~Bz4QdJJ*UDZox2UGR}SNg@lmOvhCc~ z*f_UeXv(=#I#*7>VZx2ObEN~UoGUTl=-@)E;YtCRZ>SVp$p9yG5hEFZ!`wI!spd)n zSk+vK0Vin7FL{7f&6OB%f;SH22dtbcF<|9fi2Fp%q4kxL!b1#l^)8dUwJ zwEf{(wJj@8iYDVnKB`eSU+;ml-t2`@%_)0jDM`+a46xhDbBj2+&Ih>1A>6aky#(-SYyE{R3f#y57wfLs z6w1p~$bp;6!9DX$M+J~S@D6vJAaElETnsX4h9a5tvPhC3L@qB~bOzkL@^z0k_hS{T4PF*TDrgdXp+dzsE? z>V|VR035Pl9n5&-RePFdS{7KAr2vPOqR9=M$vXA1Yy5>w;EsF`;OK{2pkn-kpp9Pw z)r;5JfJKKaT$4qCb{TaXHjb$QA{y0EYy*+b1XI;6Ah- zw13P)xT`>~eFoJC!>{2XL(a_#upp3gaR1#5+L(Jmzp4TBnx{~WHedpJ1ch8JFk~Sw z>F+gN+i+VD?gMXwcIhn8rz`>e>J^TI3E-MW>f}6R-pL}>WMOa0k#jN+`RyUVUC;#D zg|~oS^$6%wpF{^Qr+}X>0PKcr3Fc&>Z>uv@C);pwDs@2bZWhYP!rvGx?_|q{d`t<*XEb#=aOb=N+L@CVBGqImZf&+a zCQEa3$~@#kC);pasdG=f6tuIi0PO-y&tvX%>Mv=oY3U$nD zJ#gMegnQ46pq+3r=;zmgcG+zRc9D~c>z+jo9&D+`E6$LmyFqlmCYw;-Zooma{sR@~ z)_^|YL1&&@|GXo*pivH7k!msl+$Sew3%XJnxajt0K%3M6Bd&YFNy9}tWG^aovK2eX z1aL1%7;KRDrA@eG-Wr6w+;*H_VD~qLiVI`{_;>o)k`{8xa3EJT1O_>#iy_?va0eR? zDV=N%;Zjb%Z2s$@O>w@iqt!I}tLjGk!=p`D23I}N4Be@$(|iSA zf3Ih7b<{zqpDB4WF_5X1(peKe+rASze%u8eKLn#KKXt;UZ+Adf$_TO+vTqshLLJ5c z52HucO=lrNVae5XWOLm!V@n-ObU11!b+DN<$RuU+YsrBq*lYT;?AwJpmNKniF0Q1< zJCo>Q$=v$@&y=sj6{r!Y&y&`0$-I}S!H_~pI&2H8Z1C|BX4VgZ^-! zje3-;x0PBD!M`v*J_)rL^+$<1VJhH*2Fi~aA7s&@_rUHYJ9zD=M%4AFQ`}k8OC$9s XsPq=LnkwKG00000NkvXXu0mjfhAk5^ literal 0 HcmV?d00001 diff --git a/packages/stream_chat_flutter_core/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png b/packages/stream_chat_flutter_core/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..a6d6b8609df07bf62e5100a53a01510388bd2b22 GIT binary patch literal 2665 zcmV-v3YPVWP)oFh3q0MFesq&64WThn3$;G69TfjsAv=f2G9}p zgSx99+!YV6qME!>9MD13x)k(+XE7W?_O4LoLb5ND8 zaV{9+P@>42xDfRiYBMSgD$0!vssptcb;&?u9u(LLBKmkZ>RMD=kvD3h`sk6!QYtBa ztlZI#nu$8lJ^q2Z79UTgZe>BU73(Aospiq+?SdMt8lDZ;*?@tyWVZVS_Q7S&*tJaiRlJ z+aSMOmbg3@h5}v;A*c8SbqM3icg-`Cnwl;7Ts%A1RkNIp+Txl-Ckkvg4oxrqGA5ewEgYqwtECD<_3Egu)xGllKt&J8g&+=ac@Jq4-?w6M3b*>w5 z69N3O%=I^6&UL5gZ!}trC7bUj*12xLdkNs~Bz4QdJJ*UDZox2UGR}SNg@lmOvhCc~ z*f_UeXv(=#I#*7>VZx2ObEN~UoGUTl=-@)E;YtCRZ>SVp$p9yG5hEFZ!`wI!spd)n zSk+vK0Vin7FL{7f&6OB%f;SH22dtbcF<|9fi2Fp%q4kxL!b1#l^)8dUwJ zwEf{(wJj@8iYDVnKB`eSU+;ml-t2`@%_)0jDM`+a46xhDbBj2+&Ih>1A>6aky#(-SYyE{R3f#y57wfLs z6w1p~$bp;6!9DX$M+J~S@D6vJAaElETnsX4h9a5tvPhC3L@qB~bOzkL@^z0k_hS{T4PF*TDrgdXp+dzsE? z>V|VR035Pl9n5&-RePFdS{7KAr2vPOqR9=M$vXA1Yy5>w;EsF`;OK{2pkn-kpp9Pw z)r;5JfJKKaT$4qCb{TaXHjb$QA{y0EYy*+b1XI;6Ah- zw13P)xT`>~eFoJC!>{2XL(a_#upp3gaR1#5+L(Jmzp4TBnx{~WHedpJ1ch8JFk~Sw z>F+gN+i+VD?gMXwcIhn8rz`>e>J^TI3E-MW>f}6R-pL}>WMOa0k#jN+`RyUVUC;#D zg|~oS^$6%wpF{^Qr+}X>0PKcr3Fc&>Z>uv@C);pwDs@2bZWhYP!rvGx?_|q{d`t<*XEb#=aOb=N+L@CVBGqImZf&+a zCQEa3$~@#kC);pasdG=f6tuIi0PO-y&tvX%>Mv=oY3U$nD zJ#gMegnQ46pq+3r=;zmgcG+zRc9D~c>z+jo9&D+`E6$LmyFqlmCYw;-Zooma{sR@~ z)_^|YL1&&@|GXo*pivH7k!msl+$Sew3%XJnxajt0K%3M6Bd&YFNy9}tWG^aovK2eX z1aL1%7;KRDrA@eG-Wr6w+;*H_VD~qLiVI`{_;>o)k`{8xa3EJT1O_>#iy_?va0eR? zDV=N%;Zjb%Z2s$@O>w@iqt!I}tLjGk!=p`D23I}N4Be@$(|iSA zf3Ih7b<{zqpDB4WF_5X1(peKe+rASze%u8eKLn#KKXt;UZ+Adf$_TO+vTqshLLJ5c z52HucO=lrNVae5XWOLm!V@n-ObU11!b+DN<$RuU+YsrBq*lYT;?AwJpmNKniF0Q1< zJCo>Q$=v$@&y=sj6{r!Y&y&`0$-I}S!H_~pI&2H8Z1C|BX4VgZ^-! zje3-;x0PBD!M`v*J_)rL^+$<1VJhH*2Fi~aA7s&@_rUHYJ9zD=M%4AFQ`}k8OC$9s XsPq=LnkwKG00000NkvXXu0mjfhAk5^ literal 0 HcmV?d00001 diff --git a/packages/stream_chat_flutter_core/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png b/packages/stream_chat_flutter_core/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..75b2d164a5a98e212cca15ea7bf2ab5de5108680 GIT binary patch literal 3831 zcmVjJBgitF5mAp-i>4+KS_oR{|13AP->1TD4=w)g|)JHOx|a2Wk1Va z!k)vP$UcQ#mdj%wNQoaJ!w>jv_6&JPyutpQps?s5dmDQ>`%?Bvj>o<%kYG!YW6H-z zu`g$@mp`;qDR!51QaS}|ZToSuAGcJ7$2HF0z`ln4t!#Yg46>;vGG9N9{V@9z#}6v* zfP?}r6b{*-C*)(S>NECI_E~{QYzN5SXRmVnP<=gzP+_Sp(Aza_hKlZ{C1D&l*(7IKXxQC1Z9#6wx}YrGcn~g%;icdw>T0Rf^w0{ z$_wn1J+C0@!jCV<%Go5LA45e{5gY9PvZp8uM$=1}XDI+9m7!A95L>q>>oe0$nC->i zeexUIvq%Uk<-$>DiDb?!In)lAmtuMWxvWlk`2>4lNuhSsjAf2*2tjT`y;@d}($o)S zn(+W&hJ1p0xy@oxP%AM15->wPLp{H!k)BdBD$toBpJh+crWdsNV)qsHaqLg2_s|Ih z`8E9z{E3sA!}5aKu?T!#enD(wLw?IT?k-yWVHZ8Akz4k5(TZJN^zZgm&zM28sfTD2BYJ|Fde3Xzh;;S` z=GXTnY4Xc)8nYoz6&vF;P7{xRF-{|2Xs5>a5)@BrnQ}I(_x7Cgpx#5&Td^4Q9_FnQ zX5so*;#8-J8#c$OlA&JyPp$LKUhC~-e~Ij!L%uSMu!-VZG7Hx-L{m2DVR2i=GR(_% zCVD!4N`I)&Q5S`?P&fQZ=4#Dgt_v2-DzkT}K(9gF0L(owe-Id$Rc2qZVLqI_M_DyO z9@LC#U28_LU{;wGZ&))}0R2P4MhajKCd^K#D+JJ&JIXZ_p#@+7J9A&P<0kdRujtQ_ zOy>3=C$kgi6$0pW06KaLz!21oOryKM3ZUOWqppndxfH}QpgjEJ`j7Tzn5bk6K&@RA?vl##y z$?V~1E(!wB5rH`>3nc&@)|#<1dN2cMzzm=PGhQ|Yppne(C-Vlt450IXc`J4R0W@I7 zd1e5uW6juvO%ni(WX7BsKx3MLngO7rHO;^R5I~0^nE^9^E_eYLgiR9&KnJ)pBbfno zSVnW$0R+&6jOOsZ82}nJ126+c|%svPo;TeUku<2G7%?$oft zyaO;tVo}(W)VsTUhq^XmFi#2z%-W9a{7mXn{uzivYQ_d6b7VJG{77naW(vHt-uhnY zVN#d!JTqVh(7r-lhtXVU6o})aZbDt_;&wJVGl2FKYFBFpU-#9U)z#(A%=IVnqytR$SY-sO( z($oNE09{D^@OuYPz&w~?9>Fl5`g9u&ecFGhqX=^#fmR=we0CJw+5xna*@oHnkahk+ z9aWeE3v|An+O5%?4fA&$Fgu~H_YmqR!yIU!bFCk4!#pAj%(lI(A5n)n@Id#M)O9Yx zJU9oKy{sRAIV3=5>(s8n{8ryJ!;ho}%pn6hZKTKbqk=&m=f*UnK$zW3YQP*)pw$O* zIfLA^!-bmBl6%d_n$#tP8Zd_(XdA*z*WH|E_yILwjtI~;jK#v-6jMl^?<%Y%`gvpwv&cFb$||^v4D&V=aNy?NGo620jL3VZnA%s zH~I|qPzB~e(;p;b^gJr7Ure#7?8%F0m4vzzPy^^(q4q1OdthF}Fi*RmVZN1OwTsAP zn9CZP`FazX3^kG(KodIZ=Kty8DLTy--UKfa1$6XugS zk%6v$Kmxt6U!YMx0JQ)0qX*{CXwZZk$vEROidEc7=J-1;peNat!vS<3P-FT5po>iE z!l3R+<`#x|+_hw!HjQGV=8!q|76y8L7N8gP3$%0kfush|u0uU^?dKBaeRSBUpOZ0c z62;D&Mdn2}N}xHRFTRI?zRv=>=AjHgH}`2k4WK=#AHB)UFrR-J87GgX*x5fL^W2#d z=(%K8-oZfMO=i{aWRDg=FX}UubM4eotRDcn;OR#{3q=*?3mE3_oJ-~prjhxh%PgQT zyn)Qozaq0@o&|LEgS{Ind4Swsr;b`u185hZPOBLL<`d2%^Yp1?oL)=jnLi;Zo0ZDliTtQ^b5SmfIMe{T==zZkbvn$KTQGlbG8w}s@M3TZnde;1Am46P3juKb zl9GU&3F=q`>j!`?SyH#r@O59%@aMX^rx}Nxe<>NqpUp5=lX1ojGDIR*-D^SDuvCKF z?3$xG(gVUsBERef_YjPFl^rU9EtD{pt z0CXwpN7BN3!8>hajGaTVk-wl=9rxmfWtIhC{mheHgStLi^+Nz12a?4r(fz)?3A%at zMlvQmL<2-R)-@G1wJ0^zQK%mR=r4d{Y3fHp){nWXUL#|CqXl(+v+qDh>FkF9`eWrW zfr^D%LNfOcTNvtx0JXR35J0~Jpi2#P3Q&80w+nqNfc}&G0A~*)lGHKv=^FE+b(37|)zL;KLF>oiGfb(?&1 zV3XRu!Sw>@quKiab%g6jun#oZ%!>V#A%+lNc?q>6+VvyAn=kf_6z^(TZUa4Eelh{{ zqFX-#dY(EV@7l$NE&kv9u9BR8&Ojd#ZGJ6l8_BW}^r?DIS_rU2(XaGOK z225E@kH5Opf+CgD^{y29jD4gHbGf{1MD6ggQ&%>UG4WyPh5q_tb`{@_34B?xfSO*| zZv8!)q;^o-bz`MuxXk*G^}(6)ACb@=Lfs`Hxoh>`Y0NE8QRQ!*p|SH@{r8=%RKd4p z+#Ty^-0kb=-H-O`nAA3_6>2z(D=~Tbs(n8LHxD0`R0_ATFqp-SdY3(bZ3;VUM?J=O zKCNsxsgt@|&nKMC=*+ZqmLHhX1KHbAJs{nGVMs6~TiF%Q)P@>!koa$%oS zjXa=!5>P`vC-a}ln!uH1ooeI&v?=?v7?1n~P(wZ~0>xWxd_Aw;+}9#eULM7M8&E?Y zC-ZLhi3RoM92SXUb-5i-Lmt5_rfjE{6y^+24`y$1lywLyHO!)Boa7438K4#iLe?rh z2O~YGSgFUBH?og*6=r9rme=peP~ah`(8Zt7V)j5!V0KPFf_mebo3z95U8(up$-+EA^9dTRLq>Yl)YMBuch9%=e5B`Vnb>o zt03=kq;k2TgGe4|lGne&zJa~h(UGutjP_zr?a7~#b)@15XNA>Dj(m=gg2Q5V4-$)D|Q9}R#002ovPDHLkV1o7DH3k3x literal 0 HcmV?d00001 diff --git a/packages/stream_chat_flutter_core/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png b/packages/stream_chat_flutter_core/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png new file mode 100644 index 0000000000000000000000000000000000000000..c4df70d39da7941ef3f6dcb7f06a192d8dcb308d GIT binary patch literal 1888 zcmV-m2cP(fP)x~L`~4d)Rspd&<9kFh{hn*KP1LP0~$;u(LfAu zp%fx&qLBcRHx$G|3q(bv@+b;o0*D|jwD-Q9uQR(l*ST}s+uPgQ-MeFwZ#GS?b332? z&Tk$&_miXn3IGq)AmQ)3sisq{raD4(k*bHvpCe-TdWq^NRTEVM)i9xbgQ&ccnUVx* zEY%vS%gDcSg=!tuIK8$Th2_((_h^+7;R|G{n06&O2#6%LK`a}n?h_fL18btz<@lFG za}xS}u?#DBMB> zw^b($1Z)`9G?eP95EKi&$eOy@K%h;ryrR3la%;>|o*>CgB(s>dDcNOXg}CK9SPmD? zmr-s{0wRmxUnbDrYfRvnZ@d z6johZ2sMX{YkGSKWd}m|@V7`Degt-43=2M?+jR%8{(H$&MLLmS;-|JxnX2pnz;el1jsvqQz}pGSF<`mqEXRQ5sC4#BbwnB_4` zc5bFE-Gb#JV3tox9fp-vVEN{(tOCpRse`S+@)?%pz+zVJXSooTrNCUg`R6`hxwb{) zC@{O6MKY8tfZ5@!yy=p5Y|#+myRL=^{tc(6YgAnkg3I(Cd!r5l;|;l-MQ8B`;*SCE z{u)uP^C$lOPM z5d~UhKhRRmvv{LIa^|oavk1$QiEApSrP@~Jjbg`<*dW4TO?4qG%a%sTPUFz(QtW5( zM)lA+5)0TvH~aBaOAs|}?u2FO;yc-CZ1gNM1dAxJ?%m?YsGR`}-xk2*dxC}r5j$d* zE!#Vtbo69h>V4V`BL%_&$} z+oJAo@jQ^Tk`;%xw-4G>hhb&)B?##U+(6Fi7nno`C<|#PVA%$Y{}N-?(Gc$1%tr4Pc}}hm~yY#fTOe!@v9s-ik$dX~|ygArPhByaXn8 zpI^FUjNWMsTFKTP3X7m?UK)3m zp6rI^_zxRYrx6_QmhoWoDR`fp4R7gu6;gdO)!KexaoO2D88F9x#TM1(9Bn7g;|?|o z)~$n&Lh#hCP6_LOPD>a)NmhW})LADx2kq=X7}7wYRj-0?dXr&bHaRWCfSqvzFa=sn z-8^gSyn-RmH=BZ{AJZ~!8n5621GbUJV7Qvs%JNv&$%Q17s_X%s-41vAPfIR>;x0Wlqr5?09S>x#%Qkt>?(&XjFRY}*L6BeQ3 z<6XEBh^S7>AbwGm@XP{RkeEKj6@_o%oV?hDuUpUJ+r#JZO?!IUc;r0R?>mi)*ZpQ) z#((dn=A#i_&EQn|hd)N$#A*fjBFuiHcYvo?@y1 z5|fV=a^a~d!c-%ZbMNqkMKiSzM{Yq=7_c&1H!mXk60Uv32dV;vMg&-kQ)Q{+PFtwc zj|-uQ;b^gts??J*9VxxOro}W~Q9j4Em|zSRv)(WSO9$F$s=Ydu%Q+5DOid~lwk&we zY%W(Z@ofdwPHncEZzZgmqS|!gTj3wQq9rxQy+^eNYKr1mj&?tm@wkO*9@UtnRMG>c aR{jt9+;fr}hV%pg00001^@s67{VYS000c7NklQEG_j zup^)eW&WUIApqy$=APz8jE@awGp)!bsTjDbrJO`$x^ZR^dr;>)LW>{ zs70vpsD38v)19rI=GNk1b(0?Js9~rjsQsu*K;@SD40RB-3^gKU-MYC7G!Bw{fZsqp zih4iIi;Hr_xZ033Iu{sQxLS=}yBXgLMn40d++>aQ0#%8D1EbGZp7+ z5=mK?t31BkVYbGOxE9`i748x`YgCMwL$qMsChbSGSE1`p{nSmadR zcQ#R)(?!~dmtD0+D2!K zR9%!Xp1oOJzm(vbLvT^$IKp@+W2=-}qTzTgVtQ!#Y7Gxz}stUIm<1;oBQ^Sh2X{F4ibaOOx;5ZGSNK z0maF^@(UtV$=p6DXLgRURwF95C=|U8?osGhgOED*b z7woJ_PWXBD>V-NjQAm{~T%sjyJ{5tn2f{G%?J!KRSrrGvQ1(^`YLA5B!~eycY(e5_ z*%aa{at13SxC(=7JT7$IQF~R3sy`Nn%EMv!$-8ZEAryB*yB1k&stni)=)8-ODo41g zkJu~roIgAih94tb=YsL%iH5@^b~kU9M-=aqgXIrbtxMpFy5mekFm#edF9z7RQ6V}R zBIhbXs~pMzt0VWy1Fi$^fh+1xxLDoK09&5&MJl(q#THjPm(0=z2H2Yfm^a&E)V+a5 zbi>08u;bJsDRUKR9(INSc7XyuWv(JsD+BB*0hS)FO&l&7MdViuur@-<-EHw>kHRGY zqoT}3fDv2-m{NhBG8X}+rgOEZ;amh*DqN?jEfQdqxdj08`Sr=C-KmT)qU1 z+9Cl)a1mgXxhQiHVB}l`m;-RpmKy?0*|yl?FXvJkFxuu!fKlcmz$kN(a}i*saM3nr z0!;a~_%Xqy24IxA2rz<+08=B-Q|2PT)O4;EaxP^6qixOv7-cRh?*T?zZU`{nIM-at zTKYWr9rJ=tppQ9I#Z#mLgINVB!pO-^FOcvFw6NhV0gztuO?g ztoA*C-52Q-Z-P#xB4HAY3KQVd%dz1S4PA3vHp0aa=zAO?FCt zC_GaTyVBg2F!bBr3U@Zy2iJgIAt>1sf$JWA9kh{;L+P*HfUBX1Zy{4MgNbDfBV_ly z!y#+753arsZUt@366jIC0klaC@ckuk!qu=pAyf7&QmiBUT^L1&tOHzsK)4n|pmrVT zs2($4=?s~VejTFHbFdDOwG;_58LkIj1Fh@{glkO#F1>a==ymJS$z;gdedT1zPx4Kj ztjS`y_C}%af-RtpehdQDt3a<=W5C4$)9W@QAse;WUry$WYmr51ml9lkeunUrE`-3e zmq1SgSOPNEE-Mf+AGJ$g0M;3@w!$Ej;hMh=v=I+Lpz^n%Pg^MgwyqOkNyu2c^of)C z1~ALor3}}+RiF*K4+4{(1%1j3pif1>sv0r^mTZ?5Jd-It!tfPfiG_p$AY*Vfak%FG z4z#;wLtw&E&?}w+eKG^=#jF7HQzr8rV0mY<1YAJ_uGz~$E13p?F^fPSzXSn$8UcI$ z8er9{5w5iv0qf8%70zV71T1IBB1N}R5Kp%NO0=5wJalZt8;xYp;b{1K) zHY>2wW-`Sl{=NpR%iu3(u6l&)rc%%cSA#aV7WCowfbFR4wcc{LQZv~o1u_`}EJA3>ki`?9CKYTA!rhO)if*zRdd}Kn zEPfYbhoVE~!FI_2YbC5qAj1kq;xP6%J8+?2PAs?`V3}nyFVD#sV3+uP`pi}{$l9U^ zSz}_M9f7RgnnRhaoIJgT8us!1aB&4!*vYF07Hp&}L zCRlop0oK4DL@ISz{2_BPlezc;xj2|I z23RlDNpi9LgTG_#(w%cMaS)%N`e>~1&a3<{Xy}>?WbF>OOLuO+j&hc^YohQ$4F&ze z+hwnro1puQjnKm;vFG~o>`kCeUIlkA-2tI?WBKCFLMBY=J{hpSsQ=PDtU$=duS_hq zHpymHt^uuV1q@uc4bFb{MdG*|VoW@15Osrqt2@8ll0qO=j*uOXn{M0UJX#SUztui9FN4)K3{9!y8PC-AHHvpVTU;x|-7P+taAtyglk#rjlH2 z5Gq8ik}BPaGiM{#Woyg;*&N9R2{J0V+WGB69cEtH7F?U~Kbi6ksi*`CFXsi931q7Y zGO82?whBhN%w1iDetv%~wM*Y;E^)@Vl?VDj-f*RX>{;o_=$fU!&KAXbuadYZ46Zbg z&6jMF=49$uL^73y;;N5jaHYv)BTyfh&`qVLYn?`o6BCA_z-0niZz=qPG!vonK3MW_ zo$V96zM!+kJRs{P-5-rQVse0VBH*n6A58)4uc&gfHMa{gIhV2fGf{st>E8sKyP-$8zp~wJX^A*@DI&-;8>gANXZj zU)R+Y)PB?=)a|Kj>8NXEu^S_h^7R`~Q&7*Kn!xyvzVv&^>?^iu;S~R2e-2fJx-oUb cX)(b1KSk$MOV07*qoM6N<$f&6$jw%VRuvdN2+38CZWny1cRtlsl+0_KtW)EU14Ei(F!UtWuj4IK+3{sK@>rh zs1Z;=(DD&U6+tlyL?UnHVN^&g6QhFi2#HS+*qz;(>63G(`|jRtW|nz$Pv7qTovP!^ zP_jES{mr@O-02w%!^a?^1ZP!_KmQiz0L~jZ=W@Qt`8wzOoclQsAS<5YdH;a(4bGLE zk8s}1If(PSIgVi!XE!5kA?~z*sobvNyohr;=Q_@h2@$6Flyej3J)D-6YfheRGl`HEcPk|~huT_2-U?PfL=4BPV)f1o!%rQ!NMt_MYw-5bUSwQ9Z&zC>u zOrl~UJglJNa%f50Ok}?WB{on`Ci`p^Y!xBA?m@rcJXLxtrE0FhRF3d*ir>yzO|BD$ z3V}HpFcCh6bTzY}Nt_(W%QYd3NG)jJ4<`F<1Od) zfQblTdC&h2lCz`>y?>|9o2CdvC8qZeIZt%jN;B7Hdn2l*k4M4MFEtq`q_#5?}c$b$pf_3y{Y!cRDafZBEj-*OD|gz#PBDeu3QoueOesLzB+O zxjf2wvf6Wwz>@AiOo2mO4=TkAV+g~%_n&R;)l#!cBxjuoD$aS-`IIJv7cdX%2{WT7 zOm%5rs(wqyPE^k5SIpUZ!&Lq4<~%{*>_Hu$2|~Xa;iX*tz8~G6O3uFOS?+)tWtdi| zV2b#;zRN!m@H&jd=!$7YY6_}|=!IU@=SjvGDFtL;aCtw06U;-v^0%k0FOyESt z1Wv$={b_H&8FiRV?MrzoHWd>%v6KTRU;-v^Miiz+@q`(BoT!+<37CKhoKb)|8!+RG z6BQFU^@fRW;s8!mOf2QViKQGk0TVER6EG1`#;Nm39Do^PoT!+<37AD!%oJe86(=et zZ~|sLzU>V-qYiU6V8$0GmU7_K8|Fd0B?+9Un1BhKAz#V~Fk^`mJtlCX#{^8^M8!me z8Yg;8-~>!e<-iG;h*0B1kBKm}hItVGY6WnjVpgnTTAC$rqQ^v)4KvOtpY|sIj@WYg zyw##ZZ5AC2IKNC;^hwg9BPk0wLStlmBr;E|$5GoAo$&Ui_;S9WY62n3)i49|T%C#i017z3J=$RF|KyZWnci*@lW4 z=AKhNN6+m`Q!V3Ye68|8y@%=am>YD0nG99M)NWc20%)gwO!96j7muR}Fr&54SxKP2 zP30S~lt=a*qDlbu3+Av57=9v&vr<6g0&`!8E2fq>I|EJGKs}t|{h7+KT@)LfIV-3K zK)r_fr2?}FFyn*MYoLC>oV-J~eavL2ho4a4^r{E-8m2hi>~hA?_vIG4a*KT;2eyl1 zh_hUvUJpNCFwBvRq5BI*srSle>c6%n`#VNsyC|MGa{(P&08p=C9+WUw9Hl<1o9T4M zdD=_C0F7#o8A_bRR?sFNmU0R6tW`ElnF8p53IdHo#S9(JoZCz}fHwJ6F<&?qrpVqE zte|m%89JQD+XwaPU#%#lVs-@-OL);|MdfINd6!XwP2h(eyafTUsoRkA%&@fe?9m@jw-v(yTTiV2(*fthQH9}SqmsRPVnwwbV$1E(_lkmo&S zF-truCU914_$jpqjr(>Ha4HkM4YMT>m~NosUu&UZ>zirfHo%N6PPs9^_o$WqPA0#5 z%tG>qFCL+b*0s?sZ;Sht0nE7Kl>OVXy=gjWxxK;OJ3yGd7-pZf7JYNcZo2*1SF`u6 zHJyRRxGw9mDlOiXqVMsNe#WX`fC`vrtjSQ%KmLcl(lC>ZOQzG^%iql2w-f_K@r?OE zwCICifM#L-HJyc7Gm>Ern?+Sk3&|Khmu4(~3qa$(m6Ub^U0E5RHq49za|XklN#?kP zl;EstdW?(_4D>kwjWy2f!LM)y?F94kyU3`W!6+AyId-89v}sXJpuic^NLL7GJItl~ zsiuB98AI-(#Mnm|=A-R6&2fwJ0JVSY#Q>&3$zFh|@;#%0qeF=j5Ajq@4i0tIIW z&}sk$&fGwoJpe&u-JeGLi^r?dO`m=y(QO{@h zQqAC7$rvz&5+mo3IqE?h=a~6m>%r5Quapvzq;{y~p zJpyXOBgD9VrW7@#p6l7O?o3feml(DtSL>D^R) zZUY%T2b0-vBAFN7VB;M88!~HuOXi4KcI6aRQ&h|XQ0A?m%j2=l1f0cGP}h(oVfJ`N zz#PpmFC*ieab)zJK<4?^k=g%OjPnkANzbAbmGZHoVRk*mTfm75s_cWVa`l*f$B@xu z5E*?&@seIo#*Y~1rBm!7sF9~~u6Wrj5oICUOuz}CS)jdNIznfzCA(stJ(7$c^e5wN z?lt>eYgbA!kvAR7zYSD&*r1$b|(@;9dcZ^67R0 zXAXJKa|5Sdmj!g578Nwt6d$sXuc&MWezA0Whd`94$h{{?1IwXP4)Tx4obDK%xoFZ_Z zjjHJ_P@R_e5blG@yEjnaJb`l;s%Lb2&=8$&Ct-fV`E^4CUs)=jTk!I}2d&n!f@)bm z@ z_4Dc86+3l2*p|~;o-Sb~oXb_RuLmoifDU^&Te$*FevycC0*nE3Xws8gsWp|Rj2>SM zns)qcYj?^2sd8?N!_w~4v+f-HCF|a$TNZDoNl$I1Uq87euoNgKb6&r26TNrfkUa@o zfdiFA@p{K&mH3b8i!lcoz)V{n8Q@g(vR4ns4r6w;K z>1~ecQR0-<^J|Ndg5fvVUM9g;lbu-){#ghGw(fg>L zh)T5Ljb%lWE;V9L!;Cqk>AV1(rULYF07ZBJbGb9qbSoLAd;in9{)95YqX$J43-dY7YU*k~vrM25 zxh5_IqO0LYZW%oxQ5HOzmk4x{atE*vipUk}sh88$b2tn?!ujEHn`tQLe&vo}nMb&{ zio`xzZ&GG6&ZyN3jnaQy#iVqXE9VT(3tWY$n-)uWDQ|tc{`?fq2F`oQ{;d3aWPg4Hp-(iE{ry>MIPWL> iW8Zci7-kcv6Uzs@r-FtIZ-&5|)J Q1PU{Fy85}Sb4q9e0B4a5jsO4v literal 0 HcmV?d00001 diff --git a/packages/stream_chat_flutter_core/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png b/packages/stream_chat_flutter_core/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..9da19eacad3b03bb08bbddbbf4ac48dd78b3d838 GIT binary patch literal 68 zcmeAS@N?(olHy`uVBq!ia0vp^j3CUx0wlM}@Gt=>Zci7-kcv6Uzs@r-FtIZ-&5|)J Q1PU{Fy85}Sb4q9e0B4a5jsO4v literal 0 HcmV?d00001 diff --git a/packages/stream_chat_flutter_core/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png b/packages/stream_chat_flutter_core/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..9da19eacad3b03bb08bbddbbf4ac48dd78b3d838 GIT binary patch literal 68 zcmeAS@N?(olHy`uVBq!ia0vp^j3CUx0wlM}@Gt=>Zci7-kcv6Uzs@r-FtIZ-&5|)J Q1PU{Fy85}Sb4q9e0B4a5jsO4v literal 0 HcmV?d00001 diff --git a/packages/stream_chat_flutter_core/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md b/packages/stream_chat_flutter_core/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md new file mode 100644 index 00000000..89c2725b --- /dev/null +++ b/packages/stream_chat_flutter_core/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md @@ -0,0 +1,5 @@ +# Launch Screen Assets + +You can customize the launch screen with your own desired assets by replacing the image files in this directory. + +You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images. \ No newline at end of file diff --git a/packages/stream_chat_flutter_core/example/ios/Runner/Base.lproj/LaunchScreen.storyboard b/packages/stream_chat_flutter_core/example/ios/Runner/Base.lproj/LaunchScreen.storyboard new file mode 100644 index 00000000..f2e259c7 --- /dev/null +++ b/packages/stream_chat_flutter_core/example/ios/Runner/Base.lproj/LaunchScreen.storyboard @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/stream_chat_flutter_core/example/ios/Runner/Base.lproj/Main.storyboard b/packages/stream_chat_flutter_core/example/ios/Runner/Base.lproj/Main.storyboard new file mode 100644 index 00000000..f3c28516 --- /dev/null +++ b/packages/stream_chat_flutter_core/example/ios/Runner/Base.lproj/Main.storyboard @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/stream_chat_flutter_core/example/ios/Runner/Info.plist b/packages/stream_chat_flutter_core/example/ios/Runner/Info.plist new file mode 100644 index 00000000..a060db61 --- /dev/null +++ b/packages/stream_chat_flutter_core/example/ios/Runner/Info.plist @@ -0,0 +1,45 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + example + CFBundlePackageType + APPL + CFBundleShortVersionString + $(FLUTTER_BUILD_NAME) + CFBundleSignature + ???? + CFBundleVersion + $(FLUTTER_BUILD_NUMBER) + LSRequiresIPhoneOS + + UILaunchStoryboardName + LaunchScreen + UIMainStoryboardFile + Main + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UIViewControllerBasedStatusBarAppearance + + + diff --git a/packages/stream_chat_flutter_core/example/ios/Runner/Runner-Bridging-Header.h b/packages/stream_chat_flutter_core/example/ios/Runner/Runner-Bridging-Header.h new file mode 100644 index 00000000..308a2a56 --- /dev/null +++ b/packages/stream_chat_flutter_core/example/ios/Runner/Runner-Bridging-Header.h @@ -0,0 +1 @@ +#import "GeneratedPluginRegistrant.h" diff --git a/packages/stream_chat_flutter_core/example/lib/main.dart b/packages/stream_chat_flutter_core/example/lib/main.dart new file mode 100644 index 00000000..11655b66 --- /dev/null +++ b/packages/stream_chat_flutter_core/example/lib/main.dart @@ -0,0 +1,117 @@ +import 'package:flutter/material.dart'; + +void main() { + runApp(MyApp()); +} + +class MyApp extends StatelessWidget { + // This widget is the root of your application. + @override + Widget build(BuildContext context) { + return MaterialApp( + title: 'Flutter Demo', + theme: ThemeData( + // This is the theme of your application. + // + // Try running your application with "flutter run". You'll see the + // application has a blue toolbar. Then, without quitting the app, try + // changing the primarySwatch below to Colors.green and then invoke + // "hot reload" (press "r" in the console where you ran "flutter run", + // or simply save your changes to "hot reload" in a Flutter IDE). + // Notice that the counter didn't reset back to zero; the application + // is not restarted. + primarySwatch: Colors.blue, + // This makes the visual density adapt to the platform that you run + // the app on. For desktop platforms, the controls will be smaller and + // closer together (more dense) than on mobile platforms. + visualDensity: VisualDensity.adaptivePlatformDensity, + ), + home: MyHomePage(title: 'Flutter Demo Home Page'), + ); + } +} + +class MyHomePage extends StatefulWidget { + MyHomePage({Key key, this.title}) : super(key: key); + + // This widget is the home page of your application. It is stateful, meaning + // that it has a State object (defined below) that contains fields that affect + // how it looks. + + // This class is the configuration for the state. It holds the values (in this + // case the title) provided by the parent (in this case the App widget) and + // used by the build method of the State. Fields in a Widget subclass are + // always marked "final". + + final String title; + + @override + _MyHomePageState createState() => _MyHomePageState(); +} + +class _MyHomePageState extends State { + int _counter = 0; + + void _incrementCounter() { + setState(() { + // This call to setState tells the Flutter framework that something has + // changed in this State, which causes it to rerun the build method below + // so that the display can reflect the updated values. If we changed + // _counter without calling setState(), then the build method would not be + // called again, and so nothing would appear to happen. + _counter++; + }); + } + + @override + Widget build(BuildContext context) { + // This method is rerun every time setState is called, for instance as done + // by the _incrementCounter method above. + // + // The Flutter framework has been optimized to make rerunning build methods + // fast, so that you can just rebuild anything that needs updating rather + // than having to individually change instances of widgets. + return Scaffold( + appBar: AppBar( + // Here we take the value from the MyHomePage object that was created by + // the App.build method, and use it to set our appbar title. + title: Text(widget.title), + ), + body: Center( + // Center is a layout widget. It takes a single child and positions it + // in the middle of the parent. + child: Column( + // Column is also a layout widget. It takes a list of children and + // arranges them vertically. By default, it sizes itself to fit its + // children horizontally, and tries to be as tall as its parent. + // + // Invoke "debug painting" (press "p" in the console, choose the + // "Toggle Debug Paint" action from the Flutter Inspector in Android + // Studio, or the "Toggle Debug Paint" command in Visual Studio Code) + // to see the wireframe for each widget. + // + // Column has various properties to control how it sizes itself and + // how it positions its children. Here we use mainAxisAlignment to + // center the children vertically; the main axis here is the vertical + // axis because Columns are vertical (the cross axis would be + // horizontal). + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + 'You have pushed the button this many times:', + ), + Text( + '$_counter', + style: Theme.of(context).textTheme.headline4, + ), + ], + ), + ), + floatingActionButton: FloatingActionButton( + onPressed: _incrementCounter, + tooltip: 'Increment', + child: Icon(Icons.add), + ), // This trailing comma makes auto-formatting nicer for build methods. + ); + } +} diff --git a/packages/stream_chat_flutter_core/example/pubspec.yaml b/packages/stream_chat_flutter_core/example/pubspec.yaml new file mode 100644 index 00000000..b0071176 --- /dev/null +++ b/packages/stream_chat_flutter_core/example/pubspec.yaml @@ -0,0 +1,77 @@ +name: example +description: Example app for testing stream_chat_flutter_core + +# The following line prevents the package from being accidentally published to +# pub.dev using `pub publish`. This is preferred for private packages. +publish_to: 'none' # Remove this line if you wish to publish to pub.dev + +# The following defines the version and build number for your application. +# A version number is three numbers separated by dots, like 1.2.43 +# followed by an optional build number separated by a +. +# Both the version and the builder number may be overridden in flutter +# build by specifying --build-name and --build-number, respectively. +# In Android, build-name is used as versionName while build-number used as versionCode. +# Read more about Android versioning at https://developer.android.com/studio/publish/versioning +# In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion. +# Read more about iOS versioning at +# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html +version: 1.0.0+1 + +environment: + sdk: ">=2.7.0 <3.0.0" + +dependencies: + flutter: + sdk: flutter + stream_chat_flutter_core: + path: ../ + + # The following adds the Cupertino Icons font to your application. + # Use with the CupertinoIcons class for iOS style icons. + cupertino_icons: ^1.0.0 + +dev_dependencies: + flutter_test: + sdk: flutter + +# For information on the generic Dart part of this file, see the +# following page: https://dart.dev/tools/pub/pubspec + +# The following section is specific to Flutter. +flutter: + + # The following line ensures that the Material Icons font is + # included with your application, so that you can use the icons in + # the material Icons class. + uses-material-design: true + + # To add assets to your application, add an assets section, like this: + # assets: + # - images/a_dot_burr.jpeg + # - images/a_dot_ham.jpeg + + # An image asset can refer to one or more resolution-specific "variants", see + # https://flutter.dev/assets-and-images/#resolution-aware. + + # For details regarding adding assets from package dependencies, see + # https://flutter.dev/assets-and-images/#from-packages + + # To add custom fonts to your application, add a fonts section here, + # in this "flutter" section. Each entry in this list should have a + # "family" key with the font family name, and a "fonts" key with a + # list giving the asset and other descriptors for the font. For + # example: + # fonts: + # - family: Schyler + # fonts: + # - asset: fonts/Schyler-Regular.ttf + # - asset: fonts/Schyler-Italic.ttf + # style: italic + # - family: Trajan Pro + # fonts: + # - asset: fonts/TrajanPro.ttf + # - asset: fonts/TrajanPro_Bold.ttf + # weight: 700 + # + # For details regarding fonts from package dependencies, + # see https://flutter.dev/custom-fonts/#from-packages diff --git a/packages/stream_chat_flutter_core/example/test/widget_test.dart b/packages/stream_chat_flutter_core/example/test/widget_test.dart new file mode 100644 index 00000000..747db1da --- /dev/null +++ b/packages/stream_chat_flutter_core/example/test/widget_test.dart @@ -0,0 +1,30 @@ +// This is a basic Flutter widget test. +// +// To perform an interaction with a widget in your test, use the WidgetTester +// utility that Flutter provides. For example, you can send tap and scroll +// gestures. You can also use WidgetTester to find child widgets in the widget +// tree, read text, and verify that the values of widget properties are correct. + +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import 'package:example/main.dart'; + +void main() { + testWidgets('Counter increments smoke test', (WidgetTester tester) async { + // Build our app and trigger a frame. + await tester.pumpWidget(MyApp()); + + // Verify that our counter starts at 0. + expect(find.text('0'), findsOneWidget); + expect(find.text('1'), findsNothing); + + // Tap the '+' icon and trigger a frame. + await tester.tap(find.byIcon(Icons.add)); + await tester.pump(); + + // Verify that our counter has incremented. + expect(find.text('0'), findsNothing); + expect(find.text('1'), findsOneWidget); + }); +} From 82acc9767586392a20a95e4f1f6960b82f7d41c2 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Fri, 22 Jan 2021 17:22:18 +0100 Subject: [PATCH 07/39] fix formatting --- .../app/src/main/java/example/example/MainActivity.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/stream_chat_flutter_core/example/android/app/src/main/java/example/example/MainActivity.java b/packages/stream_chat_flutter_core/example/android/app/src/main/java/example/example/MainActivity.java index e30c2bec..334c0856 100644 --- a/packages/stream_chat_flutter_core/example/android/app/src/main/java/example/example/MainActivity.java +++ b/packages/stream_chat_flutter_core/example/android/app/src/main/java/example/example/MainActivity.java @@ -2,5 +2,4 @@ package example.example; import io.flutter.embedding.android.FlutterActivity; -public class MainActivity extends FlutterActivity { -} +public class MainActivity extends FlutterActivity {} From 88777da2a7d59a08aead82c7a5f56a096edfd9a1 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Fri, 22 Jan 2021 17:23:50 +0100 Subject: [PATCH 08/39] fix formatting --- .../test/stream_chat_flutter_core_test.dart | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/stream_chat_flutter_core/test/stream_chat_flutter_core_test.dart b/packages/stream_chat_flutter_core/test/stream_chat_flutter_core_test.dart index 3e26a916..ee9468ab 100644 --- a/packages/stream_chat_flutter_core/test/stream_chat_flutter_core_test.dart +++ b/packages/stream_chat_flutter_core/test/stream_chat_flutter_core_test.dart @@ -1,5 +1,3 @@ import 'package:flutter_test/flutter_test.dart'; -import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; - void main() {} From 1bb6b0438142baf6084bed6121881454f6865190 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Fri, 22 Jan 2021 17:35:30 +0100 Subject: [PATCH 09/39] fix analyze --- .../stream_chat_flutter_core/lib/src/message_list_view.dart | 2 +- .../lib/src/message_search_list_view.dart | 1 - packages/stream_chat_flutter_core/lib/src/user_list_view.dart | 4 ++-- .../test/stream_chat_flutter_core_test.dart | 2 -- 4 files changed, 3 insertions(+), 6 deletions(-) diff --git a/packages/stream_chat_flutter_core/lib/src/message_list_view.dart b/packages/stream_chat_flutter_core/lib/src/message_list_view.dart index d0a59266..6e0c1b2b 100644 --- a/packages/stream_chat_flutter_core/lib/src/message_list_view.dart +++ b/packages/stream_chat_flutter_core/lib/src/message_list_view.dart @@ -58,7 +58,7 @@ class MessageListView extends StatefulWidget { this.messageListController, }) : super(key: key); - MessageListController messageListController; + final MessageListController messageListController; final Widget Function(BuildContext, List) messageListBuilder; diff --git a/packages/stream_chat_flutter_core/lib/src/message_search_list_view.dart b/packages/stream_chat_flutter_core/lib/src/message_search_list_view.dart index f27f7c8e..94aedeba 100644 --- a/packages/stream_chat_flutter_core/lib/src/message_search_list_view.dart +++ b/packages/stream_chat_flutter_core/lib/src/message_search_list_view.dart @@ -2,7 +2,6 @@ import 'dart:convert'; import 'package:flutter/material.dart'; import 'package:stream_chat/stream_chat.dart'; -import 'lazy_load_scroll_view.dart'; import 'message_search_bloc.dart'; /// Callback called when tapping on a user diff --git a/packages/stream_chat_flutter_core/lib/src/user_list_view.dart b/packages/stream_chat_flutter_core/lib/src/user_list_view.dart index 1b841333..6fe03577 100644 --- a/packages/stream_chat_flutter_core/lib/src/user_list_view.dart +++ b/packages/stream_chat_flutter_core/lib/src/user_list_view.dart @@ -154,12 +154,12 @@ class _UserListViewState extends State temp = users..sort((curr, next) => curr.name.compareTo(next.name)); } final groupedUsers = >{}; - for (var e in temp) { + for (final e in temp) { final alphabet = e.name[0]?.toUpperCase(); groupedUsers[alphabet] = [...groupedUsers[alphabet] ?? [], e]; } final items = []; - for (var key in groupedUsers.keys) { + for (final key in groupedUsers.keys) { items.add(ListHeaderItem(key)); items.addAll(groupedUsers[key].map((e) => ListUserItem(e))); } diff --git a/packages/stream_chat_flutter_core/test/stream_chat_flutter_core_test.dart b/packages/stream_chat_flutter_core/test/stream_chat_flutter_core_test.dart index ee9468ab..ab73b3a2 100644 --- a/packages/stream_chat_flutter_core/test/stream_chat_flutter_core_test.dart +++ b/packages/stream_chat_flutter_core/test/stream_chat_flutter_core_test.dart @@ -1,3 +1 @@ -import 'package:flutter_test/flutter_test.dart'; - void main() {} From c5caf5bfc36e7049cadb0a7955cbd344ff48370c Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Mon, 25 Jan 2021 12:54:55 +0530 Subject: [PATCH 10/39] rfac: Added core prefixes --- ..._view.dart => channel_list_view_core.dart} | 18 +++++++++--------- .../lib/src/channels_bloc.dart | 6 +++--- ..._view.dart => message_list_view_core.dart} | 10 +++++----- .../lib/src/message_search_bloc.dart | 4 ++-- ...art => message_search_list_view_core.dart} | 14 +++++++------- ...stream_chat.dart => stream_chat_core.dart} | 19 ++++++++++--------- ...ist_view.dart => user_list_view_core.dart} | 10 +++++----- .../lib/src/users_bloc.dart | 4 ++-- .../lib/stream_chat_flutter_core.dart | 10 +++++----- 9 files changed, 48 insertions(+), 47 deletions(-) rename packages/stream_chat_flutter_core/lib/src/{channel_list_view.dart => channel_list_view_core.dart} (92%) rename packages/stream_chat_flutter_core/lib/src/{message_list_view.dart => message_list_view_core.dart} (94%) rename packages/stream_chat_flutter_core/lib/src/{message_search_list_view.dart => message_search_list_view_core.dart} (93%) rename packages/stream_chat_flutter_core/lib/src/{stream_chat.dart => stream_chat_core.dart} (88%) rename packages/stream_chat_flutter_core/lib/src/{user_list_view.dart => user_list_view_core.dart} (96%) diff --git a/packages/stream_chat_flutter_core/lib/src/channel_list_view.dart b/packages/stream_chat_flutter_core/lib/src/channel_list_view_core.dart similarity index 92% rename from packages/stream_chat_flutter_core/lib/src/channel_list_view.dart rename to packages/stream_chat_flutter_core/lib/src/channel_list_view_core.dart index 38417190..f1b7d258 100644 --- a/packages/stream_chat_flutter_core/lib/src/channel_list_view.dart +++ b/packages/stream_chat_flutter_core/lib/src/channel_list_view_core.dart @@ -6,7 +6,7 @@ import 'package:flutter/material.dart'; import 'package:stream_chat/stream_chat.dart'; import 'package:stream_chat_flutter_core/src/channels_bloc.dart'; -import 'stream_chat.dart'; +import 'stream_chat_core.dart'; /// ![screenshot](https://raw.githubusercontent.com/GetStream/stream-chat-flutter/master/screenshots/channel_list_view.png) /// ![screenshot](https://raw.githubusercontent.com/GetStream/stream-chat-flutter/master/screenshots/channel_list_view_paint.png) @@ -36,14 +36,14 @@ import 'stream_chat.dart'; /// ``` /// /// -/// Make sure to have a [StreamChat] ancestor in order to provide the information about the channels. +/// Make sure to have a [StreamChatCore] ancestor in order to provide the information about the channels. /// The widget uses a [ListView.custom] to render the list of channels. /// /// The widget components render the ui based on the first ancestor of type [StreamChatTheme]. /// Modify it to change the widget appearance. -class ChannelListView extends StatefulWidget { +class ChannelListViewCore extends StatefulWidget { /// Instantiate a new ChannelListView - ChannelListView({ + ChannelListViewCore({ Key key, this.filter, this.options, @@ -96,10 +96,10 @@ class ChannelListView extends StatefulWidget { final bool pullToRefresh; @override - _ChannelListViewState createState() => _ChannelListViewState(); + _ChannelListViewCoreState createState() => _ChannelListViewCoreState(); } -class _ChannelListViewState extends State +class _ChannelListViewCoreState extends State with WidgetsBindingObserver { @override Widget build(BuildContext context) { @@ -197,7 +197,7 @@ class _ChannelListViewState extends State options: widget.options, ); - final client = StreamChat.of(context).client; + final client = StreamChatCore.of(context).client; _subscription = client .on( @@ -221,7 +221,7 @@ class _ChannelListViewState extends State } @override - void didUpdateWidget(ChannelListView oldWidget) { + void didUpdateWidget(ChannelListViewCore oldWidget) { super.didUpdateWidget(oldWidget); if (widget.filter?.toString() != oldWidget.filter?.toString() || @@ -247,7 +247,7 @@ class _ChannelListViewState extends State } } -/// Controller used for paginating data in [ChannelListView] +/// Controller used for paginating data in [ChannelListViewCore] class ChannelListController { VoidCallback paginateData; } diff --git a/packages/stream_chat_flutter_core/lib/src/channels_bloc.dart b/packages/stream_chat_flutter_core/lib/src/channels_bloc.dart index a9ab1682..2eef0f8a 100644 --- a/packages/stream_chat_flutter_core/lib/src/channels_bloc.dart +++ b/packages/stream_chat_flutter_core/lib/src/channels_bloc.dart @@ -3,7 +3,7 @@ import 'dart:async'; import 'package:flutter/material.dart'; import 'package:rxdart/rxdart.dart'; import 'package:stream_chat/stream_chat.dart'; -import 'package:stream_chat_flutter_core/src/stream_chat.dart'; +import 'package:stream_chat_flutter_core/src/stream_chat_core.dart'; /// Widget dedicated to the management of a channel list with pagination class ChannelsBloc extends StatefulWidget { @@ -79,7 +79,7 @@ class ChannelsBlocState extends State Map options, bool onlyOffline = false, }) async { - final client = StreamChat.of(context).client; + final client = StreamChatCore.of(context).client; if (client.state?.user == null || _queryChannelsLoadingController.value == true) { @@ -120,7 +120,7 @@ class ChannelsBlocState extends State void initState() { super.initState(); - final client = StreamChat.of(context).client; + final client = StreamChatCore.of(context).client; if (!widget.lockChannelsOrder) { _subscriptions.add(client.on(EventType.messageNew).listen((e) { diff --git a/packages/stream_chat_flutter_core/lib/src/message_list_view.dart b/packages/stream_chat_flutter_core/lib/src/message_list_view_core.dart similarity index 94% rename from packages/stream_chat_flutter_core/lib/src/message_list_view.dart rename to packages/stream_chat_flutter_core/lib/src/message_list_view_core.dart index d0a59266..859b3513 100644 --- a/packages/stream_chat_flutter_core/lib/src/message_list_view.dart +++ b/packages/stream_chat_flutter_core/lib/src/message_list_view_core.dart @@ -45,9 +45,9 @@ import 'stream_channel.dart'; /// /// The widget components render the ui based on the first ancestor of type [StreamChatTheme]. /// Modify it to change the widget appearance. -class MessageListView extends StatefulWidget { +class MessageListViewCore extends StatefulWidget { /// Instantiate a new MessageListView - MessageListView({ + MessageListViewCore({ Key key, this.showScrollToBottom = true, this.parentMessage, @@ -58,7 +58,7 @@ class MessageListView extends StatefulWidget { this.messageListController, }) : super(key: key); - MessageListController messageListController; + final MessageListController messageListController; final Widget Function(BuildContext, List) messageListBuilder; @@ -78,10 +78,10 @@ class MessageListView extends StatefulWidget { final Widget Function(DateTime) dateDividerBuilder; @override - _MessageListViewState createState() => _MessageListViewState(); + _MessageListViewCoreState createState() => _MessageListViewCoreState(); } -class _MessageListViewState extends State { +class _MessageListViewCoreState extends State { StreamChannelState streamChannel; bool get _upToDate => streamChannel.channel.state.isUpToDate; diff --git a/packages/stream_chat_flutter_core/lib/src/message_search_bloc.dart b/packages/stream_chat_flutter_core/lib/src/message_search_bloc.dart index 67b6455d..ca6a983d 100644 --- a/packages/stream_chat_flutter_core/lib/src/message_search_bloc.dart +++ b/packages/stream_chat_flutter_core/lib/src/message_search_bloc.dart @@ -2,7 +2,7 @@ import 'package:flutter/material.dart'; import 'package:rxdart/rxdart.dart'; import 'package:stream_chat/stream_chat.dart'; -import 'stream_chat.dart'; +import 'stream_chat_core.dart'; /// Widget dedicated to the management of a message list with pagination class MessageSearchBloc extends StatefulWidget { @@ -122,7 +122,7 @@ class MessageSearchBlocState extends State String query, PaginationParams pagination, }) { - final client = StreamChat.of(context).client; + final client = StreamChatCore.of(context).client; return client.search( filter, sort, diff --git a/packages/stream_chat_flutter_core/lib/src/message_search_list_view.dart b/packages/stream_chat_flutter_core/lib/src/message_search_list_view_core.dart similarity index 93% rename from packages/stream_chat_flutter_core/lib/src/message_search_list_view.dart rename to packages/stream_chat_flutter_core/lib/src/message_search_list_view_core.dart index f27f7c8e..4486c1f5 100644 --- a/packages/stream_chat_flutter_core/lib/src/message_search_list_view.dart +++ b/packages/stream_chat_flutter_core/lib/src/message_search_list_view_core.dart @@ -2,7 +2,6 @@ import 'dart:convert'; import 'package:flutter/material.dart'; import 'package:stream_chat/stream_chat.dart'; -import 'lazy_load_scroll_view.dart'; import 'message_search_bloc.dart'; /// Callback called when tapping on a user @@ -12,7 +11,7 @@ typedef MessageSearchItemTapCallback = void Function(GetMessageResponse); typedef MessageSearchItemBuilder = Widget Function( BuildContext, GetMessageResponse); -/// Builder used when [MessageSearchListView] is empty +/// Builder used when [MessageSearchListViewCore] is empty typedef EmptyMessageSearchBuilder = Widget Function( BuildContext context, String searchQuery); @@ -44,9 +43,9 @@ typedef EmptyMessageSearchBuilder = Widget Function( /// /// The widget components render the ui based on the first ancestor of type [StreamChatTheme]. /// Modify it to change the widget appearance. -class MessageSearchListView extends StatefulWidget { +class MessageSearchListViewCore extends StatefulWidget { /// Instantiate a new MessageSearchListView - const MessageSearchListView({ + const MessageSearchListViewCore({ Key key, this.messageQuery, this.filters, @@ -98,10 +97,11 @@ class MessageSearchListView extends StatefulWidget { final WidgetBuilder loadingBuilder; @override - _MessageSearchListViewState createState() => _MessageSearchListViewState(); + _MessageSearchListViewCoreState createState() => + _MessageSearchListViewCoreState(); } -class _MessageSearchListViewState extends State { +class _MessageSearchListViewCoreState extends State { @override void initState() { super.initState(); @@ -167,7 +167,7 @@ class _MessageSearchListViewState extends State { } @override - void didUpdateWidget(MessageSearchListView oldWidget) { + void didUpdateWidget(MessageSearchListViewCore oldWidget) { super.didUpdateWidget(oldWidget); if (widget.filters?.toString() != oldWidget.filters?.toString() || jsonEncode(widget.sortOptions) != jsonEncode(oldWidget.sortOptions) || diff --git a/packages/stream_chat_flutter_core/lib/src/stream_chat.dart b/packages/stream_chat_flutter_core/lib/src/stream_chat_core.dart similarity index 88% rename from packages/stream_chat_flutter_core/lib/src/stream_chat.dart rename to packages/stream_chat_flutter_core/lib/src/stream_chat_core.dart index 17ec62b6..e8312638 100644 --- a/packages/stream_chat_flutter_core/lib/src/stream_chat.dart +++ b/packages/stream_chat_flutter_core/lib/src/stream_chat_core.dart @@ -24,12 +24,12 @@ import 'package:stream_chat/stream_chat.dart'; /// } /// } /// -/// Use [StreamChat.of] to get the current [StreamChatState] instance. -class StreamChat extends StatefulWidget { +/// Use [StreamChatCore.of] to get the current [StreamChatCoreState] instance. +class StreamChatCore extends StatefulWidget { final Client client; final Widget child; - StreamChat({ + StreamChatCore({ Key key, @required this.client, @required this.child, @@ -38,13 +38,13 @@ class StreamChat extends StatefulWidget { ); @override - StreamChatState createState() => StreamChatState(); + StreamChatCoreState createState() => StreamChatCoreState(); - /// Use this method to get the current [StreamChatState] instance - static StreamChatState of(BuildContext context) { - StreamChatState streamChatState; + /// Use this method to get the current [StreamChatCoreState] instance + static StreamChatCoreState of(BuildContext context) { + StreamChatCoreState streamChatState; - streamChatState = context.findAncestorStateOfType(); + streamChatState = context.findAncestorStateOfType(); if (streamChatState == null) { throw Exception( @@ -56,7 +56,8 @@ class StreamChat extends StatefulWidget { } /// The current state of the StreamChat widget -class StreamChatState extends State with WidgetsBindingObserver { +class StreamChatCoreState extends State + with WidgetsBindingObserver { Client get client => widget.client; Timer _disconnectTimer; diff --git a/packages/stream_chat_flutter_core/lib/src/user_list_view.dart b/packages/stream_chat_flutter_core/lib/src/user_list_view_core.dart similarity index 96% rename from packages/stream_chat_flutter_core/lib/src/user_list_view.dart rename to packages/stream_chat_flutter_core/lib/src/user_list_view_core.dart index 1b841333..ae1cefdc 100644 --- a/packages/stream_chat_flutter_core/lib/src/user_list_view.dart +++ b/packages/stream_chat_flutter_core/lib/src/user_list_view_core.dart @@ -35,9 +35,9 @@ import 'package:stream_chat_flutter_core/src/users_bloc.dart'; /// /// The widget components render the ui based on the first ancestor of type [StreamChatTheme]. /// Modify it to change the widget appearance. -class UserListView extends StatefulWidget { +class UserListViewCore extends StatefulWidget { /// Instantiate a new UserListView - const UserListView({ + const UserListViewCore({ Key key, this.filter, this.options, @@ -98,10 +98,10 @@ class UserListView extends StatefulWidget { final bool groupAlphabetically; @override - _UserListViewState createState() => _UserListViewState(); + _UserListViewCoreState createState() => _UserListViewCoreState(); } -class _UserListViewState extends State +class _UserListViewCoreState extends State with WidgetsBindingObserver { @override void initState() { @@ -217,7 +217,7 @@ class _UserListViewState extends State } @override - void didUpdateWidget(UserListView oldWidget) { + void didUpdateWidget(UserListViewCore oldWidget) { super.didUpdateWidget(oldWidget); if (widget.filter?.toString() != oldWidget.filter?.toString() || jsonEncode(widget.sort) != jsonEncode(oldWidget.sort) || diff --git a/packages/stream_chat_flutter_core/lib/src/users_bloc.dart b/packages/stream_chat_flutter_core/lib/src/users_bloc.dart index 91cbaf80..489461ef 100644 --- a/packages/stream_chat_flutter_core/lib/src/users_bloc.dart +++ b/packages/stream_chat_flutter_core/lib/src/users_bloc.dart @@ -2,7 +2,7 @@ import 'package:flutter/material.dart'; import 'package:rxdart/rxdart.dart'; import 'package:stream_chat/stream_chat.dart'; -import 'stream_chat.dart'; +import 'stream_chat_core.dart'; /// Widget dedicated to the management of a users list with pagination class UsersBloc extends StatefulWidget { @@ -56,7 +56,7 @@ class UsersBlocState extends State Map options, PaginationParams pagination, }) async { - final client = StreamChat.of(context).client; + final client = StreamChatCore.of(context).client; if (client.state?.user == null || _queryUsersLoadingController.value == true) { diff --git a/packages/stream_chat_flutter_core/lib/stream_chat_flutter_core.dart b/packages/stream_chat_flutter_core/lib/stream_chat_flutter_core.dart index 638f71be..75424b4f 100644 --- a/packages/stream_chat_flutter_core/lib/stream_chat_flutter_core.dart +++ b/packages/stream_chat_flutter_core/lib/stream_chat_flutter_core.dart @@ -1,12 +1,12 @@ library stream_chat_flutter_core; -export 'src/channel_list_view.dart'; +export 'src/channel_list_view_core.dart'; export 'src/channels_bloc.dart'; export 'src/lazy_load_scroll_view.dart'; -export 'src/message_list_view.dart'; +export 'src/message_list_view_core.dart'; export 'src/message_search_bloc.dart'; -export 'src/message_search_list_view.dart'; +export 'src/message_search_list_view_core.dart'; export 'src/stream_channel.dart'; -export 'src/stream_chat.dart'; -export 'src/user_list_view.dart'; +export 'src/stream_chat_core.dart'; +export 'src/user_list_view_core.dart'; export 'src/users_bloc.dart'; From e9fd5c96f5a3f381666c8a80aa156e66f29559fc Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Mon, 25 Jan 2021 17:29:25 +0530 Subject: [PATCH 11/39] feat: Added load data to controllers --- .../lib/src/channel_list_view_core.dart | 13 +++++++++++++ .../lib/src/message_search_list_view_core.dart | 14 ++++++++++++++ .../lib/src/user_list_view_core.dart | 13 +++++++++++++ 3 files changed, 40 insertions(+) diff --git a/packages/stream_chat_flutter_core/lib/src/channel_list_view_core.dart b/packages/stream_chat_flutter_core/lib/src/channel_list_view_core.dart index f1b7d258..b03b523e 100644 --- a/packages/stream_chat_flutter_core/lib/src/channel_list_view_core.dart +++ b/packages/stream_chat_flutter_core/lib/src/channel_list_view_core.dart @@ -168,6 +168,17 @@ class _ChannelListViewCoreState extends State return widget.errorBuilder(snapshot.error); } + void loadData() { + final channelsBloc = ChannelsBloc.of(context); + + channelsBloc.queryChannels( + filter: widget.filter, + sortOptions: widget.sort, + paginationParams: widget.pagination, + options: widget.options, + ); + } + void paginateData() { final channelsBloc = ChannelsBloc.of(context); @@ -216,6 +227,7 @@ class _ChannelListViewCoreState extends State }); if (widget.channelListController != null) { + widget.channelListController.loadData = loadData; widget.channelListController.paginateData = paginateData; } } @@ -249,5 +261,6 @@ class _ChannelListViewCoreState extends State /// Controller used for paginating data in [ChannelListViewCore] class ChannelListController { + VoidCallback loadData; VoidCallback paginateData; } diff --git a/packages/stream_chat_flutter_core/lib/src/message_search_list_view_core.dart b/packages/stream_chat_flutter_core/lib/src/message_search_list_view_core.dart index 4486c1f5..99245d37 100644 --- a/packages/stream_chat_flutter_core/lib/src/message_search_list_view_core.dart +++ b/packages/stream_chat_flutter_core/lib/src/message_search_list_view_core.dart @@ -115,6 +115,7 @@ class _MessageSearchListViewCoreState extends State { ); if (widget.messageSearchListController != null) { + widget.messageSearchListController.loadData = loadData; widget.messageSearchListController.paginateData = paginateData; } } @@ -152,6 +153,18 @@ class _MessageSearchListViewCoreState extends State { ); } + void loadData() { + final messageSearchBloc = MessageSearchBloc.of(context); + + messageSearchBloc.search( + filter: widget.filters, + sort: widget.sortOptions, + query: widget.messageQuery, + pagination: widget.paginationParams, + messageFilter: widget.messageFilters, + ); + } + void paginateData() { final messageSearchBloc = MessageSearchBloc.of(context); @@ -190,5 +203,6 @@ class _MessageSearchListViewCoreState extends State { /// Controller used for paginating data in [ChannelListView] class MessageSearchListController { + VoidCallback loadData; VoidCallback paginateData; } diff --git a/packages/stream_chat_flutter_core/lib/src/user_list_view_core.dart b/packages/stream_chat_flutter_core/lib/src/user_list_view_core.dart index 41d5d668..b4e0b6c4 100644 --- a/packages/stream_chat_flutter_core/lib/src/user_list_view_core.dart +++ b/packages/stream_chat_flutter_core/lib/src/user_list_view_core.dart @@ -115,6 +115,7 @@ class _UserListViewCoreState extends State ); if (widget.userListController != null) { + widget.userListController.loadData = loadData; widget.userListController.paginateData = paginateData; } } @@ -203,6 +204,17 @@ class _UserListViewCoreState extends State ); } + void loadData() { + final usersBloc = UsersBloc.of(context); + + usersBloc.queryUsers( + filter: widget.filter, + sort: widget.sort, + pagination: widget.pagination, + options: widget.options, + ); + } + void paginateData() { final usersBloc = UsersBloc.of(context); @@ -276,5 +288,6 @@ class ListUserItem extends ListItem { /// Controller used for paginating data in [ChannelListView] class UserListController { + VoidCallback loadData; VoidCallback paginateData; } From 1cbdbf5d6fbbfd0bbd9c7bef32798737d07bcb8a Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Mon, 25 Jan 2021 18:39:06 +0530 Subject: [PATCH 12/39] rfac: Changed names according to convention --- ..._view_core.dart => channel_list_core.dart} | 12 ++++----- ..._view_core.dart => message_list_core.dart} | 8 +++--- ...ore.dart => message_search_list_core.dart} | 26 +++++-------------- ...ist_view_core.dart => user_list_core.dart} | 10 +++---- .../lib/stream_chat_flutter_core.dart | 8 +++--- 5 files changed, 26 insertions(+), 38 deletions(-) rename packages/stream_chat_flutter_core/lib/src/{channel_list_view_core.dart => channel_list_core.dart} (95%) rename packages/stream_chat_flutter_core/lib/src/{message_list_view_core.dart => message_list_core.dart} (95%) rename packages/stream_chat_flutter_core/lib/src/{message_search_list_view_core.dart => message_search_list_core.dart} (86%) rename packages/stream_chat_flutter_core/lib/src/{user_list_view_core.dart => user_list_core.dart} (96%) diff --git a/packages/stream_chat_flutter_core/lib/src/channel_list_view_core.dart b/packages/stream_chat_flutter_core/lib/src/channel_list_core.dart similarity index 95% rename from packages/stream_chat_flutter_core/lib/src/channel_list_view_core.dart rename to packages/stream_chat_flutter_core/lib/src/channel_list_core.dart index b03b523e..627c5131 100644 --- a/packages/stream_chat_flutter_core/lib/src/channel_list_view_core.dart +++ b/packages/stream_chat_flutter_core/lib/src/channel_list_core.dart @@ -41,9 +41,9 @@ import 'stream_chat_core.dart'; /// /// The widget components render the ui based on the first ancestor of type [StreamChatTheme]. /// Modify it to change the widget appearance. -class ChannelListViewCore extends StatefulWidget { +class ChannelListCore extends StatefulWidget { /// Instantiate a new ChannelListView - ChannelListViewCore({ + ChannelListCore({ Key key, this.filter, this.options, @@ -96,10 +96,10 @@ class ChannelListViewCore extends StatefulWidget { final bool pullToRefresh; @override - _ChannelListViewCoreState createState() => _ChannelListViewCoreState(); + _ChannelListCoreState createState() => _ChannelListCoreState(); } -class _ChannelListViewCoreState extends State +class _ChannelListCoreState extends State with WidgetsBindingObserver { @override Widget build(BuildContext context) { @@ -233,7 +233,7 @@ class _ChannelListViewCoreState extends State } @override - void didUpdateWidget(ChannelListViewCore oldWidget) { + void didUpdateWidget(ChannelListCore oldWidget) { super.didUpdateWidget(oldWidget); if (widget.filter?.toString() != oldWidget.filter?.toString() || @@ -259,7 +259,7 @@ class _ChannelListViewCoreState extends State } } -/// Controller used for paginating data in [ChannelListViewCore] +/// Controller used for paginating data in [ChannelListCore] class ChannelListController { VoidCallback loadData; VoidCallback paginateData; diff --git a/packages/stream_chat_flutter_core/lib/src/message_list_view_core.dart b/packages/stream_chat_flutter_core/lib/src/message_list_core.dart similarity index 95% rename from packages/stream_chat_flutter_core/lib/src/message_list_view_core.dart rename to packages/stream_chat_flutter_core/lib/src/message_list_core.dart index 859b3513..e04e1ea8 100644 --- a/packages/stream_chat_flutter_core/lib/src/message_list_view_core.dart +++ b/packages/stream_chat_flutter_core/lib/src/message_list_core.dart @@ -45,9 +45,9 @@ import 'stream_channel.dart'; /// /// The widget components render the ui based on the first ancestor of type [StreamChatTheme]. /// Modify it to change the widget appearance. -class MessageListViewCore extends StatefulWidget { +class MessageListCore extends StatefulWidget { /// Instantiate a new MessageListView - MessageListViewCore({ + MessageListCore({ Key key, this.showScrollToBottom = true, this.parentMessage, @@ -78,10 +78,10 @@ class MessageListViewCore extends StatefulWidget { final Widget Function(DateTime) dateDividerBuilder; @override - _MessageListViewCoreState createState() => _MessageListViewCoreState(); + _MessageListCoreState createState() => _MessageListCoreState(); } -class _MessageListViewCoreState extends State { +class _MessageListCoreState extends State { StreamChannelState streamChannel; bool get _upToDate => streamChannel.channel.state.isUpToDate; diff --git a/packages/stream_chat_flutter_core/lib/src/message_search_list_view_core.dart b/packages/stream_chat_flutter_core/lib/src/message_search_list_core.dart similarity index 86% rename from packages/stream_chat_flutter_core/lib/src/message_search_list_view_core.dart rename to packages/stream_chat_flutter_core/lib/src/message_search_list_core.dart index 99245d37..f011ee53 100644 --- a/packages/stream_chat_flutter_core/lib/src/message_search_list_view_core.dart +++ b/packages/stream_chat_flutter_core/lib/src/message_search_list_core.dart @@ -4,17 +4,6 @@ import 'package:flutter/material.dart'; import 'package:stream_chat/stream_chat.dart'; import 'message_search_bloc.dart'; -/// Callback called when tapping on a user -typedef MessageSearchItemTapCallback = void Function(GetMessageResponse); - -/// Builder used to create a custom [ListUserItem] from a [User] -typedef MessageSearchItemBuilder = Widget Function( - BuildContext, GetMessageResponse); - -/// Builder used when [MessageSearchListViewCore] is empty -typedef EmptyMessageSearchBuilder = Widget Function( - BuildContext context, String searchQuery); - /// /// It shows the list of searched messages. /// @@ -43,9 +32,9 @@ typedef EmptyMessageSearchBuilder = Widget Function( /// /// The widget components render the ui based on the first ancestor of type [StreamChatTheme]. /// Modify it to change the widget appearance. -class MessageSearchListViewCore extends StatefulWidget { +class MessageSearchListCore extends StatefulWidget { /// Instantiate a new MessageSearchListView - const MessageSearchListViewCore({ + const MessageSearchListCore({ Key key, this.messageQuery, this.filters, @@ -89,7 +78,7 @@ class MessageSearchListViewCore extends StatefulWidget { final Widget Function(List) childBuilder; /// The builder used when the channel list is empty. - final EmptyMessageSearchBuilder emptyBuilder; + final WidgetBuilder emptyBuilder; /// The builder that will be used in case of error final Widget Function(Error error) errorBuilder; @@ -97,11 +86,10 @@ class MessageSearchListViewCore extends StatefulWidget { final WidgetBuilder loadingBuilder; @override - _MessageSearchListViewCoreState createState() => - _MessageSearchListViewCoreState(); + _MessageSearchListCoreState createState() => _MessageSearchListCoreState(); } -class _MessageSearchListViewCoreState extends State { +class _MessageSearchListCoreState extends State { @override void initState() { super.initState(); @@ -145,7 +133,7 @@ class _MessageSearchListViewCoreState extends State { final items = snapshot.data; if (items.isEmpty) { - return widget.emptyBuilder(context, widget.messageQuery); + return widget.emptyBuilder(context); } return widget.childBuilder(snapshot.data); @@ -180,7 +168,7 @@ class _MessageSearchListViewCoreState extends State { } @override - void didUpdateWidget(MessageSearchListViewCore oldWidget) { + void didUpdateWidget(MessageSearchListCore oldWidget) { super.didUpdateWidget(oldWidget); if (widget.filters?.toString() != oldWidget.filters?.toString() || jsonEncode(widget.sortOptions) != jsonEncode(oldWidget.sortOptions) || diff --git a/packages/stream_chat_flutter_core/lib/src/user_list_view_core.dart b/packages/stream_chat_flutter_core/lib/src/user_list_core.dart similarity index 96% rename from packages/stream_chat_flutter_core/lib/src/user_list_view_core.dart rename to packages/stream_chat_flutter_core/lib/src/user_list_core.dart index b4e0b6c4..8edf8752 100644 --- a/packages/stream_chat_flutter_core/lib/src/user_list_view_core.dart +++ b/packages/stream_chat_flutter_core/lib/src/user_list_core.dart @@ -35,9 +35,9 @@ import 'package:stream_chat_flutter_core/src/users_bloc.dart'; /// /// The widget components render the ui based on the first ancestor of type [StreamChatTheme]. /// Modify it to change the widget appearance. -class UserListViewCore extends StatefulWidget { +class UserListCore extends StatefulWidget { /// Instantiate a new UserListView - const UserListViewCore({ + const UserListCore({ Key key, this.filter, this.options, @@ -98,10 +98,10 @@ class UserListViewCore extends StatefulWidget { final bool groupAlphabetically; @override - _UserListViewCoreState createState() => _UserListViewCoreState(); + _UserListCoreState createState() => _UserListCoreState(); } -class _UserListViewCoreState extends State +class _UserListCoreState extends State with WidgetsBindingObserver { @override void initState() { @@ -229,7 +229,7 @@ class _UserListViewCoreState extends State } @override - void didUpdateWidget(UserListViewCore oldWidget) { + void didUpdateWidget(UserListCore oldWidget) { super.didUpdateWidget(oldWidget); if (widget.filter?.toString() != oldWidget.filter?.toString() || jsonEncode(widget.sort) != jsonEncode(oldWidget.sort) || diff --git a/packages/stream_chat_flutter_core/lib/stream_chat_flutter_core.dart b/packages/stream_chat_flutter_core/lib/stream_chat_flutter_core.dart index 75424b4f..d82b57fd 100644 --- a/packages/stream_chat_flutter_core/lib/stream_chat_flutter_core.dart +++ b/packages/stream_chat_flutter_core/lib/stream_chat_flutter_core.dart @@ -1,12 +1,12 @@ library stream_chat_flutter_core; -export 'src/channel_list_view_core.dart'; +export 'src/channel_list_core.dart'; export 'src/channels_bloc.dart'; export 'src/lazy_load_scroll_view.dart'; -export 'src/message_list_view_core.dart'; +export 'src/message_list_core.dart'; export 'src/message_search_bloc.dart'; -export 'src/message_search_list_view_core.dart'; +export 'src/message_search_list_core.dart'; export 'src/stream_channel.dart'; export 'src/stream_chat_core.dart'; -export 'src/user_list_view_core.dart'; +export 'src/user_list_core.dart'; export 'src/users_bloc.dart'; From 0f7a16dc6d8dd5be8fb3e662406b641103a16894 Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Mon, 25 Jan 2021 19:29:32 +0530 Subject: [PATCH 13/39] feat: Added channel_list new implementation, fixed upper repo --- .../lib/src/channel_file_display_screen.dart | 1 - .../lib/src/channel_header.dart | 1 - .../lib/src/channel_list_view.dart | 346 +++++++---------- .../lib/src/channel_media_display_screen.dart | 1 - .../flutter_widgets/lib/src/channel_name.dart | 1 - .../lib/src/channels_bloc.dart | 185 --------- .../lib/src/full_screen_media.dart | 1 - .../flutter_widgets/lib/src/image_header.dart | 2 +- .../lib/src/lazy_load_scroll_view.dart | 148 -------- .../lib/src/media_list_view.dart | 1 - .../lib/src/message_actions_modal.dart | 2 +- .../lib/src/message_input.dart | 1 - .../lib/src/message_list_view.dart | 2 - .../lib/src/message_search_bloc.dart | 150 -------- .../lib/src/message_search_list_view.dart | 3 +- .../lib/src/stream_channel.dart | 356 ------------------ .../flutter_widgets/lib/src/stream_chat.dart | 73 +--- .../lib/src/typing_indicator.dart | 2 +- .../lib/src/user_list_view.dart | 41 -- .../flutter_widgets/lib/src/users_bloc.dart | 108 ------ .../lib/stream_chat_flutter.dart | 8 +- packages/flutter_widgets/pubspec.yaml | 4 +- .../lib/src/channel_list_core.dart | 20 +- 23 files changed, 160 insertions(+), 1297 deletions(-) delete mode 100644 packages/flutter_widgets/lib/src/channels_bloc.dart delete mode 100644 packages/flutter_widgets/lib/src/lazy_load_scroll_view.dart delete mode 100644 packages/flutter_widgets/lib/src/message_search_bloc.dart delete mode 100644 packages/flutter_widgets/lib/src/stream_channel.dart delete mode 100644 packages/flutter_widgets/lib/src/users_bloc.dart diff --git a/packages/flutter_widgets/lib/src/channel_file_display_screen.dart b/packages/flutter_widgets/lib/src/channel_file_display_screen.dart index d5aef38c..244729a4 100644 --- a/packages/flutter_widgets/lib/src/channel_file_display_screen.dart +++ b/packages/flutter_widgets/lib/src/channel_file_display_screen.dart @@ -1,5 +1,4 @@ import 'package:flutter/material.dart'; -import 'package:stream_chat_flutter/src/lazy_load_scroll_view.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; class ChannelFileDisplayScreen extends StatefulWidget { diff --git a/packages/flutter_widgets/lib/src/channel_header.dart b/packages/flutter_widgets/lib/src/channel_header.dart index 8b920f23..73ee23a5 100644 --- a/packages/flutter_widgets/lib/src/channel_header.dart +++ b/packages/flutter_widgets/lib/src/channel_header.dart @@ -9,7 +9,6 @@ import 'package:stream_chat_flutter/src/stream_chat_theme.dart'; import '../stream_chat_flutter.dart'; import './channel_name.dart'; import 'channel_image.dart'; -import 'stream_channel.dart'; /// ![screenshot](https://raw.githubusercontent.com/GetStream/stream-chat-flutter/master/screenshots/channel_header.png) /// ![screenshot](https://raw.githubusercontent.com/GetStream/stream-chat-flutter/master/screenshots/channel_header_paint.png) diff --git a/packages/flutter_widgets/lib/src/channel_list_view.dart b/packages/flutter_widgets/lib/src/channel_list_view.dart index 0cc7634b..617b7ce9 100644 --- a/packages/flutter_widgets/lib/src/channel_list_view.dart +++ b/packages/flutter_widgets/lib/src/channel_list_view.dart @@ -5,16 +5,13 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_slidable/flutter_slidable.dart'; import 'package:shimmer/shimmer.dart'; -import 'package:stream_chat/stream_chat.dart'; -import 'package:stream_chat_flutter/src/channels_bloc.dart'; import 'package:stream_chat_flutter/src/stream_svg_icon.dart'; import 'package:stream_chat_flutter/src/utils.dart'; +import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; import '../stream_chat_flutter.dart'; import 'channel_bottom_sheet.dart'; import 'channel_preview.dart'; -import 'stream_channel.dart'; -import 'stream_chat.dart'; /// Callback called when tapping on a channel typedef ChannelTapCallback = void Function(Channel, Widget); @@ -158,168 +155,161 @@ class _ChannelListViewState extends State with WidgetsBindingObserver { final ScrollController _scrollController = ScrollController(); final SlidableController _slideController = SlidableController(); + final ChannelListController _channelListController = ChannelListController(); @override Widget build(BuildContext context) { - final channelsBloc = ChannelsBloc.of(context); - if (!widget.pullToRefresh) { - return _buildListView(channelsBloc); + _channelListController.loadData(); } return RefreshIndicator( onRefresh: () async { - return channelsBloc.queryChannels( - filter: widget.filter, - sortOptions: widget.sort, - paginationParams: widget.pagination, - options: widget.options, - ); + _channelListController.loadData(); }, - child: _buildListView(channelsBloc), + child: ChannelListCore( + channelListController: _channelListController, + listBuilder: (context, list) { + return _buildListView(list); + }, + emptyBuilder: (BuildContext context) { + return _buildEmptyWidget(); + }, + errorBuilder: (Error error) { + return _buildErrorWidget(context); + }, + loadingBuilder: (BuildContext context) { + return _buildLoadingWidget(); + }, + pagination: widget.pagination, + options: widget.options, + sort: widget.sort, + filter: widget.filter, + ), ); } - StreamBuilder> _buildListView( - ChannelsBlocState channelsBlocState, + Widget _buildListView( + List channels, ) { - return StreamBuilder>( - stream: channelsBlocState.channelsStream, - builder: (context, snapshot) { - var child; - if (snapshot.hasError) { - child = _buildErrorWidget( - snapshot, - context, - channelsBlocState, - ); - } else if (!snapshot.hasData) { - child = _buildLoadingWidget(); - } else { - final channels = snapshot.data; + var child; - if (channels.isEmpty && widget.emptyBuilder != null) { - child = widget.emptyBuilder(context); - } - - if (channels.isEmpty && widget.emptyBuilder == null) { - child = LayoutBuilder( - builder: (context, viewportConstraints) { - return SingleChildScrollView( - physics: AlwaysScrollableScrollPhysics(), - child: Stack( - children: [ - ConstrainedBox( - constraints: BoxConstraints( - minHeight: viewportConstraints.maxHeight, - ), - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Padding( - padding: const EdgeInsets.all(8.0), - child: StreamSvgIcon.message( - size: 136, - color: StreamChatTheme.of(context) - .colorTheme - .greyGainsboro, - ), - ), - Padding( - padding: const EdgeInsets.all(8.0), - child: Text( - 'Let’s start chatting!', - style: StreamChatTheme.of(context) - .textTheme - .headline, - ), - ), - Padding( - padding: const EdgeInsets.symmetric( - vertical: 8.0, - horizontal: 52, - ), - child: Text( - 'How about sending your first message to a friend?', - textAlign: TextAlign.center, - style: StreamChatTheme.of(context) - .textTheme - .body - .copyWith( - color: StreamChatTheme.of(context) - .colorTheme - .grey, - ), - ), - ), - ], - ), - ), - if (widget.onStartChatPressed != null) - Positioned( - right: 0, - left: 0, - bottom: 32, - child: Center( - child: FlatButton( - onPressed: widget.onStartChatPressed, - child: Text( - 'Start a chat', - style: StreamChatTheme.of(context) - .textTheme - .bodyBold - .copyWith( - color: StreamChatTheme.of(context) - .colorTheme - .accentBlue, - ), - ), - ), - ), - ), - ], - ), - ); - }, - ); - } - - if (channels.isNotEmpty) { - if (widget.crossAxisCount > 1) { - child = GridView.builder( - padding: widget.padding, - gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( - crossAxisCount: widget.crossAxisCount), - itemCount: channels.length, - physics: AlwaysScrollableScrollPhysics(), - controller: _scrollController, - itemBuilder: (context, index) { - return _gridItemBuilder(context, index, channels); - }, - ); - } else { - child = ListView.separated( - padding: widget.padding, - physics: AlwaysScrollableScrollPhysics(), - itemCount: - channels.isNotEmpty ? channels.length + 1 : channels.length, - separatorBuilder: (_, index) { - if (widget.separatorBuilder != null) { - return widget.separatorBuilder(context, index); - } - return _separatorBuilder(context, index); - }, - itemBuilder: (context, index) { - return _listItemBuilder(context, index, channels); - }, - ); + if (channels.isNotEmpty) { + if (widget.crossAxisCount > 1) { + child = GridView.builder( + padding: widget.padding, + gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: widget.crossAxisCount), + itemCount: channels.length, + physics: AlwaysScrollableScrollPhysics(), + controller: _scrollController, + itemBuilder: (context, index) { + return _gridItemBuilder(context, index, channels); + }, + ); + } else { + child = ListView.separated( + padding: widget.padding, + physics: AlwaysScrollableScrollPhysics(), + itemCount: + channels.isNotEmpty ? channels.length + 1 : channels.length, + separatorBuilder: (_, index) { + if (widget.separatorBuilder != null) { + return widget.separatorBuilder(context, index); } - } - } + return _separatorBuilder(context, index); + }, + itemBuilder: (context, index) { + return _listItemBuilder(context, index, channels); + }, + ); + } + } - return AnimatedSwitcher( - child: child, - duration: Duration(milliseconds: 500), + return AnimatedSwitcher( + child: child, + duration: Duration(milliseconds: 500), + ); + } + + Widget _buildEmptyWidget() { + if (widget.emptyBuilder != null) { + return widget.emptyBuilder(context); + } + + return LayoutBuilder( + builder: (context, viewportConstraints) { + return SingleChildScrollView( + physics: AlwaysScrollableScrollPhysics(), + child: Stack( + children: [ + ConstrainedBox( + constraints: BoxConstraints( + minHeight: viewportConstraints.maxHeight, + ), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Padding( + padding: const EdgeInsets.all(8.0), + child: StreamSvgIcon.message( + size: 136, + color: StreamChatTheme.of(context) + .colorTheme + .greyGainsboro, + ), + ), + Padding( + padding: const EdgeInsets.all(8.0), + child: Text( + 'Let’s start chatting!', + style: StreamChatTheme.of(context).textTheme.headline, + ), + ), + Padding( + padding: const EdgeInsets.symmetric( + vertical: 8.0, + horizontal: 52, + ), + child: Text( + 'How about sending your first message to a friend?', + textAlign: TextAlign.center, + style: StreamChatTheme.of(context) + .textTheme + .body + .copyWith( + color: + StreamChatTheme.of(context).colorTheme.grey, + ), + ), + ), + ], + ), + ), + if (widget.onStartChatPressed != null) + Positioned( + right: 0, + left: 0, + bottom: 32, + child: Center( + child: FlatButton( + onPressed: widget.onStartChatPressed, + child: Text( + 'Start a chat', + style: StreamChatTheme.of(context) + .textTheme + .bodyBold + .copyWith( + color: StreamChatTheme.of(context) + .colorTheme + .accentBlue, + ), + ), + ), + ), + ), + ], + ), ); }, ); @@ -443,27 +433,8 @@ class _ChannelListViewState extends State } Widget _buildErrorWidget( - AsyncSnapshot> snapshot, BuildContext context, - ChannelsBlocState channelsBlocState, ) { - if (snapshot.error is Error) { - print((snapshot.error as Error).stackTrace); - } - - if (widget.errorBuilder != null) { - return widget.errorBuilder(snapshot.error); - } - - var message = snapshot.error.toString(); - if (snapshot.error is DioError) { - final dioError = snapshot.error as DioError; - if (dioError.type == DioErrorType.RESPONSE) { - message = dioError.message; - } else { - message = 'Check your connection and retry'; - } - } return Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, @@ -484,20 +455,9 @@ class _ChannelListViewState extends State ), style: Theme.of(context).textTheme.headline6, ), - Padding( - padding: const EdgeInsets.only( - top: 16.0, - ), - child: Text(message), - ), FlatButton( onPressed: () { - channelsBlocState.queryChannels( - filter: widget.filter, - sortOptions: widget.sort, - paginationParams: widget.pagination, - options: widget.options, - ); + _channelListController.loadData(); }, child: Text('Retry'), ), @@ -714,14 +674,7 @@ class _ChannelListViewState extends State if (_scrollController.position.maxScrollExtent == _scrollController.offset && _scrollController.offset != 0) { - channelsProvider.queryChannels( - filter: widget.filter, - sortOptions: widget.sort, - paginationParams: widget.pagination.copyWith( - offset: channelsProvider.channels?.length ?? 0, - ), - options: widget.options, - ); + _channelListController.paginateData(); } } @@ -759,12 +712,7 @@ class _ChannelListViewState extends State EventType.channelVisible, ) .listen((event) { - channelsBloc.queryChannels( - filter: widget.filter, - sortOptions: widget.sort, - paginationParams: widget.pagination, - options: widget.options, - ); + _channelListController.loadData(); }); } @@ -777,13 +725,7 @@ class _ChannelListViewState extends State widget.pagination?.toJson()?.toString() != oldWidget.pagination?.toJson()?.toString() || widget.options?.toString() != oldWidget.options?.toString()) { - final channelsBloc = ChannelsBloc.of(context); - channelsBloc.queryChannels( - filter: widget.filter, - sortOptions: widget.sort, - paginationParams: widget.pagination, - options: widget.options, - ); + _channelListController.loadData(); } } diff --git a/packages/flutter_widgets/lib/src/channel_media_display_screen.dart b/packages/flutter_widgets/lib/src/channel_media_display_screen.dart index 8b69fac8..85b571bc 100644 --- a/packages/flutter_widgets/lib/src/channel_media_display_screen.dart +++ b/packages/flutter_widgets/lib/src/channel_media_display_screen.dart @@ -1,5 +1,4 @@ import 'package:flutter/material.dart'; -import 'package:stream_chat_flutter/src/lazy_load_scroll_view.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; import 'package:video_player/video_player.dart'; diff --git a/packages/flutter_widgets/lib/src/channel_name.dart b/packages/flutter_widgets/lib/src/channel_name.dart index bf3eff22..1d51f3fc 100644 --- a/packages/flutter_widgets/lib/src/channel_name.dart +++ b/packages/flutter_widgets/lib/src/channel_name.dart @@ -3,7 +3,6 @@ import 'package:flutter/rendering.dart'; import 'package:stream_chat/stream_chat.dart'; import '../stream_chat_flutter.dart'; -import 'stream_channel.dart'; /// It shows the current [Channel] name using a [Text] widget. /// diff --git a/packages/flutter_widgets/lib/src/channels_bloc.dart b/packages/flutter_widgets/lib/src/channels_bloc.dart deleted file mode 100644 index c3c5c6f1..00000000 --- a/packages/flutter_widgets/lib/src/channels_bloc.dart +++ /dev/null @@ -1,185 +0,0 @@ -import 'dart:async'; - -import 'package:flutter/material.dart'; -import 'package:rxdart/rxdart.dart'; -import 'package:stream_chat/stream_chat.dart'; -import 'package:stream_chat_flutter/src/stream_chat.dart'; -import 'package:stream_chat_flutter/stream_chat_flutter.dart'; - -/// Widget dedicated to the management of a channel list with pagination -class ChannelsBloc extends StatefulWidget { - /// The widget child - final Widget child; - - /// Set this to true to prevent channels to be brought to the top of the list when a new message arrives - final bool lockChannelsOrder; - - /// Comparator used to sort the channels when a message.new event is received - final Comparator channelsComparator; - - /// Function used to evaluate if a channel should be added to the list when a message.new event is received - final bool Function(Event) shouldAddChannel; - - /// Instantiate a new ChannelsBloc - const ChannelsBloc({ - Key key, - this.child, - this.lockChannelsOrder = false, - this.channelsComparator, - this.shouldAddChannel, - }) : super(key: key); - - @override - ChannelsBlocState createState() => ChannelsBlocState(); - - /// Use this method to get the current [ChannelsBlocState] instance - static ChannelsBlocState of(BuildContext context) { - ChannelsBlocState streamChatState; - - streamChatState = context.findAncestorStateOfType(); - - if (streamChatState == null) { - throw Exception('You must have a ChannelsBloc widget as ancestor'); - } - - return streamChatState; - } -} - -/// The current state of the [ChannelsBloc] -class ChannelsBlocState extends State - with AutomaticKeepAliveClientMixin { - @override - Widget build(BuildContext context) { - super.build(context); - return widget.child; - } - - /// The current channel list - List get channels => _channelsController.value; - - /// The current channel list as a stream - Stream> get channelsStream => _channelsController.stream; - - final BehaviorSubject _queryChannelsLoadingController = - BehaviorSubject.seeded(false); - - final BehaviorSubject> _channelsController = BehaviorSubject(); - - /// The stream notifying the state of queryChannel call - Stream get queryChannelsLoading => - _queryChannelsLoadingController.stream; - - final List _hiddenChannels = []; - - /// Calls [client.queryChannels] updating [queryChannelsLoading] stream - Future queryChannels({ - Map filter, - List sortOptions, - PaginationParams paginationParams, - Map options, - bool onlyOffline = false, - }) async { - final client = StreamChat.of(context).client; - - if (client.state?.user == null || - _queryChannelsLoadingController.value == true) { - return; - } - _queryChannelsLoadingController.sink.add(true); - - try { - final clear = paginationParams == null || - paginationParams.offset == null || - paginationParams.offset == 0; - final oldChannels = List.from(channels ?? []); - final _channels = await client.queryChannels( - filter: filter, - sort: sortOptions, - options: options, - paginationParams: paginationParams, - onlyOffline: onlyOffline, - ); - - if (clear) { - _channelsController.add(_channels); - } else { - final l = oldChannels + _channels; - _channelsController.add(l); - } - _queryChannelsLoadingController.sink.add(false); - } catch (err, stackTrace) { - print(err); - print(stackTrace); - _queryChannelsLoadingController.addError(err, stackTrace); - } - } - - final List _subscriptions = []; - - @override - void initState() { - super.initState(); - - final client = StreamChat.of(context).client; - - if (!widget.lockChannelsOrder) { - _subscriptions.add(client.on(EventType.messageNew).listen((e) { - final newChannels = List.from(channels ?? []); - final index = newChannels.indexWhere((c) => c.cid == e.cid); - if (index > -1) { - if (index > 0) { - final channel = newChannels.removeAt(index); - newChannels.insert(0, channel); - } - } else if (widget.shouldAddChannel != null && - widget.shouldAddChannel(e)) { - final hiddenIndex = _hiddenChannels.indexWhere((c) => c.cid == e.cid); - if (hiddenIndex > -1) { - newChannels.insert(0, _hiddenChannels[hiddenIndex]); - _hiddenChannels.removeAt(hiddenIndex); - } else { - if (client.state?.channels != null && - client.state?.channels[e.cid] != null) { - newChannels.insert(0, client.state.channels[e.cid]); - } - } - } - - if (widget.channelsComparator != null) { - newChannels.sort(widget.channelsComparator); - } - _channelsController.add(newChannels); - })); - } - - _subscriptions.add(client.on(EventType.channelHidden).listen((event) async { - final newChannels = List.from(channels ?? []); - final channelIndex = newChannels.indexWhere((c) => c.cid == event.cid); - if (channelIndex > -1) { - final channel = newChannels.removeAt(channelIndex); - _hiddenChannels.add(channel); - _channelsController.add(newChannels); - } - })); - - _subscriptions.add(client - .on(EventType.channelDeleted, EventType.notificationRemovedFromChannel) - .listen((e) { - final channel = e.channel; - _channelsController - .add(List.from(channels..removeWhere((c) => c.cid == channel.cid))); - })); - } - - @override - void dispose() { - _channelsController.close(); - _queryChannelsLoadingController.close(); - _subscriptions.forEach((s) => s.cancel()); - super.dispose(); - } - - @override - bool get wantKeepAlive => true; -} diff --git a/packages/flutter_widgets/lib/src/full_screen_media.dart b/packages/flutter_widgets/lib/src/full_screen_media.dart index 473c9a05..98ed3384 100644 --- a/packages/flutter_widgets/lib/src/full_screen_media.dart +++ b/packages/flutter_widgets/lib/src/full_screen_media.dart @@ -6,7 +6,6 @@ import 'package:photo_view/photo_view.dart'; import 'package:stream_chat_flutter/src/image_footer.dart'; import 'package:stream_chat_flutter/src/image_header.dart'; import 'package:video_player/video_player.dart'; -import 'stream_channel.dart'; import '../stream_chat_flutter.dart'; diff --git a/packages/flutter_widgets/lib/src/image_header.dart b/packages/flutter_widgets/lib/src/image_header.dart index 1694f0b7..f31ee3a6 100644 --- a/packages/flutter_widgets/lib/src/image_header.dart +++ b/packages/flutter_widgets/lib/src/image_header.dart @@ -2,8 +2,8 @@ import 'package:flutter/material.dart'; import 'package:stream_chat/stream_chat.dart'; import 'package:stream_chat_flutter/src/stream_chat_theme.dart'; import 'package:stream_chat_flutter/src/stream_svg_icon.dart'; +import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; import 'image_actions_modal.dart'; -import 'stream_channel.dart'; class ImageHeader extends StatelessWidget implements PreferredSizeWidget { /// True if this header shows the leading back button diff --git a/packages/flutter_widgets/lib/src/lazy_load_scroll_view.dart b/packages/flutter_widgets/lib/src/lazy_load_scroll_view.dart deleted file mode 100644 index bc1f4d1b..00000000 --- a/packages/flutter_widgets/lib/src/lazy_load_scroll_view.dart +++ /dev/null @@ -1,148 +0,0 @@ -import 'package:flutter/foundation.dart'; -import 'package:flutter/widgets.dart'; - -enum _LoadingStatus { LOADING, STABLE } - -/// A widget that wraps a [Widget] and will trigger [onEndOfPage]/[onStartOfPage] when it -/// reaches the bottom/start of the list -class LazyLoadScrollView extends StatefulWidget { - /// The [Widget] that this widget watches for changes on - final Widget child; - - /// Called when the [child] reaches the start of the list - final AsyncCallback onStartOfPage; - - /// Called when the [child] reaches the end of the list - final AsyncCallback onEndOfPage; - - /// Called when the list scrolling starts - final VoidCallback onPageScrollStart; - - /// Called when the list scrolling ends - final VoidCallback onPageScrollEnd; - - /// Called every time the [child] is in-between the list - final VoidCallback onInBetweenOfPage; - - /// The offset to take into account when triggering [onEndOfPage]/[onStartOfPage] in pixels - final double scrollOffset; - - /// Used to determine if loading of new data has finished. You should use set this if you aren't using a FutureBuilder or StreamBuilder - final bool isLoading; - - /// Initiates a LazyLoadScrollView widget - const LazyLoadScrollView({ - Key key, - @required this.child, - this.onStartOfPage, - this.onEndOfPage, - this.onPageScrollStart, - this.onPageScrollEnd, - this.onInBetweenOfPage, - this.isLoading = false, - this.scrollOffset = 100, - }) : assert(child != null), - super(key: key); - - @override - State createState() => _LazyLoadScrollViewState(); -} - -class _LazyLoadScrollViewState extends State { - _LoadingStatus _loadMoreStatus = _LoadingStatus.STABLE; - double _scrollPosition = 0.0; - - @override - Widget build(BuildContext context) { - return NotificationListener( - child: widget.child, - onNotification: _onNotification, - ); - } - - bool _onNotification(Notification notification) { - if (notification is ScrollStartNotification) { - if (widget.onPageScrollStart != null) { - widget.onPageScrollStart(); - return true; - } - } - if (notification is ScrollEndNotification) { - if (widget.onPageScrollEnd != null) { - widget.onPageScrollEnd(); - return true; - } - } - if (notification is ScrollUpdateNotification) { - final pixels = notification.metrics.pixels; - final maxScrollExtent = notification.metrics.maxScrollExtent; - final minScrollExtent = notification.metrics.minScrollExtent; - final scrollOffset = widget.scrollOffset; - - if (pixels > (minScrollExtent + scrollOffset) && - pixels < (maxScrollExtent - scrollOffset)) { - if (widget.onInBetweenOfPage != null) { - widget.onInBetweenOfPage(); - return true; - } - } - - final extentBefore = notification.metrics.extentBefore; - final extentAfter = notification.metrics.extentAfter; - final scrollingDown = _scrollPosition < pixels; - - if (scrollOffset == null || scrollOffset == 0) { - if (extentAfter == 0) { - _onEndOfPage(); - } - if (extentBefore == 0) { - _onStartOfPage(); - } - } else { - if (scrollingDown) { - if (extentAfter <= scrollOffset) { - _onEndOfPage(); - } - } else { - if (extentBefore <= scrollOffset) { - _onStartOfPage(); - } - } - } - _scrollPosition = pixels; - return true; - } - if (notification is OverscrollNotification) { - if (notification.overscroll > 0) { - _onEndOfPage(); - } - if (notification.overscroll < 0) { - _onStartOfPage(); - } - return true; - } - return false; - } - - void _onEndOfPage() { - if (_loadMoreStatus != null && _loadMoreStatus == _LoadingStatus.STABLE) { - _loadMoreStatus = _LoadingStatus.LOADING; - if (widget.onEndOfPage != null) { - widget.onEndOfPage().whenComplete(() { - _loadMoreStatus = _LoadingStatus.STABLE; - }); - } - } - } - - void _onStartOfPage() { - if (_loadMoreStatus != null && _loadMoreStatus == _LoadingStatus.STABLE) { - _loadMoreStatus = _LoadingStatus.LOADING; - if (widget.onStartOfPage != null) { - widget.onStartOfPage().whenComplete(() { - _loadMoreStatus = _LoadingStatus.STABLE; - }); - } - } - } -} diff --git a/packages/flutter_widgets/lib/src/media_list_view.dart b/packages/flutter_widgets/lib/src/media_list_view.dart index 249348c4..d84a2759 100644 --- a/packages/flutter_widgets/lib/src/media_list_view.dart +++ b/packages/flutter_widgets/lib/src/media_list_view.dart @@ -4,7 +4,6 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:photo_manager/photo_manager.dart'; -import 'package:stream_chat_flutter/src/lazy_load_scroll_view.dart'; import 'package:stream_chat_flutter/src/stream_svg_icon.dart'; import '../stream_chat_flutter.dart'; diff --git a/packages/flutter_widgets/lib/src/message_actions_modal.dart b/packages/flutter_widgets/lib/src/message_actions_modal.dart index 0cda4e9d..3cee6ba4 100644 --- a/packages/flutter_widgets/lib/src/message_actions_modal.dart +++ b/packages/flutter_widgets/lib/src/message_actions_modal.dart @@ -6,9 +6,9 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:stream_chat/stream_chat.dart'; import 'package:stream_chat_flutter/src/reaction_picker.dart'; -import 'package:stream_chat_flutter/src/stream_channel.dart'; import 'package:stream_chat_flutter/src/stream_svg_icon.dart'; import 'package:stream_chat_flutter/src/utils.dart'; +import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; import 'message_input.dart'; import 'message_widget.dart'; diff --git a/packages/flutter_widgets/lib/src/message_input.dart b/packages/flutter_widgets/lib/src/message_input.dart index 01ae3d3a..35a9441e 100644 --- a/packages/flutter_widgets/lib/src/message_input.dart +++ b/packages/flutter_widgets/lib/src/message_input.dart @@ -26,7 +26,6 @@ import 'package:video_compress/video_compress.dart'; import '../stream_chat_flutter.dart'; import 'extension.dart'; import 'quoted_message_widget.dart'; -import 'stream_channel.dart'; typedef FileUploader = Future Function(PlatformFile, Channel); typedef AttachmentThumbnailBuilder = Widget Function( diff --git a/packages/flutter_widgets/lib/src/message_list_view.dart b/packages/flutter_widgets/lib/src/message_list_view.dart index 9a0fe955..f14c332a 100644 --- a/packages/flutter_widgets/lib/src/message_list_view.dart +++ b/packages/flutter_widgets/lib/src/message_list_view.dart @@ -8,7 +8,6 @@ import 'package:rxdart/rxdart.dart'; import 'package:scrollable_positioned_list/scrollable_positioned_list.dart'; import 'package:stream_chat/stream_chat.dart'; import 'package:stream_chat_flutter/src/info_tile.dart'; -import 'package:stream_chat_flutter/src/lazy_load_scroll_view.dart'; import 'package:stream_chat_flutter/src/message_widget.dart'; import 'package:stream_chat_flutter/src/stream_svg_icon.dart'; import 'package:stream_chat_flutter/src/system_message.dart'; @@ -16,7 +15,6 @@ import 'package:visibility_detector/visibility_detector.dart'; import '../stream_chat_flutter.dart'; import 'date_divider.dart'; -import 'stream_channel.dart'; import 'swipeable.dart'; import 'extension.dart'; diff --git a/packages/flutter_widgets/lib/src/message_search_bloc.dart b/packages/flutter_widgets/lib/src/message_search_bloc.dart deleted file mode 100644 index 67b6455d..00000000 --- a/packages/flutter_widgets/lib/src/message_search_bloc.dart +++ /dev/null @@ -1,150 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:rxdart/rxdart.dart'; -import 'package:stream_chat/stream_chat.dart'; - -import 'stream_chat.dart'; - -/// Widget dedicated to the management of a message list with pagination -class MessageSearchBloc extends StatefulWidget { - /// The widget child - final Widget child; - - /// Instantiate a new MessageSearchBloc - const MessageSearchBloc({ - Key key, - @required this.child, - }) : super(key: key); - - @override - MessageSearchBlocState createState() => MessageSearchBlocState(); - - /// Use this method to get the current [MessageSearchBlocState] instance - static MessageSearchBlocState of(BuildContext context) { - MessageSearchBlocState state; - - state = context.findAncestorStateOfType(); - - if (state == null) { - throw Exception('You must have a MessageSearchBloc widget as ancestor'); - } - - return state; - } -} - -/// The current state of the [MessageSearchBloc] -class MessageSearchBlocState extends State - with AutomaticKeepAliveClientMixin { - /// The current messages list - List get messageResponses => _messageResponses.value; - - /// The current messages list as a stream - Stream> get messagesStream => - _messageResponses.stream; - - final BehaviorSubject> _messageResponses = - BehaviorSubject(); - - final BehaviorSubject _queryMessagesLoadingController = - BehaviorSubject.seeded(false); - - /// The stream notifying the state of queryUsers call - Stream get queryMessagesLoading => - _queryMessagesLoadingController.stream; - - /// Calls [Client.search] updating [messageResponses] stream - Future search({ - Map filter, - Map messageFilter, - List sort, - String query, - PaginationParams pagination, - }) async { - _messageResponses.add(null); - try { - final messages = await _search( - filter: filter, - messageFilter: messageFilter, - sort: sort, - query: query, - pagination: pagination, - ); - _messageResponses.add(messages.results); - } catch (err, stk) { - _messageResponses.addError(err, stk); - } - } - - /// Calls [Client.search] updating [queryMessagesLoading] stream - Future loadMore({ - Map filter, - Map messageFilter, - List sort, - String query, - PaginationParams pagination, - }) async { - if (_queryMessagesLoadingController.value == true) { - return; - } - _queryMessagesLoadingController.add(true); - try { - final clear = pagination == null || - pagination.offset == null || - pagination.offset == 0; - - final oldMessages = List.from(messageResponses ?? []); - - final messages = await _search( - filter: filter, - messageFilter: messageFilter, - sort: sort, - query: query, - pagination: pagination, - ); - - if (clear) { - _messageResponses.add(messages.results); - } else { - final temp = oldMessages + messages.results; - _messageResponses.add(temp); - } - - _queryMessagesLoadingController.add(false); - } catch (err, stackTrace) { - _queryMessagesLoadingController.addError(err, stackTrace); - } - } - - Future _search({ - Map filter, - Map messageFilter, - List sort, - String query, - PaginationParams pagination, - }) { - final client = StreamChat.of(context).client; - return client.search( - filter, - sort, - query, - pagination, - messageFilters: messageFilter, - ); - } - - @override - Widget build(BuildContext context) { - super.build(context); - return widget.child; - } - - @override - void dispose() { - _messageResponses.close(); - _queryMessagesLoadingController.close(); - super.dispose(); - } - - @override - bool get wantKeepAlive => true; -} diff --git a/packages/flutter_widgets/lib/src/message_search_list_view.dart b/packages/flutter_widgets/lib/src/message_search_list_view.dart index b176e148..7897d2ec 100644 --- a/packages/flutter_widgets/lib/src/message_search_list_view.dart +++ b/packages/flutter_widgets/lib/src/message_search_list_view.dart @@ -4,10 +4,9 @@ import 'package:flutter/material.dart'; import 'package:stream_chat/stream_chat.dart'; import 'package:stream_chat_flutter/src/info_tile.dart'; import 'package:stream_chat_flutter/src/message_search_item.dart'; +import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; import '../stream_chat_flutter.dart'; -import 'lazy_load_scroll_view.dart'; -import 'message_search_bloc.dart'; /// Callback called when tapping on a user typedef MessageSearchItemTapCallback = void Function(GetMessageResponse); diff --git a/packages/flutter_widgets/lib/src/stream_channel.dart b/packages/flutter_widgets/lib/src/stream_channel.dart deleted file mode 100644 index 8b4b97ef..00000000 --- a/packages/flutter_widgets/lib/src/stream_channel.dart +++ /dev/null @@ -1,356 +0,0 @@ -import 'dart:async'; - -import 'package:flutter/material.dart'; -import 'package:rxdart/rxdart.dart'; -import 'package:stream_chat/stream_chat.dart'; - -enum QueryDirection { top, bottom } - -/// Widget used to provide information about the channel to the widget tree -/// -/// Use [StreamChannel.of] to get the current [StreamChannelState] instance. -class StreamChannel extends StatefulWidget { - const StreamChannel({ - Key key, - @required this.child, - @required this.channel, - this.showLoading = true, - this.initialMessageId, - }) : assert(child != null), - assert(channel != null), - super(key: key); - - final Widget child; - final Channel channel; - final bool showLoading; - - /// If passed the channel will load from this particular message. - final String initialMessageId; - - /// Use this method to get the current [StreamChannelState] instance - static StreamChannelState of(BuildContext context) { - StreamChannelState streamChannelState; - - streamChannelState = context.findAncestorStateOfType(); - - if (streamChannelState == null) { - throw Exception( - 'You must have a StreamChannel widget at the top of your widget tree'); - } - - return streamChannelState; - } - - @override - StreamChannelState createState() => StreamChannelState(); -} - -class StreamChannelState extends State { - /// Current channel - Channel get channel => widget.channel; - - /// InitialMessageId - String get initialMessageId => widget.initialMessageId; - - /// Current channel state stream - Stream get channelStateStream => - widget.channel.state.channelStateStream; - - final _queryTopMessagesController = BehaviorSubject.seeded(false); - final _queryBottomMessagesController = BehaviorSubject.seeded(false); - - /// The stream notifying the state of [_queryTopMessages] call - Stream get queryTopMessages => _queryTopMessagesController.stream; - - /// The stream notifying the state of [_queryBottomMessages] call - Stream get queryBottomMessages => _queryBottomMessagesController.stream; - - bool _topPaginationEnded = false; - bool _bottomPaginationEnded = false; - - Future _queryTopMessages({ - int limit = 20, - bool preferOffline = false, - }) async { - if (_topPaginationEnded || _queryTopMessagesController?.value == true) { - return; - } - _queryTopMessagesController.add(true); - - if (channel.state.messages.isEmpty) { - return _queryTopMessagesController.add(false); - } - - final oldestMessage = channel.state.messages.first; - - try { - final state = await queryBeforeMessage( - oldestMessage.id, - limit: limit, - preferOffline: preferOffline, - ); - if (state.messages.isEmpty || state.messages.length < limit) { - _topPaginationEnded = true; - } - _queryTopMessagesController.add(false); - } catch (e, stk) { - _queryTopMessagesController.addError(e, stk); - } - } - - Future _queryBottomMessages({ - int limit = 20, - bool preferOffline = false, - }) async { - if (_bottomPaginationEnded || - _queryBottomMessagesController?.value == true || - channel?.state?.isUpToDate == true) return; - _queryBottomMessagesController.add(true); - - if (channel.state.messages.isEmpty) { - return _queryBottomMessagesController.add(false); - } - - final recentMessage = channel.state.messages.last; - - try { - final state = await queryAfterMessage( - recentMessage.id, - limit: limit, - preferOffline: preferOffline, - ); - if (state.messages.isEmpty || state.messages.length < limit) { - _bottomPaginationEnded = true; - } - _queryBottomMessagesController.add(false); - } catch (e, stk) { - _queryBottomMessagesController.addError(e, stk); - } - } - - /// Calls [channel.query] updating [queryMessage] stream - Future queryMessages({QueryDirection direction = QueryDirection.top}) { - if (direction == QueryDirection.top) return _queryTopMessages(); - return _queryBottomMessages(); - } - - /// Calls [channel.getReplies] updating [queryMessage] stream - Future getReplies( - String parentId, { - int limit = 50, - bool preferOffline = false, - }) async { - if (_topPaginationEnded || _queryTopMessagesController.value) return; - _queryTopMessagesController.add(true); - - Message message; - if (channel.state.threads.containsKey(parentId)) { - final thread = channel.state.threads[parentId]; - if (thread.isNotEmpty) { - message = thread.first; - } - } - - try { - final response = await channel.getReplies( - parentId, - PaginationParams( - lessThan: message?.id, - limit: limit, - ), - preferOffline: preferOffline, - ); - if (response.messages.isEmpty || response.messages.length < limit) { - _topPaginationEnded = true; - } - _queryTopMessagesController.add(false); - } catch (e, stk) { - _queryTopMessagesController.addError(e, stk); - } - } - - /// Query the channel members and watchers - Future queryMembersAndWatchers() async { - await widget.channel.query( - membersPagination: PaginationParams( - offset: channel.state.members?.length, - limit: 100, - ), - watchersPagination: PaginationParams( - offset: channel.state.watchers?.length, - limit: 100, - ), - ); - } - - /// Loads channel at specific message - Future loadChannelAtMessage( - String messageId, { - int before = 20, - int after = 20, - bool preferOffline = false, - }) { - return queryAtMessage( - messageId: messageId, - before: before, - after: after, - preferOffline: preferOffline, - ); - } - - /// - Future queryAtMessage({ - String messageId, - int before = 20, - int after = 20, - bool preferOffline = false, - }) async { - if (channel.state == null) return; - channel.state.isUpToDate = false; - channel.state.truncate(); - - if (messageId == null) { - await channel.query( - messagesPagination: PaginationParams( - limit: before, - ), - preferOffline: preferOffline, - ); - channel.state.isUpToDate = true; - return; - } - - return Future.wait([ - queryBeforeMessage( - messageId, - limit: before, - preferOffline: preferOffline, - ), - queryAfterMessage( - messageId, - limit: after, - preferOffline: preferOffline, - ), - ]); - } - - /// - Future queryBeforeMessage( - String messageId, { - int limit = 20, - bool preferOffline = false, - }) { - return channel.query( - messagesPagination: PaginationParams( - lessThan: messageId, - limit: limit, - ), - preferOffline: preferOffline, - ); - } - - /// - Future queryAfterMessage( - String messageId, { - int limit = 20, - bool preferOffline = false, - }) async { - final state = await channel.query( - messagesPagination: PaginationParams( - greaterThanOrEqual: messageId, - limit: limit, - ), - preferOffline: preferOffline, - ); - if (state.messages.isEmpty || state.messages.length < limit) { - channel.state.isUpToDate = true; - } - return state; - } - - /// - Future getMessage(String messageId) async { - var message = channel.state.messages.firstWhere( - (it) => it.id == messageId, - orElse: () => null, - ); - if (message == null) { - final response = await channel.getMessagesById([messageId]); - message = response.messages.first; - } - return message; - } - - /// Reloads the channel with latest message - Future reloadChannel() => queryAtMessage(before: 30); - - List> _futures; - - Future get _loadChannelAtMessage async { - try { - await loadChannelAtMessage(initialMessageId); - return true; - } catch (e, stk) { - print('Error: $e\nStack: $stk'); - rethrow; - } - } - - @override - void initState() { - super.initState(); - _futures = [widget.channel.initialized]; - if (initialMessageId != null) { - _futures.add(_loadChannelAtMessage); - } - } - - @override - void dispose() { - _queryTopMessagesController.close(); - _queryBottomMessagesController.close(); - super.dispose(); - } - - @override - Widget build(BuildContext context) { - Widget child = FutureBuilder>( - future: Future.wait(_futures), - initialData: [ - channel.state != null, - if (initialMessageId != null) false, - ], - builder: (context, snapshot) { - if (snapshot.hasError) { - if (snapshot.error is Error) { - print((snapshot.error as Error).stackTrace); - } - var message = snapshot.error.toString(); - if (snapshot.error is DioError) { - final dioError = snapshot.error as DioError; - if (dioError.type == DioErrorType.RESPONSE) { - message = dioError.message; - } else { - message = 'Check your connection and retry'; - } - } - return Center( - child: Text(message), - ); - } - final initialized = snapshot.data[0]; - final dataLoaded = initialMessageId == null ? true : snapshot.data[1]; - if (widget.showLoading && (!initialized || !dataLoaded)) { - return Center( - child: CircularProgressIndicator(), - ); - } - return widget.child; - }, - ); - if (initialMessageId != null) { - child = Material(child: child); - } - return child; - } -} diff --git a/packages/flutter_widgets/lib/src/stream_chat.dart b/packages/flutter_widgets/lib/src/stream_chat.dart index 5d1b7484..ee7e049f 100644 --- a/packages/flutter_widgets/lib/src/stream_chat.dart +++ b/packages/flutter_widgets/lib/src/stream_chat.dart @@ -6,6 +6,7 @@ import 'package:flutter_app_badger/flutter_app_badger.dart'; import 'package:flutter_portal/flutter_portal.dart'; import 'package:stream_chat/stream_chat.dart'; import 'package:stream_chat_flutter/src/stream_chat_theme.dart'; +import 'package:stream_chat_flutter/stream_chat_flutter.dart'; /// Widget used to provide information about the chat to the widget tree /// @@ -61,9 +62,8 @@ class StreamChat extends StatefulWidget { } /// The current state of the StreamChat widget -class StreamChatState extends State with WidgetsBindingObserver { +class StreamChatState extends State { Client get client => widget.client; - Timer _disconnectTimer; @override Widget build(BuildContext context) { @@ -81,7 +81,10 @@ class StreamChatState extends State with WidgetsBindingObserver { accentColor: streamTheme.colorTheme.accentBlue, scaffoldBackgroundColor: streamTheme.colorTheme.white, ), - child: widget.child, + child: StreamChatCore( + child: widget.child, + client: client, + ), ); }, ), @@ -106,7 +109,6 @@ class StreamChatState extends State with WidgetsBindingObserver { @override void initState() { super.initState(); - WidgetsBinding.instance.addObserver(this); client.state?.totalUnreadCountStream?.listen((count) { if (count > 0) { FlutterAppBadger.updateBadgeCount(count); @@ -115,67 +117,4 @@ class StreamChatState extends State with WidgetsBindingObserver { } }); } - - StreamSubscription _newMessageSubscription; - - @override - void didChangeAppLifecycleState(AppLifecycleState state) { - if (client.state?.user != null) { - if (state == AppLifecycleState.paused) { - if (client.showLocalNotification != null) { - _newMessageSubscription = client - .on(EventType.messageNew) - .where((e) => e.user?.id != user.id) - .where((e) => e.message.silent != true) - .where((e) => e.message.shadowed != true) - .listen((event) async { - final channel = client.channel( - event.channelType, - id: event.channelId, - ); - - client.showLocalNotification( - event.message, - ChannelModel( - id: channel.id, - createdAt: channel.createdAt, - extraData: channel.extraData, - type: channel.type, - memberCount: channel.memberCount, - frozen: channel.frozen, - cid: channel.cid, - deletedAt: channel.deletedAt, - config: channel.config, - createdBy: channel.createdBy, - updatedAt: channel.updatedAt, - lastMessageAt: channel.lastMessageAt, - ), - ); - }); - _disconnectTimer = Timer(client.backgroundKeepAlive, () { - client.disconnect(); - }); - } else { - client.disconnect(); - } - } else if (state == AppLifecycleState.resumed) { - _newMessageSubscription?.cancel(); - if (_disconnectTimer?.isActive == true) { - _disconnectTimer.cancel(); - } else { - if (client.wsConnectionStatus.value == - ConnectionStatus.disconnected) { - NotificationService.handleIosMessageQueue(client); - client.connect(); - } - } - } - } - } - - @override - void dispose() { - WidgetsBinding.instance.removeObserver(this); - super.dispose(); - } } diff --git a/packages/flutter_widgets/lib/src/typing_indicator.dart b/packages/flutter_widgets/lib/src/typing_indicator.dart index 177e9e21..c9e579be 100644 --- a/packages/flutter_widgets/lib/src/typing_indicator.dart +++ b/packages/flutter_widgets/lib/src/typing_indicator.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; import 'package:lottie/lottie.dart'; import 'package:stream_chat/stream_chat.dart'; -import 'package:stream_chat_flutter/src/stream_channel.dart'; +import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; /// Widget to show the current list of typing users class TypingIndicator extends StatelessWidget { diff --git a/packages/flutter_widgets/lib/src/user_list_view.dart b/packages/flutter_widgets/lib/src/user_list_view.dart index c5bac576..be34d3db 100644 --- a/packages/flutter_widgets/lib/src/user_list_view.dart +++ b/packages/flutter_widgets/lib/src/user_list_view.dart @@ -2,8 +2,6 @@ import 'dart:convert'; import 'package:flutter/material.dart'; import 'package:stream_chat/stream_chat.dart'; -import 'package:stream_chat_flutter/src/lazy_load_scroll_view.dart'; -import 'package:stream_chat_flutter/src/users_bloc.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; import 'user_item.dart'; @@ -513,42 +511,3 @@ class _UserListViewState extends State } } } - -abstract class ListItem { - String get key { - if (this is ListHeaderItem) { - final header = (this as ListHeaderItem).heading; - return 'HEADER-$header'; - } - if (this is ListUserItem) { - final user = (this as ListUserItem).user; - return 'USER-${user.id}'; - } - return null; - } - - Widget when({ - @required Widget Function(String heading) headerItem, - @required Widget Function(User user) userItem, - }) { - if (this is ListHeaderItem) { - return headerItem((this as ListHeaderItem).heading); - } - if (this is ListUserItem) { - return userItem((this as ListUserItem).user); - } - return SizedBox(); - } -} - -class ListHeaderItem extends ListItem { - final String heading; - - ListHeaderItem(this.heading); -} - -class ListUserItem extends ListItem { - final User user; - - ListUserItem(this.user); -} diff --git a/packages/flutter_widgets/lib/src/users_bloc.dart b/packages/flutter_widgets/lib/src/users_bloc.dart deleted file mode 100644 index 91cbaf80..00000000 --- a/packages/flutter_widgets/lib/src/users_bloc.dart +++ /dev/null @@ -1,108 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:rxdart/rxdart.dart'; -import 'package:stream_chat/stream_chat.dart'; - -import 'stream_chat.dart'; - -/// Widget dedicated to the management of a users list with pagination -class UsersBloc extends StatefulWidget { - /// The widget child - final Widget child; - - /// Instantiate a new UsersBloc - const UsersBloc({ - Key key, - @required this.child, - }) : super(key: key); - - @override - UsersBlocState createState() => UsersBlocState(); - - /// Use this method to get the current [UsersBlocState] instance - static UsersBlocState of(BuildContext context) { - UsersBlocState state; - - state = context.findAncestorStateOfType(); - - if (state == null) { - throw Exception('You must have a UsersBloc widget as ancestor'); - } - - return state; - } -} - -/// The current state of the [UsersBloc] -class UsersBlocState extends State - with AutomaticKeepAliveClientMixin { - /// The current users list - List get users => _usersController.value; - - /// The current users list as a stream - Stream> get usersStream => _usersController.stream; - - final BehaviorSubject> _usersController = BehaviorSubject(); - - final BehaviorSubject _queryUsersLoadingController = - BehaviorSubject.seeded(false); - - /// The stream notifying the state of queryUsers call - Stream get queryUsersLoading => _queryUsersLoadingController.stream; - - /// Calls [Client.queryUsers] updating [queryUsersLoading] stream - Future queryUsers({ - Map filter, - List sort, - Map options, - PaginationParams pagination, - }) async { - final client = StreamChat.of(context).client; - - if (client.state?.user == null || - _queryUsersLoadingController.value == true) { - return; - } - _queryUsersLoadingController.add(true); - try { - final clear = pagination == null || - pagination.offset == null || - pagination.offset == 0; - - final oldUsers = List.from(users ?? []); - - final usersResponse = await client.queryUsers( - filter: filter, - sort: sort, - options: options, - pagination: pagination, - ); - - if (clear) { - _usersController.add(usersResponse.users); - } else { - final temp = oldUsers + usersResponse.users; - _usersController.add(temp); - } - - _queryUsersLoadingController.add(false); - } catch (err, stackTrace) { - _queryUsersLoadingController.addError(err, stackTrace); - } - } - - @override - Widget build(BuildContext context) { - super.build(context); - return widget.child; - } - - @override - void dispose() { - _usersController.close(); - _queryUsersLoadingController.close(); - super.dispose(); - } - - @override - bool get wantKeepAlive => true; -} diff --git a/packages/flutter_widgets/lib/stream_chat_flutter.dart b/packages/flutter_widgets/lib/stream_chat_flutter.dart index df113234..c4e0e2e1 100644 --- a/packages/flutter_widgets/lib/stream_chat_flutter.dart +++ b/packages/flutter_widgets/lib/stream_chat_flutter.dart @@ -7,7 +7,6 @@ export 'src/channel_list_header.dart'; export 'src/channel_list_view.dart'; export 'src/channel_name.dart'; export 'src/channel_preview.dart'; -export 'src/channels_bloc.dart'; export 'src/date_divider.dart'; export 'src/deleted_message.dart'; export 'src/file_attachment.dart'; @@ -22,8 +21,6 @@ export 'src/message_text.dart'; export 'src/message_widget.dart'; export 'src/reaction_picker.dart'; export 'src/sending_indicator.dart'; -export 'src/stream_channel.dart'; -export 'src/stream_chat.dart'; export 'src/stream_chat_theme.dart'; export 'src/stream_neumorphic_button.dart'; export 'src/stream_svg_icon.dart'; @@ -35,11 +32,8 @@ export 'src/user_item.dart'; export 'src/user_item.dart'; export 'src/user_list_view.dart'; export 'src/user_list_view.dart'; -export 'src/users_bloc.dart'; -export 'src/users_bloc.dart'; export 'src/utils.dart'; export 'src/video_attachment.dart'; -export 'src/message_search_bloc.dart'; export 'src/message_search_item.dart'; export 'src/message_search_list_view.dart'; export 'src/unread_indicator.dart'; @@ -47,3 +41,5 @@ export 'src/option_list_tile.dart'; export 'src/channel_file_display_screen.dart'; export 'src/channel_media_display_screen.dart'; export 'src/info_tile.dart'; +export 'src/stream_chat.dart'; +export 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; diff --git a/packages/flutter_widgets/pubspec.yaml b/packages/flutter_widgets/pubspec.yaml index 2b82aca2..059d4fb2 100644 --- a/packages/flutter_widgets/pubspec.yaml +++ b/packages/flutter_widgets/pubspec.yaml @@ -11,6 +11,9 @@ environment: dependencies: flutter: sdk: flutter + # TODO: Change over to main dependency when deployed + stream_chat_flutter_core: + path: ../stream_chat_flutter_core flutter_app_badger: ^1.1.2 photo_view: ^0.10.3 rxdart: ^0.24.1 @@ -29,7 +32,6 @@ dependencies: image_picker: ^0.6.7+17 flutter_keyboard_visibility: ^4.0.2 mime: ^0.9.7 - stream_chat: ^0.2.24 video_compress: ^2.1.1 visibility_detector: ^0.1.5 http_parser: ^3.1.4 diff --git a/packages/stream_chat_flutter_core/lib/src/channel_list_core.dart b/packages/stream_chat_flutter_core/lib/src/channel_list_core.dart index 627c5131..972de62f 100644 --- a/packages/stream_chat_flutter_core/lib/src/channel_list_core.dart +++ b/packages/stream_chat_flutter_core/lib/src/channel_list_core.dart @@ -49,7 +49,6 @@ class ChannelListCore extends StatefulWidget { this.options, this.sort, this.pagination, - this.pullToRefresh = true, @required this.errorBuilder, @required this.emptyBuilder, @required this.loadingBuilder, @@ -92,9 +91,6 @@ class ChannelListCore extends StatefulWidget { /// message_limit: how many messages should be included to each channel final PaginationParams pagination; - /// Set it to false to disable the pull-to-refresh widget - final bool pullToRefresh; - @override _ChannelListCoreState createState() => _ChannelListCoreState(); } @@ -105,21 +101,7 @@ class _ChannelListCoreState extends State Widget build(BuildContext context) { final channelsBloc = ChannelsBloc.of(context); - if (!widget.pullToRefresh) { - return _buildListView(channelsBloc); - } - - return RefreshIndicator( - onRefresh: () async { - return channelsBloc.queryChannels( - filter: widget.filter, - sortOptions: widget.sort, - paginationParams: widget.pagination, - options: widget.options, - ); - }, - child: _buildListView(channelsBloc), - ); + return _buildListView(channelsBloc); } StreamBuilder> _buildListView( From 5594a9a116b1a2082fe3de4a4725181a06ec9236 Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Mon, 25 Jan 2021 20:24:22 +0530 Subject: [PATCH 14/39] feat: Added fixes and user_list_view.dart --- .../example/lib/new_chat_screen.dart | 519 +++++++++--------- .../lib/src/channel_list_view.dart | 54 +- .../lib/src/user_list_view.dart | 352 +++++------- .../lib/src/user_list_core.dart | 20 +- 4 files changed, 429 insertions(+), 516 deletions(-) diff --git a/packages/flutter_widgets/example/lib/new_chat_screen.dart b/packages/flutter_widgets/example/lib/new_chat_screen.dart index 5772f5e2..baddd570 100644 --- a/packages/flutter_widgets/example/lib/new_chat_screen.dart +++ b/packages/flutter_widgets/example/lib/new_chat_screen.dart @@ -135,287 +135,284 @@ class _NewChatScreenState extends State { centerTitle: true, ), body: ValueListenableBuilder( - valueListenable: StreamChat.of(context).client.wsConnectionStatus, - builder: (context, status, _) { - String statusString = ''; - bool showStatus = true; + valueListenable: StreamChat.of(context).client.wsConnectionStatus, + builder: (context, status, _) { + String statusString = ''; + bool showStatus = true; - switch (status) { - case ConnectionStatus.connected: - statusString = 'Connected'; - showStatus = false; - break; - case ConnectionStatus.connecting: - statusString = 'Reconnecting...'; - break; - case ConnectionStatus.disconnected: - statusString = 'Disconnected'; - break; - } - return InfoTile( - showMessage: showStatus, - tileAnchor: Alignment.topCenter, - childAnchor: Alignment.topCenter, - message: statusString, - child: StreamChannel( - showLoading: false, - channel: channel, - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - ChipsInputTextField( - key: _chipInputTextFieldStateKey, - controller: _controller, - focusNode: _searchFocusNode, - chipBuilder: (context, user) { - return GestureDetector( - onTap: () { - _chipInputTextFieldState.removeItem(user); - _searchFocusNode.requestFocus(); - }, - child: Stack( - alignment: AlignmentDirectional.centerStart, + switch (status) { + case ConnectionStatus.connected: + statusString = 'Connected'; + showStatus = false; + break; + case ConnectionStatus.connecting: + statusString = 'Reconnecting...'; + break; + case ConnectionStatus.disconnected: + statusString = 'Disconnected'; + break; + } + return InfoTile( + showMessage: showStatus, + tileAnchor: Alignment.topCenter, + childAnchor: Alignment.topCenter, + message: statusString, + child: StreamChannel( + showLoading: false, + channel: channel, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + ChipsInputTextField( + key: _chipInputTextFieldStateKey, + controller: _controller, + focusNode: _searchFocusNode, + chipBuilder: (context, user) { + return GestureDetector( + onTap: () { + _chipInputTextFieldState.removeItem(user); + _searchFocusNode.requestFocus(); + }, + child: Stack( + alignment: AlignmentDirectional.centerStart, + children: [ + Container( + decoration: BoxDecoration( + color: StreamChatTheme.of(context) + .colorTheme + .greyGainsboro, + borderRadius: BorderRadius.circular(12), + ), + padding: const EdgeInsets.only(left: 24), + child: Padding( + padding: const EdgeInsets.fromLTRB(8, 4, 12, 4), + child: Text( + user.name, + maxLines: 1, + style: TextStyle( + color: StreamChatTheme.of(context) + .colorTheme + .black, + ), + ), + ), + ), + Container( + foregroundDecoration: BoxDecoration( + color: StreamChatTheme.of(context) + .colorTheme + .overlay, + shape: BoxShape.circle, + ), + child: UserAvatar( + showOnlineStatus: false, + user: user, + constraints: BoxConstraints.tightFor( + height: 24, + width: 24, + ), + ), + ), + StreamSvgIcon.close(), + ], + ), + ); + }, + onChipAdded: (user) { + setState(() => _selectedUsers.add(user)); + }, + onChipRemoved: (user) { + setState(() => _selectedUsers.remove(user)); + }, + ), + if (!_isSearchActive && !_selectedUsers.isNotEmpty) + Container( + child: InkWell( + onTap: () { + Navigator.pushNamed( + context, + Routes.NEW_GROUP_CHAT, + ); + }, + child: Padding( + padding: const EdgeInsets.symmetric(vertical: 8), + child: Row( children: [ - Container( - decoration: BoxDecoration( - color: StreamChatTheme.of(context) - .colorTheme - .greyGainsboro, - borderRadius: BorderRadius.circular(12), - ), - padding: const EdgeInsets.only(left: 24), - child: Padding( - padding: - const EdgeInsets.fromLTRB(8, 4, 12, 4), - child: Text( - user.name, - maxLines: 1, - style: TextStyle( - color: StreamChatTheme.of(context) - .colorTheme - .black, - ), + StreamNeumorphicButton( + child: Center( + child: StreamSvgIcon.contacts( + color: StreamChatTheme.of(context) + .colorTheme + .accentBlue, + size: 24, ), ), ), - Container( - foregroundDecoration: BoxDecoration( - color: StreamChatTheme.of(context) - .colorTheme - .overlay, - shape: BoxShape.circle, - ), - child: UserAvatar( - showOnlineStatus: false, - user: user, - constraints: BoxConstraints.tightFor( - height: 24, - width: 24, - ), - ), + SizedBox(width: 8), + Text( + 'Create a Group', + style: StreamChatTheme.of(context) + .textTheme + .bodyBold, ), - StreamSvgIcon.close(), ], ), - ); - }, - onChipAdded: (user) { - setState(() => _selectedUsers.add(user)); - }, - onChipRemoved: (user) { - setState(() => _selectedUsers.remove(user)); - }, + ), + ), ), - if (!_isSearchActive && !_selectedUsers.isNotEmpty) - Container( - child: InkWell( - onTap: () { - Navigator.pushNamed( - context, - Routes.NEW_GROUP_CHAT, - ); - }, - child: Padding( - padding: const EdgeInsets.symmetric(vertical: 8), - child: Row( - children: [ - StreamNeumorphicButton( - child: Center( - child: StreamSvgIcon.contacts( - color: StreamChatTheme.of(context) - .colorTheme - .accentBlue, - size: 24, - ), - ), - ), - SizedBox(width: 8), - Text( - 'Create a Group', - style: StreamChatTheme.of(context) - .textTheme - .bodyBold, - ), - ], - ), - ), - ), + if (_showUserList) + Container( + width: double.maxFinite, + decoration: BoxDecoration( + gradient: + StreamChatTheme.of(context).colorTheme.bgGradient, ), - if (_showUserList) - 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 - ? "Matches for \"$_userNameQuery\"" - : 'On the platform', - style: StreamChatTheme.of(context) - .textTheme - .footnote - .copyWith( - color: StreamChatTheme.of(context) - .colorTheme - .black - .withOpacity(.5))), + child: Padding( + padding: const EdgeInsets.symmetric( + vertical: 8, + horizontal: 8, ), + child: Text( + _isSearchActive + ? "Matches for \"$_userNameQuery\"" + : 'On the platform', + style: StreamChatTheme.of(context) + .textTheme + .footnote + .copyWith( + color: StreamChatTheme.of(context) + .colorTheme + .black + .withOpacity(.5))), ), - Expanded( - child: _showUserList - ? GestureDetector( - behavior: HitTestBehavior.opaque, - onPanDown: (_) => - FocusScope.of(context).unfocus(), - child: UsersBloc( - child: UserListView( - selectedUsers: _selectedUsers, - groupAlphabetically: - _isSearchActive ? false : true, - onUserTap: (user, _) { - _controller.clear(); - if (!_selectedUsers.contains(user)) { - _chipInputTextFieldState - ..addItem(user) - ..pauseItemAddition(); - } else { - _chipInputTextFieldState.removeItem(user); - } - }, - pagination: PaginationParams( - limit: 25, - ), - filter: { - if (_userNameQuery.isNotEmpty) - 'name': { - r'$autocomplete': _userNameQuery, - }, - 'id': { - r'$ne': StreamChat.of(context).user.id, + ), + Expanded( + child: _showUserList + ? GestureDetector( + behavior: HitTestBehavior.opaque, + onPanDown: (_) => FocusScope.of(context).unfocus(), + child: UsersBloc( + child: UserListView( + selectedUsers: _selectedUsers, + groupAlphabetically: + _isSearchActive ? false : true, + onUserTap: (user, _) { + _controller.clear(); + if (!_selectedUsers.contains(user)) { + _chipInputTextFieldState + ..addItem(user) + ..pauseItemAddition(); + } else { + _chipInputTextFieldState.removeItem(user); + } + }, + pagination: PaginationParams( + limit: 25, + ), + filter: { + if (_userNameQuery.isNotEmpty) + 'name': { + r'$autocomplete': _userNameQuery, }, + 'id': { + r'$ne': StreamChat.of(context).user.id, }, - sort: [ - SortOption( - 'name', - direction: 1, - ), - ], - 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, - ), + }, + sort: [ + SortOption( + 'name', + direction: 1, + ), + ], + 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 user matches these keywords...', - style: StreamChatTheme.of( - context) - .textTheme - .footnote - .copyWith( - color: StreamChatTheme - .of(context) - .colorTheme - .black - .withOpacity( - .5)), - ), - ], - ), + ), + Text( + 'No user matches these keywords...', + style: StreamChatTheme.of( + context) + .textTheme + .footnote + .copyWith( + color: StreamChatTheme + .of(context) + .colorTheme + .black + .withOpacity(.5)), + ), + ], ), ), - ); - }, - ); - }, - ), + ), + ); + }, + ); + }, ), - ) - : FutureBuilder( - future: channel.initialized, - builder: (context, snapshot) { - if (snapshot.data == true) { - return MessageListView(); - } - - return Center( - child: Text( - 'No chats here yet...', - style: TextStyle( - fontSize: 12, - color: StreamChatTheme.of(context) - .colorTheme - .black - .withOpacity(.5), - ), - ), - ); - }, ), - ), - MessageInput( - focusNode: _messageInputFocusNode, - preMessageSending: (message) async { - await channel.watch(); - return message; - }, - onMessageSent: (m) { - Navigator.pushNamedAndRemoveUntil( - context, - Routes.CHANNEL_PAGE, - ModalRoute.withName(Routes.HOME), - arguments: ChannelPageArgs(channel: channel), - ); - }, - ), - ], - ), + ) + : FutureBuilder( + future: channel.initialized, + builder: (context, snapshot) { + if (snapshot.data == true) { + return MessageListView(); + } + + return Center( + child: Text( + 'No chats here yet...', + style: TextStyle( + fontSize: 12, + color: StreamChatTheme.of(context) + .colorTheme + .black + .withOpacity(.5), + ), + ), + ); + }, + ), + ), + MessageInput( + focusNode: _messageInputFocusNode, + preMessageSending: (message) async { + await channel.watch(); + return message; + }, + onMessageSent: (m) { + Navigator.pushNamedAndRemoveUntil( + context, + Routes.CHANNEL_PAGE, + ModalRoute.withName(Routes.HOME), + arguments: ChannelPageArgs(channel: channel), + ); + }, + ), + ], ), - ); - }), + ), + ); + }, + ), ); } } diff --git a/packages/flutter_widgets/lib/src/channel_list_view.dart b/packages/flutter_widgets/lib/src/channel_list_view.dart index 617b7ce9..84eb1298 100644 --- a/packages/flutter_widgets/lib/src/channel_list_view.dart +++ b/packages/flutter_widgets/lib/src/channel_list_view.dart @@ -159,34 +159,36 @@ class _ChannelListViewState extends State @override Widget build(BuildContext context) { - if (!widget.pullToRefresh) { - _channelListController.loadData(); - } - - return RefreshIndicator( - onRefresh: () async { - _channelListController.loadData(); + var child = ChannelListCore( + channelListController: _channelListController, + listBuilder: (context, list) { + return _buildListView(list); }, - child: ChannelListCore( - channelListController: _channelListController, - listBuilder: (context, list) { - return _buildListView(list); - }, - emptyBuilder: (BuildContext context) { - return _buildEmptyWidget(); - }, - errorBuilder: (Error error) { - return _buildErrorWidget(context); - }, - loadingBuilder: (BuildContext context) { - return _buildLoadingWidget(); - }, - pagination: widget.pagination, - options: widget.options, - sort: widget.sort, - filter: widget.filter, - ), + emptyBuilder: (BuildContext context) { + return _buildEmptyWidget(); + }, + errorBuilder: (Error error) { + return _buildErrorWidget(context); + }, + loadingBuilder: (BuildContext context) { + return _buildLoadingWidget(); + }, + pagination: widget.pagination, + options: widget.options, + sort: widget.sort, + filter: widget.filter, ); + + if (!widget.pullToRefresh) { + return child; + } else { + return RefreshIndicator( + onRefresh: () async { + _channelListController.loadData(); + }, + child: child, + ); + } } Widget _buildListView( diff --git a/packages/flutter_widgets/lib/src/user_list_view.dart b/packages/flutter_widgets/lib/src/user_list_view.dart index be34d3db..7027a875 100644 --- a/packages/flutter_widgets/lib/src/user_list_view.dart +++ b/packages/flutter_widgets/lib/src/user_list_view.dart @@ -140,209 +140,170 @@ class _UserListViewState extends State with WidgetsBindingObserver { bool get _isListView => widget.crossAxisCount == 1; - @override - void initState() { - super.initState(); - final usersBloc = UsersBloc.of(context); - usersBloc.queryUsers( - filter: widget.filter, - sort: widget.sort, - pagination: widget.pagination, - options: widget.options, - ); - } + UserListController _userListController = UserListController(); @override Widget build(BuildContext context) { - final usersBloc = UsersBloc.of(context); - - if (!widget.pullToRefresh) { - return _buildListView(usersBloc); - } - - return RefreshIndicator( - onRefresh: () async { - return usersBloc.queryUsers( - filter: widget.filter, - sort: widget.sort, - options: widget.options, - pagination: widget.pagination, + var child = UserListCore( + errorBuilder: (err) { + return _buildError(err); + }, + emptyBuilder: (context) { + return _buildEmpty(); + }, + loadingBuilder: (context) { + return LayoutBuilder( + builder: (context, viewportConstraints) { + return SingleChildScrollView( + physics: AlwaysScrollableScrollPhysics(), + child: ConstrainedBox( + constraints: BoxConstraints( + minHeight: viewportConstraints.maxHeight, + ), + child: Center( + child: CircularProgressIndicator(), + ), + ), + ); + }, ); }, - child: _buildListView(usersBloc), + listBuilder: (context, list) { + return _buildListView(list); + }, + pagination: widget.pagination, + options: widget.options, + sort: widget.sort, + filter: widget.filter, + groupAlphabetically: widget.groupAlphabetically, + userListController: _userListController, ); + + if (!widget.pullToRefresh) { + return child; + } else { + return RefreshIndicator( + onRefresh: () async { + _userListController.loadData(); + }, + child: child, + ); + } } bool get isListAlreadySorted => widget.sort?.any((e) => e.field == 'name' && e.direction == 1) ?? false; - Stream> _buildUserStream( - UsersBlocState usersBlocState, - ) { - return usersBlocState.usersStream.map( - (users) { - if (widget.groupAlphabetically) { - var temp = users; - if (!isListAlreadySorted) { - temp = users..sort((curr, next) => curr.name.compareTo(next.name)); - } - final groupedUsers = >{}; - for (var e in temp) { - final alphabet = e.name[0]?.toUpperCase(); - groupedUsers[alphabet] = [...groupedUsers[alphabet] ?? [], e]; - } - final items = []; - for (var key in groupedUsers.keys) { - items.add(ListHeaderItem(key)); - items.addAll(groupedUsers[key].map((e) => ListUserItem(e))); - } - return items; - } - return users.map((e) => ListUserItem(e)).toList(); - }, + Widget _buildError(Error error) { + print((error).stackTrace); + + if (widget.errorBuilder != null) { + return widget.errorBuilder(error); + } + + var message = error.toString(); + if (error is DioError) { + final dioError = error as DioError; + if (dioError.type == DioErrorType.RESPONSE) { + message = dioError.message; + } else { + message = 'Check your connection and retry'; + } + } + return Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text.rich( + TextSpan( + children: [ + WidgetSpan( + child: Padding( + padding: const EdgeInsets.only( + right: 2.0, + ), + child: Icon(Icons.error_outline), + ), + ), + TextSpan(text: 'Error loading channels'), + ], + ), + style: Theme.of(context).textTheme.headline6, + ), + Padding( + padding: const EdgeInsets.only( + top: 16.0, + ), + child: Text(message), + ), + FlatButton( + onPressed: () { + _userListController.loadData(); + }, + child: Text('Retry'), + ), + ], + ), ); } - StreamBuilder> _buildListView( - UsersBlocState usersBlocState, - ) { - return StreamBuilder( - stream: _buildUserStream(usersBlocState), - builder: (context, snapshot) { - if (snapshot.hasError) { - if (snapshot.error is Error) { - print((snapshot.error as Error).stackTrace); - } + Widget _buildEmpty() { + if (widget.emptyBuilder != null) { + return widget.emptyBuilder(context); + } - if (widget.errorBuilder != null) { - return widget.errorBuilder(snapshot.error); - } - - var message = snapshot.error.toString(); - if (snapshot.error is DioError) { - final dioError = snapshot.error as DioError; - if (dioError.type == DioErrorType.RESPONSE) { - message = dioError.message; - } else { - message = 'Check your connection and retry'; - } - } - return Center( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Text.rich( - TextSpan( - children: [ - WidgetSpan( - child: Padding( - padding: const EdgeInsets.only( - right: 2.0, - ), - child: Icon(Icons.error_outline), - ), - ), - TextSpan(text: 'Error loading channels'), - ], - ), - style: Theme.of(context).textTheme.headline6, - ), - Padding( - padding: const EdgeInsets.only( - top: 16.0, - ), - child: Text(message), - ), - FlatButton( - onPressed: () { - usersBlocState.queryUsers( - filter: widget.filter, - sort: widget.sort, - pagination: widget.pagination, - options: widget.options, - ); - }, - child: Text('Retry'), - ), - ], + if (widget.emptyBuilder == null) { + return LayoutBuilder( + builder: (context, viewportConstraints) { + return SingleChildScrollView( + physics: AlwaysScrollableScrollPhysics(), + child: ConstrainedBox( + constraints: BoxConstraints( + minHeight: viewportConstraints.maxHeight, + ), + child: Center( + child: Text('There are no users currently'), + ), ), ); - } + }, + ); + } + } - if (!snapshot.hasData) { - return LayoutBuilder( - builder: (context, viewportConstraints) { - return SingleChildScrollView( - physics: AlwaysScrollableScrollPhysics(), - child: ConstrainedBox( - constraints: BoxConstraints( - minHeight: viewportConstraints.maxHeight, - ), - child: Center( - child: CircularProgressIndicator(), - ), - ), - ); + Widget _buildListView( + List items, + ) { + final child = _isListView + ? ListView.separated( + physics: AlwaysScrollableScrollPhysics(), + itemCount: items.isNotEmpty ? items.length + 1 : items.length, + separatorBuilder: (_, index) { + if (widget.separatorBuilder != null) { + return widget.separatorBuilder(context, index); + } + return _separatorBuilder(context, index); + }, + itemBuilder: (context, index) { + return _listItemBuilder(context, index, items); + }, + ) + : GridView.builder( + gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: widget.crossAxisCount, + ), + itemCount: items.isNotEmpty ? items.length + 1 : items.length, + physics: AlwaysScrollableScrollPhysics(), + itemBuilder: (context, index) { + return _gridItemBuilder(context, index, items); }, ); - } - final items = snapshot.data; - - if (items.isEmpty && widget.emptyBuilder != null) { - return widget.emptyBuilder(context); - } - - if (items.isEmpty && widget.emptyBuilder == null) { - return LayoutBuilder( - builder: (context, viewportConstraints) { - return SingleChildScrollView( - physics: AlwaysScrollableScrollPhysics(), - child: ConstrainedBox( - constraints: BoxConstraints( - minHeight: viewportConstraints.maxHeight, - ), - child: Center( - child: Text('There are no users currently'), - ), - ), - ); - }, - ); - } - - final child = _isListView - ? ListView.separated( - physics: AlwaysScrollableScrollPhysics(), - itemCount: items.isNotEmpty ? items.length + 1 : items.length, - separatorBuilder: (_, index) { - if (widget.separatorBuilder != null) { - return widget.separatorBuilder(context, index); - } - return _separatorBuilder(context, index); - }, - itemBuilder: (context, index) { - return _listItemBuilder(context, index, items); - }, - ) - : GridView.builder( - gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( - crossAxisCount: widget.crossAxisCount, - ), - itemCount: items.isNotEmpty ? items.length + 1 : items.length, - physics: AlwaysScrollableScrollPhysics(), - itemBuilder: (context, index) { - return _gridItemBuilder(context, index, items); - }, - ); - - return LazyLoadScrollView( - onEndOfPage: () async { - return _listenUserPagination(usersBlocState); - }, - child: child, - ); + return LazyLoadScrollView( + onEndOfPage: () async { + return _userListController.paginateData(); }, + child: child, ); } @@ -481,33 +442,4 @@ class _UserListViewState extends State color: StreamChatTheme.of(context).colorTheme.greyWhisper, ); } - - void _listenUserPagination(UsersBlocState usersProvider) { - usersProvider.queryUsers( - filter: widget.filter, - sort: widget.sort, - pagination: widget.pagination.copyWith( - offset: usersProvider.users?.length ?? 0, - ), - options: widget.options, - ); - } - - @override - void didUpdateWidget(UserListView oldWidget) { - super.didUpdateWidget(oldWidget); - if (widget.filter?.toString() != oldWidget.filter?.toString() || - jsonEncode(widget.sort) != jsonEncode(oldWidget.sort) || - widget.pagination?.toJson()?.toString() != - oldWidget.pagination?.toJson()?.toString() || - widget.options?.toString() != oldWidget.options?.toString()) { - final usersBloc = UsersBloc.of(context); - usersBloc.queryUsers( - filter: widget.filter, - sort: widget.sort, - pagination: widget.pagination, - options: widget.options, - ); - } - } } diff --git a/packages/stream_chat_flutter_core/lib/src/user_list_core.dart b/packages/stream_chat_flutter_core/lib/src/user_list_core.dart index 8edf8752..bb8f81fe 100644 --- a/packages/stream_chat_flutter_core/lib/src/user_list_core.dart +++ b/packages/stream_chat_flutter_core/lib/src/user_list_core.dart @@ -43,7 +43,6 @@ class UserListCore extends StatefulWidget { this.options, this.sort, this.pagination, - this.pullToRefresh = true, this.groupAlphabetically = false, @required this.errorBuilder, @required this.emptyBuilder, @@ -89,9 +88,6 @@ class UserListCore extends StatefulWidget { /// message_limit: how many messages should be included to each channel final PaginationParams pagination; - /// Set it to false to disable the pull-to-refresh widget - final bool pullToRefresh; - /// Set it to true to group users by their first character /// /// defaults to false @@ -124,21 +120,7 @@ class _UserListCoreState extends State Widget build(BuildContext context) { final usersBloc = UsersBloc.of(context); - if (!widget.pullToRefresh) { - return _buildListView(usersBloc); - } - - return RefreshIndicator( - onRefresh: () async { - return usersBloc.queryUsers( - filter: widget.filter, - sort: widget.sort, - options: widget.options, - pagination: widget.pagination, - ); - }, - child: _buildListView(usersBloc), - ); + return _buildListView(usersBloc); } bool get isListAlreadySorted => From a79f551332206265cdf5ecec0888acec884ab90e Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Mon, 25 Jan 2021 20:59:50 +0530 Subject: [PATCH 15/39] feat: Converted message_list_view.dart --- .../lib/src/message_list_view.dart | 551 +++++++++--------- .../lib/src/message_list_core.dart | 9 +- 2 files changed, 268 insertions(+), 292 deletions(-) diff --git a/packages/flutter_widgets/lib/src/message_list_view.dart b/packages/flutter_widgets/lib/src/message_list_view.dart index f14c332a..bb11a469 100644 --- a/packages/flutter_widgets/lib/src/message_list_view.dart +++ b/packages/flutter_widgets/lib/src/message_list_view.dart @@ -236,309 +236,288 @@ class _MessageListViewState extends State { bool _inBetweenList = false; + MessageListController _messageListController = MessageListController(); + @override Widget build(BuildContext context) { - final messagesStream = _isThreadConversation - ? streamChannel.channel.state.threadsStream - .where((threads) => threads.containsKey(widget.parentMessage.id)) - .map((threads) => threads[widget.parentMessage.id]) - : streamChannel.channel.state?.messagesStream; + return MessageListCore( + loadingBuilder: (context) { + return Center( + child: const CircularProgressIndicator(), + ); + }, + emptyBuilder: (context) { + return Center( + child: Text( + 'No chats here yet...', + style: StreamChatTheme.of(context).textTheme.footnote.copyWith( + color: StreamChatTheme.of(context) + .colorTheme + .black + .withOpacity(.5)), + ), + ); + }, + messageListBuilder: (context, list) { + return _buildListView(list); + }, + messageListController: _messageListController, + parentMessage: widget.parentMessage, + showScrollToBottom: widget.showScrollToBottom, + ); + } - return StreamBuilder>( - stream: messagesStream?.map((messages) => messages - ?.where((e) => - (!e.isDeleted && e.shadowed != true) || - (e.isDeleted && - e.user.id == streamChannel.channel.client.state.user.id)) - ?.toList()), - builder: (context, snapshot) { - if (!snapshot.hasData) { - return Center( - child: const CircularProgressIndicator(), - ); + Widget _buildListView(List data) { + messages = data; + final newMessagesListLength = messages.length; + + if (_messageListLength != null) { + if (_bottomPaginationActive || (_inBetweenList && _upToDate)) { + if (_itemPositionListener.itemPositions.value?.isNotEmpty == true) { + final first = _itemPositionListener.itemPositions.value.first; + final diff = newMessagesListLength - _messageListLength; + if (diff > 0) { + initialIndex = first.index + diff; + initialAlignment = first.itemLeadingEdge; } + } + } else if (!_topPaginationActive && _upToDate) { + // Reset the index in-case we send any new message + initialIndex = 0; + initialAlignment = 0; + } + } - final messageList = snapshot.data?.reversed?.toList() ?? []; - if (messageList.isEmpty) { - if (_upToDate) { - return Center( - child: Text( - 'No chats here yet...', - style: StreamChatTheme.of(context) - .textTheme - .footnote - .copyWith( - color: StreamChatTheme.of(context) - .colorTheme - .black - .withOpacity(.5)), + _messageListLength = newMessagesListLength; + final _client = StreamChat.of(context).client; + + return Stack( + alignment: Alignment.center, + children: [ + ValueListenableBuilder( + valueListenable: _client.wsConnectionStatus, + builder: (context, status, _) { + String statusString = ''; + bool showStatus = true; + + switch (status) { + case ConnectionStatus.connected: + statusString = 'Connected'; + showStatus = false; + break; + case ConnectionStatus.connecting: + statusString = 'Reconnecting...'; + break; + case ConnectionStatus.disconnected: + statusString = 'Disconnected'; + break; + } + + return InfoTile( + showMessage: + widget.showConnectionStateTile ? showStatus : false, + tileAnchor: Alignment.topCenter, + childAnchor: Alignment.topCenter, + message: statusString, + child: LazyLoadScrollView( + onStartOfPage: () async { + _inBetweenList = false; + if (!_upToDate) { + _topPaginationActive = false; + _bottomPaginationActive = true; + return _paginateData( + streamChannel, + QueryDirection.bottom, + ); + } + }, + onEndOfPage: () async { + _inBetweenList = false; + _topPaginationActive = true; + _bottomPaginationActive = false; + return _paginateData( + streamChannel, + QueryDirection.top, + ); + }, + onInBetweenOfPage: () { + _inBetweenList = true; + }, + child: ScrollablePositionedList.separated( + key: ValueKey(initialIndex + initialAlignment), + itemPositionsListener: _itemPositionListener, + addAutomaticKeepAlives: true, + initialScrollIndex: initialIndex ?? 0, + initialAlignment: initialAlignment ?? 0, + physics: widget.scrollPhysics, + itemScrollController: _scrollController, + reverse: true, + itemCount: + messages.length + 2 + (_isThreadConversation ? 1 : 0), + separatorBuilder: (context, i) { + if (i == messages.length) return Offstage(); + if (i == 0) return SizedBox(height: 30); + if (i == messages.length + 1) { + final replyCount = widget.parentMessage.replyCount; + return Container( + decoration: BoxDecoration( + gradient: StreamChatTheme.of(context) + .colorTheme + .bgGradient, + ), + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Text( + '$replyCount ${replyCount == 1 ? 'Reply' : 'Replies'}', + textAlign: TextAlign.center, + style: StreamChatTheme.of(context) + .channelTheme + .channelHeaderTheme + .lastMessageAt, + ), + ), + ); + } + + final message = messages[i]; + final nextMessage = messages[i - 1]; + if (!Jiffy(message.createdAt.toLocal()).isSame( + nextMessage.createdAt.toLocal(), + Units.DAY, + )) { + final divider = widget.dateDividerBuilder != null + ? widget.dateDividerBuilder( + nextMessage.createdAt.toLocal(), + ) + : DateDivider( + dateTime: nextMessage.createdAt.toLocal(), + ); + return Padding( + padding: const EdgeInsets.symmetric(vertical: 12.0), + child: divider, + ); + } + final timeDiff = + Jiffy(nextMessage.createdAt.toLocal()).diff( + message.createdAt.toLocal(), + Units.MINUTE, + ); + + final isNextUserSame = + message.user.id == nextMessage.user?.id; + final isThread = message.replyCount > 0; + final isDeleted = message.isDeleted; + if (timeDiff >= 1 || + !isNextUserSame || + isThread || + isDeleted) { + return SizedBox(height: 8); + } + return SizedBox(height: 2); + }, + itemBuilder: (context, i) { + if (i == messages.length + 2) { + if (widget.parentMessageBuilder != null) { + return widget.parentMessageBuilder( + context, + widget.parentMessage, + ); + } else { + return buildParentMessage(widget.parentMessage); + } + } + if (i == messages.length + 1) { + return _buildLoadingIndicator( + streamChannel, + QueryDirection.top, + ); + } + if (i == 0) { + return _buildLoadingIndicator( + streamChannel, + QueryDirection.bottom, + ); + } + final message = messages[i - 1]; + + Widget messageWidget; + + if (i == 1) { + messageWidget = _buildBottomMessage( + context, + message, + messages, + streamChannel, + ); + } else if (i == messages.length - 1) { + messageWidget = _buildTopMessage( + context, + message, + messages, + streamChannel, + ); + } else { + if (widget.messageBuilder != null) { + messageWidget = Builder( + key: ValueKey('MESSAGE-${message.id}'), + builder: (context) => widget.messageBuilder( + context, + MessageDetails( + context, + message, + messages, + i, + ), + messages), + ); + } else { + messageWidget = buildMessage(message, messages, i); + } + } + return messageWidget; + }, + ), ), ); - } - } else { - messages = messageList; - } - - final newMessagesListLength = messages.length; - - if (_messageListLength != null) { - if (_bottomPaginationActive || (_inBetweenList && _upToDate)) { - if (_itemPositionListener.itemPositions.value?.isNotEmpty == - true) { - final first = _itemPositionListener.itemPositions.value.first; - final diff = newMessagesListLength - _messageListLength; - if (diff > 0) { - initialIndex = first.index + diff; - initialAlignment = first.itemLeadingEdge; - } + }), + if (widget.showScrollToBottom) _buildScrollToBottom(), + Positioned( + top: 20.0, + child: ValueListenableBuilder>( + valueListenable: _itemPositionListener.itemPositions, + builder: (context, values, _) { + final items = _itemPositionListener.itemPositions?.value; + if (items.isEmpty || messages.isEmpty) { + return SizedBox(); } - } else if (!_topPaginationActive && _upToDate) { - // Reset the index in-case we send any new message - initialIndex = 0; - initialAlignment = 0; - } - } - _messageListLength = newMessagesListLength; - final _client = StreamChat.of(context).client; + var index = _getTopElement(values).index; - return Stack( - alignment: Alignment.center, - children: [ - ValueListenableBuilder( - valueListenable: _client.wsConnectionStatus, - builder: (context, status, _) { - String statusString = ''; - bool showStatus = true; + if (index > messages.length) { + return SizedBox(); + } - switch (status) { - case ConnectionStatus.connected: - statusString = 'Connected'; - showStatus = false; - break; - case ConnectionStatus.connecting: - statusString = 'Reconnecting...'; - break; - case ConnectionStatus.disconnected: - statusString = 'Disconnected'; - break; - } + if (index == messages.length) { + index = max(index - 1, 0); + } - return InfoTile( - showMessage: - widget.showConnectionStateTile ? showStatus : false, - tileAnchor: Alignment.topCenter, - childAnchor: Alignment.topCenter, - message: statusString, - child: LazyLoadScrollView( - onStartOfPage: () async { - _inBetweenList = false; - if (!_upToDate) { - _topPaginationActive = false; - _bottomPaginationActive = true; - return _paginateData( - streamChannel, - QueryDirection.bottom, - ); - } - }, - onEndOfPage: () async { - _inBetweenList = false; - _topPaginationActive = true; - _bottomPaginationActive = false; - return _paginateData( - streamChannel, - QueryDirection.top, - ); - }, - onInBetweenOfPage: () { - _inBetweenList = true; - }, - child: ScrollablePositionedList.separated( - key: ValueKey(initialIndex + initialAlignment), - itemPositionsListener: _itemPositionListener, - addAutomaticKeepAlives: true, - initialScrollIndex: initialIndex ?? 0, - initialAlignment: initialAlignment ?? 0, - physics: widget.scrollPhysics, - itemScrollController: _scrollController, - reverse: true, - itemCount: messages.length + - 2 + - (_isThreadConversation ? 1 : 0), - separatorBuilder: (context, i) { - if (i == messages.length) return Offstage(); - if (i == 0) return SizedBox(height: 30); - if (i == messages.length + 1) { - final replyCount = - widget.parentMessage.replyCount; - return Container( - decoration: BoxDecoration( - gradient: StreamChatTheme.of(context) - .colorTheme - .bgGradient, - ), - child: Padding( - padding: const EdgeInsets.all(8.0), - child: Text( - '$replyCount ${replyCount == 1 ? 'Reply' : 'Replies'}', - textAlign: TextAlign.center, - style: StreamChatTheme.of(context) - .channelTheme - .channelHeaderTheme - .lastMessageAt, - ), - ), - ); - } - - final message = messages[i]; - final nextMessage = messages[i - 1]; - if (!Jiffy(message.createdAt.toLocal()).isSame( - nextMessage.createdAt.toLocal(), - Units.DAY, - )) { - final divider = widget.dateDividerBuilder != null - ? widget.dateDividerBuilder( - nextMessage.createdAt.toLocal(), - ) - : DateDivider( - dateTime: nextMessage.createdAt.toLocal(), - ); - return Padding( - padding: - const EdgeInsets.symmetric(vertical: 12.0), - child: divider, - ); - } - final timeDiff = - Jiffy(nextMessage.createdAt.toLocal()).diff( - message.createdAt.toLocal(), - Units.MINUTE, - ); - - final isNextUserSame = - message.user.id == nextMessage.user?.id; - final isThread = message.replyCount > 0; - final isDeleted = message.isDeleted; - if (timeDiff >= 1 || - !isNextUserSame || - isThread || - isDeleted) { - return SizedBox(height: 8); - } - return SizedBox(height: 2); - }, - itemBuilder: (context, i) { - if (i == messages.length + 2) { - if (widget.parentMessageBuilder != null) { - return widget.parentMessageBuilder( - context, - widget.parentMessage, - ); - } else { - return buildParentMessage(widget.parentMessage); - } - } - if (i == messages.length + 1) { - return _buildLoadingIndicator( - streamChannel, - QueryDirection.top, - ); - } - if (i == 0) { - return _buildLoadingIndicator( - streamChannel, - QueryDirection.bottom, - ); - } - final message = messages[i - 1]; - - Widget messageWidget; - - if (i == 1) { - messageWidget = _buildBottomMessage( - context, - message, - messages, - streamChannel, - ); - } else if (i == messages.length - 1) { - messageWidget = _buildTopMessage( - context, - message, - messages, - streamChannel, - ); - } else { - if (widget.messageBuilder != null) { - messageWidget = Builder( - key: - ValueKey('MESSAGE-${message.id}'), - builder: (context) => widget.messageBuilder( - context, - MessageDetails( - context, - message, - messages, - i, - ), - messages), - ); - } else { - messageWidget = - buildMessage(message, messages, i); - } - } - return messageWidget; - }, - ), - ), + return widget.dateDividerBuilder != null + ? widget.dateDividerBuilder( + messages[index].createdAt.toLocal(), + ) + : DateDivider( + dateTime: messages[index].createdAt.toLocal(), ); - }), - if (widget.showScrollToBottom) _buildScrollToBottom(), - Positioned( - top: 20.0, - child: ValueListenableBuilder>( - valueListenable: _itemPositionListener.itemPositions, - builder: (context, values, _) { - final items = _itemPositionListener.itemPositions?.value; - if (items.isEmpty || messages.isEmpty) { - return SizedBox(); - } - - var index = _getTopElement(values).index; - - if (index > messages.length) { - return SizedBox(); - } - - if (index == messages.length) { - index = max(index - 1, 0); - } - - return widget.dateDividerBuilder != null - ? widget.dateDividerBuilder( - messages[index].createdAt.toLocal(), - ) - : DateDivider( - dateTime: messages[index].createdAt.toLocal(), - ); - }, - ), - ), - ], - ); - }); + }, + ), + ), + ], + ); } Future _paginateData( StreamChannelState channel, QueryDirection direction) { - if (!_isThreadConversation) { - return channel.queryMessages(direction: direction); - } else { - return channel.getReplies(widget.parentMessage.id); - } + return _messageListController.paginateData(direction: direction); } ItemPosition _getTopElement(Iterable values) { diff --git a/packages/stream_chat_flutter_core/lib/src/message_list_core.dart b/packages/stream_chat_flutter_core/lib/src/message_list_core.dart index e04e1ea8..a13c186c 100644 --- a/packages/stream_chat_flutter_core/lib/src/message_list_core.dart +++ b/packages/stream_chat_flutter_core/lib/src/message_list_core.dart @@ -51,7 +51,6 @@ class MessageListCore extends StatefulWidget { Key key, this.showScrollToBottom = true, this.parentMessage, - this.dateDividerBuilder, @required this.loadingBuilder, @required this.emptyBuilder, @required this.messageListBuilder, @@ -74,9 +73,6 @@ class MessageListCore extends StatefulWidget { /// Parent message in case of a thread final Message parentMessage; - /// Builder used to render date dividers - final Widget Function(DateTime) dateDividerBuilder; - @override _MessageListCoreState createState() => _MessageListCoreState(); } @@ -128,7 +124,8 @@ class _MessageListCoreState extends State { }); } - Future paginateData(QueryDirection direction) { + Future paginateData( + {QueryDirection direction = QueryDirection.bottom}) { if (!_isThreadConversation) { return streamChannel.queryMessages(direction: direction); } else { @@ -162,5 +159,5 @@ class _MessageListCoreState extends State { /// Controller used for paginating data in [ChannelListView] class MessageListController { - Function(QueryDirection direction) paginateData; + Function({QueryDirection direction}) paginateData; } From 016cff2c294919c61940b38f7a29ee945af52f4e Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Mon, 25 Jan 2021 21:17:17 +0530 Subject: [PATCH 16/39] feat: Converted message_search_list_view.dart --- .../lib/src/message_search_list_view.dart | 371 ++++++++---------- 1 file changed, 163 insertions(+), 208 deletions(-) diff --git a/packages/flutter_widgets/lib/src/message_search_list_view.dart b/packages/flutter_widgets/lib/src/message_search_list_view.dart index 7897d2ec..0e8f9223 100644 --- a/packages/flutter_widgets/lib/src/message_search_list_view.dart +++ b/packages/flutter_widgets/lib/src/message_search_list_view.dart @@ -119,23 +119,115 @@ class MessageSearchListView extends StatefulWidget { } class _MessageSearchListViewState extends State { - @override - void initState() { - super.initState(); - final messageSearchBloc = MessageSearchBloc.of(context); - messageSearchBloc.search( - filter: widget.filters, - sort: widget.sortOptions, - query: widget.messageQuery, - pagination: widget.paginationParams, - messageFilter: widget.messageFilters, - ); - } + MessageSearchListController _messageSearchListController = + MessageSearchListController(); @override Widget build(BuildContext context) { - final messageSearchBloc = MessageSearchBloc.of(context); - return _buildListView(messageSearchBloc); + return MessageSearchListCore( + filters: widget.filters, + sortOptions: widget.sortOptions, + messageQuery: widget.messageQuery, + paginationParams: widget.paginationParams, + messageFilters: widget.messageFilters, + messageSearchListController: _messageSearchListController, + emptyBuilder: (context) { + if (widget.emptyBuilder != null) { + return widget.emptyBuilder(context, widget.messageQuery); + } + return LayoutBuilder( + builder: (context, viewportConstraints) { + return SingleChildScrollView( + physics: AlwaysScrollableScrollPhysics(), + child: ConstrainedBox( + constraints: BoxConstraints( + minHeight: viewportConstraints.maxHeight, + ), + child: Center( + child: Text('There are no messages currently'), + ), + ), + ); + }, + ); + }, + errorBuilder: (error) { + if (error is Error) { + print((error).stackTrace); + } + + if (widget.errorBuilder != null) { + return widget.errorBuilder(error); + } + + var message = error.toString(); + if (error is DioError) { + final dioError = error as DioError; + if (dioError.type == DioErrorType.RESPONSE) { + message = dioError.message; + } else { + message = 'Check your connection and retry'; + } + } + return InfoTile( + showMessage: widget.showErrorTile, + tileAnchor: Alignment.topCenter, + childAnchor: Alignment.topCenter, + message: 'An error occurred.', + child: Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text.rich( + TextSpan( + children: [ + WidgetSpan( + child: Padding( + padding: const EdgeInsets.only(right: 2.0), + child: Icon(Icons.error_outline), + ), + ), + TextSpan(text: 'Error loading messages'), + ], + ), + style: Theme.of(context).textTheme.headline6, + ), + Padding( + padding: const EdgeInsets.only(top: 16.0), + child: Text(message), + ), + RaisedButton( + onPressed: () { + _messageSearchListController.loadData(); + }, + child: Text('Retry'), + ), + ], + ), + ), + ); + }, + loadingBuilder: (context) { + return LayoutBuilder( + builder: (context, viewportConstraints) { + return SingleChildScrollView( + physics: AlwaysScrollableScrollPhysics(), + child: ConstrainedBox( + constraints: BoxConstraints( + minHeight: viewportConstraints.maxHeight, + ), + child: Center( + child: CircularProgressIndicator(), + ), + ), + ); + }, + ); + }, + childBuilder: (list) { + return _buildListView(list); + }, + ); } Widget _separatorBuilder(BuildContext context, int index) { @@ -156,8 +248,9 @@ class _MessageSearchListViewState extends State { ); } - Widget _buildQueryProgressIndicator( - context, MessageSearchBlocState messageSearchBloc) { + Widget _buildQueryProgressIndicator(context) { + MessageSearchBlocState messageSearchBloc = MessageSearchBloc.of(context); + return StreamBuilder( stream: messageSearchBloc.queryMessagesLoading, initialData: false, @@ -186,204 +279,66 @@ class _MessageSearchListViewState extends State { }); } - Widget _buildListView(MessageSearchBlocState messageSearchBloc) { - return StreamBuilder>( - stream: messageSearchBloc.messagesStream, - builder: (context, snapshot) { - if (snapshot.hasError) { - if (snapshot.error is Error) { - print((snapshot.error as Error).stackTrace); - } + Widget _buildListView(List data) { + final items = data; - if (widget.errorBuilder != null) { - return widget.errorBuilder(snapshot.error); - } - - var message = snapshot.error.toString(); - if (snapshot.error is DioError) { - final dioError = snapshot.error as DioError; - if (dioError.type == DioErrorType.RESPONSE) { - message = dioError.message; - } else { - message = 'Check your connection and retry'; - } - } - return InfoTile( - showMessage: widget.showErrorTile, - tileAnchor: Alignment.topCenter, - childAnchor: Alignment.topCenter, - message: 'An error occurred.', - child: Center( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Text.rich( - TextSpan( - children: [ - WidgetSpan( - child: Padding( - padding: const EdgeInsets.only(right: 2.0), - child: Icon(Icons.error_outline), - ), - ), - TextSpan(text: 'Error loading messages'), - ], - ), - style: Theme.of(context).textTheme.headline6, - ), - Padding( - padding: const EdgeInsets.only(top: 16.0), - child: Text(message), - ), - RaisedButton( - onPressed: () { - messageSearchBloc.search( - filter: widget.filters, - sort: widget.sortOptions, - query: widget.messageQuery, - pagination: widget.paginationParams, - messageFilter: widget.messageFilters, - ); - }, - child: Text('Retry'), - ), - ], - ), - ), - ); + Widget child = ListView.separated( + physics: AlwaysScrollableScrollPhysics(), + itemCount: items.isNotEmpty ? items.length + 1 : items.length, + separatorBuilder: (_, index) { + if (widget.separatorBuilder != null) { + return widget.separatorBuilder(context, index); } - - if (!snapshot.hasData) { - return LayoutBuilder( - builder: (context, viewportConstraints) { - return SingleChildScrollView( - physics: AlwaysScrollableScrollPhysics(), - child: ConstrainedBox( - constraints: BoxConstraints( - minHeight: viewportConstraints.maxHeight, - ), - child: Center( - child: CircularProgressIndicator(), - ), - ), - ); - }, - ); + return _separatorBuilder(context, index); + }, + itemBuilder: (context, index) { + if (index < items.length) { + return _listItemBuilder(context, items[index]); } - - final items = snapshot.data; - - if (items.isEmpty) { - if (widget.emptyBuilder != null) { - return widget.emptyBuilder(context, widget.messageQuery); - } - return LayoutBuilder( - builder: (context, viewportConstraints) { - return SingleChildScrollView( - physics: AlwaysScrollableScrollPhysics(), - child: ConstrainedBox( - constraints: BoxConstraints( - minHeight: viewportConstraints.maxHeight, - ), - child: Center( - child: Text('There are no messages currently'), - ), - ), - ); - }, - ); - } - - Widget child = ListView.separated( - physics: AlwaysScrollableScrollPhysics(), - itemCount: items.isNotEmpty ? items.length + 1 : items.length, - separatorBuilder: (_, index) { - if (widget.separatorBuilder != null) { - return widget.separatorBuilder(context, index); - } - return _separatorBuilder(context, index); - }, - itemBuilder: (context, index) { - if (index < items.length) { - return _listItemBuilder(context, items[index]); - } - return _buildQueryProgressIndicator(context, messageSearchBloc); - }, - ); - if (widget.pullToRefresh) { - child = RefreshIndicator( - onRefresh: () async => messageSearchBloc.search( - filter: widget.filters, - sort: widget.sortOptions, - query: widget.messageQuery, - pagination: widget.paginationParams, - messageFilter: widget.messageFilters, - ), - child: child, - ); - } - - child = LazyLoadScrollView( - onEndOfPage: () => messageSearchBloc.loadMore( - filter: widget.filters, - sort: widget.sortOptions, - pagination: widget.paginationParams.copyWith( - offset: messageSearchBloc.messageResponses?.length ?? 0, - ), - query: widget.messageQuery, - messageFilter: widget.messageFilters, - ), - child: child, - ); - - if (widget.showResultCount) { - child = Column( - children: [ - Container( - width: double.maxFinite, - decoration: BoxDecoration( - gradient: StreamChatTheme.of(context).colorTheme.bgGradient, - ), - child: Padding( - padding: const EdgeInsets.symmetric( - vertical: 8, - horizontal: 8, - ), - child: Text( - '${items.length} results', - style: TextStyle( - color: StreamChatTheme.of(context).colorTheme.grey, - ), - ), - ), - ), - Expanded(child: child), - ], - ); - } - return child; + return _buildQueryProgressIndicator(context); }, ); - } - - @override - void didUpdateWidget(MessageSearchListView oldWidget) { - super.didUpdateWidget(oldWidget); - if (widget.filters?.toString() != oldWidget.filters?.toString() || - jsonEncode(widget.sortOptions) != jsonEncode(oldWidget.sortOptions) || - widget.paginationParams?.toJson()?.toString() != - oldWidget.paginationParams?.toJson()?.toString() || - widget.messageQuery?.toString() != oldWidget.messageQuery?.toString() || - widget.messageFilters?.toString() != - oldWidget.messageFilters?.toString()) { - final messageSearchBloc = MessageSearchBloc.of(context); - messageSearchBloc.search( - filter: widget.filters, - sort: widget.sortOptions, - query: widget.messageQuery, - pagination: widget.paginationParams, - messageFilter: widget.messageFilters, + if (widget.pullToRefresh) { + child = RefreshIndicator( + onRefresh: () async { + _messageSearchListController.loadData(); + }, + child: child, ); } + + child = LazyLoadScrollView( + onEndOfPage: () async { + return _messageSearchListController.paginateData(); + }, + child: child, + ); + + if (widget.showResultCount) { + child = Column( + children: [ + Container( + width: double.maxFinite, + decoration: BoxDecoration( + gradient: StreamChatTheme.of(context).colorTheme.bgGradient, + ), + child: Padding( + padding: const EdgeInsets.symmetric( + vertical: 8, + horizontal: 8, + ), + child: Text( + '${items.length} results', + style: TextStyle( + color: StreamChatTheme.of(context).colorTheme.grey, + ), + ), + ), + ), + Expanded(child: child), + ], + ); + } + return child; } } From ec858e497376e31772a991733d78b75f5335cf56 Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Tue, 26 Jan 2021 18:44:02 +0530 Subject: [PATCH 17/39] doc: Added some docs for core code --- .../lib/src/channel_list_core.dart | 32 +++++++++++++++---- .../lib/src/message_list_core.dart | 27 ++++++++++------ .../lib/src/message_search_list_core.dart | 9 ++++-- .../lib/src/user_list_core.dart | 26 +++++++++++++-- 4 files changed, 73 insertions(+), 21 deletions(-) diff --git a/packages/stream_chat_flutter_core/lib/src/channel_list_core.dart b/packages/stream_chat_flutter_core/lib/src/channel_list_core.dart index 972de62f..5b992470 100644 --- a/packages/stream_chat_flutter_core/lib/src/channel_list_core.dart +++ b/packages/stream_chat_flutter_core/lib/src/channel_list_core.dart @@ -11,14 +11,15 @@ import 'stream_chat_core.dart'; /// ![screenshot](https://raw.githubusercontent.com/GetStream/stream-chat-flutter/master/screenshots/channel_list_view.png) /// ![screenshot](https://raw.githubusercontent.com/GetStream/stream-chat-flutter/master/screenshots/channel_list_view_paint.png) /// -/// It shows the list of current channels. +/// [ChannelListCore] is a simplified class that allows fetching a list of channels while exposing UI builders. +/// A [ChannelListController] is used to reload and paginate data. /// /// ```dart /// class ChannelListPage extends StatelessWidget { /// @override /// Widget build(BuildContext context) { /// return Scaffold( -/// body: ChannelListView( +/// body: ChannelListCore( /// filter: { /// 'members': { /// '\$in': [StreamChat.of(context).user.id], @@ -28,7 +29,24 @@ import 'stream_chat_core.dart'; /// pagination: PaginationParams( /// limit: 20, /// ), -/// channelWidget: ChannelPage(), +/// errorBuilder: (err) { +/// return Center( +/// child: Text('An error has occured'), +/// ); +/// }, +/// emptyBuilder: (context) { +/// return Center( +/// child: Text('Nothing here...'), +/// ); +/// }, +/// emptyBuilder: (context) { +/// return Center( +/// child: CircularProgressIndicator(), +/// ); +/// }, +/// listBuilder: (context, list) { +/// return ChannelPage(list); +/// } /// ), /// ); /// } @@ -37,10 +55,6 @@ import 'stream_chat_core.dart'; /// /// /// Make sure to have a [StreamChatCore] ancestor in order to provide the information about the channels. -/// The widget uses a [ListView.custom] to render the list of channels. -/// -/// The widget components render the ui based on the first ancestor of type [StreamChatTheme]. -/// Modify it to change the widget appearance. class ChannelListCore extends StatefulWidget { /// Instantiate a new ChannelListView ChannelListCore({ @@ -56,13 +70,17 @@ class ChannelListCore extends StatefulWidget { this.channelListController, }) : super(key: key); + /// A [ChannelListController] allows reloading and pagination. + /// Use [ChannelListController.loadData] and [ChannelListController.paginateData] respectively for reloading and pagination. final ChannelListController channelListController; /// The builder that will be used in case of error final Widget Function(Error error) errorBuilder; + /// The builder that will be used in case of loading final WidgetBuilder loadingBuilder; + /// The builder which is used when list of channels loads final Function(BuildContext, List) listBuilder; /// The builder used when the channel list is empty. diff --git a/packages/stream_chat_flutter_core/lib/src/message_list_core.dart b/packages/stream_chat_flutter_core/lib/src/message_list_core.dart index a13c186c..6ef92569 100644 --- a/packages/stream_chat_flutter_core/lib/src/message_list_core.dart +++ b/packages/stream_chat_flutter_core/lib/src/message_list_core.dart @@ -8,7 +8,8 @@ import 'stream_channel.dart'; /// ![screenshot](https://raw.githubusercontent.com/GetStream/stream-chat-flutter/master/screenshots/message_listview.png) /// ![screenshot](https://raw.githubusercontent.com/GetStream/stream-chat-flutter/master/screenshots/message_listview_paint.png) /// -/// It shows the list of messages of the current channel. +/// [MessageListCore] is a simplified class that allows fetching a list of messages while exposing UI builders. +/// A [MessageListController] is used to paginate data. /// /// ```dart /// class ChannelPage extends StatelessWidget { @@ -19,19 +20,25 @@ import 'stream_channel.dart'; /// @override /// Widget build(BuildContext context) { /// return Scaffold( -/// appBar: ChannelHeader(), /// body: Column( /// children: [ /// Expanded( -/// child: MessageListView( -/// threadBuilder: (_, parentMessage) { -/// return ThreadPage( -/// parent: parentMessage, -/// ); -/// }, +/// child: MessageListCore( +/// emptyBuilder: (context) { +/// return Center( +/// child: Text('Nothing here...'), +/// ); +/// }, +/// emptyBuilder: (context) { +/// return Center( +/// child: CircularProgressIndicator(), +/// ); +/// }, +/// messageListBuilder: (context, list) { +/// return MessagesPage(list); +/// } /// ), /// ), -/// MessageInput(), /// ], /// ), /// ); @@ -57,6 +64,8 @@ class MessageListCore extends StatefulWidget { this.messageListController, }) : super(key: key); + /// A [MessageListController] allows pagination. + /// Use [ChannelListController.paginateData] pagination. final MessageListController messageListController; final Widget Function(BuildContext, List) messageListBuilder; diff --git a/packages/stream_chat_flutter_core/lib/src/message_search_list_core.dart b/packages/stream_chat_flutter_core/lib/src/message_search_list_core.dart index f011ee53..de0af711 100644 --- a/packages/stream_chat_flutter_core/lib/src/message_search_list_core.dart +++ b/packages/stream_chat_flutter_core/lib/src/message_search_list_core.dart @@ -5,14 +5,15 @@ import 'package:stream_chat/stream_chat.dart'; import 'message_search_bloc.dart'; /// -/// It shows the list of searched messages. +/// [MessageSearchListCore] is a simplified class that allows searching for messages across channels while exposing UI builders. +/// A [MessageSearchListController] is used to load and paginate data. /// /// ```dart /// class MessageSearchPage extends StatelessWidget { /// @override /// Widget build(BuildContext context) { /// return Scaffold( -/// body: MessageSearchListView( +/// body: MessageSearchListCore( /// messageQuery: _channelQuery, /// filters: { /// 'members': { @@ -48,6 +49,8 @@ class MessageSearchListCore extends StatefulWidget { this.messageSearchListController, }) : super(key: key); + /// A [MessageSearchListController] allows reloading and pagination. + /// Use [MessageSearchListController.loadData] and [MessageSearchListController.paginateData] respectively for reloading and pagination. final MessageSearchListController messageSearchListController; /// Message String to search on @@ -75,6 +78,7 @@ class MessageSearchListCore extends StatefulWidget { /// You can also filter other built-in channel fields. final Map messageFilters; + /// The builder that is used when the search messages are fetched final Widget Function(List) childBuilder; /// The builder used when the channel list is empty. @@ -83,6 +87,7 @@ class MessageSearchListCore extends StatefulWidget { /// The builder that will be used in case of error final Widget Function(Error error) errorBuilder; + /// The builder that will be used in case of loading final WidgetBuilder loadingBuilder; @override diff --git a/packages/stream_chat_flutter_core/lib/src/user_list_core.dart b/packages/stream_chat_flutter_core/lib/src/user_list_core.dart index bb8f81fe..d08e06ae 100644 --- a/packages/stream_chat_flutter_core/lib/src/user_list_core.dart +++ b/packages/stream_chat_flutter_core/lib/src/user_list_core.dart @@ -5,14 +5,15 @@ import 'package:stream_chat/stream_chat.dart'; import 'package:stream_chat_flutter_core/src/users_bloc.dart'; /// -/// It shows the list of current users. +/// [UserListCore] is a simplified class that allows fetching users while exposing UI builders. +/// A [UserListController] is used to load and paginate data. /// /// ```dart /// class UsersListPage extends StatelessWidget { /// @override /// Widget build(BuildContext context) { /// return Scaffold( -/// body: UsersListView( +/// body: UsersListCore( /// filter: { /// 'members': { /// '\$in': [StreamChat.of(context).user.id], @@ -22,7 +23,24 @@ import 'package:stream_chat_flutter_core/src/users_bloc.dart'; /// pagination: PaginationParams( /// limit: 20, /// ), -/// channelWidget: ChannelPage(), +/// errorBuilder: (err) { +/// return Center( +/// child: Text('An error has occured'), +/// ); +/// }, +/// emptyBuilder: (context) { +/// return Center( +/// child: Text('Nothing here...'), +/// ); +/// }, +/// emptyBuilder: (context) { +/// return Center( +/// child: CircularProgressIndicator(), +/// ); +/// }, +/// listBuilder: (context, list) { +/// return UsersPage(list); +/// } /// ), /// ); /// } @@ -51,6 +69,8 @@ class UserListCore extends StatefulWidget { this.userListController, }) : super(key: key); + /// A [UserListController] allows reloading and pagination. + /// Use [UserListController.loadData] and [UserListController.paginateData] respectively for reloading and pagination. final UserListController userListController; /// The builder that will be used in case of error From b2108c49dc985dd7f732dca3c652351485fce11f Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Tue, 26 Jan 2021 19:46:17 +0530 Subject: [PATCH 18/39] doc: Added basic README.md --- packages/stream_chat_flutter_core/README.md | 75 ++++++++++++++++++--- 1 file changed, 65 insertions(+), 10 deletions(-) diff --git a/packages/stream_chat_flutter_core/README.md b/packages/stream_chat_flutter_core/README.md index 0acef57c..30ab07f5 100644 --- a/packages/stream_chat_flutter_core/README.md +++ b/packages/stream_chat_flutter_core/README.md @@ -1,14 +1,69 @@ -# stream_chat_flutter_core +# Official Flutter SDK Core for [Stream Chat](https://getstream.io/chat/) -Stream Chat Official Flutter SDK Core. Build and customize your own chat experience using Dart and Flutter. +

+ Flutter Chat +

-## Getting Started +> The official Flutter core components for Stream Chat, a service for +> building chat applications. -This project is a starting point for a Dart -[package](https://flutter.dev/developing-packages/), -a library module containing code that can be shared easily across -multiple Flutter or Dart projects. +[![Pub](https://img.shields.io/pub/v/stream_chat_flutter.svg)](https://pub.dartlang.org/packages/stream_chat_flutter) +![](https://img.shields.io/badge/platform-flutter%20%7C%20flutter%20web-ff69b4.svg?style=flat-square) +[![Gitter](https://badges.gitter.im/GetStream/stream-chat-flutter.svg)](https://gitter.im/GetStream/stream-chat-flutter?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge) +![CI](https://github.com/GetStream/stream-chat-flutter/workflows/CI/badge.svg?branch=master) + -For help getting started with Flutter, view our -[online documentation](https://flutter.dev/docs), which offers tutorials, -samples, guidance on mobile development, and a full API reference. +**Quick Links** + +- [Register](https://getstream.io/chat/trial/) to get an API key for Stream Chat +- [Flutter Chat Tutorial](https://getstream.io/chat/flutter/tutorial/) +- [Chat UI Kit](https://getstream.io/chat/ui-kit/) + +## Flutter Chat Tutorial + +The best place to start is the [Flutter Chat Tutorial](https://getstream.io/chat/flutter/tutorial/). +It teaches you how to use this SDK and also shows how to make frequently required changes. + +## Example App + +This repo includes a fully functional example app with setup instructions. +The example is available under the [example](https://github.com/GetStream/stream-chat-flutter/tree/master/example) folder. + +## Add dependency +Add this to your package's pubspec.yaml file, use the latest version [![Pub](https://img.shields.io/pub/v/stream_chat_flutter.svg)](https://pub.dartlang.org/packages/stream_chat_flutter) +```yaml +dependencies: + stream_chat_flutter_core: ^latest_version +``` + +You should then run `flutter packages get` + +This package requires no custom setup on any platform since it does not depend on any platform-specific dependency + +## Docs + +This package provides business logic to fetch common things required for integrating Stream Chat into your application. +The core package allows more customisation and hence provides business logic but no UI components. +Please use the stream_chat_flutter package for the full fledged suite of UI components. + +### Business logic components + +We provide widgets dedicated to business logic and state management: + +- [StreamChatCore](https://pub.dev/documentation/stream_chat_flutter/latest/stream_chat_flutter/StreamChat-class.html) +- [StreamChannel](https://pub.dev/documentation/stream_chat_flutter/latest/stream_chat_flutter/StreamChannel-class.html) +- [ChannelsBloc](https://pub.dev/documentation/stream_chat_flutter/0.2.0-alpha+2/stream_chat_flutter/ChannelsBloc-class.html) +- [MessageSearchBloc] +- [UsersBloc] +- [ChannelListCore] +- [MessageListCore] +- [MessageSearchListCore] +- [UserListCore] + +## Contributing + +We welcome code changes that improve this library or fix a problem, +please make sure to follow all best practices and add tests if applicable before submitting a Pull Request on Github. +We are pleased to merge your code into the official repository. +Make sure to sign our [Contributor License Agreement (CLA)](https://docs.google.com/forms/d/e/1FAIpQLScFKsKkAJI7mhCr7K9rEIOpqIDThrWxuvxnwUq2XkHyG154vQ/viewform) first. +See our license file for more details. From 27ee546eb0d9762bcb82b01f3343b092b762f08d Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Tue, 26 Jan 2021 20:10:39 +0530 Subject: [PATCH 19/39] fix: Fix analysis issues --- .../lib/src/message_search_list_view.dart | 2 -- .../lib/src/user_list_view.dart | 32 ++++++++----------- 2 files changed, 14 insertions(+), 20 deletions(-) diff --git a/packages/flutter_widgets/lib/src/message_search_list_view.dart b/packages/flutter_widgets/lib/src/message_search_list_view.dart index 0e8f9223..f1711cb8 100644 --- a/packages/flutter_widgets/lib/src/message_search_list_view.dart +++ b/packages/flutter_widgets/lib/src/message_search_list_view.dart @@ -1,5 +1,3 @@ -import 'dart:convert'; - import 'package:flutter/material.dart'; import 'package:stream_chat/stream_chat.dart'; import 'package:stream_chat_flutter/src/info_tile.dart'; diff --git a/packages/flutter_widgets/lib/src/user_list_view.dart b/packages/flutter_widgets/lib/src/user_list_view.dart index 7027a875..30c5db76 100644 --- a/packages/flutter_widgets/lib/src/user_list_view.dart +++ b/packages/flutter_widgets/lib/src/user_list_view.dart @@ -1,5 +1,3 @@ -import 'dart:convert'; - import 'package:flutter/material.dart'; import 'package:stream_chat/stream_chat.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; @@ -252,23 +250,21 @@ class _UserListViewState extends State return widget.emptyBuilder(context); } - if (widget.emptyBuilder == null) { - return LayoutBuilder( - builder: (context, viewportConstraints) { - return SingleChildScrollView( - physics: AlwaysScrollableScrollPhysics(), - child: ConstrainedBox( - constraints: BoxConstraints( - minHeight: viewportConstraints.maxHeight, - ), - child: Center( - child: Text('There are no users currently'), - ), + return LayoutBuilder( + builder: (context, viewportConstraints) { + return SingleChildScrollView( + physics: AlwaysScrollableScrollPhysics(), + child: ConstrainedBox( + constraints: BoxConstraints( + minHeight: viewportConstraints.maxHeight, ), - ); - }, - ); - } + child: Center( + child: Text('There are no users currently'), + ), + ), + ); + }, + ); } Widget _buildListView( From 29837e23f5965ae10e1259d404c9d43b8f07bcf0 Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Tue, 26 Jan 2021 21:37:20 +0530 Subject: [PATCH 20/39] fix: analysis issues --- .../example/lib/main.dart | 4 ++++ .../lib/src/channel_list_core.dart | 3 +++ .../lib/src/message_list_core.dart | 2 ++ .../lib/src/message_search_list_core.dart | 3 +++ .../lib/src/stream_channel.dart | 16 +++++++++++++++- .../lib/src/stream_chat_core.dart | 4 ++++ .../lib/src/user_list_core.dart | 12 ++++++++++++ 7 files changed, 43 insertions(+), 1 deletion(-) diff --git a/packages/stream_chat_flutter_core/example/lib/main.dart b/packages/stream_chat_flutter_core/example/lib/main.dart index 11655b66..496f7c82 100644 --- a/packages/stream_chat_flutter_core/example/lib/main.dart +++ b/packages/stream_chat_flutter_core/example/lib/main.dart @@ -4,6 +4,7 @@ void main() { runApp(MyApp()); } +// ignore: public_member_api_docs class MyApp extends StatelessWidget { // This widget is the root of your application. @override @@ -31,7 +32,9 @@ class MyApp extends StatelessWidget { } } +// ignore: public_member_api_docs class MyHomePage extends StatefulWidget { + // ignore: public_member_api_docs MyHomePage({Key key, this.title}) : super(key: key); // This widget is the home page of your application. It is stateful, meaning @@ -43,6 +46,7 @@ class MyHomePage extends StatefulWidget { // used by the build method of the State. Fields in a Widget subclass are // always marked "final". + // ignore: public_member_api_docs final String title; @override diff --git a/packages/stream_chat_flutter_core/lib/src/channel_list_core.dart b/packages/stream_chat_flutter_core/lib/src/channel_list_core.dart index 5b992470..41c6f81d 100644 --- a/packages/stream_chat_flutter_core/lib/src/channel_list_core.dart +++ b/packages/stream_chat_flutter_core/lib/src/channel_list_core.dart @@ -261,6 +261,9 @@ class _ChannelListCoreState extends State /// Controller used for paginating data in [ChannelListCore] class ChannelListController { + /// Call this function to reload data VoidCallback loadData; + + /// Call this function to load further data VoidCallback paginateData; } diff --git a/packages/stream_chat_flutter_core/lib/src/message_list_core.dart b/packages/stream_chat_flutter_core/lib/src/message_list_core.dart index 6ef92569..1cd5162e 100644 --- a/packages/stream_chat_flutter_core/lib/src/message_list_core.dart +++ b/packages/stream_chat_flutter_core/lib/src/message_list_core.dart @@ -68,6 +68,7 @@ class MessageListCore extends StatefulWidget { /// Use [ChannelListController.paginateData] pagination. final MessageListController messageListController; + /// Function called when messages are fetched final Widget Function(BuildContext, List) messageListBuilder; /// Function used to build a loading widget @@ -168,5 +169,6 @@ class _MessageListCoreState extends State { /// Controller used for paginating data in [ChannelListView] class MessageListController { + /// Call this function to load further data Function({QueryDirection direction}) paginateData; } diff --git a/packages/stream_chat_flutter_core/lib/src/message_search_list_core.dart b/packages/stream_chat_flutter_core/lib/src/message_search_list_core.dart index de0af711..e5ff9527 100644 --- a/packages/stream_chat_flutter_core/lib/src/message_search_list_core.dart +++ b/packages/stream_chat_flutter_core/lib/src/message_search_list_core.dart @@ -196,6 +196,9 @@ class _MessageSearchListCoreState extends State { /// Controller used for paginating data in [ChannelListView] class MessageSearchListController { + /// Call this function to reload data VoidCallback loadData; + + /// Call this function to load further data VoidCallback paginateData; } diff --git a/packages/stream_chat_flutter_core/lib/src/stream_channel.dart b/packages/stream_chat_flutter_core/lib/src/stream_channel.dart index 8b4b97ef..85f7ecca 100644 --- a/packages/stream_chat_flutter_core/lib/src/stream_channel.dart +++ b/packages/stream_chat_flutter_core/lib/src/stream_channel.dart @@ -4,12 +4,20 @@ import 'package:flutter/material.dart'; import 'package:rxdart/rxdart.dart'; import 'package:stream_chat/stream_chat.dart'; -enum QueryDirection { top, bottom } +/// Specifies query direction for pagination +enum QueryDirection { + /// Query earlier messages + top, + + /// Query later messages + bottom, +} /// Widget used to provide information about the channel to the widget tree /// /// Use [StreamChannel.of] to get the current [StreamChannelState] instance. class StreamChannel extends StatefulWidget { + // ignore: public_member_api_docs const StreamChannel({ Key key, @required this.child, @@ -20,8 +28,13 @@ class StreamChannel extends StatefulWidget { assert(channel != null), super(key: key); + // ignore: public_member_api_docs final Widget child; + + /// [channel] specifies the channel with which child should be wrapped final Channel channel; + + /// Shows a loading indicator final bool showLoading; /// If passed the channel will load from this particular message. @@ -45,6 +58,7 @@ class StreamChannel extends StatefulWidget { StreamChannelState createState() => StreamChannelState(); } +// ignore: public_member_api_docs class StreamChannelState extends State { /// Current channel Channel get channel => widget.channel; diff --git a/packages/stream_chat_flutter_core/lib/src/stream_chat_core.dart b/packages/stream_chat_flutter_core/lib/src/stream_chat_core.dart index e8312638..b04542fc 100644 --- a/packages/stream_chat_flutter_core/lib/src/stream_chat_core.dart +++ b/packages/stream_chat_flutter_core/lib/src/stream_chat_core.dart @@ -26,9 +26,12 @@ import 'package:stream_chat/stream_chat.dart'; /// /// Use [StreamChatCore.of] to get the current [StreamChatCoreState] instance. class StreamChatCore extends StatefulWidget { + // ignore: public_member_api_docs final Client client; + // ignore: public_member_api_docs final Widget child; + // ignore: public_member_api_docs StreamChatCore({ Key key, @required this.client, @@ -58,6 +61,7 @@ class StreamChatCore extends StatefulWidget { /// The current state of the StreamChat widget class StreamChatCoreState extends State with WidgetsBindingObserver { + // ignore: public_member_api_docs Client get client => widget.client; Timer _disconnectTimer; diff --git a/packages/stream_chat_flutter_core/lib/src/user_list_core.dart b/packages/stream_chat_flutter_core/lib/src/user_list_core.dart index d08e06ae..e323591f 100644 --- a/packages/stream_chat_flutter_core/lib/src/user_list_core.dart +++ b/packages/stream_chat_flutter_core/lib/src/user_list_core.dart @@ -249,7 +249,9 @@ class _UserListCoreState extends State } } +// ignore: public_member_api_docs abstract class ListItem { + // ignore: public_member_api_docs String get key { if (this is ListHeaderItem) { final header = (this as ListHeaderItem).heading; @@ -262,6 +264,7 @@ abstract class ListItem { return null; } + // ignore: public_member_api_docs Widget when({ @required Widget Function(String heading) headerItem, @required Widget Function(User user) userItem, @@ -276,20 +279,29 @@ abstract class ListItem { } } +// ignore: public_member_api_docs class ListHeaderItem extends ListItem { + // ignore: public_member_api_docs final String heading; + // ignore: public_member_api_docs ListHeaderItem(this.heading); } +// ignore: public_member_api_docs class ListUserItem extends ListItem { + // ignore: public_member_api_docs final User user; + // ignore: public_member_api_docs ListUserItem(this.user); } /// Controller used for paginating data in [ChannelListView] class UserListController { + /// Call this function to reload data VoidCallback loadData; + + /// Call this function to load further data VoidCallback paginateData; } From 882b60bb8e038d07eab271fdd195e126673c2f4c Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Tue, 26 Jan 2021 21:47:12 +0530 Subject: [PATCH 21/39] fix: added stream_chat --- packages/flutter_widgets/pubspec.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/flutter_widgets/pubspec.yaml b/packages/flutter_widgets/pubspec.yaml index 059d4fb2..80f5da1e 100644 --- a/packages/flutter_widgets/pubspec.yaml +++ b/packages/flutter_widgets/pubspec.yaml @@ -14,6 +14,7 @@ dependencies: # TODO: Change over to main dependency when deployed stream_chat_flutter_core: path: ../stream_chat_flutter_core + stream_chat: ^0.2.24+1 flutter_app_badger: ^1.1.2 photo_view: ^0.10.3 rxdart: ^0.24.1 From bc53c7aa026aa5501293c8b23f44d51652aa6e76 Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Tue, 26 Jan 2021 21:55:23 +0530 Subject: [PATCH 22/39] fix: dry run stuff --- packages/stream_chat_flutter_core/pubspec.yaml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/packages/stream_chat_flutter_core/pubspec.yaml b/packages/stream_chat_flutter_core/pubspec.yaml index e318c67d..0a9be7a8 100644 --- a/packages/stream_chat_flutter_core/pubspec.yaml +++ b/packages/stream_chat_flutter_core/pubspec.yaml @@ -1,8 +1,9 @@ name: stream_chat_flutter_core -description: Stream Chat Official Flutter SDK Core. Build and customize your own chat experience using Dart and Flutter. +homepage: https://github.com/GetStream/stream-chat-flutter +description: Stream Chat official Flutter SDK Core. Build your own chat experience using Dart and Flutter. version: 0.0.1 -author: -homepage: +repository: https://github.com/GetStream/stream-chat-flutter +issue_tracker: https://github.com/GetStream/stream-chat-flutter/issues environment: sdk: ">=2.7.0 <3.0.0" From 6983b05a7258c1735a64f6d1b85ba75e2095f875 Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Tue, 26 Jan 2021 22:59:52 +0530 Subject: [PATCH 23/39] fix: analysis and text --- packages/flutter_widgets/pubspec.yaml | 1 - .../lib/stream_chat_flutter_core.dart | 1 + .../stream_chat_flutter_core/pubspec.yaml | 1 + .../stream_chat_flutter_core/test/mocks.dart | 10 ++ .../test/stream_chat_flutter_core_test.dart | 93 ++++++++++++++++++- 5 files changed, 104 insertions(+), 2 deletions(-) create mode 100644 packages/stream_chat_flutter_core/test/mocks.dart diff --git a/packages/flutter_widgets/pubspec.yaml b/packages/flutter_widgets/pubspec.yaml index 80f5da1e..059d4fb2 100644 --- a/packages/flutter_widgets/pubspec.yaml +++ b/packages/flutter_widgets/pubspec.yaml @@ -14,7 +14,6 @@ dependencies: # TODO: Change over to main dependency when deployed stream_chat_flutter_core: path: ../stream_chat_flutter_core - stream_chat: ^0.2.24+1 flutter_app_badger: ^1.1.2 photo_view: ^0.10.3 rxdart: ^0.24.1 diff --git a/packages/stream_chat_flutter_core/lib/stream_chat_flutter_core.dart b/packages/stream_chat_flutter_core/lib/stream_chat_flutter_core.dart index d82b57fd..623c4424 100644 --- a/packages/stream_chat_flutter_core/lib/stream_chat_flutter_core.dart +++ b/packages/stream_chat_flutter_core/lib/stream_chat_flutter_core.dart @@ -10,3 +10,4 @@ export 'src/stream_channel.dart'; export 'src/stream_chat_core.dart'; export 'src/user_list_core.dart'; export 'src/users_bloc.dart'; +export 'package:stream_chat/stream_chat.dart'; diff --git a/packages/stream_chat_flutter_core/pubspec.yaml b/packages/stream_chat_flutter_core/pubspec.yaml index 0a9be7a8..c0c2a55a 100644 --- a/packages/stream_chat_flutter_core/pubspec.yaml +++ b/packages/stream_chat_flutter_core/pubspec.yaml @@ -16,6 +16,7 @@ dependencies: sdk: flutter dev_dependencies: + mockito: ^4.1.4 flutter_test: sdk: flutter diff --git a/packages/stream_chat_flutter_core/test/mocks.dart b/packages/stream_chat_flutter_core/test/mocks.dart new file mode 100644 index 00000000..37fa5530 --- /dev/null +++ b/packages/stream_chat_flutter_core/test/mocks.dart @@ -0,0 +1,10 @@ +import 'package:mockito/mockito.dart'; +import 'package:stream_chat/stream_chat.dart'; + +class MockClient extends Mock implements Client {} + +class MockClientState extends Mock implements ClientState {} + +class MockChannel extends Mock implements Channel {} + +class MockChannelState extends Mock implements ChannelClientState {} diff --git a/packages/stream_chat_flutter_core/test/stream_chat_flutter_core_test.dart b/packages/stream_chat_flutter_core/test/stream_chat_flutter_core_test.dart index ab73b3a2..be619db5 100644 --- a/packages/stream_chat_flutter_core/test/stream_chat_flutter_core_test.dart +++ b/packages/stream_chat_flutter_core/test/stream_chat_flutter_core_test.dart @@ -1 +1,92 @@ -void main() {} +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:mockito/mockito.dart'; +import 'package:stream_chat/stream_chat.dart'; +import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; + +import 'mocks.dart'; + +void main() { + testWidgets( + 'it should show basic channel information', + (WidgetTester tester) async { + // final client = MockClient(); + // final clientState = MockClientState(); + // final channel = MockChannel(); + // final channelState = MockChannelState(); + // final lastMessageAt = DateTime.parse('2020-06-22 12:00:00'); + // + // when(client.state).thenReturn(clientState); + // when(clientState.user).thenReturn(OwnUser(id: 'user-id')); + // when(channel.lastMessageAt).thenReturn(lastMessageAt); + // when(channel.state).thenReturn(channelState); + // when(channel.client).thenReturn(client); + // when(channel.isMuted).thenReturn(false); + // when(channel.isMutedStream).thenAnswer((i) => Stream.value(false)); + // when(channel.extraDataStream).thenAnswer((i) => Stream.value({ + // 'name': 'test name', + // })); + // when(channel.extraData).thenReturn({ + // 'name': 'test name', + // }); + // when(channelState.unreadCount).thenReturn(1); + // when(channelState.unreadCountStream).thenAnswer((i) => Stream.value(1)); + // when(channelState.membersStream).thenAnswer((i) => Stream.value([ + // Member( + // userId: 'user-id', + // user: User(id: 'user-id'), + // ) + // ])); + // when(channelState.members).thenReturn([ + // Member( + // userId: 'user-id', + // user: User(id: 'user-id'), + // ), + // ]); + // when(channelState.messages).thenReturn([ + // Message( + // text: 'hello', + // user: User(id: 'other-user'), + // ) + // ]); + // when(channelState.messagesStream).thenAnswer((i) => Stream.value([ + // Message( + // text: 'hello', + // user: User(id: 'other-user'), + // ) + // ])); + + await tester.pumpWidget( + // MaterialApp( + // home: StreamChatCore( + // client: client, + // child: StreamChannel( + // channel: channel, + // child: Scaffold( + // body: MessageListCore( + // loadingBuilder: (context) { + // return Center(); + // }, + // emptyBuilder: (context) { + // return Center(); + // }, + // messageListBuilder: (context, list) { + // return ListView.builder( + // itemBuilder: (context, position) { + // return Text(list[position].text); + // }, + // itemCount: list.length, + // ); + // }, + // ), + // ), + // ), + // ), + // ), + MaterialApp(home: Scaffold(body: Text('hello'))), + ); + + expect(find.text('hello'), findsOneWidget); + }, + ); +} From fa6e6aff17732ec4ae6d7404d92acf4615664345 Mon Sep 17 00:00:00 2001 From: Neevash Ramdial Date: Wed, 27 Jan 2021 06:35:30 -0400 Subject: [PATCH 24/39] WIP example --- .../example/lib/main.dart | 316 ++++++++++++------ .../example/test/widget_test.dart | 30 -- 2 files changed, 222 insertions(+), 124 deletions(-) delete mode 100644 packages/stream_chat_flutter_core/example/test/widget_test.dart diff --git a/packages/stream_chat_flutter_core/example/lib/main.dart b/packages/stream_chat_flutter_core/example/lib/main.dart index 496f7c82..f0c5ebd9 100644 --- a/packages/stream_chat_flutter_core/example/lib/main.dart +++ b/packages/stream_chat_flutter_core/example/lib/main.dart @@ -1,121 +1,249 @@ import 'package:flutter/material.dart'; +import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; -void main() { - runApp(MyApp()); +Future main() async { + /// Create a new instance of [Client] passing the apikey obtained from your + /// project dashboard. + final client = Client('b67pax5b2wdq'); + + /// Set the current user. 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.setUser( + User( + id: 'cool-shadow-7', + extraData: { + 'image': + 'https://getstream.io/random_png/?id=cool-shadow-7&name=Cool+shadow', + }, + ), + 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VyX2lkIjoiY29vbC1zaGFkb3ctNyJ9.gkOlCRb1qgy4joHPaxFwPOdXcGvSPvp6QY0S4mpRkVo', + ); + + runApp( + StreamExample( + client: client, + ), + ); } -// ignore: public_member_api_docs -class MyApp extends StatelessWidget { - // This widget is the root of your application. +class StreamExample extends StatelessWidget { + const StreamExample({ + Key key, + @required this.client, + }) : super(key: key); + + final Client client; + @override Widget build(BuildContext context) { return MaterialApp( - title: 'Flutter Demo', - theme: ThemeData( - // This is the theme of your application. - // - // Try running your application with "flutter run". You'll see the - // application has a blue toolbar. Then, without quitting the app, try - // changing the primarySwatch below to Colors.green and then invoke - // "hot reload" (press "r" in the console where you ran "flutter run", - // or simply save your changes to "hot reload" in a Flutter IDE). - // Notice that the counter didn't reset back to zero; the application - // is not restarted. - primarySwatch: Colors.blue, - // This makes the visual density adapt to the platform that you run - // the app on. For desktop platforms, the controls will be smaller and - // closer together (more dense) than on mobile platforms. - visualDensity: VisualDensity.adaptivePlatformDensity, + title: 'Stream Chat Dart Example', + home: _HomeScreen(), + builder: (context, child) => StreamChatCore( + client: client, + child: ChannelsBloc(child: child), ), - home: MyHomePage(title: 'Flutter Demo Home Page'), ); } } -// ignore: public_member_api_docs -class MyHomePage extends StatefulWidget { - // ignore: public_member_api_docs - MyHomePage({Key key, this.title}) : super(key: key); - - // This widget is the home page of your application. It is stateful, meaning - // that it has a State object (defined below) that contains fields that affect - // how it looks. - - // This class is the configuration for the state. It holds the values (in this - // case the title) provided by the parent (in this case the App widget) and - // used by the build method of the State. Fields in a Widget subclass are - // always marked "final". - - // ignore: public_member_api_docs - final String title; - +class _HomeScreen extends StatelessWidget { @override - _MyHomePageState createState() => _MyHomePageState(); + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: Text('Channels'), + ), + body: ChannelListCore( + emptyBuilder: (BuildContext context) { + return Center( + child: Text('Looks like you are not in any channels'), + ); + }, + loadingBuilder: (BuildContext context) { + return Center( + child: SizedBox( + height: 100.0, + width: 100.0, + child: CircularProgressIndicator(), + ), + ); + }, + errorBuilder: (Error error) { + return Center( + child: + Text('Oh no, something went wrong. Please check your config.'), + ); + }, + listBuilder: (BuildContext context, List channels) => + ListView.builder( + itemCount: channels.length, + itemBuilder: (BuildContext context, int index) { + final item = channels[index]; + return ListTile( + title: Text(item.name), + subtitle: Text(item.state.lastMessage.text), + onTap: () { + Navigator.of(context).push( + MaterialPageRoute( + builder: (context) => StreamChannel( + channel: item, + child: MessageScreen(), + ), + ), + ); + }, + ); + }, + ), + ), + ); + } } -class _MyHomePageState extends State { - int _counter = 0; +class MessageScreen extends StatefulWidget { + @override + _MessageScreenState createState() => _MessageScreenState(); +} - void _incrementCounter() { - setState(() { - // This call to setState tells the Flutter framework that something has - // changed in this State, which causes it to rerun the build method below - // so that the display can reflect the updated values. If we changed - // _counter without calling setState(), then the build method would not be - // called again, and so nothing would appear to happen. - _counter++; - }); +class _MessageScreenState extends State { + TextEditingController _controller; + ScrollController _scrollController; + + @override + void initState() { + super.initState(); + _controller = TextEditingController(); + _scrollController = ScrollController(); + } + + @override + void dispose() { + _controller.dispose(); + _scrollController.dispose(); + super.dispose(); + } + + void _updateList() { + _scrollController.animateTo( + _scrollController.position.maxScrollExtent, + duration: const Duration(milliseconds: 200), + curve: Curves.easeOut, + ); } @override Widget build(BuildContext context) { - // This method is rerun every time setState is called, for instance as done - // by the _incrementCounter method above. - // - // The Flutter framework has been optimized to make rerunning build methods - // fast, so that you can just rebuild anything that needs updating rather - // than having to individually change instances of widgets. return Scaffold( - appBar: AppBar( - // Here we take the value from the MyHomePage object that was created by - // the App.build method, and use it to set our appbar title. - title: Text(widget.title), - ), - body: Center( - // Center is a layout widget. It takes a single child and positions it - // in the middle of the parent. - child: Column( - // Column is also a layout widget. It takes a list of children and - // arranges them vertically. By default, it sizes itself to fit its - // children horizontally, and tries to be as tall as its parent. - // - // Invoke "debug painting" (press "p" in the console, choose the - // "Toggle Debug Paint" action from the Flutter Inspector in Android - // Studio, or the "Toggle Debug Paint" command in Visual Studio Code) - // to see the wireframe for each widget. - // - // Column has various properties to control how it sizes itself and - // how it positions its children. Here we use mainAxisAlignment to - // center the children vertically; the main axis here is the vertical - // axis because Columns are vertical (the cross axis would be - // horizontal). - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Text( - 'You have pushed the button this many times:', + body: Column( + children: [ + Expanded( + child: MessageListCore( + emptyBuilder: (BuildContext context) { + return Center( + child: Text('Looks like you are not in any channels'), + ); + }, + loadingBuilder: (BuildContext context) { + return Center( + child: SizedBox( + height: 100.0, + width: 100.0, + child: CircularProgressIndicator(), + ), + ); + }, + messageListBuilder: + (BuildContext context, List messages) { + return ListView.builder( + controller: _scrollController, + itemCount: messages.length, + reverse: true, + itemBuilder: (BuildContext context, int index) { + final item = messages[index]; + final client = StreamChatCore.of(context).client; + if (item.user.id == client.uid) { + return Align( + alignment: Alignment.centerRight, + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Text(item.text), + ), + ); + } else { + return Align( + alignment: Alignment.centerLeft, + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Text(item.text), + ), + ); + } + }, + ); + }, ), - Text( - '$_counter', - style: Theme.of(context).textTheme.headline4, + ), + Padding( + padding: const EdgeInsets.all(8.0), + child: Row( + children: [ + Expanded( + child: TextField( + controller: _controller, + decoration: const InputDecoration( + hintText: 'Enter your message', + ), + ), + ), + Material( + type: MaterialType.circle, + color: Colors.blue, + clipBehavior: Clip.hardEdge, + child: InkWell( + onTap: () async { + if (_controller.value.text.isNotEmpty) { + //TODO(Nash): Send Message + // await widget.channel.sendMessage( + // Message(text: _controller.value.text), + // ); + _controller.clear(); + _updateList(); + } + }, + child: const Padding( + padding: EdgeInsets.all(8.0), + child: Center( + child: Icon( + Icons.send, + color: Colors.white, + ), + ), + ), + ), + ) + ], ), - ], - ), + ) + ], ), - floatingActionButton: FloatingActionButton( - onPressed: _incrementCounter, - tooltip: 'Increment', - child: Icon(Icons.add), - ), // This trailing comma makes auto-formatting nicer for build methods. ); } } + +extension on Client { + String get uid => state.user.id; +} + +extension on Channel { + String get name { + final _channelName = extraData['name']; + if (_channelName != null) { + return _channelName; + } else { + return cid; + } + } +} \ No newline at end of file diff --git a/packages/stream_chat_flutter_core/example/test/widget_test.dart b/packages/stream_chat_flutter_core/example/test/widget_test.dart deleted file mode 100644 index 747db1da..00000000 --- a/packages/stream_chat_flutter_core/example/test/widget_test.dart +++ /dev/null @@ -1,30 +0,0 @@ -// This is a basic Flutter widget test. -// -// To perform an interaction with a widget in your test, use the WidgetTester -// utility that Flutter provides. For example, you can send tap and scroll -// gestures. You can also use WidgetTester to find child widgets in the widget -// tree, read text, and verify that the values of widget properties are correct. - -import 'package:flutter/material.dart'; -import 'package:flutter_test/flutter_test.dart'; - -import 'package:example/main.dart'; - -void main() { - testWidgets('Counter increments smoke test', (WidgetTester tester) async { - // Build our app and trigger a frame. - await tester.pumpWidget(MyApp()); - - // Verify that our counter starts at 0. - expect(find.text('0'), findsOneWidget); - expect(find.text('1'), findsNothing); - - // Tap the '+' icon and trigger a frame. - await tester.tap(find.byIcon(Icons.add)); - await tester.pump(); - - // Verify that our counter has incremented. - expect(find.text('0'), findsNothing); - expect(find.text('1'), findsOneWidget); - }); -} From 3e9734133d9525140368e28f14e308f36e52dbc7 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Wed, 27 Jan 2021 14:11:58 +0100 Subject: [PATCH 25/39] add streamchatcore tests --- .../example/ios/Flutter/Debug.xcconfig | 1 + .../example/ios/Flutter/Release.xcconfig | 1 + .../lib/src/stream_chat_core.dart | 1 + .../stream_chat_flutter_core/pubspec.yaml | 39 +--- .../test/stream_chat_core_test.dart | 189 ++++++++++++++++++ .../test/stream_chat_flutter_core_test.dart | 92 --------- 6 files changed, 194 insertions(+), 129 deletions(-) create mode 100644 packages/stream_chat_flutter_core/test/stream_chat_core_test.dart delete mode 100644 packages/stream_chat_flutter_core/test/stream_chat_flutter_core_test.dart diff --git a/packages/stream_chat_flutter_core/example/ios/Flutter/Debug.xcconfig b/packages/stream_chat_flutter_core/example/ios/Flutter/Debug.xcconfig index 592ceee8..e8efba11 100644 --- a/packages/stream_chat_flutter_core/example/ios/Flutter/Debug.xcconfig +++ b/packages/stream_chat_flutter_core/example/ios/Flutter/Debug.xcconfig @@ -1 +1,2 @@ +#include "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" #include "Generated.xcconfig" diff --git a/packages/stream_chat_flutter_core/example/ios/Flutter/Release.xcconfig b/packages/stream_chat_flutter_core/example/ios/Flutter/Release.xcconfig index 592ceee8..399e9340 100644 --- a/packages/stream_chat_flutter_core/example/ios/Flutter/Release.xcconfig +++ b/packages/stream_chat_flutter_core/example/ios/Flutter/Release.xcconfig @@ -1 +1,2 @@ +#include "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" #include "Generated.xcconfig" diff --git a/packages/stream_chat_flutter_core/lib/src/stream_chat_core.dart b/packages/stream_chat_flutter_core/lib/src/stream_chat_core.dart index b04542fc..73f5eb61 100644 --- a/packages/stream_chat_flutter_core/lib/src/stream_chat_core.dart +++ b/packages/stream_chat_flutter_core/lib/src/stream_chat_core.dart @@ -142,6 +142,7 @@ class StreamChatCoreState extends State @override void dispose() { WidgetsBinding.instance.removeObserver(this); + _disconnectTimer?.cancel(); super.dispose(); } } diff --git a/packages/stream_chat_flutter_core/pubspec.yaml b/packages/stream_chat_flutter_core/pubspec.yaml index c0c2a55a..5037e5e8 100644 --- a/packages/stream_chat_flutter_core/pubspec.yaml +++ b/packages/stream_chat_flutter_core/pubspec.yaml @@ -19,40 +19,5 @@ dev_dependencies: mockito: ^4.1.4 flutter_test: sdk: flutter - -# For information on the generic Dart part of this file, see the -# following page: https://dart.dev/tools/pub/pubspec - -# The following section is specific to Flutter. -flutter: - - # To add assets to your package, add an assets section, like this: - # assets: - # - images/a_dot_burr.jpeg - # - images/a_dot_ham.jpeg - # - # For details regarding assets in packages, see - # https://flutter.dev/assets-and-images/#from-packages - # - # An image asset can refer to one or more resolution-specific "variants", see - # https://flutter.dev/assets-and-images/#resolution-aware. - - # To add custom fonts to your package, add a fonts section here, - # in this "flutter" section. Each entry in this list should have a - # "family" key with the font family name, and a "fonts" key with a - # list giving the asset and other descriptors for the font. For - # example: - # fonts: - # - family: Schyler - # fonts: - # - asset: fonts/Schyler-Regular.ttf - # - asset: fonts/Schyler-Italic.ttf - # style: italic - # - family: Trajan Pro - # fonts: - # - asset: fonts/TrajanPro.ttf - # - asset: fonts/TrajanPro_Bold.ttf - # weight: 700 - # - # For details regarding fonts in packages, see - # https://flutter.dev/custom-fonts/#from-packages + fake_async: ^1.1.0 + \ No newline at end of file diff --git a/packages/stream_chat_flutter_core/test/stream_chat_core_test.dart b/packages/stream_chat_flutter_core/test/stream_chat_core_test.dart new file mode 100644 index 00000000..714375de --- /dev/null +++ b/packages/stream_chat_flutter_core/test/stream_chat_core_test.dart @@ -0,0 +1,189 @@ +import 'dart:async'; + +import 'package:fake_async/fake_async.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; +import 'package:mockito/mockito.dart'; + +import 'mocks.dart'; + +class MockShowLocalNotifications extends Mock { + void call(Message m, ChannelModel cm); +} + +void main() { + testWidgets( + 'StreamChatCore.of(context) should throw an exception', + (WidgetTester tester) async { + await tester.pumpWidget( + Builder( + builder: (context) { + expect(() => StreamChatCore.of(context), throwsException); + return Container(); + }, + ), + ); + }, + ); + + testWidgets( + 'StreamChatCore.of(context) should return the StreamChatCore ancestor', + (WidgetTester tester) async { + final client = MockClient(); + await tester.pumpWidget( + StreamChatCore( + client: client, + child: Builder( + builder: (context) { + final sc = StreamChatCore.of(context); + expect(sc, isNotNull); + return Container(); + }, + ), + ), + ); + }, + ); + + testWidgets( + 'StreamChatCore.of(context).client should return the client', + (WidgetTester tester) async { + final client = MockClient(); + final clientState = MockClientState(); + when(client.state).thenReturn(clientState); + when(clientState.user).thenReturn( + OwnUser( + id: 'test', + ), + ); + final userStream = Stream.value( + OwnUser( + id: 'test', + ), + ); + when(clientState.userStream).thenAnswer( + (_) => userStream, + ); + + await tester.pumpWidget( + StreamChatCore( + client: client, + child: Builder( + builder: (context) { + final sc = StreamChatCore.of(context); + expect(sc.client, client); + expect(sc.user, client.state.user); + expect(sc.userStream, userStream); + return Container(); + }, + ), + ), + ); + }, + ); + + testWidgets( + 'StreamChatCore should disconnect on background', + (WidgetTester tester) async { + await fakeAsync((_async) { + final client = MockClient(); + final clientState = MockClientState(); + final channel = MockChannel(); + when(client.state).thenReturn(clientState); + when(clientState.user).thenReturn( + OwnUser( + id: 'test', + ), + ); + final showLocalNotificationMock = MockShowLocalNotifications().call; + when(client.showLocalNotification) + .thenReturn(showLocalNotificationMock); + when(client.backgroundKeepAlive).thenReturn(Duration( + seconds: 4, + )); + final eventStreamController = StreamController(); + when(client.on(EventType.messageNew)) + .thenAnswer((_) => eventStreamController.stream); + + when(client.channel('test', id: 'testid')).thenReturn(channel); + + final scKey = GlobalKey(); + tester.pumpWidget( + StreamChatCore( + key: scKey, + client: client, + child: Builder( + builder: (context) { + return Container(); + }, + ), + ), + ); + + final sc = scKey.currentState; + sc.didChangeAppLifecycleState(AppLifecycleState.paused); + + _async.elapse(Duration(seconds: 5)); + + verify(client.disconnect()).called(1); + }); + }, + ); + + testWidgets( + 'StreamChatCore should handle notifications when on background and connected', + (WidgetTester tester) async { + final client = MockClient(); + final clientState = MockClientState(); + final channel = MockChannel(); + when(client.state).thenReturn(clientState); + when(clientState.user).thenReturn( + OwnUser( + id: 'test', + ), + ); + final showLocalNotificationMock = MockShowLocalNotifications().call; + when(client.showLocalNotification).thenReturn(showLocalNotificationMock); + when(client.backgroundKeepAlive).thenReturn(Duration( + seconds: 4, + )); + final eventStreamController = StreamController(); + when(client.on(EventType.messageNew)) + .thenAnswer((_) => eventStreamController.stream); + + when(client.channel('test', id: 'testid')).thenReturn(channel); + + final scKey = GlobalKey(); + await tester.pumpWidget( + StreamChatCore( + key: scKey, + client: client, + child: Builder( + builder: (context) { + return Container(); + }, + ), + ), + ); + + final sc = scKey.currentState; + sc.didChangeAppLifecycleState(AppLifecycleState.paused); + final event = Event( + type: EventType.messageNew, + message: Message(text: 'hey'), + channelType: 'test', + channelId: 'testid', + user: User(id: 'other user'), + ); + eventStreamController.add(event); + + await untilCalled(showLocalNotificationMock(any, any)); + + verify(showLocalNotificationMock( + event.message, + any, + )).called(1); + }, + ); +} diff --git a/packages/stream_chat_flutter_core/test/stream_chat_flutter_core_test.dart b/packages/stream_chat_flutter_core/test/stream_chat_flutter_core_test.dart deleted file mode 100644 index be619db5..00000000 --- a/packages/stream_chat_flutter_core/test/stream_chat_flutter_core_test.dart +++ /dev/null @@ -1,92 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_test/flutter_test.dart'; -import 'package:mockito/mockito.dart'; -import 'package:stream_chat/stream_chat.dart'; -import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; - -import 'mocks.dart'; - -void main() { - testWidgets( - 'it should show basic channel information', - (WidgetTester tester) async { - // final client = MockClient(); - // final clientState = MockClientState(); - // final channel = MockChannel(); - // final channelState = MockChannelState(); - // final lastMessageAt = DateTime.parse('2020-06-22 12:00:00'); - // - // when(client.state).thenReturn(clientState); - // when(clientState.user).thenReturn(OwnUser(id: 'user-id')); - // when(channel.lastMessageAt).thenReturn(lastMessageAt); - // when(channel.state).thenReturn(channelState); - // when(channel.client).thenReturn(client); - // when(channel.isMuted).thenReturn(false); - // when(channel.isMutedStream).thenAnswer((i) => Stream.value(false)); - // when(channel.extraDataStream).thenAnswer((i) => Stream.value({ - // 'name': 'test name', - // })); - // when(channel.extraData).thenReturn({ - // 'name': 'test name', - // }); - // when(channelState.unreadCount).thenReturn(1); - // when(channelState.unreadCountStream).thenAnswer((i) => Stream.value(1)); - // when(channelState.membersStream).thenAnswer((i) => Stream.value([ - // Member( - // userId: 'user-id', - // user: User(id: 'user-id'), - // ) - // ])); - // when(channelState.members).thenReturn([ - // Member( - // userId: 'user-id', - // user: User(id: 'user-id'), - // ), - // ]); - // when(channelState.messages).thenReturn([ - // Message( - // text: 'hello', - // user: User(id: 'other-user'), - // ) - // ]); - // when(channelState.messagesStream).thenAnswer((i) => Stream.value([ - // Message( - // text: 'hello', - // user: User(id: 'other-user'), - // ) - // ])); - - await tester.pumpWidget( - // MaterialApp( - // home: StreamChatCore( - // client: client, - // child: StreamChannel( - // channel: channel, - // child: Scaffold( - // body: MessageListCore( - // loadingBuilder: (context) { - // return Center(); - // }, - // emptyBuilder: (context) { - // return Center(); - // }, - // messageListBuilder: (context, list) { - // return ListView.builder( - // itemBuilder: (context, position) { - // return Text(list[position].text); - // }, - // itemCount: list.length, - // ); - // }, - // ), - // ), - // ), - // ), - // ), - MaterialApp(home: Scaffold(body: Text('hello'))), - ); - - expect(find.text('hello'), findsOneWidget); - }, - ); -} From 303abee427b89dc2a5f980b4e5619b07578eb12e Mon Sep 17 00:00:00 2001 From: Neevash Ramdial Date: Wed, 27 Jan 2021 22:14:14 -0400 Subject: [PATCH 26/39] Complete example - Update code - Add docs --- .../example/lib/main.dart | 226 +++++++++++------- 1 file changed, 134 insertions(+), 92 deletions(-) diff --git a/packages/stream_chat_flutter_core/example/lib/main.dart b/packages/stream_chat_flutter_core/example/lib/main.dart index f0c5ebd9..a4237148 100644 --- a/packages/stream_chat_flutter_core/example/lib/main.dart +++ b/packages/stream_chat_flutter_core/example/lib/main.dart @@ -28,19 +28,31 @@ Future main() async { ); } +/// Example application using Stream Chat core widgets. +/// Stream Chat Core is a set of Flutter wrappers which provide basic functionality +/// for building Flutter applications using Stream. +/// If you'd prefer using pre-made UI widgets for your app, please see our other +/// package, `stream_chat_flutter`. class StreamExample extends StatelessWidget { + /// Minimal example using Stream's core Flutter package. + /// If you'd prefer using pre-made UI widgets for your app, please see our other + /// package, `stream_chat_flutter`. const StreamExample({ Key key, @required this.client, }) : super(key: key); + /// Instance of Stream Client. + /// Stream's [Client] 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 Client client; @override Widget build(BuildContext context) { return MaterialApp( - title: 'Stream Chat Dart Example', - home: _HomeScreen(), + title: 'Stream Chat Core Example', + home: HomeScreen(), builder: (context, child) => StreamChatCore( client: client, child: ChannelsBloc(child: child), @@ -49,7 +61,12 @@ class StreamExample extends StatelessWidget { } } -class _HomeScreen extends StatelessWidget { +/// Basic layout displaying a list of [Channel]s the user is a part of. +/// This is implemented using [ChannelListCore]. +/// +/// [ChannelListCore] is a `builder` with callbacks for constructing UIs based +/// on different scenarios. +class HomeScreen extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( @@ -81,15 +98,21 @@ class _HomeScreen extends StatelessWidget { ListView.builder( itemCount: channels.length, itemBuilder: (BuildContext context, int index) { - final item = channels[index]; + final _item = channels[index]; return ListTile( - title: Text(item.name), - subtitle: Text(item.state.lastMessage.text), + title: Text(_item.name), + subtitle: Text(_item.state.lastMessage.text), onTap: () { + /// Display a list of messages when the user taps on an item. + /// We can use [StreamChannel] to wrap our [MessageScreen] screen + /// with the selected channel. + /// + /// This allows us to use a built-in inherited widget for accessing + /// our `channel` later on. Navigator.of(context).push( MaterialPageRoute( builder: (context) => StreamChannel( - channel: item, + channel: _item, child: MessageScreen(), ), ), @@ -103,6 +126,12 @@ class _HomeScreen extends StatelessWidget { } } +/// A list of messages sent in the current channel. +/// When a user taps on a channel in [HomeScreen], a navigator push [MessageScreen] +/// to display the list of messages in the selected channel. +/// +/// This is implemented using [MessageListCore], a convenience builder with +/// callbacks for building UIs based on different api results. class MessageScreen extends StatefulWidget { @override _MessageScreenState createState() => _MessageScreenState(); @@ -136,108 +165,121 @@ class _MessageScreenState extends State { @override Widget build(BuildContext context) { + /// To access the current channel, we can use the `.of()` method on [StreamChannel] + /// to fetch the closest instance. + final channel = StreamChannel.of(context).channel; return Scaffold( - body: Column( - children: [ - Expanded( - child: MessageListCore( - emptyBuilder: (BuildContext context) { - return Center( - child: Text('Looks like you are not in any channels'), - ); - }, - loadingBuilder: (BuildContext context) { - return Center( - child: SizedBox( - height: 100.0, - width: 100.0, - child: CircularProgressIndicator(), - ), - ); - }, - messageListBuilder: - (BuildContext context, List messages) { - return ListView.builder( - controller: _scrollController, - itemCount: messages.length, - reverse: true, - itemBuilder: (BuildContext context, int index) { - final item = messages[index]; - final client = StreamChatCore.of(context).client; - if (item.user.id == client.uid) { - return Align( - alignment: Alignment.centerRight, - child: Padding( - padding: const EdgeInsets.all(8.0), - child: Text(item.text), - ), - ); - } else { - return Align( - alignment: Alignment.centerLeft, - child: Padding( - padding: const EdgeInsets.all(8.0), - child: Text(item.text), - ), - ); - } - }, - ); - }, - ), - ), - Padding( - padding: const EdgeInsets.all(8.0), - child: Row( - children: [ - Expanded( - child: TextField( - controller: _controller, - decoration: const InputDecoration( - hintText: 'Enter your message', + appBar: AppBar( + title: Text(channel.name), + ), + body: SafeArea( + child: Column( + children: [ + Expanded( + child: MessageListCore( + emptyBuilder: (BuildContext context) { + return Center( + child: Text('Looks like you are not in any channels'), + ); + }, + loadingBuilder: (BuildContext context) { + return Center( + child: SizedBox( + height: 100.0, + width: 100.0, + child: CircularProgressIndicator(), ), - ), - ), - Material( - type: MaterialType.circle, - color: Colors.blue, - clipBehavior: Clip.hardEdge, - child: InkWell( - onTap: () async { - if (_controller.value.text.isNotEmpty) { - //TODO(Nash): Send Message - // await widget.channel.sendMessage( - // Message(text: _controller.value.text), - // ); - _controller.clear(); - _updateList(); + ); + }, + messageListBuilder: ( + BuildContext context, + List messages, + ) { + return ListView.builder( + controller: _scrollController, + itemCount: messages.length, + reverse: true, + itemBuilder: (BuildContext context, int index) { + final item = messages[index]; + final client = StreamChatCore.of(context).client; + if (item.user.id == client.uid) { + return Align( + alignment: Alignment.centerRight, + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Text(item.text), + ), + ); + } else { + return Align( + alignment: Alignment.centerLeft, + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Text(item.text), + ), + ); } }, - child: const Padding( - padding: EdgeInsets.all(8.0), - child: Center( - child: Icon( - Icons.send, - color: Colors.white, - ), + ); + }, + ), + ), + Padding( + padding: const EdgeInsets.all(8.0), + child: Row( + children: [ + Expanded( + child: TextField( + controller: _controller, + decoration: const InputDecoration( + hintText: 'Enter your message', ), ), ), - ) - ], - ), - ) - ], + Material( + type: MaterialType.circle, + color: Colors.blue, + clipBehavior: Clip.hardEdge, + child: InkWell( + onTap: () async { + if (_controller.value.text.isNotEmpty) { + await channel.sendMessage( + Message(text: _controller.value.text), + ); + _controller.clear(); + _updateList(); + } + }, + child: const Padding( + padding: EdgeInsets.all(8.0), + child: Center( + child: Icon( + Icons.send, + color: Colors.white, + ), + ), + ), + ), + ) + ], + ), + ) + ], + ), ), ); } } +/// Extensions can be used to add functionality to the SDK. In the examples +/// below, we add two simple extensions to the [Client] and [Channel]. extension on Client { + /// Fetches the current user id. String get uid => state.user.id; } extension on Channel { + /// Fetches the name of the channel by accessing [extraData] or [cid]. String get name { final _channelName = extraData['name']; if (_channelName != null) { From 18f104cda7be27f95e1cca17886c54d9aefa429b Mon Sep 17 00:00:00 2001 From: Neevash Ramdial Date: Wed, 27 Jan 2021 22:17:45 -0400 Subject: [PATCH 27/39] fixed lint warnings - Removed Unused imports --- .../test/stream_chat_flutter_core_test.dart | 5 ----- 1 file changed, 5 deletions(-) diff --git a/packages/stream_chat_flutter_core/test/stream_chat_flutter_core_test.dart b/packages/stream_chat_flutter_core/test/stream_chat_flutter_core_test.dart index be619db5..2100f744 100644 --- a/packages/stream_chat_flutter_core/test/stream_chat_flutter_core_test.dart +++ b/packages/stream_chat_flutter_core/test/stream_chat_flutter_core_test.dart @@ -1,10 +1,5 @@ import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; -import 'package:mockito/mockito.dart'; -import 'package:stream_chat/stream_chat.dart'; -import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; - -import 'mocks.dart'; void main() { testWidgets( From e7d68aa36aef47ccfaccd9a91812261f9257741e Mon Sep 17 00:00:00 2001 From: Neevash Ramdial Date: Wed, 27 Jan 2021 22:30:37 -0400 Subject: [PATCH 28/39] expose error builder in message list core --- .../lib/src/message_list_core.dart | 24 +++++++++++++++---- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/packages/stream_chat_flutter_core/lib/src/message_list_core.dart b/packages/stream_chat_flutter_core/lib/src/message_list_core.dart index 1cd5162e..81d29a6b 100644 --- a/packages/stream_chat_flutter_core/lib/src/message_list_core.dart +++ b/packages/stream_chat_flutter_core/lib/src/message_list_core.dart @@ -5,6 +5,12 @@ import 'package:flutter/material.dart'; import 'package:stream_chat/stream_chat.dart'; import 'stream_channel.dart'; +/// A signature for a callback which exposes an error and returns a function. +/// This Callback can be used in cases where an API failure occurs and the widget +/// is unable to render data. +typedef StreamErrorBuilder = Widget Function( + BuildContext context, Object error); + /// ![screenshot](https://raw.githubusercontent.com/GetStream/stream-chat-flutter/master/screenshots/message_listview.png) /// ![screenshot](https://raw.githubusercontent.com/GetStream/stream-chat-flutter/master/screenshots/message_listview_paint.png) /// @@ -61,6 +67,7 @@ class MessageListCore extends StatefulWidget { @required this.loadingBuilder, @required this.emptyBuilder, @required this.messageListBuilder, + @required this.errorWidgetBuilder, this.messageListController, }) : super(key: key); @@ -77,6 +84,11 @@ class MessageListCore extends StatefulWidget { /// Function used to build an empty widget final WidgetBuilder emptyBuilder; + /// Callback triggered when an error occurs while performing the given request. + /// This parameter can be used to display an error message to users in the event + /// of a connection failure. + final StreamErrorBuilder errorWidgetBuilder; + /// If true will show a scroll to bottom message when there are new messages and the scroll offset is not zero final bool showScrollToBottom; @@ -118,9 +130,10 @@ class _MessageListCoreState extends State { ?.toList()), builder: (context, snapshot) { if (!snapshot.hasData) { - return widget.loadingBuilder(context); - } - + return widget.loadingBuilder(context); + } else if (snapshot.hasError) { + return widget.errorWidgetBuilder(context, snapshot.error); + } else { final messageList = snapshot.data?.reversed?.toList() ?? []; if (messageList.isEmpty) { if (_upToDate) { @@ -129,9 +142,10 @@ class _MessageListCoreState extends State { } else { messages = messageList; } - return widget.messageListBuilder(context, messages); - }); + } + }, + ); } Future paginateData( From 24d528fc792a9cf11503bb6342e59eef0b9b8163 Mon Sep 17 00:00:00 2001 From: Neevash Ramdial Date: Wed, 27 Jan 2021 22:30:49 -0400 Subject: [PATCH 29/39] update sample app with new api --- .../stream_chat_flutter_core/example/lib/main.dart | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/packages/stream_chat_flutter_core/example/lib/main.dart b/packages/stream_chat_flutter_core/example/lib/main.dart index a4237148..218bba1c 100644 --- a/packages/stream_chat_flutter_core/example/lib/main.dart +++ b/packages/stream_chat_flutter_core/example/lib/main.dart @@ -222,6 +222,16 @@ class _MessageScreenState extends State { }, ); }, + errorWidgetBuilder: (BuildContext context, error) { + print(error?.toString()); + return Center( + child: SizedBox( + height: 100.0, + width: 100.0, + child: Text('Oh no, an error occured. Please see logs.'), + ), + ); + }, ), ), Padding( From cb90146a8b78b9626889a1d431f4eb05f6194850 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Thu, 28 Jan 2021 10:25:52 +0100 Subject: [PATCH 30/39] fix examples --- .gitignore | 1 + .../example/android/gradle.properties | 1 + .../gradle/wrapper/gradle-wrapper.properties | 2 +- packages/dart_client/example/lib/main.dart | 2 +- .../example/android/gradle.properties | 1 + .../gradle/wrapper/gradle-wrapper.properties | 2 +- .../example/ios/Flutter/Debug.xcconfig | 1 + .../example/ios/Flutter/Release.xcconfig | 1 + .../example/lib/main.dart | 103 +++++++++--------- 9 files changed, 62 insertions(+), 52 deletions(-) diff --git a/.gitignore b/.gitignore index 6d180702..f7a23bbb 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ .DS_Store .atom/ .idea/ +.vscode/ .packages .pub/ diff --git a/packages/dart_client/example/android/gradle.properties b/packages/dart_client/example/android/gradle.properties index 94adc3a3..a6738207 100644 --- a/packages/dart_client/example/android/gradle.properties +++ b/packages/dart_client/example/android/gradle.properties @@ -1,3 +1,4 @@ org.gradle.jvmargs=-Xmx1536M android.useAndroidX=true android.enableJetifier=true +android.enableR8=true diff --git a/packages/dart_client/example/android/gradle/wrapper/gradle-wrapper.properties b/packages/dart_client/example/android/gradle/wrapper/gradle-wrapper.properties index 296b146b..de2ccd60 100644 --- a/packages/dart_client/example/android/gradle/wrapper/gradle-wrapper.properties +++ b/packages/dart_client/example/android/gradle/wrapper/gradle-wrapper.properties @@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.2-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-6.5-bin.zip diff --git a/packages/dart_client/example/lib/main.dart b/packages/dart_client/example/lib/main.dart index 8f68c100..ed21df76 100644 --- a/packages/dart_client/example/lib/main.dart +++ b/packages/dart_client/example/lib/main.dart @@ -156,7 +156,7 @@ class _MessageViewState extends State { /// Convenience method for scrolling the list view when a new message is sent. void _updateList() { _scrollController.animateTo( - _scrollController.position.maxScrollExtent, + 0, duration: const Duration(milliseconds: 200), curve: Curves.easeOut, ); diff --git a/packages/stream_chat_flutter_core/example/android/gradle.properties b/packages/stream_chat_flutter_core/example/android/gradle.properties index 94adc3a3..a6738207 100644 --- a/packages/stream_chat_flutter_core/example/android/gradle.properties +++ b/packages/stream_chat_flutter_core/example/android/gradle.properties @@ -1,3 +1,4 @@ org.gradle.jvmargs=-Xmx1536M android.useAndroidX=true android.enableJetifier=true +android.enableR8=true diff --git a/packages/stream_chat_flutter_core/example/android/gradle/wrapper/gradle-wrapper.properties b/packages/stream_chat_flutter_core/example/android/gradle/wrapper/gradle-wrapper.properties index 296b146b..de2ccd60 100644 --- a/packages/stream_chat_flutter_core/example/android/gradle/wrapper/gradle-wrapper.properties +++ b/packages/stream_chat_flutter_core/example/android/gradle/wrapper/gradle-wrapper.properties @@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.2-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-6.5-bin.zip diff --git a/packages/stream_chat_flutter_core/example/ios/Flutter/Debug.xcconfig b/packages/stream_chat_flutter_core/example/ios/Flutter/Debug.xcconfig index 592ceee8..e8efba11 100644 --- a/packages/stream_chat_flutter_core/example/ios/Flutter/Debug.xcconfig +++ b/packages/stream_chat_flutter_core/example/ios/Flutter/Debug.xcconfig @@ -1 +1,2 @@ +#include "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" #include "Generated.xcconfig" diff --git a/packages/stream_chat_flutter_core/example/ios/Flutter/Release.xcconfig b/packages/stream_chat_flutter_core/example/ios/Flutter/Release.xcconfig index 592ceee8..399e9340 100644 --- a/packages/stream_chat_flutter_core/example/ios/Flutter/Release.xcconfig +++ b/packages/stream_chat_flutter_core/example/ios/Flutter/Release.xcconfig @@ -1 +1,2 @@ +#include "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" #include "Generated.xcconfig" diff --git a/packages/stream_chat_flutter_core/example/lib/main.dart b/packages/stream_chat_flutter_core/example/lib/main.dart index a4237148..19945dff 100644 --- a/packages/stream_chat_flutter_core/example/lib/main.dart +++ b/packages/stream_chat_flutter_core/example/lib/main.dart @@ -55,7 +55,7 @@ class StreamExample extends StatelessWidget { home: HomeScreen(), builder: (context, child) => StreamChatCore( client: client, - child: ChannelsBloc(child: child), + child: child, ), ); } @@ -73,53 +73,58 @@ class HomeScreen extends StatelessWidget { appBar: AppBar( title: Text('Channels'), ), - body: ChannelListCore( - emptyBuilder: (BuildContext context) { - return Center( - child: Text('Looks like you are not in any channels'), - ); - }, - loadingBuilder: (BuildContext context) { - return Center( - child: SizedBox( - height: 100.0, - width: 100.0, - child: CircularProgressIndicator(), - ), - ); - }, - errorBuilder: (Error error) { - return Center( - child: - Text('Oh no, something went wrong. Please check your config.'), - ); - }, - listBuilder: (BuildContext context, List channels) => - ListView.builder( - itemCount: channels.length, - itemBuilder: (BuildContext context, int index) { - final _item = channels[index]; - return ListTile( - title: Text(_item.name), - subtitle: Text(_item.state.lastMessage.text), - onTap: () { - /// Display a list of messages when the user taps on an item. - /// We can use [StreamChannel] to wrap our [MessageScreen] screen - /// with the selected channel. - /// - /// This allows us to use a built-in inherited widget for accessing - /// our `channel` later on. - Navigator.of(context).push( - MaterialPageRoute( - builder: (context) => StreamChannel( - channel: _item, - child: MessageScreen(), - ), - ), - ); - }, + body: ChannelsBloc( + child: ChannelListCore( + emptyBuilder: (BuildContext context) { + return Center( + child: Text('Looks like you are not in any channels'), ); }, + loadingBuilder: (BuildContext context) { + return Center( + child: SizedBox( + height: 100.0, + width: 100.0, + child: CircularProgressIndicator(), + ), + ); + }, + errorBuilder: (Error error) { + return Center( + child: Text( + 'Oh no, something went wrong. Please check your config.'), + ); + }, + listBuilder: ( + BuildContext context, + List channels, + ) => + ListView.builder( + itemCount: channels.length, + itemBuilder: (BuildContext context, int index) { + final _item = channels[index]; + return ListTile( + title: Text(_item.name), + subtitle: Text(_item.state.lastMessage.text), + onTap: () { + /// Display a list of messages when the user taps on an item. + /// We can use [StreamChannel] to wrap our [MessageScreen] screen + /// with the selected channel. + /// + /// This allows us to use a built-in inherited widget for accessing + /// our `channel` later on. + Navigator.of(context).push( + MaterialPageRoute( + builder: (context) => StreamChannel( + channel: _item, + child: MessageScreen(), + ), + ), + ); + }, + ); + }, + ), ), ), ); @@ -157,7 +162,7 @@ class _MessageScreenState extends State { void _updateList() { _scrollController.animateTo( - _scrollController.position.maxScrollExtent, + 0, duration: const Duration(milliseconds: 200), curve: Curves.easeOut, ); @@ -179,7 +184,7 @@ class _MessageScreenState extends State { child: MessageListCore( emptyBuilder: (BuildContext context) { return Center( - child: Text('Looks like you are not in any channels'), + child: Text('Nothing here yet'), ); }, loadingBuilder: (BuildContext context) { @@ -288,4 +293,4 @@ extension on Channel { return cid; } } -} \ No newline at end of file +} From 55c1cae390daa6874a16445f50c9befda4225efe Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Thu, 28 Jan 2021 12:02:06 +0100 Subject: [PATCH 31/39] update default apikey and users --- .gitignore | 1 + .../example/lib/choose_user_page.dart | 89 +++++++++++++------ 2 files changed, 65 insertions(+), 25 deletions(-) diff --git a/.gitignore b/.gitignore index 6d180702..f7a23bbb 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ .DS_Store .atom/ .idea/ +.vscode/ .packages .pub/ diff --git a/packages/flutter_widgets/example/lib/choose_user_page.dart b/packages/flutter_widgets/example/lib/choose_user_page.dart index 6da88a29..8e36e748 100644 --- a/packages/flutter_widgets/example/lib/choose_user_page.dart +++ b/packages/flutter_widgets/example/lib/choose_user_page.dart @@ -11,52 +11,91 @@ import 'routes/routes.dart'; const kStreamApiKey = 'STREAM_API_KEY'; const kStreamUserId = 'STREAM_USER_ID'; const kStreamToken = 'STREAM_TOKEN'; -const kDefaultStreamApiKey = 'uj7qrdbfrzvg'; +const kDefaultStreamApiKey = 'kv7mcsxr24p8'; class ChooseUserPage extends StatelessWidget { @override Widget build(BuildContext context) { final users = { - 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoidmlzaGFsIn0.lCz-idDgaZ-xszjnuB_hTfeIOhTFmJtTB2fEjhwrcCI': - User( - id: 'vishal', - extraData: { - 'name': 'Vishal', - }, - ), - 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoic3VwZXItYmFuZC05In0.1AvvK2TvEsfcxAlfOK2iUMFACucnk6N-Q8f5bbfnjCM': - User( - id: 'super-band-9', - extraData: { - 'name': 'John Doe', - }, - ), - 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoic2FsdmF0b3JlIn0.uQ-pz8e1_C-Df2rIFpkkvWf7g4M0aW2Zkr7pHVhmU1Y': + 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoic2FsdmF0b3JlIn0.pgiJz7sIc7iP29BHKFwe3nLm5-OaR_1l2P-SlgiC9a8': User( id: 'salvatore', extraData: { - 'name': 'Salvatore', + 'name': 'Salvatore Giordano', + 'image': + 'https://ca.slack-edge.com/T02RM6X6B-USKK9FFRT-30c415e207a9-512', }, ), - 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoidG9tbWFzbyJ9.feLvMytd5_3-eETKrRdwtO3jcA0HxD8woFUbFU7gQCg': + 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoic2FoaWwifQ.WnIUoB5gR2kcAsFhiDvkiD6zdHXZ-VSU2aQWWkhsvfo': + User( + id: 'sahil', + extraData: { + 'name': 'Sahil Kumar', + 'image': + 'https://ca.slack-edge.com/T02RM6X6B-U01EYU51M89-bbc152b40321-512', + }, + ), + 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoiYmVuIn0.nAz2sNFGQwY7rl2Og2z3TGHUsdpnN53tOsUglJFvLmg': + User( + id: 'ben', + extraData: { + 'name': 'Ben Golden', + 'image': + 'https://ca.slack-edge.com/T02RM6X6B-U01AXAF23MG-f57403a3cb0d-512', + }, + ), + 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoidGhpZXJyeSJ9.lEq6TrZtHzjoNtf7HHRufUPyGo_pa8vg4_XhEBp4ckY': + User( + id: 'thierry', + extraData: { + 'name': 'Thierry Schellenbach', + 'image': + 'https://ca.slack-edge.com/T02RM6X6B-U02RM6X6D-g28a1278a98e-512', + }, + ), + 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoidG9tbWFzbyJ9.GLSI0ESshERMo2WjUpysD709NEtn1zmGimUN2an7g9o': User( id: 'tommaso', extraData: { - 'name': 'Tommaso', + 'name': 'Tommaso Barbugli', + 'image': + 'https://ca.slack-edge.com/T02RM6X6B-U02U7SJP4-0f65a5997877-512', }, ), - 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoiamFhcCJ9.VLkLs6KH-jRRkPvhYMa4_lqN0R6WiK7JhJVmYvxcKz0': + 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoiZGV2ZW4ifQ.z3zI4PqJnNhc-1o-VKcmb6BnnQ0oxFNCRHwEulHqcWc': User( - id: 'jaap', + id: 'deven', extraData: { - 'name': 'Jaap', + 'name': 'Deven Joshi', + 'image': + 'https://ca.slack-edge.com/T02RM6X6B-U01AM7ELPTL-8a60da32704c-512', + }, + ), + 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoibmVldmFzaCJ9.3EdHegTxibrz3A9cTiKmpEyawwcCVB8FXnoFzr4eKvw': + User( + id: 'neevash', + extraData: { + 'name': 'Neevash Ramdial', + 'image': + 'https://ca.slack-edge.com/T02RM6X6B-U01DZ046DS8-b00d321d2880-512', + }, + ), + 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoicWF0ZXN0MSJ9.fnelU7HcP7QoEEsCGteNlF1fppofzNlrnpDQuIgeKCU': + User( + id: 'qatest1', + extraData: { + 'name': 'QA test 1', + }, + ), + 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoicWF0ZXN0MiJ9.vSCqAEbs2WVmMWsOsa7065Fsjq-rsTih6qsHPynl7XM': + User( + id: 'qatest2', + extraData: { + 'name': 'QA test 2', }, ), }; - users.updateAll( - (_, value) => value..extraData['image'] = getRandomPicUrl(value)); - return Scaffold( backgroundColor: StreamChatTheme.of(context).colorTheme.whiteSnow, body: SafeArea( From b480d73da0cfe71db0bc62c5c6e755dfe6e463a5 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Thu, 28 Jan 2021 12:17:34 +0100 Subject: [PATCH 32/39] bump version of example app --- packages/flutter_widgets/example/pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/flutter_widgets/example/pubspec.yaml b/packages/flutter_widgets/example/pubspec.yaml index fea15136..d3116148 100644 --- a/packages/flutter_widgets/example/pubspec.yaml +++ b/packages/flutter_widgets/example/pubspec.yaml @@ -1,7 +1,7 @@ name: example description: A new Flutter project. publish_to: 'none' -version: 1.1.3+6 +version: 1.2.0 environment: sdk: ">=2.2.2 <3.0.0" From c741a86c3ee3a4a555f226861db73ede57948d17 Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Thu, 28 Jan 2021 17:22:44 +0530 Subject: [PATCH 33/39] feat: Added better README.md for both packages, added LICENSE --- packages/flutter_widgets/README.md | 10 +- packages/stream_chat_flutter_core/LICENSE | 220 +++++++++++++++++- packages/stream_chat_flutter_core/README.md | 47 ++-- .../lib/src/message_list_core.dart | 25 +- 4 files changed, 271 insertions(+), 31 deletions(-) diff --git a/packages/flutter_widgets/README.md b/packages/flutter_widgets/README.md index c5e4c9e7..8104e83a 100644 --- a/packages/flutter_widgets/README.md +++ b/packages/flutter_widgets/README.md @@ -60,13 +60,9 @@ If it seems related to the [flutter file picker plugin](https://github.com/migue ## Docs -### Business logic components - -We provide 3 Widgets dedicated to business logic and state management: - -- [StreamChat](https://pub.dev/documentation/stream_chat_flutter/latest/stream_chat_flutter/StreamChat-class.html) -- [StreamChannel](https://pub.dev/documentation/stream_chat_flutter/latest/stream_chat_flutter/StreamChannel-class.html) -- [ChannelsBloc](https://pub.dev/documentation/stream_chat_flutter/0.2.0-alpha+2/stream_chat_flutter/ChannelsBloc-class.html) +This package provides UI components required for integrating Stream Chat into your application. +Alternatively, you may use the core package (stream_chat_flutter_core) which allows more customisation and provides business logic but no UI components. +If you require the maximum amount of control over the API, please use the low level client package: stream_chat. ### UI Components diff --git a/packages/stream_chat_flutter_core/LICENSE b/packages/stream_chat_flutter_core/LICENSE index ba75c69f..49088d47 100644 --- a/packages/stream_chat_flutter_core/LICENSE +++ b/packages/stream_chat_flutter_core/LICENSE @@ -1 +1,219 @@ -TODO: Add your license here. +SOURCE CODE LICENSE AGREEMENT + +IMPORTANT - READ THIS CAREFULLY BEFORE DOWNLOADING, INSTALLING, USING OR +ELECTRONICALLY ACCESSING THIS PROPRIETARY PRODUCT. + +THIS IS A LEGAL AGREEMENT BETWEEN STREAM.IO, INC. (“STREAM.IO”) AND THE +BUSINESS ENTITY OR PERSON FOR WHOM YOU (“YOU”) ARE ACTING (“CUSTOMER”) AS THE +LICENSEE OF THE PROPRIETARY SOFTWARE INTO WHICH THIS AGREEMENT HAS BEEN +INCLUDED (THE “AGREEMENT”). YOU AGREE THAT YOU ARE THE CUSTOMER, OR YOU ARE AN +EMPLOYEE OR AGENT OF CUSTOMER AND ARE ENTERING INTO THIS AGREEMENT FOR LICENSE +OF THE SOFTWARE BY CUSTOMER FOR CUSTOMER’S BUSINESS PURPOSES AS DESCRIBED IN +AND IN ACCORDANCE WITH THIS AGREEMENT. YOU HEREBY AGREE THAT YOU ENTER INTO +THIS AGREEMENT ON BEHALF OF CUSTOMER AND THAT YOU HAVE THE AUTHORITY TO BIND +CUSTOMER TO THIS AGREEMENT. + +STREAM.IO IS WILLING TO LICENSE THE SOFTWARE TO CUSTOMER ONLY ON THE FOLLOWING +CONDITIONS: (1) YOU ARE A CURRENT CUSTOMER OF STREAM.IO; (2) YOU ARE NOT A +COMPETITOR OF STREAM.IO; AND (3) THAT YOU ACCEPT ALL THE TERMS IN THIS +AGREEMENT. BY DOWNLOADING, INSTALLING, CONFIGURING, ACCESSING OR OTHERWISE +USING THE SOFTWARE, INCLUDING ANY UPDATES, UPGRADES, OR NEWER VERSIONS, YOU +REPRESENT, WARRANT AND ACKNOWLEDGE THAT (A) CUSTOMER IS A CURRENT CUSTOMER OF +STREAM.IO; (B) CUSTOMER IS NOT A COMPETITOR OF STREAM.IO; AND THAT (C) YOU HAVE +READ THIS AGREEMENT, UNDERSTAND THIS AGREEMENT, AND THAT CUSTOMER AGREES TO BE +BOUND BY ALL THE TERMS OF THIS AGREEMENT. + +IF YOU DO NOT AGREE TO ALL THE TERMS AND CONDITIONS OF THIS AGREEMENT, +STREAM.IO IS UNWILLING TO LICENSE THE SOFTWARE TO CUSTOMER, AND THEREFORE, DO +NOT COMPLETE THE DOWNLOAD PROCESS, ACCESS OR OTHERWISE USE THE SOFTWARE, AND +CUSTOMER SHOULD IMMEDIATELY RETURN THE SOFTWARE AND CEASE ANY USE OF THE +SOFTWARE. + +1. SOFTWARE. The Stream.io software accompanying this Agreement, may include +Source Code, Executable Object Code, associated media, printed materials and +documentation (collectively, the “Software”). The Software also includes any +updates or upgrades to or new versions of the original Software, if and when +made available to you by Stream.io. “Source Code” means computer programming +code in human readable form that is not suitable for machine execution without +the intervening steps of interpretation or compilation. “Executable Object +Code" means the computer programming code in any other form than Source Code +that is not readily perceivable by humans and suitable for machine execution +without the intervening steps of interpretation or compilation. “Site” means a +Customer location controlled by Customer. “Authorized User” means any employee +or contractor of Customer working at the Site, who has signed a written +confidentiality agreement with Customer or is otherwise bound in writing by +confidentiality and use obligations at least as restrictive as those imposed +under this Agreement. + +2. LICENSE GRANT. Subject to the terms and conditions of this Agreement, in +consideration for the representations, warranties, and covenants made by +Customer in this Agreement, Stream.io grants to Customer, during the term of +this Agreement, a personal, non-exclusive, non-transferable, non-sublicensable +license to: + +a. install and use Software Source Code on password protected computers at a Site, +restricted to Authorized Users; + +b. create derivative works, improvements (whether or not patentable), extensions +and other modifications to the Software Source Code (“Modifications”) to build +unique scalable newsfeeds, activity streams, and in-app messaging via Stream’s +application program interface (“API”); + +c. compile the Software Source Code to create Executable Object Code versions of +the Software Source Code and Modifications to build such newsfeeds, activity +streams, and in-app messaging via the API; + +d. install, execute and use such Executable Object Code versions solely for +Customer’s internal business use (including development of websites through +which data generated by Stream services will be streamed (“Apps”)); + +e. use and distribute such Executable Object Code as part of Customer’s Apps; and + +f. make electronic copies of the Software and Modifications as required for backup +or archival purposes. + +3. RESTRICTIONS. Customer is responsible for all activities that occur in +connection with the Software. Customer will not, and will not attempt to: (a) +sublicense or transfer the Software or any Source Code related to the Software +or any of Customer’s rights under this Agreement, except as otherwise provided +in this Agreement, (b) use the Software Source Code for the benefit of a third +party or to operate a service; (c) allow any third party to access or use the +Software Source Code; (d) sublicense or distribute the Software Source Code or +any Modifications in Source Code or other derivative works based on any part of +the Software Source Code; (e) use the Software in any manner that competes with +Stream.io or its business; or (e) otherwise use the Software in any manner that +exceeds the scope of use permitted in this Agreement. Customer shall use the +Software in compliance with any accompanying documentation any laws applicable +to Customer. + +4. OPEN SOURCE. Customer and its Authorized Users shall not use any software or +software components that are open source in conjunction with the Software +Source Code or any Modifications in Source Code or in any way that could +subject the Software to any open source licenses. + +5. CONTRACTORS. Under the rights granted to Customer under this Agreement, +Customer may permit its employees, contractors, and agencies of Customer to +become Authorized Users to exercise the rights to the Software granted to +Customer in accordance with this Agreement solely on behalf of Customer to +provide services to Customer; provided that Customer shall be liable for the +acts and omissions of all Authorized Users to the extent any of such acts or +omissions, if performed by Customer, would constitute a breach of, or otherwise +give rise to liability to Customer under, this Agreement. Customer shall not +and shall not permit any Authorized User to use the Software except as +expressly permitted in this Agreement. + +6. COMPETITIVE PRODUCT DEVELOPMENT. Customer shall not use the Software in any way +to engage in the development of products or services which could be reasonably +construed to provide a complete or partial functional or commercial alternative +to Stream.io’s products or services (a “Competitive Product”). Customer shall +ensure that there is no direct or indirect use of, or sharing of, Software +source code, or other information based upon or derived from the Software to +develop such products or services. Without derogating from the generality of +the foregoing, development of Competitive Products shall include having direct +or indirect access to, supervising, consulting or assisting in the development +of, or producing any specifications, documentation, object code or source code +for, all or part of a Competitive Product. + +7. LIMITATION ON MODIFICATIONS. Notwithstanding any provision in this Agreement, +Modifications may only be created and used by Customer as permitted by this +Agreement and Modification Source Code may not be distributed to third parties. +Customer will not assert against Stream.io, its affiliates, or their customers, +direct or indirect, agents and contractors, in any way, any patent rights that +Customer may obtain relating to any Modifications for Stream.io, its +affiliates’, or their customers’, direct or indirect, agents’ and contractors’ +manufacture, use, import, offer for sale or sale of any Stream.io products or +services. + +8. DELIVERY AND ACCEPTANCE. The Software will be delivered electronically pursuant +to Stream.io standard download procedures. The Software is deemed accepted upon +delivery. + +9. IMPLEMENTATION AND SUPPORT. Stream.io has no obligation under this Agreement to +provide any support or consultation concerning the Software. + +10. TERM AND TERMINATION. The term of this Agreement begins when the Software is +downloaded or accessed and shall continue until terminated. Either party may +terminate this Agreement upon written notice. This Agreement shall +automatically terminate if Customer is or becomes a competitor of Stream.io or +makes or sells any Competitive Products. Upon termination of this Agreement for +any reason, (a) all rights granted to Customer in this Agreement immediately +cease to exist, (b) Customer must promptly discontinue all use of the Software +and return to Stream.io or destroy all copies of the Software in Customer’s +possession or control. Any continued use of the Software by Customer or attempt +by Customer to exercise any rights under this Agreement after this Agreement +has terminated shall be considered copyright infringement and subject Customer +to applicable remedies for copyright infringement. Sections 2, 5, 6, 8 and 9 +shall survive expiration or termination of this Agreement for any reason. + +11. OWNERSHIP. As between the parties, the Software and all worldwide intellectual +property rights and proprietary rights relating thereto or embodied therein, +are the exclusive property of Stream.io and its suppliers. Stream.io and its +suppliers reserve all rights in and to the Software not expressly granted to +Customer in this Agreement, and no other licenses or rights are granted by +implication, estoppel or otherwise. + +12. WARRANTY DISCLAIMER. USE OF THIS SOFTWARE IS ENTIRELY AT YOURS AND CUSTOMER’S +OWN RISK. THE SOFTWARE IS PROVIDED “AS IS” WITHOUT ANY WARRANTY OF ANY KIND +WHATSOEVER. STREAM.IO DOES NOT MAKE, AND HEREBY DISCLAIMS, ANY WARRANTY OF ANY +KIND, WHETHER EXPRESS, IMPLIED, STATUTORY OR OTHERWISE, INCLUDING WITHOUT +LIMITATION, THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +PURPOSE, TITLE, NON-INFRINGEMENT OF THIRD-PARTY RIGHTS, RESULTS, EFFORTS, +QUALITY OR QUIET ENJOYMENT. STREAM.IO DOES NOT WARRANT THAT THE SOFTWARE IS +ERROR-FREE, WILL FUNCTION WITHOUT INTERRUPTION, WILL MEET ANY SPECIFIC NEED +THAT CUSTOMER HAS, THAT ALL DEFECTS WILL BE CORRECTED OR THAT IT IS +SUFFICIENTLY DOCUMENTED TO BE USABLE BY CUSTOMER. TO THE EXTENT THAT STREAM.IO +MAY NOT DISCLAIM ANY WARRANTY AS A MATTER OF APPLICABLE LAW, THE SCOPE AND +DURATION OF SUCH WARRANTY WILL BE THE MINIMUM PERMITTED UNDER SUCH LAW. +CUSTOMER ACKNOWLEDGES THAT IT HAS RELIED ON NO WARRANTIES OTHER THAN THE +EXPRESS WARRANTIES IN THIS AGREEMENT. + +13. LIMITATION OF LIABILITY. TO THE FULLEST EXTENT PERMISSIBLE BY LAW, STREAM.IO’S +TOTAL LIABILITY FOR ALL DAMAGES ARISING OUT OF OR RELATED TO THE SOFTWARE OR +THIS AGREEMENT, WHETHER IN CONTRACT, TORT (INCLUDING NEGLIGENCE) OR OTHERWISE, +SHALL NOT EXCEED $100. IN NO EVENT WILL STREAM.IO BE LIABLE FOR ANY INDIRECT, +CONSEQUENTIAL, EXEMPLARY, PUNITIVE, SPECIAL OR INCIDENTAL DAMAGES OF ANY KIND +WHATSOEVER, INCLUDING ANY LOST DATA AND LOST PROFITS, ARISING FROM OR RELATING +TO THE SOFTWARE EVEN IF STREAM.IO HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH +DAMAGES. CUSTOMER ACKNOWLEDGES THAT THIS PROVISION REFLECTS THE AGREED UPON +ALLOCATION OF RISK FOR THIS AGREEMENT AND THAT STREAM.IO WOULD NOT ENTER INTO +THIS AGREEMENT WITHOUT THESE LIMITATIONS ON ITS LIABILITY. + +14. General. Customer may not assign or transfer this Agreement, by operation of +law or otherwise, or any of its rights under this Agreement (including the +license rights granted to Customer) to any third party without Stream.io’s +prior written consent, which consent will not be unreasonably withheld or +delayed. Stream.io may assign this Agreement, without consent, including, but +limited to, affiliate or any successor to all or substantially all its business +or assets to which this Agreement relates, whether by merger, sale of assets, +sale of stock, reorganization or otherwise. Any attempted assignment or +transfer in violation of the foregoing will be null and void. Stream.io shall +not be liable hereunder by reason of any failure or delay in the performance of +its obligations hereunder for any cause which is beyond the reasonable control. +All notices, consents, and approvals under this Agreement must be delivered in +writing by courier, by electronic mail, or by certified or registered mail, +(postage prepaid and return receipt requested) to the other party at the +address set forth in the customer agreement between Stream.io and Customer and +will be effective upon receipt or when delivery is refused. This Agreement will +be governed by and interpreted in accordance with the laws of the State of +Colorado, without reference to its choice of laws rules. The United Nations +Convention on Contracts for the International Sale of Goods does not apply to +this Agreement. Any action or proceeding arising from or relating to this +Agreement shall be brought in a federal or state court in Denver, Colorado, and +each party irrevocably submits to the jurisdiction and venue of any such court +in any such action or proceeding. All waivers must be in writing. Any waiver or +failure to enforce any provision of this Agreement on one occasion will not be +deemed a waiver of any other provision or of such provision on any other +occasion. If any provision of this Agreement is unenforceable, such provision +will be changed and interpreted to accomplish the objectives of such provision +to the greatest extent possible under applicable law and the remaining +provisions will continue in full force and effect. Customer shall not violate +any applicable law, rule or regulation, including those regarding the export of +technical data. The headings of Sections of this Agreement are for convenience +and are not to be used in interpreting this Agreement. As used in this +Agreement, the word “including” means “including but not limited to.” This +Agreement (including all exhibits and attachments) constitutes the entire +agreement between the parties regarding the subject hereof and supersedes all +prior or contemporaneous agreements, understandings and communication, whether +written or oral. This Agreement may be amended only by a written document +signed by both parties. The terms of any purchase order or similar document +submitted by Customer to Stream.io will have no effect. diff --git a/packages/stream_chat_flutter_core/README.md b/packages/stream_chat_flutter_core/README.md index 30ab07f5..a62da863 100644 --- a/packages/stream_chat_flutter_core/README.md +++ b/packages/stream_chat_flutter_core/README.md @@ -27,7 +27,7 @@ It teaches you how to use this SDK and also shows how to make frequently require ## Example App This repo includes a fully functional example app with setup instructions. -The example is available under the [example](https://github.com/GetStream/stream-chat-flutter/tree/master/example) folder. +The example is available under the [example](https://github.com/GetStream/stream-chat-flutter-core/tree/master/example) folder. ## Add dependency Add this to your package's pubspec.yaml file, use the latest version [![Pub](https://img.shields.io/pub/v/stream_chat_flutter.svg)](https://pub.dartlang.org/packages/stream_chat_flutter) @@ -44,21 +44,42 @@ This package requires no custom setup on any platform since it does not depend o This package provides business logic to fetch common things required for integrating Stream Chat into your application. The core package allows more customisation and hence provides business logic but no UI components. -Please use the stream_chat_flutter package for the full fledged suite of UI components. +Please use the stream_chat_flutter package for the full fledged suite of UI components or stream_chat for the low-level client. -### Business logic components +The package primarily contains three types of classes: -We provide widgets dedicated to business logic and state management: +1) Business Logic Components +2) Core Components +3) Core Controllers -- [StreamChatCore](https://pub.dev/documentation/stream_chat_flutter/latest/stream_chat_flutter/StreamChat-class.html) -- [StreamChannel](https://pub.dev/documentation/stream_chat_flutter/latest/stream_chat_flutter/StreamChannel-class.html) -- [ChannelsBloc](https://pub.dev/documentation/stream_chat_flutter/0.2.0-alpha+2/stream_chat_flutter/ChannelsBloc-class.html) -- [MessageSearchBloc] -- [UsersBloc] -- [ChannelListCore] -- [MessageListCore] -- [MessageSearchListCore] -- [UserListCore] +### Business Logic Components + +These components allow you to have the maximum and lower-level control of the queries being executed. +The BLoCs we provide are: + +1) ChannelsBloc +2) MessageSearchBloc +3) UsersBloc + +### Core Components + +Core components usually are an easy way to fetch data associated with Stream Chat which are decoupled from UI and often expose UI builders. +Data fetching can be controlled with the controllers of the respective core components. + +1) ChannelListCore (Fetch a list of channels) +2) MessageListCore (Fetch a list of messages from a channel) +3) MessageSearchListCore (Fetch a list of search messages) +4) UserListCore (Fetch a list of users) +5) StreamChatCore (This is different from the other core components - it is a version of StreamChat decoupled from theme and initialisations.) + +### Core Controllers + +Core Controllers are supplied to respective CoreList widgets which allows reloading and pagination of data whenever needed. + +1) ChannelListController +2) MessageListController +3) MessageSearchListController +4) ChannelListController ## Contributing diff --git a/packages/stream_chat_flutter_core/lib/src/message_list_core.dart b/packages/stream_chat_flutter_core/lib/src/message_list_core.dart index 81d29a6b..e6754ee8 100644 --- a/packages/stream_chat_flutter_core/lib/src/message_list_core.dart +++ b/packages/stream_chat_flutter_core/lib/src/message_list_core.dart @@ -35,14 +35,19 @@ typedef StreamErrorBuilder = Widget Function( /// child: Text('Nothing here...'), /// ); /// }, -/// emptyBuilder: (context) { +/// loadingBuilder: (context) { /// return Center( /// child: CircularProgressIndicator(), /// ); /// }, /// messageListBuilder: (context, list) { /// return MessagesPage(list); -/// } +/// }, +/// errorWidgetBuilder: (context, err) { +/// return Center( +/// child: Text('Error'), +/// ); +/// }, /// ), /// ), /// ], @@ -122,14 +127,14 @@ class _MessageListCoreState extends State { : streamChannel.channel.state?.messagesStream; return StreamBuilder>( - stream: messagesStream?.map((messages) => messages - ?.where((e) => - (!e.isDeleted && e.shadowed != true) || - (e.isDeleted && - e.user.id == streamChannel.channel.client.state.user.id)) - ?.toList()), - builder: (context, snapshot) { - if (!snapshot.hasData) { + stream: messagesStream?.map((messages) => messages + ?.where((e) => + (!e.isDeleted && e.shadowed != true) || + (e.isDeleted && + e.user.id == streamChannel.channel.client.state.user.id)) + ?.toList()), + builder: (context, snapshot) { + if (!snapshot.hasData) { return widget.loadingBuilder(context); } else if (snapshot.hasError) { return widget.errorWidgetBuilder(context, snapshot.error); From af3779eb21e75c40b8392e650a8c0730c189ff84 Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Thu, 28 Jan 2021 17:28:03 +0530 Subject: [PATCH 34/39] fix: Added CHANGELOG.md and pubspec.yaml changes --- packages/flutter_widgets/CHANGELOG.md | 4 ++++ packages/flutter_widgets/pubspec.yaml | 2 +- packages/stream_chat_flutter_core/CHANGELOG.md | 4 ++-- packages/stream_chat_flutter_core/pubspec.yaml | 2 +- 4 files changed, 8 insertions(+), 4 deletions(-) diff --git a/packages/flutter_widgets/CHANGELOG.md b/packages/flutter_widgets/CHANGELOG.md index 8ca3aa04..8a80dbc5 100644 --- a/packages/flutter_widgets/CHANGELOG.md +++ b/packages/flutter_widgets/CHANGELOG.md @@ -1,3 +1,7 @@ +## 1.0.0-rc + +- Integrated new-ui, split package functionality, + ## 0.2.20+2 - Added `shouldAddChannel` to ChannelsBloc in order to check if a channel has to be added to the list when a new message arrives diff --git a/packages/flutter_widgets/pubspec.yaml b/packages/flutter_widgets/pubspec.yaml index 059d4fb2..9321f992 100644 --- a/packages/flutter_widgets/pubspec.yaml +++ b/packages/flutter_widgets/pubspec.yaml @@ -1,7 +1,7 @@ name: stream_chat_flutter homepage: https://github.com/GetStream/stream-chat-flutter description: Stream Chat official Flutter SDK. Build your own chat experience using Dart and Flutter. -version: 0.2.13+1 +version: 1.0.0-rc repository: https://github.com/GetStream/stream-chat-flutter issue_tracker: https://github.com/GetStream/stream-chat-flutter/issues diff --git a/packages/stream_chat_flutter_core/CHANGELOG.md b/packages/stream_chat_flutter_core/CHANGELOG.md index ac071598..ec6ae55e 100644 --- a/packages/stream_chat_flutter_core/CHANGELOG.md +++ b/packages/stream_chat_flutter_core/CHANGELOG.md @@ -1,3 +1,3 @@ -## [0.0.1] - TODO: Add release date. +## 1.0.0-rc -* TODO: Describe initial release. +* First release diff --git a/packages/stream_chat_flutter_core/pubspec.yaml b/packages/stream_chat_flutter_core/pubspec.yaml index 5037e5e8..19dd8eb0 100644 --- a/packages/stream_chat_flutter_core/pubspec.yaml +++ b/packages/stream_chat_flutter_core/pubspec.yaml @@ -1,7 +1,7 @@ name: stream_chat_flutter_core homepage: https://github.com/GetStream/stream-chat-flutter description: Stream Chat official Flutter SDK Core. Build your own chat experience using Dart and Flutter. -version: 0.0.1 +version: 1.0.0-rc repository: https://github.com/GetStream/stream-chat-flutter issue_tracker: https://github.com/GetStream/stream-chat-flutter/issues From 2a3e5529b8903582b8bfba35a2a3175789b70137 Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Thu, 28 Jan 2021 17:34:56 +0530 Subject: [PATCH 35/39] fix: Fixed errorBuilder warning --- .../flutter_widgets/lib/src/message_list_view.dart | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/packages/flutter_widgets/lib/src/message_list_view.dart b/packages/flutter_widgets/lib/src/message_list_view.dart index bb11a469..c6378bb6 100644 --- a/packages/flutter_widgets/lib/src/message_list_view.dart +++ b/packages/flutter_widgets/lib/src/message_list_view.dart @@ -264,6 +264,18 @@ class _MessageListViewState extends State { messageListController: _messageListController, parentMessage: widget.parentMessage, showScrollToBottom: widget.showScrollToBottom, + errorWidgetBuilder: (BuildContext context, Object error) { + return Center( + child: Text( + 'Something went wrong', + style: StreamChatTheme.of(context).textTheme.footnote.copyWith( + color: StreamChatTheme.of(context) + .colorTheme + .black + .withOpacity(.5)), + ), + ); + }, ); } From 212a48c0b29136239f75599f84e8d4e42cd0a7fe Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Thu, 28 Jan 2021 17:51:28 +0530 Subject: [PATCH 36/39] fix: changed over dependency --- packages/flutter_widgets/lib/src/attachment_error.dart | 3 +-- packages/flutter_widgets/lib/src/attachment_title.dart | 2 +- packages/flutter_widgets/lib/src/channel_header.dart | 2 +- packages/flutter_widgets/lib/src/channel_image.dart | 2 +- packages/flutter_widgets/lib/src/channel_list_header.dart | 2 +- packages/flutter_widgets/lib/src/channel_name.dart | 2 +- packages/flutter_widgets/lib/src/channel_preview.dart | 2 +- packages/flutter_widgets/lib/src/channel_unread_indicator.dart | 2 +- packages/flutter_widgets/lib/src/file_attachment.dart | 2 +- packages/flutter_widgets/lib/src/image_footer.dart | 2 +- packages/flutter_widgets/lib/src/image_group.dart | 2 +- packages/flutter_widgets/lib/src/image_header.dart | 2 +- packages/flutter_widgets/lib/src/message_actions_modal.dart | 2 +- packages/flutter_widgets/lib/src/message_input.dart | 2 +- packages/flutter_widgets/lib/src/message_list_view.dart | 2 +- packages/flutter_widgets/lib/src/message_reactions_modal.dart | 2 +- packages/flutter_widgets/lib/src/message_search_item.dart | 2 +- packages/flutter_widgets/lib/src/message_search_list_view.dart | 2 +- packages/flutter_widgets/lib/src/message_text.dart | 2 +- packages/flutter_widgets/lib/src/quoted_message_widget.dart | 2 +- packages/flutter_widgets/lib/src/stream_chat.dart | 2 +- packages/flutter_widgets/lib/src/stream_chat_theme.dart | 2 +- packages/flutter_widgets/lib/src/thread_header.dart | 2 +- packages/flutter_widgets/lib/src/typing_indicator.dart | 2 +- packages/flutter_widgets/lib/src/user_avatar.dart | 2 +- packages/flutter_widgets/lib/src/user_item.dart | 2 +- packages/flutter_widgets/lib/src/user_list_view.dart | 2 +- packages/flutter_widgets/lib/src/user_reaction_display.dart | 2 +- packages/flutter_widgets/lib/src/utils.dart | 2 +- 29 files changed, 29 insertions(+), 30 deletions(-) diff --git a/packages/flutter_widgets/lib/src/attachment_error.dart b/packages/flutter_widgets/lib/src/attachment_error.dart index d24423c4..46b7b814 100644 --- a/packages/flutter_widgets/lib/src/attachment_error.dart +++ b/packages/flutter_widgets/lib/src/attachment_error.dart @@ -1,8 +1,7 @@ import 'dart:io'; import 'package:flutter/material.dart'; -import 'package:stream_chat/stream_chat.dart'; - +import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; import '../stream_chat_flutter.dart'; class AttachmentError extends StatelessWidget { diff --git a/packages/flutter_widgets/lib/src/attachment_title.dart b/packages/flutter_widgets/lib/src/attachment_title.dart index 7ed42a59..af5a1115 100644 --- a/packages/flutter_widgets/lib/src/attachment_title.dart +++ b/packages/flutter_widgets/lib/src/attachment_title.dart @@ -1,5 +1,5 @@ import 'package:flutter/material.dart'; -import 'package:stream_chat/stream_chat.dart'; +import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; import 'stream_chat_theme.dart'; import 'utils.dart'; diff --git a/packages/flutter_widgets/lib/src/channel_header.dart b/packages/flutter_widgets/lib/src/channel_header.dart index 73ee23a5..fb681d59 100644 --- a/packages/flutter_widgets/lib/src/channel_header.dart +++ b/packages/flutter_widgets/lib/src/channel_header.dart @@ -1,5 +1,5 @@ import 'package:flutter/material.dart'; -import 'package:stream_chat/stream_chat.dart'; +import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; import 'package:stream_chat_flutter/src/back_button.dart'; import 'package:stream_chat_flutter/src/channel_info.dart'; import 'package:stream_chat_flutter/src/channel_name.dart'; diff --git a/packages/flutter_widgets/lib/src/channel_image.dart b/packages/flutter_widgets/lib/src/channel_image.dart index 2f5862f3..18d67dfb 100644 --- a/packages/flutter_widgets/lib/src/channel_image.dart +++ b/packages/flutter_widgets/lib/src/channel_image.dart @@ -1,6 +1,6 @@ import 'package:cached_network_image/cached_network_image.dart'; import 'package:flutter/material.dart'; -import 'package:stream_chat/stream_chat.dart'; +import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; import 'package:stream_chat_flutter/src/group_image.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; diff --git a/packages/flutter_widgets/lib/src/channel_list_header.dart b/packages/flutter_widgets/lib/src/channel_list_header.dart index e8583d11..8ced0422 100644 --- a/packages/flutter_widgets/lib/src/channel_list_header.dart +++ b/packages/flutter_widgets/lib/src/channel_list_header.dart @@ -2,7 +2,7 @@ import 'dart:ui'; import 'package:flutter/material.dart'; import 'package:flutter_svg/flutter_svg.dart'; -import 'package:stream_chat/stream_chat.dart'; +import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; import 'package:stream_chat_flutter/src/stream_neumorphic_button.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; diff --git a/packages/flutter_widgets/lib/src/channel_name.dart b/packages/flutter_widgets/lib/src/channel_name.dart index 1d51f3fc..7907de9a 100644 --- a/packages/flutter_widgets/lib/src/channel_name.dart +++ b/packages/flutter_widgets/lib/src/channel_name.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; -import 'package:stream_chat/stream_chat.dart'; +import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; import '../stream_chat_flutter.dart'; diff --git a/packages/flutter_widgets/lib/src/channel_preview.dart b/packages/flutter_widgets/lib/src/channel_preview.dart index 3564be7a..eddd0ea6 100644 --- a/packages/flutter_widgets/lib/src/channel_preview.dart +++ b/packages/flutter_widgets/lib/src/channel_preview.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter/widgets.dart'; import 'package:jiffy/jiffy.dart'; -import 'package:stream_chat/stream_chat.dart'; +import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; import 'package:stream_chat_flutter/src/stream_svg_icon.dart'; import '../stream_chat_flutter.dart'; diff --git a/packages/flutter_widgets/lib/src/channel_unread_indicator.dart b/packages/flutter_widgets/lib/src/channel_unread_indicator.dart index c15db13a..cb9dc362 100644 --- a/packages/flutter_widgets/lib/src/channel_unread_indicator.dart +++ b/packages/flutter_widgets/lib/src/channel_unread_indicator.dart @@ -1,5 +1,5 @@ import 'package:flutter/material.dart'; -import 'package:stream_chat/stream_chat.dart'; +import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; import 'package:stream_chat_flutter/src/stream_chat_theme.dart'; class ChannelUnreadIndicator extends StatelessWidget { diff --git a/packages/flutter_widgets/lib/src/file_attachment.dart b/packages/flutter_widgets/lib/src/file_attachment.dart index dc7fae4b..0fff2445 100644 --- a/packages/flutter_widgets/lib/src/file_attachment.dart +++ b/packages/flutter_widgets/lib/src/file_attachment.dart @@ -3,7 +3,7 @@ import 'dart:io'; import 'package:cached_network_image/cached_network_image.dart'; import 'package:file_picker/file_picker.dart'; import 'package:flutter/material.dart'; -import 'package:stream_chat/stream_chat.dart'; +import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; import 'package:stream_chat_flutter/src/stream_chat_theme.dart'; import 'package:stream_chat_flutter/src/stream_svg_icon.dart'; import 'package:stream_chat_flutter/src/utils.dart'; diff --git a/packages/flutter_widgets/lib/src/image_footer.dart b/packages/flutter_widgets/lib/src/image_footer.dart index 0131561e..4e87423e 100644 --- a/packages/flutter_widgets/lib/src/image_footer.dart +++ b/packages/flutter_widgets/lib/src/image_footer.dart @@ -7,7 +7,7 @@ import 'package:dio/dio.dart'; import 'package:esys_flutter_share/esys_flutter_share.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; -import 'package:stream_chat/stream_chat.dart'; +import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; import 'package:stream_chat_flutter/src/stream_chat_theme.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; diff --git a/packages/flutter_widgets/lib/src/image_group.dart b/packages/flutter_widgets/lib/src/image_group.dart index bb577a4b..3a290c60 100644 --- a/packages/flutter_widgets/lib/src/image_group.dart +++ b/packages/flutter_widgets/lib/src/image_group.dart @@ -1,6 +1,6 @@ import 'package:cached_network_image/cached_network_image.dart'; import 'package:flutter/material.dart'; -import 'package:stream_chat/stream_chat.dart'; +import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; import 'package:stream_chat_flutter/src/full_screen_media.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; diff --git a/packages/flutter_widgets/lib/src/image_header.dart b/packages/flutter_widgets/lib/src/image_header.dart index f31ee3a6..e4c28d53 100644 --- a/packages/flutter_widgets/lib/src/image_header.dart +++ b/packages/flutter_widgets/lib/src/image_header.dart @@ -1,5 +1,5 @@ import 'package:flutter/material.dart'; -import 'package:stream_chat/stream_chat.dart'; +import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; import 'package:stream_chat_flutter/src/stream_chat_theme.dart'; import 'package:stream_chat_flutter/src/stream_svg_icon.dart'; import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; diff --git a/packages/flutter_widgets/lib/src/message_actions_modal.dart b/packages/flutter_widgets/lib/src/message_actions_modal.dart index 3cee6ba4..74490ddd 100644 --- a/packages/flutter_widgets/lib/src/message_actions_modal.dart +++ b/packages/flutter_widgets/lib/src/message_actions_modal.dart @@ -4,7 +4,7 @@ import 'dart:ui'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; -import 'package:stream_chat/stream_chat.dart'; +import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; import 'package:stream_chat_flutter/src/reaction_picker.dart'; import 'package:stream_chat_flutter/src/stream_svg_icon.dart'; import 'package:stream_chat_flutter/src/utils.dart'; diff --git a/packages/flutter_widgets/lib/src/message_input.dart b/packages/flutter_widgets/lib/src/message_input.dart index 35a9441e..59d748c2 100644 --- a/packages/flutter_widgets/lib/src/message_input.dart +++ b/packages/flutter_widgets/lib/src/message_input.dart @@ -13,7 +13,7 @@ import 'package:http_parser/http_parser.dart' as httpParser; import 'package:image_picker/image_picker.dart'; import 'package:mime/mime.dart'; import 'package:photo_manager/photo_manager.dart'; -import 'package:stream_chat/stream_chat.dart'; +import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; import 'package:stream_chat_flutter/src/compress_video_service.dart'; import 'package:stream_chat_flutter/src/media_list_view.dart'; import 'package:stream_chat_flutter/src/message_list_view.dart'; diff --git a/packages/flutter_widgets/lib/src/message_list_view.dart b/packages/flutter_widgets/lib/src/message_list_view.dart index c6378bb6..9ce29117 100644 --- a/packages/flutter_widgets/lib/src/message_list_view.dart +++ b/packages/flutter_widgets/lib/src/message_list_view.dart @@ -6,7 +6,7 @@ import 'package:flutter/material.dart'; import 'package:jiffy/jiffy.dart'; import 'package:rxdart/rxdart.dart'; import 'package:scrollable_positioned_list/scrollable_positioned_list.dart'; -import 'package:stream_chat/stream_chat.dart'; +import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; import 'package:stream_chat_flutter/src/info_tile.dart'; import 'package:stream_chat_flutter/src/message_widget.dart'; import 'package:stream_chat_flutter/src/stream_svg_icon.dart'; diff --git a/packages/flutter_widgets/lib/src/message_reactions_modal.dart b/packages/flutter_widgets/lib/src/message_reactions_modal.dart index 1ee3486d..eeb3d58b 100644 --- a/packages/flutter_widgets/lib/src/message_reactions_modal.dart +++ b/packages/flutter_widgets/lib/src/message_reactions_modal.dart @@ -1,7 +1,7 @@ import 'dart:ui'; import 'package:flutter/material.dart'; -import 'package:stream_chat/stream_chat.dart'; +import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; import 'package:stream_chat_flutter/src/reaction_bubble.dart'; import 'package:stream_chat_flutter/src/reaction_picker.dart'; import 'package:stream_chat_flutter/src/stream_chat.dart'; diff --git a/packages/flutter_widgets/lib/src/message_search_item.dart b/packages/flutter_widgets/lib/src/message_search_item.dart index 9c951a56..4d8d9148 100644 --- a/packages/flutter_widgets/lib/src/message_search_item.dart +++ b/packages/flutter_widgets/lib/src/message_search_item.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; import 'package:jiffy/jiffy.dart'; -import 'package:stream_chat/stream_chat.dart'; +import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; /// It shows the current [Message] preview. diff --git a/packages/flutter_widgets/lib/src/message_search_list_view.dart b/packages/flutter_widgets/lib/src/message_search_list_view.dart index f1711cb8..64f92f2d 100644 --- a/packages/flutter_widgets/lib/src/message_search_list_view.dart +++ b/packages/flutter_widgets/lib/src/message_search_list_view.dart @@ -1,5 +1,5 @@ import 'package:flutter/material.dart'; -import 'package:stream_chat/stream_chat.dart'; +import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; import 'package:stream_chat_flutter/src/info_tile.dart'; import 'package:stream_chat_flutter/src/message_search_item.dart'; import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; diff --git a/packages/flutter_widgets/lib/src/message_text.dart b/packages/flutter_widgets/lib/src/message_text.dart index c64d9520..373acbfc 100644 --- a/packages/flutter_widgets/lib/src/message_text.dart +++ b/packages/flutter_widgets/lib/src/message_text.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_markdown/flutter_markdown.dart'; -import 'package:stream_chat/stream_chat.dart'; +import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; import 'stream_chat_theme.dart'; import 'utils.dart'; diff --git a/packages/flutter_widgets/lib/src/quoted_message_widget.dart b/packages/flutter_widgets/lib/src/quoted_message_widget.dart index e4e10e17..8b0912eb 100644 --- a/packages/flutter_widgets/lib/src/quoted_message_widget.dart +++ b/packages/flutter_widgets/lib/src/quoted_message_widget.dart @@ -2,7 +2,7 @@ import 'dart:math'; import 'package:cached_network_image/cached_network_image.dart'; import 'package:flutter/material.dart'; -import 'package:stream_chat/stream_chat.dart'; +import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; import 'package:video_player/video_player.dart'; import 'attachment_error.dart'; diff --git a/packages/flutter_widgets/lib/src/stream_chat.dart b/packages/flutter_widgets/lib/src/stream_chat.dart index ee7e049f..37a163ec 100644 --- a/packages/flutter_widgets/lib/src/stream_chat.dart +++ b/packages/flutter_widgets/lib/src/stream_chat.dart @@ -4,7 +4,7 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_app_badger/flutter_app_badger.dart'; import 'package:flutter_portal/flutter_portal.dart'; -import 'package:stream_chat/stream_chat.dart'; +import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; import 'package:stream_chat_flutter/src/stream_chat_theme.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; diff --git a/packages/flutter_widgets/lib/src/stream_chat_theme.dart b/packages/flutter_widgets/lib/src/stream_chat_theme.dart index 3945a033..c65f36cc 100644 --- a/packages/flutter_widgets/lib/src/stream_chat_theme.dart +++ b/packages/flutter_widgets/lib/src/stream_chat_theme.dart @@ -1,6 +1,6 @@ import 'package:cached_network_image/cached_network_image.dart'; import 'package:flutter/material.dart'; -import 'package:stream_chat/stream_chat.dart'; +import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; import 'package:stream_chat_flutter/src/channel_header.dart'; import 'package:stream_chat_flutter/src/channel_preview.dart'; import 'package:stream_chat_flutter/src/message_input.dart'; diff --git a/packages/flutter_widgets/lib/src/thread_header.dart b/packages/flutter_widgets/lib/src/thread_header.dart index 35d554a6..b33b8bc1 100644 --- a/packages/flutter_widgets/lib/src/thread_header.dart +++ b/packages/flutter_widgets/lib/src/thread_header.dart @@ -1,5 +1,5 @@ import 'package:flutter/material.dart'; -import 'package:stream_chat/stream_chat.dart'; +import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; import 'package:stream_chat_flutter/src/stream_chat_theme.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; diff --git a/packages/flutter_widgets/lib/src/typing_indicator.dart b/packages/flutter_widgets/lib/src/typing_indicator.dart index c9e579be..14adc198 100644 --- a/packages/flutter_widgets/lib/src/typing_indicator.dart +++ b/packages/flutter_widgets/lib/src/typing_indicator.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; import 'package:lottie/lottie.dart'; -import 'package:stream_chat/stream_chat.dart'; +import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; /// Widget to show the current list of typing users diff --git a/packages/flutter_widgets/lib/src/user_avatar.dart b/packages/flutter_widgets/lib/src/user_avatar.dart index 706c4d7e..f38a9a68 100644 --- a/packages/flutter_widgets/lib/src/user_avatar.dart +++ b/packages/flutter_widgets/lib/src/user_avatar.dart @@ -1,6 +1,6 @@ import 'package:cached_network_image/cached_network_image.dart'; import 'package:flutter/material.dart'; -import 'package:stream_chat/stream_chat.dart'; +import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; import '../stream_chat_flutter.dart'; diff --git a/packages/flutter_widgets/lib/src/user_item.dart b/packages/flutter_widgets/lib/src/user_item.dart index 58a354fc..cb82e311 100644 --- a/packages/flutter_widgets/lib/src/user_item.dart +++ b/packages/flutter_widgets/lib/src/user_item.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; import 'package:jiffy/jiffy.dart'; -import 'package:stream_chat/stream_chat.dart'; +import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; import 'package:stream_chat_flutter/src/stream_svg_icon.dart'; import 'package:stream_chat_flutter/src/user_list_view.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; diff --git a/packages/flutter_widgets/lib/src/user_list_view.dart b/packages/flutter_widgets/lib/src/user_list_view.dart index 30c5db76..a53ba97a 100644 --- a/packages/flutter_widgets/lib/src/user_list_view.dart +++ b/packages/flutter_widgets/lib/src/user_list_view.dart @@ -1,5 +1,5 @@ import 'package:flutter/material.dart'; -import 'package:stream_chat/stream_chat.dart'; +import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; import 'package:stream_chat_flutter/stream_chat_flutter.dart'; import 'user_item.dart'; diff --git a/packages/flutter_widgets/lib/src/user_reaction_display.dart b/packages/flutter_widgets/lib/src/user_reaction_display.dart index 92f66aa2..af2797ca 100644 --- a/packages/flutter_widgets/lib/src/user_reaction_display.dart +++ b/packages/flutter_widgets/lib/src/user_reaction_display.dart @@ -1,5 +1,5 @@ import 'package:flutter/material.dart'; -import 'package:stream_chat/stream_chat.dart'; +import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; import 'package:stream_chat_flutter/src/user_avatar.dart'; class UserReactionDisplay extends StatelessWidget { diff --git a/packages/flutter_widgets/lib/src/utils.dart b/packages/flutter_widgets/lib/src/utils.dart index 060d2416..93d87ab2 100644 --- a/packages/flutter_widgets/lib/src/utils.dart +++ b/packages/flutter_widgets/lib/src/utils.dart @@ -1,5 +1,5 @@ import 'package:flutter/material.dart'; -import 'package:stream_chat/stream_chat.dart'; +import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; import 'package:url_launcher/url_launcher.dart'; import '../stream_chat_flutter.dart'; From 8d105b7e4508c1cafebfc5d09a6332b95c8e153b Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Thu, 28 Jan 2021 18:00:37 +0530 Subject: [PATCH 37/39] fix: duplicate imports --- packages/flutter_widgets/lib/src/image_header.dart | 1 - packages/flutter_widgets/lib/src/message_actions_modal.dart | 1 - packages/flutter_widgets/lib/src/message_search_list_view.dart | 1 - packages/flutter_widgets/lib/src/typing_indicator.dart | 1 - 4 files changed, 4 deletions(-) diff --git a/packages/flutter_widgets/lib/src/image_header.dart b/packages/flutter_widgets/lib/src/image_header.dart index e4c28d53..a818ee13 100644 --- a/packages/flutter_widgets/lib/src/image_header.dart +++ b/packages/flutter_widgets/lib/src/image_header.dart @@ -2,7 +2,6 @@ import 'package:flutter/material.dart'; import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; import 'package:stream_chat_flutter/src/stream_chat_theme.dart'; import 'package:stream_chat_flutter/src/stream_svg_icon.dart'; -import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; import 'image_actions_modal.dart'; class ImageHeader extends StatelessWidget implements PreferredSizeWidget { diff --git a/packages/flutter_widgets/lib/src/message_actions_modal.dart b/packages/flutter_widgets/lib/src/message_actions_modal.dart index 74490ddd..295bcf26 100644 --- a/packages/flutter_widgets/lib/src/message_actions_modal.dart +++ b/packages/flutter_widgets/lib/src/message_actions_modal.dart @@ -8,7 +8,6 @@ import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; import 'package:stream_chat_flutter/src/reaction_picker.dart'; import 'package:stream_chat_flutter/src/stream_svg_icon.dart'; import 'package:stream_chat_flutter/src/utils.dart'; -import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; import 'message_input.dart'; import 'message_widget.dart'; diff --git a/packages/flutter_widgets/lib/src/message_search_list_view.dart b/packages/flutter_widgets/lib/src/message_search_list_view.dart index 64f92f2d..2cbea145 100644 --- a/packages/flutter_widgets/lib/src/message_search_list_view.dart +++ b/packages/flutter_widgets/lib/src/message_search_list_view.dart @@ -2,7 +2,6 @@ import 'package:flutter/material.dart'; import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; import 'package:stream_chat_flutter/src/info_tile.dart'; import 'package:stream_chat_flutter/src/message_search_item.dart'; -import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; import '../stream_chat_flutter.dart'; diff --git a/packages/flutter_widgets/lib/src/typing_indicator.dart b/packages/flutter_widgets/lib/src/typing_indicator.dart index 14adc198..8a53c3e8 100644 --- a/packages/flutter_widgets/lib/src/typing_indicator.dart +++ b/packages/flutter_widgets/lib/src/typing_indicator.dart @@ -1,7 +1,6 @@ import 'package:flutter/material.dart'; import 'package:lottie/lottie.dart'; import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; -import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; /// Widget to show the current list of typing users class TypingIndicator extends StatelessWidget { From 34da746ac28d58c3367b715a10e07336695a1608 Mon Sep 17 00:00:00 2001 From: Deven Joshi Date: Thu, 28 Jan 2021 18:08:20 +0530 Subject: [PATCH 38/39] fix: import/export --- packages/flutter_widgets/lib/stream_chat_flutter.dart | 2 -- packages/flutter_widgets/test/src/mocks.dart | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/flutter_widgets/lib/stream_chat_flutter.dart b/packages/flutter_widgets/lib/stream_chat_flutter.dart index c4e0e2e1..3f52c8c6 100644 --- a/packages/flutter_widgets/lib/stream_chat_flutter.dart +++ b/packages/flutter_widgets/lib/stream_chat_flutter.dart @@ -1,5 +1,3 @@ -export 'package:stream_chat/stream_chat.dart'; - export 'src/back_button.dart'; export 'src/channel_header.dart'; export 'src/channel_image.dart'; diff --git a/packages/flutter_widgets/test/src/mocks.dart b/packages/flutter_widgets/test/src/mocks.dart index 37fa5530..25b438d6 100644 --- a/packages/flutter_widgets/test/src/mocks.dart +++ b/packages/flutter_widgets/test/src/mocks.dart @@ -1,5 +1,5 @@ import 'package:mockito/mockito.dart'; -import 'package:stream_chat/stream_chat.dart'; +import 'package:stream_chat_flutter_core/stream_chat_flutter_core.dart'; class MockClient extends Mock implements Client {} From ee745af2d9d72fa0b0ce463a7d43d763ba50c350 Mon Sep 17 00:00:00 2001 From: Salvatore Giordano Date: Thu, 28 Jan 2021 14:25:26 +0100 Subject: [PATCH 39/39] remove screenshots --- .../stream_chat_flutter_core/lib/src/channel_list_core.dart | 3 --- .../stream_chat_flutter_core/lib/src/message_list_core.dart | 3 --- 2 files changed, 6 deletions(-) diff --git a/packages/stream_chat_flutter_core/lib/src/channel_list_core.dart b/packages/stream_chat_flutter_core/lib/src/channel_list_core.dart index 41c6f81d..d2fd53c7 100644 --- a/packages/stream_chat_flutter_core/lib/src/channel_list_core.dart +++ b/packages/stream_chat_flutter_core/lib/src/channel_list_core.dart @@ -8,9 +8,6 @@ import 'package:stream_chat_flutter_core/src/channels_bloc.dart'; import 'stream_chat_core.dart'; -/// ![screenshot](https://raw.githubusercontent.com/GetStream/stream-chat-flutter/master/screenshots/channel_list_view.png) -/// ![screenshot](https://raw.githubusercontent.com/GetStream/stream-chat-flutter/master/screenshots/channel_list_view_paint.png) -/// /// [ChannelListCore] is a simplified class that allows fetching a list of channels while exposing UI builders. /// A [ChannelListController] is used to reload and paginate data. /// diff --git a/packages/stream_chat_flutter_core/lib/src/message_list_core.dart b/packages/stream_chat_flutter_core/lib/src/message_list_core.dart index e6754ee8..697e7e39 100644 --- a/packages/stream_chat_flutter_core/lib/src/message_list_core.dart +++ b/packages/stream_chat_flutter_core/lib/src/message_list_core.dart @@ -11,9 +11,6 @@ import 'stream_channel.dart'; typedef StreamErrorBuilder = Widget Function( BuildContext context, Object error); -/// ![screenshot](https://raw.githubusercontent.com/GetStream/stream-chat-flutter/master/screenshots/message_listview.png) -/// ![screenshot](https://raw.githubusercontent.com/GetStream/stream-chat-flutter/master/screenshots/message_listview_paint.png) -/// /// [MessageListCore] is a simplified class that allows fetching a list of messages while exposing UI builders. /// A [MessageListController] is used to paginate data. ///